Merge branch 'main' into fix-205353

This commit is contained in:
NoahHrreion
2024-02-20 11:17:45 +08:00
committed by GitHub
62 changed files with 732 additions and 487 deletions
+1 -1
View File
@@ -109,7 +109,7 @@ dependencies = [
[[package]]
name = "inno_updater"
version = "0.10.1"
version = "0.11.0"
dependencies = [
"byteorder",
"crc",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "inno_updater"
version = "0.10.1"
version = "0.11.0"
authors = ["Microsoft <monacotools@microsoft.com>"]
build = "build.rs"
+1 -1
View File
@@ -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
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=Zu PATH hinzuf
RunAfter=%1 nach der Installation ausführen
Other=Andere:
SourceFile=%1-Quelldatei
OpenWithCodeContextMenu=Mit %1 öffnen
OpenWithCodeContextMenu=Mit %1 öffnen
UpdatingVisualStudioCode=Visual Studio Code wird aktualisiert...
+1
View File
@@ -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...
+1
View File
@@ -7,3 +7,4 @@ RunAfter=Ejecutar %1 despu
Other=Otros:
SourceFile=Archivo de origen %1
OpenWithCodeContextMenu=Abrir &con %1
UpdatingVisualStudioCode=Actualizando Visual Studio Code...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=Ajouter
RunAfter=Exécuter %1 après l'installation
Other=Autre :
SourceFile=Fichier source %1
OpenWithCodeContextMenu=Ouvrir avec %1
OpenWithCodeContextMenu=Ouvrir avec %1
UpdatingVisualStudioCode=Mise à jour de Visual Studio Code...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=Hozz
RunAfter=%1 indítása a telepítés után
Other=Egyéb:
SourceFile=%1 forrásfájl
OpenWithCodeContextMenu=Megnyitás a következővel: %1
OpenWithCodeContextMenu=Megnyitás a következővel: %1
UpdatingVisualStudioCode=A Visual Studio Code frissítése...
+2 -1
View File
@@ -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
OpenWithCodeContextMenu=Apri con %1
UpdatingVisualStudioCode=Aggiornamento di Visual Studio Code...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=PATH
RunAfter=インストール後に %1 を実行する
Other=その他:
SourceFile=%1 ソース ファイル
OpenWithCodeContextMenu=%1 で開く
OpenWithCodeContextMenu=%1 で開く
UpdatingVisualStudioCode=Visual Studio Code を更新しています...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=PATH
RunAfter=설치 후 %1 실행
Other=기타:
SourceFile=%1 원본 파일
OpenWithCodeContextMenu=%1(으)로 열기
OpenWithCodeContextMenu=%1(으)로 열기
UpdatingVisualStudioCode=Visual Studio Code 업데이트 중...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=Adicione em PATH (dispon
RunAfter=Executar %1 após a instalação
Other=Outros:
SourceFile=Arquivo Fonte %1
OpenWithCodeContextMenu=Abrir com %1
OpenWithCodeContextMenu=Abrir com %1
UpdatingVisualStudioCode=Atualizando o Visual Studio Code...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=
RunAfter=Запустить %1 после установки
Other=Другое:
SourceFile=Исходный файл %1
OpenWithCodeContextMenu=Открыть с помощью %1
OpenWithCodeContextMenu=Открыть с помощью %1
UpdatingVisualStudioCode=Обновление Visual Studio Code...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=PATH'e ekle (yeniden ba
RunAfter=Kurulumdan sonra %1 uygulamasını çalıştır.
Other=Diğer:
SourceFile=%1 Kaynak Dosyası
OpenWithCodeContextMenu=%1 İle Aç
OpenWithCodeContextMenu=%1 İle Aç
UpdatingVisualStudioCode=Visual Studio Code güncelleniyor...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=
RunAfter=安装后运行 %1
Other=其他:
SourceFile=%1 源文件
OpenWithCodeContextMenu=通过 %1 打开
OpenWithCodeContextMenu=通过 %1 打开
UpdatingVisualStudioCode=正在更新 Visual Studio Code...
+2 -1
View File
@@ -6,4 +6,5 @@ AddToPath=
RunAfter=安裝後執行 %1
Other=其他:
SourceFile=%1 來源檔案
OpenWithCodeContextMenu=以 %1 開啟
OpenWithCodeContextMenu=以 %1 開啟
UpdatingVisualStudioCode=正在更新 Visual Studio Code...
Binary file not shown.
+10
View File
@@ -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"
}
]
@@ -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.",
+2 -52
View File
@@ -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": {
+1 -1
View File
@@ -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.",
+85 -85
View File
@@ -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<Uri[]>();
readonly onDidChangeFileDecorations: Event<Uri[]> = this._onDidChangeDecorations.event;
// private readonly _onDidChangeDecorations = new EventEmitter<Uri[]>();
// readonly onDidChangeFileDecorations: Event<Uri[]> = this._onDidChangeDecorations.event;
private decorations = new Map<string, FileDecoration>();
private readonly disposables: Disposable[] = [];
// private decorations = new Map<string, FileDecoration>();
// 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<void> {
const newDecorations = new Map<string, FileDecoration>();
await this.collectIncomingChangesFileDecorations(newDecorations);
const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()]));
// private async onDidChangeCurrentHistoryItemGroup(): Promise<void> {
// const newDecorations = new Map<string, FileDecoration>();
// 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<string, FileDecoration>): Promise<void> {
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<string, FileDecoration>): Promise<void> {
// 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<Change[]> {
try {
const historyProvider = this.repository.historyProvider;
const currentHistoryItemGroup = historyProvider.currentHistoryItemGroup;
// private async getIncomingChanges(): Promise<Change[]> {
// 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);
+27 -3
View File
@@ -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<Diagnostic> {
return this.diagnostics.get(uri) ?? [];
}
private async migrateInputValidationSettings(): Promise<void> {
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<boolean>('experimental.inputValidation', false) === true;
const inputValidation = config.get<boolean>('inputValidation', false);
if (!inputValidation) {
this.diagnostics.set(document.uri, undefined);
return;
+3 -56
View File
@@ -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<number>('inputValidationLength', 50);
if (lineNumber === 0) {
const inputValidationSubjectLength = config.get<number | null>('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;
}
/**
+1
View File
@@ -10,6 +10,7 @@
"chatParticipant",
"languageModels",
"defaultChatParticipant",
"chatVariableResolver",
"contribViewsRemote",
"contribStatusBarItems",
"createFileSystemWatcher",
@@ -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' }];
}
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.87.0",
"distro": "af73a537ea203329debad3df7ca7b42b4799473f",
"distro": "b314654a31bdba8cd2b0c7548e931916d03416bf",
"author": {
"name": "Microsoft Corporation"
},
@@ -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);
}
}
}
+3 -1
View File
@@ -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'),
@@ -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;
}
@@ -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<void> {
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<ClientPlatformInfoEvent, ClientPlatformInfoClassification>('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
@@ -315,7 +315,7 @@ class RenameController implements IEditorContribution {
}
cancelRenameInput(): void {
this._renameInputField.cancelInput(true);
this._renameInputField.cancelInput(true, 'cancelRenameInput command');
}
focusNextRenameSuggestion(): void {
@@ -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<RenameInputFieldResult | boolean>(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<NewSymbolName[]>[], 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<NewSymbolName>;
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
-1
View File
@@ -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) {
@@ -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;
@@ -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,
@@ -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;
@@ -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<IChatMarkdownContent> {
return {
kind: 'markdownContent',
content: MarkdownString.from(new types.MarkdownString().appendText(part.value))
};
}
export function from(part: Dto<IChatMarkdownContent>): vscode.ChatResponseTextPart {
return new types.ChatResponseTextPart(part.content.value);
}
}
export namespace ChatResponseMarkdownPart {
export function to(part: vscode.ChatResponseMarkdownPart): Dto<IChatMarkdownContent> {
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;
}
}
+1 -9
View File
@@ -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<ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>,
readonly response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>,
readonly result: vscode.ChatResult,
readonly participant: { extensionId: string; participant: string },
readonly command?: string
@@ -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');
@@ -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<unknown> {
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);
@@ -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,
@@ -195,6 +195,10 @@ export class TextDiffEditor extends AbstractTextEditor<IDiffEditorViewState> imp
control.restoreViewState(editorViewState);
if (options?.revealIfVisible) {
control.revealFirstDiff();
}
return true;
}
@@ -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<DisposableStore>());
private readonly styleOverrides = new Set<IStatusbarStyleOverride>();
@@ -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();
}
@@ -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)) {
@@ -1270,14 +1270,14 @@ class ContentReferencesListRenderer implements IListRenderer<IChatContentReferen
templateData.label.element.style.display = 'flex';
const uri = 'uri' in element.reference ? element.reference.uri : element.reference;
if (matchesSomeScheme(uri, Schemas.mailto, Schemas.http, Schemas.https)) {
templateData.label.setResource({ resource: uri, name: uri.toString() }, { icon: Codicon.globe });
} else {
templateData.label.setFile(uri, {
fileKind: FileKind.FILE,
// Should not have this live-updating data on a historical reference
fileDecorations: { badges: false, colors: false },
range: 'range' in element.reference ? element.reference.range : undefined
});
} else {
templateData.label.setResource({ resource: uri, name: uri.toString() }, { icon: Codicon.globe });
}
}
@@ -70,6 +70,7 @@ export interface IChatAgentMetadata {
description?: string;
isDefault?: boolean; // The agent invoked when no agent is specified
helpTextPrefix?: string | IMarkdownString;
helpTextVariablesPrefix?: string | IMarkdownString;
helpTextPostfix?: string | IMarkdownString;
isSecondary?: boolean; // Invoked by ctrl/cmd+enter
fullName?: string;
@@ -38,10 +38,12 @@
.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-pinned::before {
content: "\ebb2"; /* use `pinned-dirty` icon unicode for sticky-dirty indication */
font-family: 'codicon';
}
.open-editors .monaco-list .monaco-list-row.dirty:not(:hover) > .monaco-action-bar .codicon-close::before {
content: "\ea71"; /* use `circle-filled` icon unicode for dirty indication */
font-family: 'codicon';
}
.open-editors .monaco-list .monaco-list-row > .monaco-action-bar .action-close-all-files,
@@ -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) {
@@ -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 = /^<span (codesetting|codefeature)="([^\s"\:]+)(?::([^\s"]+))?">/;
const codeSettingRegex = /^<code (codesetting)="([^\s"\:]+)(?::([^\s"]+))?">/;
const codeFeatureRegex = /^<span (codefeature)="([^\s"\:]+)(?::([^\s"]+))?">/;
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 `<span><a href="${href}" class="codesetting" title="${title}" aria-role="button"><svg width="14" height="14" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M9.1 4.4L8.6 2H7.4l-.5 2.4-.7.3-2-1.3-.9.8 1.3 2-.2.7-2.4.5v1.2l2.4.5.3.8-1.3 2 .8.8 2-1.3.8.3.4 2.3h1.2l.5-2.4.8-.3 2 1.3.8-.8-1.3-2 .3-.8 2.3-.4V7.4l-2.4-.5-.3-.8 1.3-2-.8-.8-2 1.3-.7-.2zM9.4 1l.5 2.4L12 2.1l2 2-1.4 2.1 2.4.4v2.8l-2.4.5L14 12l-2 2-2.1-1.4-.5 2.4H6.6l-.5-2.4L4 13.9l-2-2 1.4-2.1L1 9.4V6.6l2.4-.5L2.1 4l2-2 2.1 1.4.4-2.4h2.8zm.6 7c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM8 9c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z"/></svg></a>`;
return `<code tabindex="0"><a href="${href}" class="codesetting" title="${title}" aria-role="button"><svg width="14" height="14" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M9.1 4.4L8.6 2H7.4l-.5 2.4-.7.3-2-1.3-.9.8 1.3 2-.2.7-2.4.5v1.2l2.4.5.3.8-1.3 2 .8.8 2-1.3.8.3.4 2.3h1.2l.5-2.4.8-.3 2 1.3.8-.8-1.3-2 .3-.8 2.3-.4V7.4l-2.4-.5-.3-.8 1.3-2-.8-.8-2 1.3-.7-.2zM9.4 1l.5 2.4L12 2.1l2 2-1.4 2.1 2.4.4v2.8l-2.4.5L14 12l-2 2-2.1-1.4-.5 2.4H6.6l-.5-2.4L4 13.9l-2-2 1.4-2.1L1 9.4V6.6l2.4-.5L2.1 4l2-2 2.1 1.4.4-2.4h2.8zm.6 7c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM8 9c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z"/></svg>
<span class="separator"></span>
<span class="setting-name">${setting.key}</span>
</a></code><code>`;
}
private renderFeature(setting: ISetting, newValue: string): string | undefined {
@@ -70,10 +70,13 @@ suite('Markdown Setting Renderer Test', () => {
test('render code setting button with value', () => {
const htmlRenderer = settingRenderer.getHtmlRenderer();
const htmlNoValue = '<span codesetting="example.booleanSetting">';
const htmlNoValue = '<code codesetting="example.booleanSetting">';
const renderedHtmlNoValue = htmlRenderer(htmlNoValue);
assert.strictEqual(renderedHtmlNoValue,
`<span><a href="code-setting://example.booleanSetting" class="codesetting" title="Try feature" aria-role="button"><svg width="14" height="14" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M9.1 4.4L8.6 2H7.4l-.5 2.4-.7.3-2-1.3-.9.8 1.3 2-.2.7-2.4.5v1.2l2.4.5.3.8-1.3 2 .8.8 2-1.3.8.3.4 2.3h1.2l.5-2.4.8-.3 2 1.3.8-.8-1.3-2 .3-.8 2.3-.4V7.4l-2.4-.5-.3-.8 1.3-2-.8-.8-2 1.3-.7-.2zM9.4 1l.5 2.4L12 2.1l2 2-1.4 2.1 2.4.4v2.8l-2.4.5L14 12l-2 2-2.1-1.4-.5 2.4H6.6l-.5-2.4L4 13.9l-2-2 1.4-2.1L1 9.4V6.6l2.4-.5L2.1 4l2-2 2.1 1.4.4-2.4h2.8zm.6 7c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM8 9c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z"/></svg></a>`);
`<code tabindex="0"><a href="code-setting://example.booleanSetting" class="codesetting" title="Try feature" aria-role="button"><svg width="14" height="14" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><path d="M9.1 4.4L8.6 2H7.4l-.5 2.4-.7.3-2-1.3-.9.8 1.3 2-.2.7-2.4.5v1.2l2.4.5.3.8-1.3 2 .8.8 2-1.3.8.3.4 2.3h1.2l.5-2.4.8-.3 2 1.3.8-.8-1.3-2 .3-.8 2.3-.4V7.4l-2.4-.5-.3-.8 1.3-2-.8-.8-2 1.3-.7-.2zM9.4 1l.5 2.4L12 2.1l2 2-1.4 2.1 2.4.4v2.8l-2.4.5L14 12l-2 2-2.1-1.4-.5 2.4H6.6l-.5-2.4L4 13.9l-2-2 1.4-2.1L1 9.4V6.6l2.4-.5L2.1 4l2-2 2.1 1.4.4-2.4h2.8zm.6 7c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zM8 9c.6 0 1-.4 1-1s-.4-1-1-1-1 .4-1 1 .4 1 1 1z"/></svg>
<span class="separator"></span>
<span class="setting-name">example.booleanSetting</span>
</a></code><code>`);
});
test('actions with no value', () => {
@@ -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();
}));
}
@@ -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; }
</style>
</head>
@@ -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 {
}
}
}
@@ -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<void> {
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
@@ -38,9 +38,9 @@ export const DOWNLOAD_URL = new RawContextKey<string>('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,
@@ -4,3 +4,4 @@
*--------------------------------------------------------------------------------------------*/
export const ShowCurrentReleaseNotesActionId = 'update.showCurrentReleaseNotes';
export const ShowCurrentReleaseNotesFromCurrentFileActionId = 'developer.showCurrentFileAsReleaseNotes';
+3 -2
View File
@@ -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() });
@@ -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',
@@ -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);
+43 -73
View File
@@ -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<ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>;
readonly response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>;
/**
* 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<ChatResponseTextPart | ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>, result: ChatResult, participant: { extensionId: string; participant: string });
private constructor(response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>, 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<ChatFollowup[]>;
@@ -225,9 +230,11 @@ declare module 'vscode' {
*/
export type ChatRequestHandler = (request: ChatRequest, context: ChatContext, response: ChatResponseStream, token: CancellationToken) => ProviderResult<ChatResult>;
/**
* 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<ChatVariableValue[]>;
}
}
@@ -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<ChatVariableValue[]>;
}
}
@@ -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`.
*/