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 ausfhren 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=Excuter %1 aprs 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 indtsa a telepts utn Other=Egyb: SourceFile=%1 forrsfjl -OpenWithCodeContextMenu=Megnyits a kvetkezvel: %1 \ No newline at end of file +OpenWithCodeContextMenu=Megnyits a kvetkezvel: %1 +UpdatingVisualStudioCode=A Visual Studio Code frisstse... \ 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=CXg[ %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 aps a instalao 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 uygulamasn altr. Other=Dier: SourceFile=%1 Kaynak Dosyas -OpenWithCodeContextMenu=%1 le A \ No newline at end of file +OpenWithCodeContextMenu=%1 le A +UpdatingVisualStudioCode=Visual Studio Code gncelleniyor... \ 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=bs Visual Studio Code... \ No newline at end of file diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index fa2fd26a466..b87cbd47f24 100644 Binary files a/build/win32/inno_updater.exe and b/build/win32/inno_updater.exe differ 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" } ] 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.", diff --git a/extensions/git/package.json b/extensions/git/package.json index c2745213c68..f1b45e74665 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", @@ -3208,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/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/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); 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; } /** 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/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/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'), 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 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..dc5131bba43 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); @@ -105,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'; @@ -177,9 +179,10 @@ 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; + const cursorBoxTop = this._getTopForPosition(); + + this._nPxAvailableAbove = cursorBoxTop + editorBox.top; this._nPxAvailableBelow = bodyBox.height - this._nPxAvailableAbove; const lineHeight = this._editor.getOption(EditorOption.lineHeight); @@ -199,16 +202,16 @@ 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; } 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,14 @@ 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); + 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; } @@ -302,14 +310,14 @@ export class RenameInputField implements IContentWidget { this._candidatesView.clearCandidates(); resolve({ - newName: candidateName ?? this._input.value, + newName, wantsPreview: supportPreview && wantsPreview }); }; - 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 +329,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 +344,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,30 +359,54 @@ 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 _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); + } } -export class CandidatesView { +class CandidatesView { private readonly _listWidget: List; private readonly _listContainer: HTMLDivElement; @@ -377,8 +414,12 @@ export 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; @@ -397,7 +438,7 @@ export class CandidatesView { } getHeight(element: NewSymbolName): number { - return that.candidateViewHeight; + return that._candidateViewHeight; } }; @@ -429,18 +470,22 @@ export 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); } - 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 @@ -451,18 +496,12 @@ export 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`; } @@ -525,9 +564,25 @@ export class CandidatesView { } return focusedIx > 0; } + + 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 */; + } + } -export class CandidateView { // TODO@ulugbekna: remove export +class CandidateView { // TODO@ulugbekna: accessibility 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/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/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 46292e1bb11..71ec5250fb9 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) { @@ -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 59a5684877c..9f4f87ac999 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'; @@ -233,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)); } @@ -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)); } @@ -340,6 +351,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; @@ -390,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 @@ -470,6 +482,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 +561,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/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 175a9213112..db879caea6b 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, }; } } @@ -2332,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 { @@ -2477,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/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/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/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; } 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(); } 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/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 .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, 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/contrib/markdown/browser/markdownSettingRenderer.ts b/src/vs/workbench/contrib/markdown/browser/markdownSettingRenderer.ts index 62bd755a1bf..72f4dfd0e2e 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..271cf0d2da5 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/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(); })); } diff --git a/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/browser/releaseNotesEditor.ts index de8129bbdf0..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'); } @@ -258,48 +261,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 +403,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 +472,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 }, '*'); }); @@ -525,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'; 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() }); 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/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); diff --git a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts index f4b2242ed46..468f12ae690 100644 --- a/src/vscode-dts/vscode.proposed.chatParticipant.d.ts +++ b/src/vscode-dts/vscode.proposed.chatParticipant.d.ts @@ -5,13 +5,15 @@ declare module 'vscode' { - // TODO@API name: Turn? + /** + * 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. @@ -36,13 +38,15 @@ declare module 'vscode' { private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; participant: string }); } - // TODO@API name: Turn? + /** + * 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. @@ -50,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 { @@ -100,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 }; } @@ -125,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; @@ -160,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} @@ -177,7 +188,7 @@ declare module 'vscode' { } /** - * A followup question suggested by the model. + * A followup question suggested by the participant. */ export interface ChatFollowup { /** @@ -185,9 +196,14 @@ declare module 'vscode' { */ prompt: string; + /** + * A title to show the user. The prompt will be shown by default, when this is unspecified. + */ + 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 +211,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; } /** @@ -213,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; @@ -225,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`. */ @@ -235,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; @@ -301,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. * @@ -323,7 +330,6 @@ declare module 'vscode' { } export interface ChatRequest { - /** * The prompt as entered by the user. * @@ -344,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. */ @@ -352,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))`. @@ -367,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 @@ -400,9 +411,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 +422,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,13 +432,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); - } - export class ChatResponseMarkdownPart { value: MarkdownString; constructor(value: string | MarkdownString); @@ -457,7 +456,6 @@ declare module 'vscode' { export class ChatResponseProgressPart { value: string; - // TODO@API inline constructor(value: string); } @@ -474,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. * @@ -489,21 +486,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 +513,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; + } +} 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`. */