mirror of
https://github.com/Microsoft/vscode
synced 2024-09-13 13:46:13 +00:00
Merge branch 'main' into aiday/indentationWithinCommentsTest
This commit is contained in:
commit
1403f05edd
|
@ -38,14 +38,14 @@
|
||||||
"configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onDoubleHash": "Enable workspace header suggestions after typing `##` in a path, for example: `[link text](##`.",
|
"configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onDoubleHash": "Enable workspace header suggestions after typing `##` in a path, for example: `[link text](##`.",
|
||||||
"configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onSingleOrDoubleHash": "Enable workspace header suggestions after typing either `##` or `#` in a path, for example: `[link text](#` or `[link text](##`.",
|
"configuration.markdown.suggest.paths.includeWorkspaceHeaderCompletions.onSingleOrDoubleHash": "Enable workspace header suggestions after typing either `##` or `#` in a path, for example: `[link text](#` or `[link text](##`.",
|
||||||
"configuration.markdown.editor.drop.enabled": "Enable dropping files into a Markdown editor while holding Shift. Requires enabling `#editor.dropIntoEditor.enabled#`.",
|
"configuration.markdown.editor.drop.enabled": "Enable dropping files into a Markdown editor while holding Shift. Requires enabling `#editor.dropIntoEditor.enabled#`.",
|
||||||
"configuration.markdown.editor.drop.always": "Always insert Markdown links.",
|
"configuration.markdown.editor.drop.enabled.always": "Always insert Markdown links.",
|
||||||
"configuration.markdown.editor.drop.smart": "Smartly create Markdown links by default when not dropping into a code block or other special element. Use the drop widget to switch between pasting as plain text or as Markdown links.",
|
"configuration.markdown.editor.drop.enabled.smart": "Smartly create Markdown links by default when not dropping into a code block or other special element. Use the drop widget to switch between pasting as plain text or as Markdown links.",
|
||||||
"configuration.markdown.editor.drop.never": "Never create Markdown links.",
|
"configuration.markdown.editor.drop.enabled.never": "Never create Markdown links.",
|
||||||
"configuration.markdown.editor.drop.copyIntoWorkspace": "Controls if files outside of the workspace that are dropped into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied dropped files should be created",
|
"configuration.markdown.editor.drop.copyIntoWorkspace": "Controls if files outside of the workspace that are dropped into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied dropped files should be created",
|
||||||
"configuration.markdown.editor.filePaste.enabled": "Enable pasting files into a Markdown editor to create Markdown links. Requires enabling `#editor.pasteAs.enabled#`.",
|
"configuration.markdown.editor.filePaste.enabled": "Enable pasting files into a Markdown editor to create Markdown links. Requires enabling `#editor.pasteAs.enabled#`.",
|
||||||
"configuration.markdown.editor.filePaste.always": "Always insert Markdown links.",
|
"configuration.markdown.editor.filePaste.enabled.always": "Always insert Markdown links.",
|
||||||
"configuration.markdown.editor.filePaste.smart": "Smartly create Markdown links by default when not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.",
|
"configuration.markdown.editor.filePaste.enabled.smart": "Smartly create Markdown links by default when not pasting into a code block or other special element. Use the paste widget to switch between pasting as plain text or as Markdown links.",
|
||||||
"configuration.markdown.editor.filePaste.never": "Never create Markdown links.",
|
"configuration.markdown.editor.filePaste.enabled.never": "Never create Markdown links.",
|
||||||
"configuration.markdown.editor.filePaste.copyIntoWorkspace": "Controls if files outside of the workspace that are pasted into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied files should be created.",
|
"configuration.markdown.editor.filePaste.copyIntoWorkspace": "Controls if files outside of the workspace that are pasted into a Markdown editor should be copied into the workspace.\n\nUse `#markdown.copyFiles.destination#` to configure where copied files should be created.",
|
||||||
"configuration.copyIntoWorkspace.mediaFiles": "Try to copy external image and video files into the workspace.",
|
"configuration.copyIntoWorkspace.mediaFiles": "Try to copy external image and video files into the workspace.",
|
||||||
"configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.",
|
"configuration.copyIntoWorkspace.never": "Do not copy external files into the workspace.",
|
||||||
|
|
|
@ -65,6 +65,7 @@
|
||||||
"contributes": {
|
"contributes": {
|
||||||
"chatParticipants": [
|
"chatParticipants": [
|
||||||
{
|
{
|
||||||
|
"id": "api-test.participant",
|
||||||
"name": "participant",
|
"name": "participant",
|
||||||
"description": "test",
|
"description": "test",
|
||||||
"isDefault": true,
|
"isDefault": true,
|
||||||
|
|
|
@ -30,7 +30,7 @@ suite('chat', () => {
|
||||||
|
|
||||||
function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> {
|
function setupParticipant(): Event<{ request: ChatRequest; context: ChatContext }> {
|
||||||
const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>();
|
const emitter = new EventEmitter<{ request: ChatRequest; context: ChatContext }>();
|
||||||
disposables.push();
|
disposables.push(emitter);
|
||||||
disposables.push(interactive.registerInteractiveSessionProvider('provider', {
|
disposables.push(interactive.registerInteractiveSessionProvider('provider', {
|
||||||
prepareSession: (_token: CancellationToken): ProviderResult<InteractiveSession> => {
|
prepareSession: (_token: CancellationToken): ProviderResult<InteractiveSession> => {
|
||||||
return {
|
return {
|
||||||
|
@ -40,7 +40,7 @@ suite('chat', () => {
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const participant = chat.createChatParticipant('participant', (request, context, _progress, _token) => {
|
const participant = chat.createChatParticipant('api-test.participant', (request, context, _progress, _token) => {
|
||||||
emitter.fire({ request, context });
|
emitter.fire({ request, context });
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
|
@ -49,12 +49,12 @@ suite('chat', () => {
|
||||||
return emitter.event;
|
return emitter.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
test('participant and slash command', async () => {
|
test('participant and slash command history', async () => {
|
||||||
const onRequest = setupParticipant();
|
const onRequest = setupParticipant();
|
||||||
commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' });
|
commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' });
|
||||||
|
|
||||||
let i = 0;
|
let i = 0;
|
||||||
onRequest(request => {
|
disposables.push(onRequest(request => {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
assert.deepStrictEqual(request.request.command, 'hello');
|
assert.deepStrictEqual(request.request.command, 'hello');
|
||||||
assert.strictEqual(request.request.prompt, 'friend');
|
assert.strictEqual(request.request.prompt, 'friend');
|
||||||
|
@ -62,10 +62,10 @@ suite('chat', () => {
|
||||||
commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' });
|
commands.executeCommand('workbench.action.chat.open', { query: '@participant /hello friend' });
|
||||||
} else {
|
} else {
|
||||||
assert.strictEqual(request.context.history.length, 1);
|
assert.strictEqual(request.context.history.length, 1);
|
||||||
assert.strictEqual(request.context.history[0].participant.name, 'participant');
|
assert.strictEqual(request.context.history[0].participant, 'api-test.participant');
|
||||||
assert.strictEqual(request.context.history[0].command, 'hello');
|
assert.strictEqual(request.context.history[0].command, 'hello');
|
||||||
}
|
}
|
||||||
});
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('participant and variable', async () => {
|
test('participant and variable', async () => {
|
||||||
|
@ -93,7 +93,7 @@ suite('chat', () => {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const deferred = new DeferredPromise<ChatResult>();
|
const deferred = new DeferredPromise<ChatResult>();
|
||||||
const participant = chat.createChatParticipant('participant', (_request, _context, _progress, _token) => {
|
const participant = chat.createChatParticipant('api-test.participant', (_request, _context, _progress, _token) => {
|
||||||
return { metadata: { key: 'value' } };
|
return { metadata: { key: 'value' } };
|
||||||
});
|
});
|
||||||
participant.isDefault = true;
|
participant.isDefault = true;
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "code-oss-dev",
|
"name": "code-oss-dev",
|
||||||
"version": "1.88.0",
|
"version": "1.88.0",
|
||||||
"distro": "ff3bff60edcc6e1f7269509e1673036c00fa62bd",
|
"distro": "7734ec27c8ec09ddc68bc3618e17d9a8b40fbfd9",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
},
|
},
|
||||||
|
|
|
@ -312,6 +312,18 @@ export class InputBox extends Widget {
|
||||||
return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd;
|
return this.input.selectionEnd === this.input.value.length && this.input.selectionStart === this.input.selectionEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSelection(): IRange | null {
|
||||||
|
const selectionStart = this.input.selectionStart;
|
||||||
|
if (selectionStart === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const selectionEnd = this.input.selectionEnd ?? selectionStart;
|
||||||
|
return {
|
||||||
|
start: selectionStart,
|
||||||
|
end: selectionEnd,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public enable(): void {
|
public enable(): void {
|
||||||
this.input.removeAttribute('disabled');
|
this.input.removeAttribute('disabled');
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,7 +237,7 @@ export class Checkbox extends Widget {
|
||||||
constructor(private title: string, private isChecked: boolean, styles: ICheckboxStyles) {
|
constructor(private title: string, private isChecked: boolean, styles: ICheckboxStyles) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.checkbox = new Toggle({ title: this.title, isChecked: this.isChecked, icon: Codicon.check, actionClassName: 'monaco-checkbox', ...unthemedToggleStyles });
|
this.checkbox = this._register(new Toggle({ title: this.title, isChecked: this.isChecked, icon: Codicon.check, actionClassName: 'monaco-checkbox', ...unthemedToggleStyles }));
|
||||||
|
|
||||||
this.domNode = this.checkbox.domNode;
|
this.domNode = this.checkbox.domNode;
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||||
import { Position } from 'vs/editor/common/core/position';
|
import { Position } from 'vs/editor/common/core/position';
|
||||||
import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration';
|
import { IEditorConfiguration } from 'vs/editor/common/config/editorConfiguration';
|
||||||
import { TokenizationRegistry } from 'vs/editor/common/languages';
|
import { TokenizationRegistry } from 'vs/editor/common/languages';
|
||||||
import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground } from 'vs/editor/common/core/editorColorRegistry';
|
import { editorCursorForeground, editorOverviewRulerBorder, editorOverviewRulerBackground, editorMultiCursorSecondaryForeground, editorMultiCursorPrimaryForeground } from 'vs/editor/common/core/editorColorRegistry';
|
||||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
||||||
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
||||||
import { EditorTheme } from 'vs/editor/common/editorTheme';
|
import { EditorTheme } from 'vs/editor/common/editorTheme';
|
||||||
|
@ -29,7 +29,9 @@ class Settings {
|
||||||
public readonly borderColor: string | null;
|
public readonly borderColor: string | null;
|
||||||
|
|
||||||
public readonly hideCursor: boolean;
|
public readonly hideCursor: boolean;
|
||||||
public readonly cursorColor: string | null;
|
public readonly cursorColorSingle: string | null;
|
||||||
|
public readonly cursorColorPrimary: string | null;
|
||||||
|
public readonly cursorColorSecondary: string | null;
|
||||||
|
|
||||||
public readonly themeType: 'light' | 'dark' | 'hcLight' | 'hcDark';
|
public readonly themeType: 'light' | 'dark' | 'hcLight' | 'hcDark';
|
||||||
public readonly backgroundColor: Color | null;
|
public readonly backgroundColor: Color | null;
|
||||||
|
@ -55,8 +57,12 @@ class Settings {
|
||||||
this.borderColor = borderColor ? borderColor.toString() : null;
|
this.borderColor = borderColor ? borderColor.toString() : null;
|
||||||
|
|
||||||
this.hideCursor = options.get(EditorOption.hideCursorInOverviewRuler);
|
this.hideCursor = options.get(EditorOption.hideCursorInOverviewRuler);
|
||||||
const cursorColor = theme.getColor(editorCursorForeground);
|
const cursorColorSingle = theme.getColor(editorCursorForeground);
|
||||||
this.cursorColor = cursorColor ? cursorColor.transparent(0.7).toString() : null;
|
this.cursorColorSingle = cursorColorSingle ? cursorColorSingle.transparent(0.7).toString() : null;
|
||||||
|
const cursorColorPrimary = theme.getColor(editorMultiCursorPrimaryForeground);
|
||||||
|
this.cursorColorPrimary = cursorColorPrimary ? cursorColorPrimary.transparent(0.7).toString() : null;
|
||||||
|
const cursorColorSecondary = theme.getColor(editorMultiCursorSecondaryForeground);
|
||||||
|
this.cursorColorSecondary = cursorColorSecondary ? cursorColorSecondary.transparent(0.7).toString() : null;
|
||||||
|
|
||||||
this.themeType = theme.type;
|
this.themeType = theme.type;
|
||||||
|
|
||||||
|
@ -189,7 +195,9 @@ class Settings {
|
||||||
&& this.renderBorder === other.renderBorder
|
&& this.renderBorder === other.renderBorder
|
||||||
&& this.borderColor === other.borderColor
|
&& this.borderColor === other.borderColor
|
||||||
&& this.hideCursor === other.hideCursor
|
&& this.hideCursor === other.hideCursor
|
||||||
&& this.cursorColor === other.cursorColor
|
&& this.cursorColorSingle === other.cursorColorSingle
|
||||||
|
&& this.cursorColorPrimary === other.cursorColorPrimary
|
||||||
|
&& this.cursorColorSecondary === other.cursorColorSecondary
|
||||||
&& this.themeType === other.themeType
|
&& this.themeType === other.themeType
|
||||||
&& Color.equals(this.backgroundColor, other.backgroundColor)
|
&& Color.equals(this.backgroundColor, other.backgroundColor)
|
||||||
&& this.top === other.top
|
&& this.top === other.top
|
||||||
|
@ -213,6 +221,11 @@ const enum OverviewRulerLane {
|
||||||
Full = 7
|
Full = 7
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Cursor = {
|
||||||
|
position: Position;
|
||||||
|
color: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
const enum ShouldRenderValue {
|
const enum ShouldRenderValue {
|
||||||
NotNeeded = 0,
|
NotNeeded = 0,
|
||||||
Maybe = 1,
|
Maybe = 1,
|
||||||
|
@ -226,10 +239,10 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
private readonly _tokensColorTrackerListener: IDisposable;
|
private readonly _tokensColorTrackerListener: IDisposable;
|
||||||
private readonly _domNode: FastDomNode<HTMLCanvasElement>;
|
private readonly _domNode: FastDomNode<HTMLCanvasElement>;
|
||||||
private _settings!: Settings;
|
private _settings!: Settings;
|
||||||
private _cursorPositions: Position[];
|
private _cursorPositions: Cursor[];
|
||||||
|
|
||||||
private _renderedDecorations: OverviewRulerDecorationsGroup[] = [];
|
private _renderedDecorations: OverviewRulerDecorationsGroup[] = [];
|
||||||
private _renderedCursorPositions: Position[] = [];
|
private _renderedCursorPositions: Cursor[] = [];
|
||||||
|
|
||||||
constructor(context: ViewContext) {
|
constructor(context: ViewContext) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -249,7 +262,7 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this._cursorPositions = [new Position(1, 1)];
|
this._cursorPositions = [{ position: new Position(1, 1), color: this._settings.cursorColorSingle }];
|
||||||
}
|
}
|
||||||
|
|
||||||
public override dispose(): void {
|
public override dispose(): void {
|
||||||
|
@ -298,9 +311,13 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
|
public override onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
|
||||||
this._cursorPositions = [];
|
this._cursorPositions = [];
|
||||||
for (let i = 0, len = e.selections.length; i < len; i++) {
|
for (let i = 0, len = e.selections.length; i < len; i++) {
|
||||||
this._cursorPositions[i] = e.selections[i].getPosition();
|
let color = this._settings.cursorColorSingle;
|
||||||
|
if (len > 1) {
|
||||||
|
color = i === 0 ? this._settings.cursorColorPrimary : this._settings.cursorColorSecondary;
|
||||||
|
}
|
||||||
|
this._cursorPositions.push({ position: e.selections[i].getPosition(), color });
|
||||||
}
|
}
|
||||||
this._cursorPositions.sort(Position.compare);
|
this._cursorPositions.sort((a, b) => Position.compare(a.position, b.position));
|
||||||
return this._markRenderingIsMaybeNeeded();
|
return this._markRenderingIsMaybeNeeded();
|
||||||
}
|
}
|
||||||
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
|
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
|
||||||
|
@ -352,7 +369,7 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
if (this._actualShouldRender === ShouldRenderValue.Maybe && !OverviewRulerDecorationsGroup.equalsArr(this._renderedDecorations, decorations)) {
|
if (this._actualShouldRender === ShouldRenderValue.Maybe && !OverviewRulerDecorationsGroup.equalsArr(this._renderedDecorations, decorations)) {
|
||||||
this._actualShouldRender = ShouldRenderValue.Needed;
|
this._actualShouldRender = ShouldRenderValue.Needed;
|
||||||
}
|
}
|
||||||
if (this._actualShouldRender === ShouldRenderValue.Maybe && !equals(this._renderedCursorPositions, this._cursorPositions, (a, b) => a.lineNumber === b.lineNumber)) {
|
if (this._actualShouldRender === ShouldRenderValue.Maybe && !equals(this._renderedCursorPositions, this._cursorPositions, (a, b) => a.position.lineNumber === b.position.lineNumber && a.color === b.color)) {
|
||||||
this._actualShouldRender = ShouldRenderValue.Needed;
|
this._actualShouldRender = ShouldRenderValue.Needed;
|
||||||
}
|
}
|
||||||
if (this._actualShouldRender === ShouldRenderValue.Maybe) {
|
if (this._actualShouldRender === ShouldRenderValue.Maybe) {
|
||||||
|
@ -443,17 +460,21 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw cursors
|
// Draw cursors
|
||||||
if (!this._settings.hideCursor && this._settings.cursorColor) {
|
if (!this._settings.hideCursor) {
|
||||||
const cursorHeight = (2 * this._settings.pixelRatio) | 0;
|
const cursorHeight = (2 * this._settings.pixelRatio) | 0;
|
||||||
const halfCursorHeight = (cursorHeight / 2) | 0;
|
const halfCursorHeight = (cursorHeight / 2) | 0;
|
||||||
const cursorX = this._settings.x[OverviewRulerLane.Full];
|
const cursorX = this._settings.x[OverviewRulerLane.Full];
|
||||||
const cursorW = this._settings.w[OverviewRulerLane.Full];
|
const cursorW = this._settings.w[OverviewRulerLane.Full];
|
||||||
canvasCtx.fillStyle = this._settings.cursorColor;
|
|
||||||
|
|
||||||
let prevY1 = -100;
|
let prevY1 = -100;
|
||||||
let prevY2 = -100;
|
let prevY2 = -100;
|
||||||
|
let prevColor: string | null = null;
|
||||||
for (let i = 0, len = this._cursorPositions.length; i < len; i++) {
|
for (let i = 0, len = this._cursorPositions.length; i < len; i++) {
|
||||||
const cursor = this._cursorPositions[i];
|
const color = this._cursorPositions[i].color;
|
||||||
|
if (!color) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const cursor = this._cursorPositions[i].position;
|
||||||
|
|
||||||
let yCenter = (viewLayout.getVerticalOffsetForLineNumber(cursor.lineNumber) * heightRatio) | 0;
|
let yCenter = (viewLayout.getVerticalOffsetForLineNumber(cursor.lineNumber) * heightRatio) | 0;
|
||||||
if (yCenter < halfCursorHeight) {
|
if (yCenter < halfCursorHeight) {
|
||||||
|
@ -464,9 +485,9 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
const y1 = yCenter - halfCursorHeight;
|
const y1 = yCenter - halfCursorHeight;
|
||||||
const y2 = y1 + cursorHeight;
|
const y2 = y1 + cursorHeight;
|
||||||
|
|
||||||
if (y1 > prevY2 + 1) {
|
if (y1 > prevY2 + 1 || color !== prevColor) {
|
||||||
// flush prev
|
// flush prev
|
||||||
if (i !== 0) {
|
if (i !== 0 && prevColor) {
|
||||||
canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1);
|
canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1);
|
||||||
}
|
}
|
||||||
prevY1 = y1;
|
prevY1 = y1;
|
||||||
|
@ -477,8 +498,12 @@ export class DecorationsOverviewRuler extends ViewPart {
|
||||||
prevY2 = y2;
|
prevY2 = y2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
prevColor = color;
|
||||||
|
canvasCtx.fillStyle = color;
|
||||||
|
}
|
||||||
|
if (prevColor) {
|
||||||
|
canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1);
|
||||||
}
|
}
|
||||||
canvasCtx.fillRect(cursorX, prevY1, cursorW, prevY2 - prevY1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._settings.renderBorder && this._settings.borderColor && this._settings.overviewRulerLanes > 0) {
|
if (this._settings.renderBorder && this._settings.borderColor && this._settings.overviewRulerLanes > 0) {
|
||||||
|
|
|
@ -35,6 +35,12 @@ class ViewCursorRenderData {
|
||||||
) { }
|
) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum CursorPlurality {
|
||||||
|
Single,
|
||||||
|
MultiPrimary,
|
||||||
|
MultiSecondary,
|
||||||
|
}
|
||||||
|
|
||||||
export class ViewCursor {
|
export class ViewCursor {
|
||||||
private readonly _context: ViewContext;
|
private readonly _context: ViewContext;
|
||||||
private readonly _domNode: FastDomNode<HTMLElement>;
|
private readonly _domNode: FastDomNode<HTMLElement>;
|
||||||
|
@ -47,11 +53,12 @@ export class ViewCursor {
|
||||||
private _isVisible: boolean;
|
private _isVisible: boolean;
|
||||||
|
|
||||||
private _position: Position;
|
private _position: Position;
|
||||||
|
private _pluralityClass: string;
|
||||||
|
|
||||||
private _lastRenderedContent: string;
|
private _lastRenderedContent: string;
|
||||||
private _renderData: ViewCursorRenderData | null;
|
private _renderData: ViewCursorRenderData | null;
|
||||||
|
|
||||||
constructor(context: ViewContext) {
|
constructor(context: ViewContext, plurality: CursorPlurality) {
|
||||||
this._context = context;
|
this._context = context;
|
||||||
const options = this._context.configuration.options;
|
const options = this._context.configuration.options;
|
||||||
const fontInfo = options.get(EditorOption.fontInfo);
|
const fontInfo = options.get(EditorOption.fontInfo);
|
||||||
|
@ -73,6 +80,8 @@ export class ViewCursor {
|
||||||
this._domNode.setDisplay('none');
|
this._domNode.setDisplay('none');
|
||||||
|
|
||||||
this._position = new Position(1, 1);
|
this._position = new Position(1, 1);
|
||||||
|
this._pluralityClass = '';
|
||||||
|
this.setPlurality(plurality);
|
||||||
|
|
||||||
this._lastRenderedContent = '';
|
this._lastRenderedContent = '';
|
||||||
this._renderData = null;
|
this._renderData = null;
|
||||||
|
@ -86,6 +95,23 @@ export class ViewCursor {
|
||||||
return this._position;
|
return this._position;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public setPlurality(plurality: CursorPlurality) {
|
||||||
|
switch (plurality) {
|
||||||
|
default:
|
||||||
|
case CursorPlurality.Single:
|
||||||
|
this._pluralityClass = '';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CursorPlurality.MultiPrimary:
|
||||||
|
this._pluralityClass = 'cursor-primary';
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CursorPlurality.MultiSecondary:
|
||||||
|
this._pluralityClass = 'cursor-secondary';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public show(): void {
|
public show(): void {
|
||||||
if (!this._isVisible) {
|
if (!this._isVisible) {
|
||||||
this._domNode.setVisibility('inherit');
|
this._domNode.setVisibility('inherit');
|
||||||
|
@ -229,7 +255,7 @@ export class ViewCursor {
|
||||||
this._domNode.domNode.textContent = this._lastRenderedContent;
|
this._domNode.domNode.textContent = this._lastRenderedContent;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._domNode.setClassName(`cursor ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ${this._renderData.textContentClassName}`);
|
this._domNode.setClassName(`cursor ${this._pluralityClass} ${MOUSE_CURSOR_TEXT_CSS_CLASS_NAME} ${this._renderData.textContentClassName}`);
|
||||||
|
|
||||||
this._domNode.setDisplay('block');
|
this._domNode.setDisplay('block');
|
||||||
this._domNode.setTop(this._renderData.top);
|
this._domNode.setTop(this._renderData.top);
|
||||||
|
|
|
@ -7,10 +7,14 @@ import 'vs/css!./viewCursors';
|
||||||
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
|
||||||
import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async';
|
import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async';
|
||||||
import { ViewPart } from 'vs/editor/browser/view/viewPart';
|
import { ViewPart } from 'vs/editor/browser/view/viewPart';
|
||||||
import { IViewCursorRenderData, ViewCursor } from 'vs/editor/browser/viewParts/viewCursors/viewCursor';
|
import { IViewCursorRenderData, ViewCursor, CursorPlurality } from 'vs/editor/browser/viewParts/viewCursors/viewCursor';
|
||||||
import { TextEditorCursorBlinkingStyle, TextEditorCursorStyle, EditorOption } from 'vs/editor/common/config/editorOptions';
|
import { TextEditorCursorBlinkingStyle, TextEditorCursorStyle, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||||
import { Position } from 'vs/editor/common/core/position';
|
import { Position } from 'vs/editor/common/core/position';
|
||||||
import { editorCursorBackground, editorCursorForeground } from 'vs/editor/common/core/editorColorRegistry';
|
import {
|
||||||
|
editorCursorBackground, editorCursorForeground,
|
||||||
|
editorMultiCursorPrimaryForeground, editorMultiCursorPrimaryBackground,
|
||||||
|
editorMultiCursorSecondaryForeground, editorMultiCursorSecondaryBackground
|
||||||
|
} from 'vs/editor/common/core/editorColorRegistry';
|
||||||
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/browser/view/renderingContext';
|
||||||
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
import { ViewContext } from 'vs/editor/common/viewModel/viewContext';
|
||||||
import * as viewEvents from 'vs/editor/common/viewEvents';
|
import * as viewEvents from 'vs/editor/common/viewEvents';
|
||||||
|
@ -57,7 +61,7 @@ export class ViewCursors extends ViewPart {
|
||||||
|
|
||||||
this._isVisible = false;
|
this._isVisible = false;
|
||||||
|
|
||||||
this._primaryCursor = new ViewCursor(this._context);
|
this._primaryCursor = new ViewCursor(this._context, CursorPlurality.Single);
|
||||||
this._secondaryCursors = [];
|
this._secondaryCursors = [];
|
||||||
this._renderData = [];
|
this._renderData = [];
|
||||||
|
|
||||||
|
@ -88,6 +92,7 @@ export class ViewCursors extends ViewPart {
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- begin event handlers
|
// --- begin event handlers
|
||||||
|
|
||||||
public override onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean {
|
public override onCompositionStart(e: viewEvents.ViewCompositionStartEvent): boolean {
|
||||||
this._isComposingInput = true;
|
this._isComposingInput = true;
|
||||||
this._updateBlinking();
|
this._updateBlinking();
|
||||||
|
@ -120,6 +125,7 @@ export class ViewCursors extends ViewPart {
|
||||||
this._secondaryCursors.length !== secondaryPositions.length
|
this._secondaryCursors.length !== secondaryPositions.length
|
||||||
|| (this._cursorSmoothCaretAnimation === 'explicit' && reason !== CursorChangeReason.Explicit)
|
|| (this._cursorSmoothCaretAnimation === 'explicit' && reason !== CursorChangeReason.Explicit)
|
||||||
);
|
);
|
||||||
|
this._primaryCursor.setPlurality(secondaryPositions.length ? CursorPlurality.MultiPrimary : CursorPlurality.Single);
|
||||||
this._primaryCursor.onCursorPositionChanged(position, pauseAnimation);
|
this._primaryCursor.onCursorPositionChanged(position, pauseAnimation);
|
||||||
this._updateBlinking();
|
this._updateBlinking();
|
||||||
|
|
||||||
|
@ -127,7 +133,7 @@ export class ViewCursors extends ViewPart {
|
||||||
// Create new cursors
|
// Create new cursors
|
||||||
const addCnt = secondaryPositions.length - this._secondaryCursors.length;
|
const addCnt = secondaryPositions.length - this._secondaryCursors.length;
|
||||||
for (let i = 0; i < addCnt; i++) {
|
for (let i = 0; i < addCnt; i++) {
|
||||||
const newCursor = new ViewCursor(this._context);
|
const newCursor = new ViewCursor(this._context, CursorPlurality.MultiSecondary);
|
||||||
this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling);
|
this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling);
|
||||||
this._secondaryCursors.push(newCursor);
|
this._secondaryCursors.push(newCursor);
|
||||||
}
|
}
|
||||||
|
@ -160,7 +166,6 @@ export class ViewCursors extends ViewPart {
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
|
public override onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean {
|
||||||
// true for inline decorations that can end up relayouting text
|
// true for inline decorations that can end up relayouting text
|
||||||
return true;
|
return true;
|
||||||
|
@ -263,6 +268,7 @@ export class ViewCursors extends ViewPart {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- end blinking logic
|
// --- end blinking logic
|
||||||
|
|
||||||
private _updateDomClassName(): void {
|
private _updateDomClassName(): void {
|
||||||
|
@ -375,16 +381,29 @@ export class ViewCursors extends ViewPart {
|
||||||
}
|
}
|
||||||
|
|
||||||
registerThemingParticipant((theme, collector) => {
|
registerThemingParticipant((theme, collector) => {
|
||||||
const caret = theme.getColor(editorCursorForeground);
|
type CursorTheme = {
|
||||||
if (caret) {
|
foreground: string;
|
||||||
let caretBackground = theme.getColor(editorCursorBackground);
|
background: string;
|
||||||
if (!caretBackground) {
|
class: string;
|
||||||
caretBackground = caret.opposite();
|
};
|
||||||
}
|
|
||||||
collector.addRule(`.monaco-editor .cursors-layer .cursor { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`);
|
const cursorThemes: CursorTheme[] = [
|
||||||
if (isHighContrast(theme.type)) {
|
{ class: '.cursor', foreground: editorCursorForeground, background: editorCursorBackground },
|
||||||
collector.addRule(`.monaco-editor .cursors-layer.has-selection .cursor { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`);
|
{ class: '.cursor-primary', foreground: editorMultiCursorPrimaryForeground, background: editorMultiCursorPrimaryBackground },
|
||||||
|
{ class: '.cursor-secondary', foreground: editorMultiCursorSecondaryForeground, background: editorMultiCursorSecondaryBackground },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const cursorTheme of cursorThemes) {
|
||||||
|
const caret = theme.getColor(cursorTheme.foreground);
|
||||||
|
if (caret) {
|
||||||
|
let caretBackground = theme.getColor(cursorTheme.background);
|
||||||
|
if (!caretBackground) {
|
||||||
|
caretBackground = caret.opposite();
|
||||||
|
}
|
||||||
|
collector.addRule(`.monaco-editor .cursors-layer ${cursorTheme.class} { background-color: ${caret}; border-color: ${caret}; color: ${caretBackground}; }`);
|
||||||
|
if (isHighContrast(theme.type)) {
|
||||||
|
collector.addRule(`.monaco-editor .cursors-layer.has-selection ${cursorTheme.class} { border-left: 1px solid ${caretBackground}; border-right: 1px solid ${caretBackground}; }`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -20,6 +20,10 @@ export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlight
|
||||||
|
|
||||||
export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hcDark: Color.white, hcLight: '#0F4A85' }, nls.localize('caret', 'Color of the editor cursor.'));
|
export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hcDark: Color.white, hcLight: '#0F4A85' }, nls.localize('caret', 'Color of the editor cursor.'));
|
||||||
export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.'));
|
export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.'));
|
||||||
|
export const editorMultiCursorPrimaryForeground = registerColor('editorMultiCursor.primary.foreground', { dark: editorCursorForeground, light: editorCursorForeground, hcDark: editorCursorForeground, hcLight: editorCursorForeground }, nls.localize('editorMultiCursorPrimaryForeground', 'Color of the primary editor cursor when multiple cursors are present.'));
|
||||||
|
export const editorMultiCursorPrimaryBackground = registerColor('editorMultiCursor.primary.background', { dark: editorCursorBackground, light: editorCursorBackground, hcDark: editorCursorBackground, hcLight: editorCursorBackground }, nls.localize('editorMultiCursorPrimaryBackground', 'The background color of the primary editor cursor when multiple cursors are present. Allows customizing the color of a character overlapped by a block cursor.'));
|
||||||
|
export const editorMultiCursorSecondaryForeground = registerColor('editorMultiCursor.secondary.foreground', { dark: editorCursorForeground, light: editorCursorForeground, hcDark: editorCursorForeground, hcLight: editorCursorForeground }, nls.localize('editorMultiCursorSecondaryForeground', 'Color of secondary editor cursors when multiple cursors are present.'));
|
||||||
|
export const editorMultiCursorSecondaryBackground = registerColor('editorMultiCursor.secondary.background', { dark: editorCursorBackground, light: editorCursorBackground, hcDark: editorCursorBackground, hcLight: editorCursorBackground }, nls.localize('editorMultiCursorSecondaryBackground', 'The background color of secondary editor cursors when multiple cursors are present. Allows customizing the color of a character overlapped by a block cursor.'));
|
||||||
export const editorWhitespaces = registerColor('editorWhitespace.foreground', { dark: '#e3e4e229', light: '#33333333', hcDark: '#e3e4e229', hcLight: '#CCCCCC' }, nls.localize('editorWhitespaces', 'Color of whitespace characters in the editor.'));
|
export const editorWhitespaces = registerColor('editorWhitespace.foreground', { dark: '#e3e4e229', light: '#33333333', hcDark: '#e3e4e229', hcLight: '#CCCCCC' }, nls.localize('editorWhitespaces', 'Color of whitespace characters in the editor.'));
|
||||||
export const editorLineNumbers = registerColor('editorLineNumber.foreground', { dark: '#858585', light: '#237893', hcDark: Color.white, hcLight: '#292929' }, nls.localize('editorLineNumbers', 'Color of editor line numbers.'));
|
export const editorLineNumbers = registerColor('editorLineNumber.foreground', { dark: '#858585', light: '#237893', hcDark: Color.white, hcLight: '#292929' }, nls.localize('editorLineNumbers', 'Color of editor line numbers.'));
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,9 @@ import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||||
import { editorFindMatchHighlight, editorFindMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
|
import { editorFindMatchHighlight, editorFindMatchHighlightBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { isHighContrast } from 'vs/platform/theme/common/theme';
|
import { isHighContrast } from 'vs/platform/theme/common/theme';
|
||||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||||
import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from '../common/types';
|
import { CodeActionAutoApply, CodeActionFilter, CodeActionItem, CodeActionKind, CodeActionSet, CodeActionTrigger, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types';
|
||||||
import { CodeActionModel, CodeActionsState } from './codeActionModel';
|
import { CodeActionModel, CodeActionsState } from 'vs/editor/contrib/codeAction/browser/codeActionModel';
|
||||||
|
import { HierarchicalKind } from 'vs/base/common/hierarchicalKind';
|
||||||
|
|
||||||
|
|
||||||
interface IActionShowOptions {
|
interface IActionShowOptions {
|
||||||
|
@ -291,7 +292,22 @@ export class CodeActionController extends Disposable implements IEditorContribut
|
||||||
if (token.isCancellationRequested) {
|
if (token.isCancellationRequested) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return { canPreview: !!action.action.edit?.edits.length };
|
|
||||||
|
let canPreview = false;
|
||||||
|
const actionKind = action.action.kind;
|
||||||
|
|
||||||
|
if (actionKind) {
|
||||||
|
const hierarchicalKind = new HierarchicalKind(actionKind);
|
||||||
|
const refactorKinds = [
|
||||||
|
CodeActionKind.RefactorExtract,
|
||||||
|
CodeActionKind.RefactorInline,
|
||||||
|
CodeActionKind.RefactorRewrite
|
||||||
|
];
|
||||||
|
|
||||||
|
canPreview = refactorKinds.some(refactorKind => refactorKind.contains(hierarchicalKind));
|
||||||
|
}
|
||||||
|
|
||||||
|
return { canPreview: canPreview || !!action.action.edit?.edits.length };
|
||||||
},
|
},
|
||||||
onFocus: (action: CodeActionItem | undefined) => {
|
onFocus: (action: CodeActionItem | undefined) => {
|
||||||
if (action && action.action) {
|
if (action && action.action) {
|
||||||
|
|
|
@ -545,9 +545,10 @@ suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => {
|
||||||
|
|
||||||
// Failing tests from issues...
|
// Failing tests from issues...
|
||||||
|
|
||||||
test.skip('issue #116843: indent after arrow function', () => {
|
test.skip('issue #208215: indent after arrow function', () => {
|
||||||
|
|
||||||
// https://github.com/microsoft/vscode/issues/116843
|
// https://github.com/microsoft/vscode/issues/208215
|
||||||
|
// consider the regex: /^\s*(var|const|let)\s+\w+\s*=\s*\(.*\)\s*=>\s*$/
|
||||||
|
|
||||||
const model = createTextModel("", languageId, {});
|
const model = createTextModel("", languageId, {});
|
||||||
disposables.add(model);
|
disposables.add(model);
|
||||||
|
@ -562,11 +563,53 @@ suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => {
|
||||||
'const add1 = (n) =>',
|
'const add1 = (n) =>',
|
||||||
' ',
|
' ',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
viewModel.type('n + 1;');
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skip('issue #208215: outdented after semicolon detected after arrow function', () => {
|
||||||
|
|
||||||
|
// Notes: we want to outdent after having detected a semi-colon which marks the end of the line, but only when we have detected an arrow function
|
||||||
|
// We could use one outdent pattern corresponding per indent pattern, and not a generic outdent and indent pattern
|
||||||
|
|
||||||
|
const model = createTextModel([
|
||||||
|
'const add1 = (n) =>',
|
||||||
|
' console.log("hi");',
|
||||||
|
].join('\n'), languageId, {});
|
||||||
|
disposables.add(model);
|
||||||
|
|
||||||
|
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
|
||||||
|
|
||||||
|
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
|
||||||
|
|
||||||
|
editor.setSelection(new Selection(2, 24, 2, 24));
|
||||||
viewModel.type("\n", 'keyboard');
|
viewModel.type("\n", 'keyboard');
|
||||||
assert.strictEqual(model.getValue(), [
|
assert.strictEqual(model.getValue(), [
|
||||||
'const add1 = (n) =>',
|
'const add1 = (n) =>',
|
||||||
' n + 1;',
|
' console.log("hi");',
|
||||||
|
'',
|
||||||
|
].join('\n'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test.skip('issue #116843: indent after arrow function', () => {
|
||||||
|
|
||||||
|
// https://github.com/microsoft/vscode/issues/116843
|
||||||
|
|
||||||
|
const model = createTextModel("", languageId, {});
|
||||||
|
disposables.add(model);
|
||||||
|
|
||||||
|
withTestCodeEditor(model, { autoIndent: "full" }, (editor, viewModel, instantiationService) => {
|
||||||
|
|
||||||
|
registerLanguage(instantiationService, languageId, Language.TypeScript, disposables);
|
||||||
|
|
||||||
|
viewModel.type([
|
||||||
|
'const add1 = (n) =>',
|
||||||
|
' n + 1;',
|
||||||
|
].join('\n'));
|
||||||
|
viewModel.type("\n", 'keyboard');
|
||||||
|
assert.strictEqual(model.getValue(), [
|
||||||
|
'const add1 = (n) =>',
|
||||||
|
' n + 1;',
|
||||||
'',
|
'',
|
||||||
].join('\n'));
|
].join('\n'));
|
||||||
});
|
});
|
||||||
|
@ -632,6 +675,7 @@ suite('`Full` Auto Indent On Type - TypeScript/JavaScript', () => {
|
||||||
test.skip('issue #43244: incorrect indentation', () => {
|
test.skip('issue #43244: incorrect indentation', () => {
|
||||||
|
|
||||||
// https://github.com/microsoft/vscode/issues/43244
|
// https://github.com/microsoft/vscode/issues/43244
|
||||||
|
// potential regex to fix: "^.*[if|while|for]\s*\(.*\)\s*",
|
||||||
|
|
||||||
const model = createTextModel([
|
const model = createTextModel([
|
||||||
'function f() {',
|
'function f() {',
|
||||||
|
|
|
@ -237,9 +237,9 @@ export class LinkDetector extends Disposable implements IEditorContribution {
|
||||||
const fsPath = resources.originalFSPath(parsedUri);
|
const fsPath = resources.originalFSPath(parsedUri);
|
||||||
|
|
||||||
let relativePath: string | null = null;
|
let relativePath: string | null = null;
|
||||||
if (fsPath.startsWith('/./')) {
|
if (fsPath.startsWith('/./') || fsPath.startsWith('\\.\\')) {
|
||||||
relativePath = `.${fsPath.substr(1)}`;
|
relativePath = `.${fsPath.substr(1)}`;
|
||||||
} else if (fsPath.startsWith('//./')) {
|
} else if (fsPath.startsWith('//./') || fsPath.startsWith('\\\\.\\')) {
|
||||||
relativePath = `.${fsPath.substr(2)}`;
|
relativePath = `.${fsPath.substr(2)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||||
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
import { IEditorProgressService } from 'vs/platform/progress/common/progress';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { CONTEXT_RENAME_INPUT_VISIBLE, NewNameSource, RenameInputField, RenameInputFieldResult } from './renameInputField';
|
import { CONTEXT_RENAME_INPUT_VISIBLE, NewNameSource, RenameInputFieldResult, RenameWidget } from './renameInputField';
|
||||||
|
|
||||||
class RenameSkeleton {
|
class RenameSkeleton {
|
||||||
|
|
||||||
|
@ -138,7 +138,7 @@ class RenameController implements IEditorContribution {
|
||||||
return editor.getContribution<RenameController>(RenameController.ID);
|
return editor.getContribution<RenameController>(RenameController.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly _renameInputField: RenameInputField;
|
private readonly _renameInputField: RenameWidget;
|
||||||
private readonly _disposableStore = new DisposableStore();
|
private readonly _disposableStore = new DisposableStore();
|
||||||
private _cts: CancellationTokenSource = new CancellationTokenSource();
|
private _cts: CancellationTokenSource = new CancellationTokenSource();
|
||||||
|
|
||||||
|
@ -153,7 +153,7 @@ class RenameController implements IEditorContribution {
|
||||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||||
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
@ITelemetryService private readonly _telemetryService: ITelemetryService,
|
||||||
) {
|
) {
|
||||||
this._renameInputField = this._disposableStore.add(this._instaService.createInstance(RenameInputField, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview']));
|
this._renameInputField = this._disposableStore.add(this._instaService.createInstance(RenameWidget, this.editor, ['acceptRenameInput', 'acceptRenameInputWithPreview']));
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
|
@ -229,23 +229,19 @@ class RenameController implements IEditorContribution {
|
||||||
|
|
||||||
const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled
|
const model = this.editor.getModel(); // @ulugbekna: assumes editor still has a model, otherwise, cts1 should've been cancelled
|
||||||
|
|
||||||
const renameCandidatesCts = new CancellationTokenSource(cts2.token);
|
|
||||||
const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model);
|
const newSymbolNamesProviders = this._languageFeaturesService.newSymbolNamesProvider.all(model);
|
||||||
const newSymbolNameProvidersResults = newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, renameCandidatesCts.token));
|
|
||||||
trace(`requested new symbol names from ${newSymbolNamesProviders.length} providers`);
|
|
||||||
|
|
||||||
const selection = this.editor.getSelection();
|
const requestRenameSuggestions = (cts: CancellationToken) => newSymbolNamesProviders.map(p => p.provideNewSymbolNames(model, loc.range, cts));
|
||||||
let selectionStart = 0;
|
|
||||||
let selectionEnd = loc.text.length;
|
|
||||||
|
|
||||||
if (!Range.isEmpty(selection) && !Range.spansMultipleLines(selection) && Range.containsRange(loc.range, selection)) {
|
|
||||||
selectionStart = Math.max(0, selection.startColumn - loc.range.startColumn);
|
|
||||||
selectionEnd = Math.min(loc.range.endColumn, selection.endColumn) - loc.range.startColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
trace('creating rename input field and awaiting its result');
|
trace('creating rename input field and awaiting its result');
|
||||||
const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
|
const supportPreview = this._bulkEditService.hasPreviewHandler() && this._configService.getValue<boolean>(this.editor.getModel().uri, 'editor.rename.enablePreview');
|
||||||
const inputFieldResult = await this._renameInputField.getInput(loc.range, loc.text, selectionStart, selectionEnd, supportPreview, newSymbolNameProvidersResults, renameCandidatesCts);
|
const inputFieldResult = await this._renameInputField.getInput(
|
||||||
|
loc.range,
|
||||||
|
loc.text,
|
||||||
|
supportPreview,
|
||||||
|
requestRenameSuggestions,
|
||||||
|
cts2
|
||||||
|
);
|
||||||
trace('received response from rename input field');
|
trace('received response from rename input field');
|
||||||
|
|
||||||
if (newSymbolNamesProviders.length > 0) { // @ulugbekna: we're interested only in telemetry for rename suggestions currently
|
if (newSymbolNamesProviders.length > 0) { // @ulugbekna: we're interested only in telemetry for rename suggestions currently
|
||||||
|
|
|
@ -23,7 +23,7 @@ import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||||
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||||
import { IDimension } from 'vs/editor/common/core/dimension';
|
import { IDimension } from 'vs/editor/common/core/dimension';
|
||||||
import { Position } from 'vs/editor/common/core/position';
|
import { Position } from 'vs/editor/common/core/position';
|
||||||
import { IRange } from 'vs/editor/common/core/range';
|
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||||
import { ScrollType } from 'vs/editor/common/editorCommon';
|
import { ScrollType } from 'vs/editor/common/editorCommon';
|
||||||
import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/common/languages';
|
import { NewSymbolName, NewSymbolNameTag, ProviderResult } from 'vs/editor/common/languages';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
|
@ -85,7 +85,13 @@ interface IRenameInputField {
|
||||||
/**
|
/**
|
||||||
* @returns a `boolean` standing for `shouldFocusEditor`, if user didn't pick a new name, or a {@link RenameInputFieldResult}
|
* @returns a `boolean` standing for `shouldFocusEditor`, if user didn't pick a new name, or a {@link RenameInputFieldResult}
|
||||||
*/
|
*/
|
||||||
getInput(where: IRange, value: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: ProviderResult<NewSymbolName[]>[], cts: CancellationTokenSource): Promise<RenameInputFieldResult | boolean>;
|
getInput(
|
||||||
|
where: IRange,
|
||||||
|
currentName: string,
|
||||||
|
supportPreview: boolean,
|
||||||
|
requestRenameSuggestions: (cts: CancellationToken) => ProviderResult<NewSymbolName[]>[],
|
||||||
|
cts: CancellationTokenSource
|
||||||
|
): Promise<RenameInputFieldResult | boolean>;
|
||||||
|
|
||||||
acceptInput(wantsPreview: boolean): void;
|
acceptInput(wantsPreview: boolean): void;
|
||||||
cancelInput(focusEditor: boolean, caller: string): void;
|
cancelInput(focusEditor: boolean, caller: string): void;
|
||||||
|
@ -94,7 +100,7 @@ interface IRenameInputField {
|
||||||
focusPreviousRenameSuggestion(): void;
|
focusPreviousRenameSuggestion(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class RenameInputField implements IRenameInputField, IContentWidget, IDisposable {
|
export class RenameWidget implements IRenameInputField, IContentWidget, IDisposable {
|
||||||
|
|
||||||
// implement IContentWidget
|
// implement IContentWidget
|
||||||
readonly allowEditorOverflow: boolean = true;
|
readonly allowEditorOverflow: boolean = true;
|
||||||
|
@ -127,6 +133,8 @@ export class RenameInputField implements IRenameInputField, IContentWidget, IDis
|
||||||
*/
|
*/
|
||||||
private _timeBeforeFirstInputFieldEdit: number | undefined;
|
private _timeBeforeFirstInputFieldEdit: number | undefined;
|
||||||
|
|
||||||
|
private _renameCandidateProvidersCts: CancellationTokenSource | undefined;
|
||||||
|
|
||||||
private readonly _visibleContextKey: IContextKey<boolean>;
|
private readonly _visibleContextKey: IContextKey<boolean>;
|
||||||
private readonly _disposables = new DisposableStore();
|
private readonly _disposables = new DisposableStore();
|
||||||
|
|
||||||
|
@ -194,6 +202,9 @@ export class RenameInputField implements IRenameInputField, IContentWidget, IDis
|
||||||
this._isEditingRenameCandidate = true;
|
this._isEditingRenameCandidate = true;
|
||||||
}
|
}
|
||||||
this._timeBeforeFirstInputFieldEdit ??= this._beforeFirstInputFieldEditSW.elapsed();
|
this._timeBeforeFirstInputFieldEdit ??= this._beforeFirstInputFieldEditSW.elapsed();
|
||||||
|
if (this._renameCandidateProvidersCts?.token.isCancellationRequested === false) {
|
||||||
|
this._renameCandidateProvidersCts.cancel();
|
||||||
|
}
|
||||||
this._renameCandidateListView?.clearFocus();
|
this._renameCandidateListView?.clearFocus();
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -346,7 +357,19 @@ export class RenameInputField implements IRenameInputField, IContentWidget, IDis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getInput(where: IRange, currentName: string, selectionStart: number, selectionEnd: number, supportPreview: boolean, candidates: ProviderResult<NewSymbolName[]>[], cts: CancellationTokenSource): Promise<RenameInputFieldResult | boolean> {
|
getInput(
|
||||||
|
where: IRange,
|
||||||
|
currentName: string,
|
||||||
|
supportPreview: boolean,
|
||||||
|
requestRenameSuggestions: (cts: CancellationToken) => ProviderResult<NewSymbolName[]>[],
|
||||||
|
cts: CancellationTokenSource
|
||||||
|
): Promise<RenameInputFieldResult | boolean> {
|
||||||
|
|
||||||
|
const { start: selectionStart, end: selectionEnd } = this._getSelection(where, currentName);
|
||||||
|
|
||||||
|
this._renameCandidateProvidersCts = new CancellationTokenSource();
|
||||||
|
const candidates = requestRenameSuggestions(this._renameCandidateProvidersCts.token);
|
||||||
|
this._updateRenameCandidates(candidates, currentName, cts.token);
|
||||||
|
|
||||||
this._isEditingRenameCandidate = false;
|
this._isEditingRenameCandidate = false;
|
||||||
|
|
||||||
|
@ -365,8 +388,12 @@ export class RenameInputField implements IRenameInputField, IContentWidget, IDis
|
||||||
const disposeOnDone = new DisposableStore();
|
const disposeOnDone = new DisposableStore();
|
||||||
|
|
||||||
disposeOnDone.add(toDisposable(() => cts.dispose(true))); // @ulugbekna: this may result in `this.cancelInput` being called twice, but it should be safe since we set it to undefined after 1st call
|
disposeOnDone.add(toDisposable(() => cts.dispose(true))); // @ulugbekna: this may result in `this.cancelInput` being called twice, but it should be safe since we set it to undefined after 1st call
|
||||||
|
disposeOnDone.add(toDisposable(() => {
|
||||||
this._updateRenameCandidates(candidates, currentName, cts.token);
|
if (this._renameCandidateProvidersCts !== undefined) {
|
||||||
|
this._renameCandidateProvidersCts.dispose(true);
|
||||||
|
this._renameCandidateProvidersCts = undefined;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
const inputResult = new DeferredPromise<RenameInputFieldResult | boolean>();
|
const inputResult = new DeferredPromise<RenameInputFieldResult | boolean>();
|
||||||
|
|
||||||
|
@ -433,6 +460,24 @@ export class RenameInputField implements IRenameInputField, IContentWidget, IDis
|
||||||
return inputResult.p;
|
return inputResult.p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This allows selecting only part of the symbol name in the input field based on the selection in the editor
|
||||||
|
*/
|
||||||
|
private _getSelection(where: IRange, currentName: string): { start: number; end: number } {
|
||||||
|
assertType(this._editor.hasModel());
|
||||||
|
|
||||||
|
const selection = this._editor.getSelection();
|
||||||
|
let start = 0;
|
||||||
|
let end = currentName.length;
|
||||||
|
|
||||||
|
if (!Range.isEmpty(selection) && !Range.spansMultipleLines(selection) && Range.containsRange(where, selection)) {
|
||||||
|
start = Math.max(0, selection.startColumn - where.startColumn);
|
||||||
|
end = Math.min(where.endColumn, selection.endColumn) - where.startColumn;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { start, end };
|
||||||
|
}
|
||||||
|
|
||||||
private _show(): void {
|
private _show(): void {
|
||||||
this._trace('invoking _show');
|
this._trace('invoking _show');
|
||||||
this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth);
|
this._editor.revealLineInCenterIfOutsideViewport(this._position!.lineNumber, ScrollType.Smooth);
|
||||||
|
@ -508,8 +553,8 @@ export class RenameInputField implements IRenameInputField, IContentWidget, IDis
|
||||||
return this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport);
|
return this._editor.getTopForLineNumber(this._position!.lineNumber) - this._editor.getTopForLineNumber(firstLineInViewport);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _trace(...args: any[]) {
|
private _trace(...args: unknown[]) {
|
||||||
this._logService.trace('RenameInputField', ...args);
|
this._logService.trace('RenameWidget', ...args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ export interface IActionDescriptor {
|
||||||
*/
|
*/
|
||||||
label: string;
|
label: string;
|
||||||
/**
|
/**
|
||||||
* Precondition rule.
|
* Precondition rule. The value should be a [context key expression](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts).
|
||||||
*/
|
*/
|
||||||
precondition?: string;
|
precondition?: string;
|
||||||
/**
|
/**
|
||||||
|
|
2
src/vs/monaco.d.ts
vendored
2
src/vs/monaco.d.ts
vendored
|
@ -1251,7 +1251,7 @@ declare namespace monaco.editor {
|
||||||
*/
|
*/
|
||||||
label: string;
|
label: string;
|
||||||
/**
|
/**
|
||||||
* Precondition rule.
|
* Precondition rule. The value should be a [context key expression](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts).
|
||||||
*/
|
*/
|
||||||
precondition?: string;
|
precondition?: string;
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
|
|
||||||
.quick-input-header {
|
.quick-input-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 8px 6px 6px 6px;
|
padding: 8px 6px 2px 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.quick-input-widget.hidden-input .quick-input-header {
|
.quick-input-widget.hidden-input .quick-input-header {
|
||||||
|
@ -323,12 +323,21 @@
|
||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Quick input separators as full-row item */
|
|
||||||
.quick-input-list .quick-input-list-separator-as-item {
|
.quick-input-list .quick-input-list-separator-as-item {
|
||||||
font-weight: 600;
|
padding: 4px 6px;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Quick input separators as full-row item */
|
||||||
|
.quick-input-list .quick-input-list-separator-as-item .label-name {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-input-list .quick-input-list-separator-as-item .label-description {
|
||||||
|
/* Override default description opacity so we don't have a contrast ratio issue. */
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hide border when the item becomes the sticky one */
|
/* Hide border when the item becomes the sticky one */
|
||||||
.quick-input-list .monaco-tree-sticky-row .quick-input-list-entry.quick-input-list-separator-as-item.quick-input-list-separator-border {
|
.quick-input-list .monaco-tree-sticky-row .quick-input-list-entry.quick-input-list-separator-as-item.quick-input-list-separator-border {
|
||||||
border-top-style: none;
|
border-top-style: none;
|
||||||
|
|
|
@ -92,6 +92,10 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the existing selection if there was one.
|
||||||
|
const visibleSelection = visibleQuickAccess?.picker?.valueSelection;
|
||||||
|
const visibleValue = visibleQuickAccess?.picker?.value;
|
||||||
|
|
||||||
// Create a picker for the provider to use with the initial value
|
// Create a picker for the provider to use with the initial value
|
||||||
// and adjust the filtering to exclude the prefix from filtering
|
// and adjust the filtering to exclude the prefix from filtering
|
||||||
const disposables = new DisposableStore();
|
const disposables = new DisposableStore();
|
||||||
|
@ -148,6 +152,11 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||||
// on the onDidHide event.
|
// on the onDidHide event.
|
||||||
picker.show();
|
picker.show();
|
||||||
|
|
||||||
|
// If the previous picker had a selection and the value is unchanged, we should set that in the new picker.
|
||||||
|
if (visibleSelection && visibleValue === value) {
|
||||||
|
picker.valueSelection = visibleSelection;
|
||||||
|
}
|
||||||
|
|
||||||
// Pick mode: return with promise
|
// Pick mode: return with promise
|
||||||
if (pick) {
|
if (pick) {
|
||||||
return pickPromise?.p;
|
return pickPromise?.p;
|
||||||
|
|
|
@ -723,7 +723,15 @@ export class QuickPick<T extends IQuickPickItem> extends QuickInput implements I
|
||||||
return this.ui.keyMods;
|
return this.ui.keyMods;
|
||||||
}
|
}
|
||||||
|
|
||||||
set valueSelection(valueSelection: Readonly<[number, number]>) {
|
get valueSelection() {
|
||||||
|
const selection = this.ui.inputBox.getSelection();
|
||||||
|
if (!selection) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return [selection.start, selection.end];
|
||||||
|
}
|
||||||
|
|
||||||
|
set valueSelection(valueSelection: Readonly<[number, number]> | undefined) {
|
||||||
this._valueSelection = valueSelection;
|
this._valueSelection = valueSelection;
|
||||||
this.valueSelectionUpdated = true;
|
this.valueSelectionUpdated = true;
|
||||||
this.update();
|
this.update();
|
||||||
|
@ -1167,7 +1175,15 @@ export class InputBox extends QuickInput implements IInputBox {
|
||||||
this.update();
|
this.update();
|
||||||
}
|
}
|
||||||
|
|
||||||
set valueSelection(valueSelection: Readonly<[number, number]>) {
|
get valueSelection() {
|
||||||
|
const selection = this.ui.inputBox.getSelection();
|
||||||
|
if (!selection) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return [selection.start, selection.end];
|
||||||
|
}
|
||||||
|
|
||||||
|
set valueSelection(valueSelection: Readonly<[number, number]> | undefined) {
|
||||||
this._valueSelection = valueSelection;
|
this._valueSelection = valueSelection;
|
||||||
this.valueSelectionUpdated = true;
|
this.valueSelectionUpdated = true;
|
||||||
this.update();
|
this.update();
|
||||||
|
|
|
@ -59,6 +59,10 @@ export class QuickInputBox extends Disposable {
|
||||||
this.findInput.inputBox.select(range);
|
this.findInput.inputBox.select(range);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSelection(): IRange | null {
|
||||||
|
return this.findInput.inputBox.getSelection();
|
||||||
|
}
|
||||||
|
|
||||||
isSelectionAtEnd(): boolean {
|
isSelectionAtEnd(): boolean {
|
||||||
return this.findInput.inputBox.isSelectionAtEnd();
|
return this.findInput.inputBox.isSelectionAtEnd();
|
||||||
}
|
}
|
||||||
|
|
|
@ -277,9 +277,8 @@ class QuickPickSeparatorElement extends BaseQuickPickItemElement {
|
||||||
class QuickInputItemDelegate implements IListVirtualDelegate<IQuickPickElement> {
|
class QuickInputItemDelegate implements IListVirtualDelegate<IQuickPickElement> {
|
||||||
getHeight(element: IQuickPickElement): number {
|
getHeight(element: IQuickPickElement): number {
|
||||||
|
|
||||||
if (!element.item) {
|
if (element instanceof QuickPickSeparatorElement) {
|
||||||
// must be a separator
|
return 30;
|
||||||
return 24;
|
|
||||||
}
|
}
|
||||||
return element.saneDetail ? 44 : 22;
|
return element.saneDetail ? 44 : 22;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,17 +20,17 @@ import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||||
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
|
import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart';
|
||||||
import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables';
|
import { AddDynamicVariableAction, IAddDynamicVariableContext } from 'vs/workbench/contrib/chat/browser/contrib/chatDynamicVariables';
|
||||||
import { ChatAgentLocation, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { ChatAgentLocation, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
|
||||||
import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
import { ChatRequestAgentPart } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||||
import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatFollowup, IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers';
|
||||||
|
|
||||||
type AgentData = {
|
interface AgentData {
|
||||||
dispose: () => void;
|
dispose: () => void;
|
||||||
name: string;
|
id: string;
|
||||||
|
extensionId: ExtensionIdentifier;
|
||||||
hasFollowups?: boolean;
|
hasFollowups?: boolean;
|
||||||
};
|
}
|
||||||
|
|
||||||
@extHostNamedCustomer(MainContext.MainThreadChatAgents2)
|
@extHostNamedCustomer(MainContext.MainThreadChatAgents2)
|
||||||
export class MainThreadChatAgents2 extends Disposable implements MainThreadChatAgentsShape2 {
|
export class MainThreadChatAgents2 extends Disposable implements MainThreadChatAgentsShape2 {
|
||||||
|
@ -48,7 +48,6 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
|
||||||
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
|
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
|
||||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||||
@IChatContributionService private readonly _chatContributionService: IChatContributionService,
|
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
|
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents2);
|
||||||
|
@ -59,7 +58,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
this._register(this._chatService.onDidPerformUserAction(e => {
|
this._register(this._chatService.onDidPerformUserAction(e => {
|
||||||
if (typeof e.agentId === 'string') {
|
if (typeof e.agentId === 'string') {
|
||||||
for (const [handle, agent] of this._agents) {
|
for (const [handle, agent] of this._agents) {
|
||||||
if (agent.name === e.agentId) {
|
if (agent.id === e.agentId) {
|
||||||
if (e.action.kind === 'vote') {
|
if (e.action.kind === 'vote') {
|
||||||
this._proxy.$acceptFeedback(handle, e.result ?? {}, e.action.direction);
|
this._proxy.$acceptFeedback(handle, e.result ?? {}, e.action.direction);
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,10 +75,16 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
this._agents.deleteAndDispose(handle);
|
this._agents.deleteAndDispose(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
$registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata, allowDynamic: boolean): void {
|
$registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: { name: string; description: string } | undefined): void {
|
||||||
const staticAgentRegistration = this._chatContributionService.registeredParticipants.find(p => p.extensionId.value === extension.value && p.name === name);
|
const staticAgentRegistration = this._chatAgentService.getAgent(id);
|
||||||
if (!staticAgentRegistration && !allowDynamic) {
|
if (!staticAgentRegistration && !dynamicProps) {
|
||||||
throw new Error(`chatParticipant must be declared in package.json: ${name}`);
|
if (this._chatAgentService.getAgentsByName(id).length) {
|
||||||
|
// Likely some extension authors will not adopt the new ID, so give a hint if they register a
|
||||||
|
// participant by name instead of ID.
|
||||||
|
throw new Error(`chatParticipant must be declared with an ID in package.json. The "id" property may be missing! "${id}"`);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`chatParticipant must be declared in package.json: ${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const impl: IChatAgentImplementation = {
|
const impl: IChatAgentImplementation = {
|
||||||
|
@ -107,10 +112,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
};
|
};
|
||||||
|
|
||||||
let disposable: IDisposable;
|
let disposable: IDisposable;
|
||||||
if (!staticAgentRegistration && allowDynamic) {
|
if (!staticAgentRegistration && dynamicProps) {
|
||||||
disposable = this._chatAgentService.registerDynamicAgent(
|
disposable = this._chatAgentService.registerDynamicAgent(
|
||||||
{
|
{
|
||||||
id: name,
|
id,
|
||||||
|
name: dynamicProps.name,
|
||||||
|
description: dynamicProps.description,
|
||||||
extensionId: extension,
|
extensionId: extension,
|
||||||
metadata: revive(metadata),
|
metadata: revive(metadata),
|
||||||
slashCommands: [],
|
slashCommands: [],
|
||||||
|
@ -118,11 +125,12 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
},
|
},
|
||||||
impl);
|
impl);
|
||||||
} else {
|
} else {
|
||||||
disposable = this._chatAgentService.registerAgent(name, impl);
|
disposable = this._chatAgentService.registerAgentImplementation(id, impl);
|
||||||
}
|
}
|
||||||
|
|
||||||
this._agents.set(handle, {
|
this._agents.set(handle, {
|
||||||
name,
|
id: id,
|
||||||
|
extensionId: extension,
|
||||||
dispose: disposable.dispose,
|
dispose: disposable.dispose,
|
||||||
hasFollowups: metadata.hasFollowups
|
hasFollowups: metadata.hasFollowups
|
||||||
});
|
});
|
||||||
|
@ -134,7 +142,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
throw new Error(`No agent with handle ${handle} registered`);
|
throw new Error(`No agent with handle ${handle} registered`);
|
||||||
}
|
}
|
||||||
data.hasFollowups = metadataUpdate.hasFollowups;
|
data.hasFollowups = metadataUpdate.hasFollowups;
|
||||||
this._chatAgentService.updateAgent(data.name, revive(metadataUpdate));
|
this._chatAgentService.updateAgent(data.id, revive(metadataUpdate));
|
||||||
}
|
}
|
||||||
|
|
||||||
async $handleProgressChunk(requestId: string, progress: IChatProgressDto): Promise<number | void> {
|
async $handleProgressChunk(requestId: string, progress: IChatProgressDto): Promise<number | void> {
|
||||||
|
@ -162,8 +170,8 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA
|
||||||
|
|
||||||
const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue()).parts;
|
const parsedRequest = this._instantiationService.createInstance(ChatRequestParser).parseChatRequest(widget.viewModel.sessionId, model.getValue()).parts;
|
||||||
const agentPart = parsedRequest.find((part): part is ChatRequestAgentPart => part instanceof ChatRequestAgentPart);
|
const agentPart = parsedRequest.find((part): part is ChatRequestAgentPart => part instanceof ChatRequestAgentPart);
|
||||||
const thisAgentName = this._agents.get(handle)?.name;
|
const thisAgentId = this._agents.get(handle)?.id;
|
||||||
if (agentPart?.agent.id !== thisAgentName) {
|
if (agentPart?.agent.id !== thisAgentId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1427,10 +1427,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||||
checkProposedApiEnabled(extension, 'mappedEditsProvider');
|
checkProposedApiEnabled(extension, 'mappedEditsProvider');
|
||||||
return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider);
|
return extHostLanguageFeatures.registerMappedEditsProvider(extension, selector, provider);
|
||||||
},
|
},
|
||||||
createChatParticipant(name: string, handler: vscode.ChatExtendedRequestHandler) {
|
createChatParticipant(id: string, handler: vscode.ChatExtendedRequestHandler) {
|
||||||
checkProposedApiEnabled(extension, 'chatParticipant');
|
checkProposedApiEnabled(extension, 'chatParticipant');
|
||||||
return extHostChatAgents2.createChatAgent(extension, name, handler);
|
return extHostChatAgents2.createChatAgent(extension, id, handler);
|
||||||
},
|
},
|
||||||
|
createDynamicChatParticipant(id: string, name: string, description: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant {
|
||||||
|
checkProposedApiEnabled(extension, 'chatParticipantAdditions');
|
||||||
|
return extHostChatAgents2.createDynamicChatAgent(extension, id, name, description, handler);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// namespace: lm
|
// namespace: lm
|
||||||
|
|
|
@ -1209,7 +1209,7 @@ export interface IExtensionChatAgentMetadata extends Dto<IChatAgentMetadata> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainThreadChatAgentsShape2 extends IDisposable {
|
export interface MainThreadChatAgentsShape2 extends IDisposable {
|
||||||
$registerAgent(handle: number, extension: ExtensionIdentifier, name: string, metadata: IExtensionChatAgentMetadata, allowDynamic: boolean): void;
|
$registerAgent(handle: number, extension: ExtensionIdentifier, id: string, metadata: IExtensionChatAgentMetadata, dynamicProps: { name: string; description: string } | undefined): void;
|
||||||
$registerAgentCompletionsProvider(handle: number, triggerCharacters: string[]): void;
|
$registerAgentCompletionsProvider(handle: number, triggerCharacters: string[]): void;
|
||||||
$unregisterAgentCompletionsProvider(handle: number): void;
|
$unregisterAgentCompletionsProvider(handle: number): void;
|
||||||
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
|
$updateAgent(handle: number, metadataUpdate: IExtensionChatAgentMetadata): void;
|
||||||
|
|
|
@ -122,7 +122,7 @@ class ChatAgentResponseStream {
|
||||||
},
|
},
|
||||||
push(part) {
|
push(part) {
|
||||||
throwIfDone(this.push);
|
throwIfDone(this.push);
|
||||||
const dto = typeConvert.ChatResponsePart.to(part);
|
const dto = typeConvert.ChatResponsePart.to(part, that._commandsConverter, that._sessionDisposables);
|
||||||
_report(dto);
|
_report(dto);
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
@ -166,12 +166,21 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
|
||||||
this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2);
|
this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents2);
|
||||||
}
|
}
|
||||||
|
|
||||||
createChatAgent(extension: IExtensionDescription, name: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant {
|
createChatAgent(extension: IExtensionDescription, id: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant {
|
||||||
const handle = ExtHostChatAgents2._idPool++;
|
const handle = ExtHostChatAgents2._idPool++;
|
||||||
const agent = new ExtHostChatAgent(extension, name, this._proxy, handle, handler);
|
const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler);
|
||||||
this._agents.set(handle, agent);
|
this._agents.set(handle, agent);
|
||||||
|
|
||||||
this._proxy.$registerAgent(handle, extension.identifier, name, {}, isProposedApiEnabled(extension, 'chatParticipantAdditions'));
|
this._proxy.$registerAgent(handle, extension.identifier, id, {}, undefined);
|
||||||
|
return agent.apiAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
createDynamicChatAgent(extension: IExtensionDescription, id: string, name: string, description: string, handler: vscode.ChatExtendedRequestHandler): vscode.ChatParticipant {
|
||||||
|
const handle = ExtHostChatAgents2._idPool++;
|
||||||
|
const agent = new ExtHostChatAgent(extension, id, this._proxy, handle, handler);
|
||||||
|
this._agents.set(handle, agent);
|
||||||
|
|
||||||
|
this._proxy.$registerAgent(handle, extension.identifier, id, {}, { name, description });
|
||||||
return agent.apiAgent;
|
return agent.apiAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,11 +240,11 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
|
||||||
{ ...ehResult, metadata: undefined };
|
{ ...ehResult, metadata: undefined };
|
||||||
|
|
||||||
// REQUEST turn
|
// REQUEST turn
|
||||||
res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), { extensionId: '', name: h.request.agentId }));
|
res.push(new extHostTypes.ChatRequestTurn(h.request.message, h.request.command, h.request.variables.variables.map(typeConvert.ChatAgentResolvedVariable.to), h.request.agentId));
|
||||||
|
|
||||||
// RESPONSE turn
|
// RESPONSE turn
|
||||||
const parts = coalesce(h.response.map(r => typeConvert.ChatResponsePart.fromContent(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: '', name: h.request.agentId }, h.request.command));
|
res.push(new extHostTypes.ChatResponseTurn(parts, result, h.request.agentId, h.request.command));
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -338,7 +347,6 @@ export class ExtHostChatAgents2 implements ExtHostChatAgentsShape2 {
|
||||||
class ExtHostChatAgent {
|
class ExtHostChatAgent {
|
||||||
|
|
||||||
private _followupProvider: vscode.ChatFollowupProvider | undefined;
|
private _followupProvider: vscode.ChatFollowupProvider | undefined;
|
||||||
private _description: string | undefined;
|
|
||||||
private _fullName: string | undefined;
|
private _fullName: string | undefined;
|
||||||
private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined;
|
private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined;
|
||||||
private _isDefault: boolean | undefined;
|
private _isDefault: boolean | undefined;
|
||||||
|
@ -437,7 +445,6 @@ class ExtHostChatAgent {
|
||||||
updateScheduled = true;
|
updateScheduled = true;
|
||||||
queueMicrotask(() => {
|
queueMicrotask(() => {
|
||||||
this._proxy.$updateAgent(this._handle, {
|
this._proxy.$updateAgent(this._handle, {
|
||||||
description: this._description,
|
|
||||||
fullName: this._fullName,
|
fullName: this._fullName,
|
||||||
icon: !this._iconPath ? undefined :
|
icon: !this._iconPath ? undefined :
|
||||||
this._iconPath instanceof URI ? this._iconPath :
|
this._iconPath instanceof URI ? this._iconPath :
|
||||||
|
@ -463,16 +470,9 @@ class ExtHostChatAgent {
|
||||||
|
|
||||||
const that = this;
|
const that = this;
|
||||||
return {
|
return {
|
||||||
get name() {
|
get id() {
|
||||||
return that.id;
|
return that.id;
|
||||||
},
|
},
|
||||||
get description() {
|
|
||||||
return that._description ?? '';
|
|
||||||
},
|
|
||||||
set description(v) {
|
|
||||||
that._description = v;
|
|
||||||
updateMetadataSoon();
|
|
||||||
},
|
|
||||||
get fullName() {
|
get fullName() {
|
||||||
checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
|
checkProposedApiEnabled(that.extension, 'defaultChatParticipant');
|
||||||
return that._fullName ?? that.extension.displayName ?? that.extension.name;
|
return that._fullName ?? that.extension.displayName ?? that.extension.name;
|
||||||
|
|
|
@ -109,8 +109,13 @@ class ChatVariableResolverResponseStream {
|
||||||
},
|
},
|
||||||
push(part) {
|
push(part) {
|
||||||
throwIfDone(this.push);
|
throwIfDone(this.push);
|
||||||
const dto = typeConvert.ChatResponsePart.to(part);
|
|
||||||
_report(dto as IChatVariableResolverProgressDto);
|
if (part instanceof extHostTypes.ChatResponseReferencePart) {
|
||||||
|
_report(typeConvert.ChatResponseReferencePart.to(part));
|
||||||
|
} else if (part instanceof extHostTypes.ChatResponseProgressPart) {
|
||||||
|
_report(typeConvert.ChatResponseProgressPart.to(part));
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -2454,7 +2454,7 @@ export namespace ChatResponseReferencePart {
|
||||||
|
|
||||||
export namespace ChatResponsePart {
|
export namespace ChatResponsePart {
|
||||||
|
|
||||||
export function to(part: vscode.ChatResponsePart): extHostProtocol.IChatProgressDto {
|
export function to(part: vscode.ChatResponsePart, commandsConverter: CommandsConverter, commandDisposables: DisposableStore): extHostProtocol.IChatProgressDto {
|
||||||
if (part instanceof types.ChatResponseMarkdownPart) {
|
if (part instanceof types.ChatResponseMarkdownPart) {
|
||||||
return ChatResponseMarkdownPart.to(part);
|
return ChatResponseMarkdownPart.to(part);
|
||||||
} else if (part instanceof types.ChatResponseAnchorPart) {
|
} else if (part instanceof types.ChatResponseAnchorPart) {
|
||||||
|
@ -2465,6 +2465,8 @@ export namespace ChatResponsePart {
|
||||||
return ChatResponseProgressPart.to(part);
|
return ChatResponseProgressPart.to(part);
|
||||||
} else if (part instanceof types.ChatResponseFileTreePart) {
|
} else if (part instanceof types.ChatResponseFileTreePart) {
|
||||||
return ChatResponseFilesPart.to(part);
|
return ChatResponseFilesPart.to(part);
|
||||||
|
} else if (part instanceof types.ChatResponseCommandButtonPart) {
|
||||||
|
return ChatResponseCommandButtonPart.to(part, commandsConverter, commandDisposables);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
kind: 'content',
|
kind: 'content',
|
||||||
|
@ -2546,7 +2548,7 @@ export namespace ChatResponseProgress {
|
||||||
};
|
};
|
||||||
} else if ('participant' in progress) {
|
} else if ('participant' in progress) {
|
||||||
checkProposedApiEnabled(extension, 'chatParticipantAdditions');
|
checkProposedApiEnabled(extension, 'chatParticipantAdditions');
|
||||||
return { agentName: progress.participant, command: progress.command, kind: 'agentDetection' };
|
return { agentId: progress.participant, command: progress.command, kind: 'agentDetection' };
|
||||||
} else if ('message' in progress) {
|
} else if ('message' in progress) {
|
||||||
return { content: MarkdownString.from(progress.message), kind: 'progressMessage' };
|
return { content: MarkdownString.from(progress.message), kind: 'progressMessage' };
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -4310,7 +4310,7 @@ export class ChatRequestTurn implements vscode.ChatRequestTurn {
|
||||||
readonly prompt: string,
|
readonly prompt: string,
|
||||||
readonly command: string | undefined,
|
readonly command: string | undefined,
|
||||||
readonly variables: vscode.ChatResolvedVariable[],
|
readonly variables: vscode.ChatResolvedVariable[],
|
||||||
readonly participant: { extensionId: string; name: string },
|
readonly participant: string,
|
||||||
) { }
|
) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4319,7 +4319,7 @@ export class ChatResponseTurn implements vscode.ChatResponseTurn {
|
||||||
constructor(
|
constructor(
|
||||||
readonly response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>,
|
readonly response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>,
|
||||||
readonly result: vscode.ChatResult,
|
readonly result: vscode.ChatResult,
|
||||||
readonly participant: { extensionId: string; name: string },
|
readonly participant: string,
|
||||||
readonly command?: string
|
readonly command?: string
|
||||||
) { }
|
) { }
|
||||||
}
|
}
|
||||||
|
|
|
@ -148,7 +148,7 @@ class EntitlementsContribution extends Disposable implements IWorkbenchContribut
|
||||||
}
|
}
|
||||||
|
|
||||||
private async enableEntitlements(session: AuthenticationSession) {
|
private async enableEntitlements(session: AuthenticationSession) {
|
||||||
const isInternal = isInternalTelemetry(this.productService, this.configurationService) ?? true;
|
const isInternal = isInternalTelemetry(this.productService, this.configurationService);
|
||||||
const showAccountsBadge = this.configurationService.inspect<boolean>(accountsBadgeConfigKey).value ?? false;
|
const showAccountsBadge = this.configurationService.inspect<boolean>(accountsBadgeConfigKey).value ?? false;
|
||||||
const showWelcomeView = this.configurationService.inspect<boolean>(chatWelcomeViewConfigKey).value ?? false;
|
const showWelcomeView = this.configurationService.inspect<boolean>(chatWelcomeViewConfigKey).value ?? false;
|
||||||
|
|
||||||
|
|
|
@ -128,7 +128,7 @@ export class ChatSubmitSecondaryAgentEditorAction extends EditorAction2 {
|
||||||
if (widget.getInput().match(/^\s*@/)) {
|
if (widget.getInput().match(/^\s*@/)) {
|
||||||
widget.acceptInput();
|
widget.acceptInput();
|
||||||
} else {
|
} else {
|
||||||
widget.acceptInputWithPrefix(`${chatAgentLeader}${secondaryAgent.id}`);
|
widget.acceptInputWithPrefix(`${chatAgentLeader}${secondaryAgent.name}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable {
|
||||||
executeImmediately: true
|
executeImmediately: true
|
||||||
}, async (prompt, progress) => {
|
}, async (prompt, progress) => {
|
||||||
const defaultAgent = chatAgentService.getDefaultAgent();
|
const defaultAgent = chatAgentService.getDefaultAgent();
|
||||||
const agents = chatAgentService.getRegisteredAgents();
|
const agents = chatAgentService.getAgents();
|
||||||
|
|
||||||
// Report prefix
|
// Report prefix
|
||||||
if (defaultAgent?.metadata.helpTextPrefix) {
|
if (defaultAgent?.metadata.helpTextPrefix) {
|
||||||
|
@ -270,7 +270,7 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable {
|
||||||
const agentWithLeader = `${chatAgentLeader}${a.id}`;
|
const agentWithLeader = `${chatAgentLeader}${a.id}`;
|
||||||
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` };
|
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` };
|
||||||
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
|
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
|
||||||
const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.metadata.description}`;
|
const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.description}`;
|
||||||
const commandText = a.slashCommands.map(c => {
|
const commandText = a.slashCommands.map(c => {
|
||||||
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` };
|
const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` };
|
||||||
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
|
const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg));
|
||||||
|
|
|
@ -112,6 +112,7 @@ export interface IChatWidget {
|
||||||
readonly providerId: string;
|
readonly providerId: string;
|
||||||
readonly supportsFileReferences: boolean;
|
readonly supportsFileReferences: boolean;
|
||||||
readonly parsedInput: IParsedChatRequest;
|
readonly parsedInput: IParsedChatRequest;
|
||||||
|
lastSelectedAgent: IChatAgentData | undefined;
|
||||||
|
|
||||||
getContrib<T extends IChatWidgetContrib>(id: string): T | undefined;
|
getContrib<T extends IChatWidgetContrib>(id: string): T | undefined;
|
||||||
reveal(item: ChatTreeItem): void;
|
reveal(item: ChatTreeItem): void;
|
||||||
|
|
|
@ -3,11 +3,13 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import { isNonEmptyArray } from 'vs/base/common/arrays';
|
||||||
import { Codicon } from 'vs/base/common/codicons';
|
import { Codicon } from 'vs/base/common/codicons';
|
||||||
import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
import { DisposableMap, DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { localize, localize2 } from 'vs/nls';
|
import { localize, localize2 } from 'vs/nls';
|
||||||
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
import { registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
|
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import { IProductService } from 'vs/platform/product/common/productService';
|
import { IProductService } from 'vs/platform/product/common/productService';
|
||||||
|
@ -20,7 +22,8 @@ import { getNewChatAction } from 'vs/workbench/contrib/chat/browser/actions/chat
|
||||||
import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
|
import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions';
|
||||||
import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
|
import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
|
||||||
import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
|
||||||
import { IChatContributionService, IChatParticipantContribution, IChatProviderContribution, IRawChatParticipantContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
import { ChatAgentLocation, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
|
import { IChatContributionService, IChatProviderContribution, IRawChatParticipantContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||||
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
import { isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||||
|
|
||||||
|
@ -64,20 +67,24 @@ const chatExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensi
|
||||||
const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatParticipantContribution[]>({
|
const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawChatParticipantContribution[]>({
|
||||||
extensionPoint: 'chatParticipants',
|
extensionPoint: 'chatParticipants',
|
||||||
jsonSchema: {
|
jsonSchema: {
|
||||||
description: localize('vscode.extension.contributes.chatParticipant', 'Contributes a Chat Participant'),
|
description: localize('vscode.extension.contributes.chatParticipant', 'Contributes a chat participant'),
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
type: 'object',
|
type: 'object',
|
||||||
defaultSnippets: [{ body: { name: '', description: '' } }],
|
defaultSnippets: [{ body: { name: '', description: '' } }],
|
||||||
required: ['name'],
|
required: ['name', 'id'],
|
||||||
properties: {
|
properties: {
|
||||||
|
id: {
|
||||||
|
description: localize('chatParticipantId', "A unique id for this chat participant."),
|
||||||
|
type: 'string'
|
||||||
|
},
|
||||||
name: {
|
name: {
|
||||||
description: localize('chatParticipantName', "Unique name for this Chat Participant."),
|
description: localize('chatParticipantName', "User-facing display name for this chat participant. The user will use '@' with this name to invoke the participant."),
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
description: {
|
description: {
|
||||||
description: localize('chatParticipantDescription', "A description of this Chat Participant, shown in the UI."),
|
description: localize('chatParticipantDescription', "A description of this chat participant, shown in the UI."),
|
||||||
type: 'string'
|
type: 'string'
|
||||||
},
|
},
|
||||||
isDefault: {
|
isDefault: {
|
||||||
|
@ -92,7 +99,7 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
commands: {
|
commands: {
|
||||||
markdownDescription: localize('chatCommandsDescription', "Commands available for this Chat Participant, which the user can invoke with a `/`."),
|
markdownDescription: localize('chatCommandsDescription', "Commands available for this chat participant, which the user can invoke with a `/`."),
|
||||||
type: 'array',
|
type: 'array',
|
||||||
items: {
|
items: {
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
|
@ -131,7 +138,7 @@ const chatParticipantExtensionPoint = extensionsRegistry.ExtensionsRegistry.regi
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
locations: {
|
locations: {
|
||||||
markdownDescription: localize('chatLocationsDescription', "Locations in which this Chat Participant is available."),
|
markdownDescription: localize('chatLocationsDescription', "Locations in which this chat participant is available."),
|
||||||
type: 'array',
|
type: 'array',
|
||||||
default: ['panel'],
|
default: ['panel'],
|
||||||
items: {
|
items: {
|
||||||
|
@ -158,12 +165,14 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
||||||
private _welcomeViewDescriptor?: IViewDescriptor;
|
private _welcomeViewDescriptor?: IViewDescriptor;
|
||||||
private _viewContainer: ViewContainer;
|
private _viewContainer: ViewContainer;
|
||||||
private _registrationDisposables = new Map<string, IDisposable>();
|
private _registrationDisposables = new Map<string, IDisposable>();
|
||||||
|
private _participantRegistrationDisposables = new DisposableMap<string>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IChatContributionService readonly _chatContributionService: IChatContributionService,
|
@IChatContributionService private readonly _chatContributionService: IChatContributionService,
|
||||||
@IProductService readonly productService: IProductService,
|
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
|
||||||
@IContextKeyService readonly contextService: IContextKeyService,
|
@IProductService private readonly productService: IProductService,
|
||||||
@ILogService readonly logService: ILogService,
|
@IContextKeyService private readonly contextService: IContextKeyService,
|
||||||
|
@ILogService private readonly logService: ILogService,
|
||||||
) {
|
) {
|
||||||
this._viewContainer = this.registerViewContainer();
|
this._viewContainer = this.registerViewContainer();
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
|
@ -243,13 +252,34 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._chatContributionService.registerChatParticipant({ ...providerDescriptor, extensionId: extension.description.identifier });
|
if (!providerDescriptor.id || !providerDescriptor.name) {
|
||||||
|
this.logService.error(`Extension '${extension.description.identifier.value}' CANNOT register participant without both id and name.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._participantRegistrationDisposables.set(
|
||||||
|
getParticipantKey(extension.description.identifier, providerDescriptor.name),
|
||||||
|
this._chatAgentService.registerAgent(
|
||||||
|
providerDescriptor.id,
|
||||||
|
{
|
||||||
|
extensionId: extension.description.identifier,
|
||||||
|
id: providerDescriptor.id,
|
||||||
|
description: providerDescriptor.description,
|
||||||
|
metadata: {},
|
||||||
|
name: providerDescriptor.name,
|
||||||
|
isDefault: providerDescriptor.isDefault,
|
||||||
|
defaultImplicitVariables: providerDescriptor.defaultImplicitVariables,
|
||||||
|
locations: isNonEmptyArray(providerDescriptor.locations) ?
|
||||||
|
providerDescriptor.locations.map(ChatAgentLocation.fromRaw) :
|
||||||
|
[ChatAgentLocation.Panel],
|
||||||
|
slashCommands: providerDescriptor.commands ?? []
|
||||||
|
} satisfies IChatAgentData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const extension of delta.removed) {
|
for (const extension of delta.removed) {
|
||||||
for (const providerDescriptor of extension.value) {
|
for (const providerDescriptor of extension.value) {
|
||||||
this._chatContributionService.deregisterChatParticipant({ ...providerDescriptor, extensionId: extension.description.identifier });
|
this._participantRegistrationDisposables.deleteAndDispose(getParticipantKey(extension.description.identifier, providerDescriptor.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -314,15 +344,14 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution {
|
||||||
|
|
||||||
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
|
registerWorkbenchContribution2(ChatExtensionPointHandler.ID, ChatExtensionPointHandler, WorkbenchPhase.BlockStartup);
|
||||||
|
|
||||||
function getParticipantKey(participant: IChatParticipantContribution): string {
|
function getParticipantKey(extensionId: ExtensionIdentifier, participantName: string): string {
|
||||||
return `${participant.extensionId.value}_${participant.name}`;
|
return `${extensionId.value}_${participantName}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChatContributionService implements IChatContributionService {
|
export class ChatContributionService implements IChatContributionService {
|
||||||
declare _serviceBrand: undefined;
|
declare _serviceBrand: undefined;
|
||||||
|
|
||||||
private _registeredProviders = new Map<string, IChatProviderContribution>();
|
private _registeredProviders = new Map<string, IChatProviderContribution>();
|
||||||
private _registeredParticipants = new Map<string, IChatParticipantContribution>();
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
) { }
|
) { }
|
||||||
|
@ -339,19 +368,7 @@ export class ChatContributionService implements IChatContributionService {
|
||||||
this._registeredProviders.delete(providerId);
|
this._registeredProviders.delete(providerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public registerChatParticipant(participant: IChatParticipantContribution): void {
|
|
||||||
this._registeredParticipants.set(getParticipantKey(participant), participant);
|
|
||||||
}
|
|
||||||
|
|
||||||
public deregisterChatParticipant(participant: IChatParticipantContribution): void {
|
|
||||||
this._registeredParticipants.delete(getParticipantKey(participant));
|
|
||||||
}
|
|
||||||
|
|
||||||
public get registeredProviders(): IChatProviderContribution[] {
|
public get registeredProviders(): IChatProviderContribution[] {
|
||||||
return Array.from(this._registeredProviders.values());
|
return Array.from(this._registeredProviders.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
public get registeredParticipants(): IChatParticipantContribution[] {
|
|
||||||
return Array.from(this._registeredParticipants.values());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -359,7 +359,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
|
||||||
private renderDetail(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
|
private renderDetail(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
|
||||||
let progressMsg: string = '';
|
let progressMsg: string = '';
|
||||||
if (element.agent && !element.agent.isDefault) {
|
if (element.agent && !element.agent.isDefault) {
|
||||||
let usingMsg = chatAgentLeader + element.agent.id;
|
let usingMsg = chatAgentLeader + element.agent.name;
|
||||||
if (element.slashCommand) {
|
if (element.slashCommand) {
|
||||||
usingMsg += ` ${chatSubcommandLeader}${element.slashCommand.name}`;
|
usingMsg += ` ${chatSubcommandLeader}${element.slashCommand.name}`;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { Location } from 'vs/editor/common/languages';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { ILabelService } from 'vs/platform/label/common/label';
|
import { ILabelService } from 'vs/platform/label/common/label';
|
||||||
import { ILogService } from 'vs/platform/log/common/log';
|
import { ILogService } from 'vs/platform/log/common/log';
|
||||||
import { ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
import { ChatRequestAgentPart, ChatRequestDynamicVariablePart, ChatRequestTextPart, IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { contentRefUrl } from '../common/annotations';
|
import { contentRefUrl } from '../common/annotations';
|
||||||
|
|
||||||
const variableRefUrl = 'http://_vscodedecoration_';
|
const variableRefUrl = 'http://_vscodedecoration_';
|
||||||
|
@ -31,7 +31,9 @@ export class ChatMarkdownDecorationsRenderer {
|
||||||
} else {
|
} else {
|
||||||
const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI)
|
const uri = part instanceof ChatRequestDynamicVariablePart && part.data.map(d => d.value).find((d): d is URI => d instanceof URI)
|
||||||
|| undefined;
|
|| undefined;
|
||||||
const title = uri ? encodeURIComponent(this.labelService.getUriLabel(uri, { relative: true })) : '';
|
const title = uri ? encodeURIComponent(this.labelService.getUriLabel(uri, { relative: true })) :
|
||||||
|
part instanceof ChatRequestAgentPart ? part.agent.id :
|
||||||
|
'';
|
||||||
|
|
||||||
result += `[${part.text}](${variableRefUrl}?${title})`;
|
result += `[${part.text}](${variableRefUrl}?${title})`;
|
||||||
}
|
}
|
||||||
|
@ -106,4 +108,3 @@ export class ChatMarkdownDecorationsRenderer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||||
private parsedChatRequest: IParsedChatRequest | undefined;
|
private parsedChatRequest: IParsedChatRequest | undefined;
|
||||||
get parsedInput() {
|
get parsedInput() {
|
||||||
if (this.parsedChatRequest === undefined) {
|
if (this.parsedChatRequest === undefined) {
|
||||||
this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel!.sessionId, this.getInput());
|
this.parsedChatRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(this.viewModel!.sessionId, this.getInput(), { selectedAgent: this._lastSelectedAgent });
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.parsedChatRequest;
|
return this.parsedChatRequest;
|
||||||
|
@ -212,6 +212,15 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _lastSelectedAgent: IChatAgentData | undefined;
|
||||||
|
set lastSelectedAgent(agent: IChatAgentData | undefined) {
|
||||||
|
this._lastSelectedAgent = agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
get lastSelectedAgent(): IChatAgentData | undefined {
|
||||||
|
return this._lastSelectedAgent;
|
||||||
|
}
|
||||||
|
|
||||||
get supportsFileReferences(): boolean {
|
get supportsFileReferences(): boolean {
|
||||||
return !!this.viewOptions.supportsFileReferences;
|
return !!this.viewOptions.supportsFileReferences;
|
||||||
}
|
}
|
||||||
|
@ -655,7 +664,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||||
'query' in opts ? opts.query :
|
'query' in opts ? opts.query :
|
||||||
`${opts.prefix} ${editorValue}`;
|
`${opts.prefix} ${editorValue}`;
|
||||||
const isUserQuery = !opts || 'prefix' in opts;
|
const isUserQuery = !opts || 'prefix' in opts;
|
||||||
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, this.inputPart.implicitContextEnabled);
|
const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, this.inputPart.implicitContextEnabled, { selectedAgent: this._lastSelectedAgent });
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
const inputState = this.collectInputState();
|
const inputState = this.collectInputState();
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
|
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||||
import { Position } from 'vs/editor/common/core/position';
|
import { Position } from 'vs/editor/common/core/position';
|
||||||
|
@ -14,7 +15,8 @@ import { CompletionContext, CompletionItem, CompletionItemKind, CompletionList }
|
||||||
import { ITextModel } from 'vs/editor/common/model';
|
import { ITextModel } from 'vs/editor/common/model';
|
||||||
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
|
||||||
import { localize } from 'vs/nls';
|
import { localize } from 'vs/nls';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
|
||||||
|
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
|
import { inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
|
@ -37,8 +39,8 @@ const placeholderDecorationType = 'chat-session-detail';
|
||||||
const slashCommandTextDecorationType = 'chat-session-text';
|
const slashCommandTextDecorationType = 'chat-session-text';
|
||||||
const variableTextDecorationType = 'chat-variable-text';
|
const variableTextDecorationType = 'chat-variable-text';
|
||||||
|
|
||||||
function agentAndCommandToKey(agent: string, subcommand: string | undefined): string {
|
function agentAndCommandToKey(agent: IChatAgentData, subcommand: string | undefined): string {
|
||||||
return subcommand ? `${agent}__${subcommand}` : agent;
|
return subcommand ? `${agent.id}__${subcommand}` : agent.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
class InputEditorDecorations extends Disposable {
|
class InputEditorDecorations extends Disposable {
|
||||||
|
@ -70,7 +72,7 @@ class InputEditorDecorations extends Disposable {
|
||||||
this.updateInputEditorDecorations();
|
this.updateInputEditorDecorations();
|
||||||
}));
|
}));
|
||||||
this._register(this.widget.onDidSubmitAgent((e) => {
|
this._register(this.widget.onDidSubmitAgent((e) => {
|
||||||
this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand?.name));
|
this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent, e.slashCommand?.name));
|
||||||
}));
|
}));
|
||||||
this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations()));
|
this._register(this.chatAgentService.onDidChangeAgents(() => this.updateInputEditorDecorations()));
|
||||||
|
|
||||||
|
@ -135,7 +137,7 @@ class InputEditorDecorations extends Disposable {
|
||||||
},
|
},
|
||||||
renderOptions: {
|
renderOptions: {
|
||||||
after: {
|
after: {
|
||||||
contentText: viewModel.inputPlaceholder ?? defaultAgent?.metadata.description ?? '',
|
contentText: viewModel.inputPlaceholder ?? defaultAgent?.description ?? '',
|
||||||
color: this.getPlaceholderColor()
|
color: this.getPlaceholderColor()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -172,14 +174,14 @@ class InputEditorDecorations extends Disposable {
|
||||||
const onlyAgentAndWhitespace = agentPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart);
|
const onlyAgentAndWhitespace = agentPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart);
|
||||||
if (onlyAgentAndWhitespace) {
|
if (onlyAgentAndWhitespace) {
|
||||||
// Agent reference with no other text - show the placeholder
|
// Agent reference with no other text - show the placeholder
|
||||||
const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, undefined));
|
const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent, undefined));
|
||||||
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentPart.agent.metadata.followupPlaceholder;
|
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentPart.agent.metadata.followupPlaceholder;
|
||||||
if (agentPart.agent.metadata.description && exactlyOneSpaceAfterPart(agentPart)) {
|
if (agentPart.agent.description && exactlyOneSpaceAfterPart(agentPart)) {
|
||||||
placeholderDecoration = [{
|
placeholderDecoration = [{
|
||||||
range: getRangeForPlaceholder(agentPart),
|
range: getRangeForPlaceholder(agentPart),
|
||||||
renderOptions: {
|
renderOptions: {
|
||||||
after: {
|
after: {
|
||||||
contentText: shouldRenderFollowupPlaceholder ? agentPart.agent.metadata.followupPlaceholder : agentPart.agent.metadata.description,
|
contentText: shouldRenderFollowupPlaceholder ? agentPart.agent.metadata.followupPlaceholder : agentPart.agent.description,
|
||||||
color: this.getPlaceholderColor(),
|
color: this.getPlaceholderColor(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -190,7 +192,7 @@ class InputEditorDecorations extends Disposable {
|
||||||
const onlyAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart);
|
const onlyAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart);
|
||||||
if (onlyAgentCommandAndWhitespace) {
|
if (onlyAgentCommandAndWhitespace) {
|
||||||
// Agent reference and subcommand with no other text - show the placeholder
|
// Agent reference and subcommand with no other text - show the placeholder
|
||||||
const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name));
|
const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent, agentSubcommandPart.command.name));
|
||||||
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder;
|
const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder;
|
||||||
if (agentSubcommandPart?.command.description && exactlyOneSpaceAfterPart(agentSubcommandPart)) {
|
if (agentSubcommandPart?.command.description && exactlyOneSpaceAfterPart(agentSubcommandPart)) {
|
||||||
placeholderDecoration = [{
|
placeholderDecoration = [{
|
||||||
|
@ -209,9 +211,10 @@ class InputEditorDecorations extends Disposable {
|
||||||
|
|
||||||
const textDecorations: IDecorationOptions[] | undefined = [];
|
const textDecorations: IDecorationOptions[] | undefined = [];
|
||||||
if (agentPart) {
|
if (agentPart) {
|
||||||
textDecorations.push({ range: agentPart.editorRange });
|
const agentHover = `(${agentPart.agent.id}) ${agentPart.agent.description}`;
|
||||||
|
textDecorations.push({ range: agentPart.editorRange, hoverMessage: new MarkdownString(agentHover) });
|
||||||
if (agentSubcommandPart) {
|
if (agentSubcommandPart) {
|
||||||
textDecorations.push({ range: agentSubcommandPart.editorRange });
|
textDecorations.push({ range: agentSubcommandPart.editorRange, hoverMessage: new MarkdownString(agentSubcommandPart.command.description) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,9 +249,9 @@ class InputEditorSlashCommandMode extends Disposable {
|
||||||
private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) {
|
private async repopulateAgentCommand(agent: IChatAgentData, slashCommand: IChatAgentCommand | undefined) {
|
||||||
let value: string | undefined;
|
let value: string | undefined;
|
||||||
if (slashCommand && slashCommand.isSticky) {
|
if (slashCommand && slashCommand.isSticky) {
|
||||||
value = `${chatAgentLeader}${agent.id} ${chatSubcommandLeader}${slashCommand.name} `;
|
value = `${chatAgentLeader}${agent.name} ${chatSubcommandLeader}${slashCommand.name} `;
|
||||||
} else if (agent.metadata.isSticky) {
|
} else if (agent.metadata.isSticky) {
|
||||||
value = `${chatAgentLeader}${agent.id} `;
|
value = `${chatAgentLeader}${agent.name} `;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
|
@ -347,13 +350,18 @@ class AgentCompletions extends Disposable {
|
||||||
const agents = this.chatAgentService.getAgents()
|
const agents = this.chatAgentService.getAgents()
|
||||||
.filter(a => !a.isDefault);
|
.filter(a => !a.isDefault);
|
||||||
return <CompletionList>{
|
return <CompletionList>{
|
||||||
suggestions: agents.map((c, i) => {
|
suggestions: agents.map((a, i) => {
|
||||||
const withAt = `@${c.id}`;
|
const withAt = `@${a.name}`;
|
||||||
|
const isDupe = !!agents.find(other => other.name === a.name && other.id !== a.id);
|
||||||
return <CompletionItem>{
|
return <CompletionItem>{
|
||||||
label: withAt,
|
// Leading space is important because detail has no space at the start by design
|
||||||
|
label: isDupe ?
|
||||||
|
{ label: withAt, description: a.description, detail: ` (${a.id})` } :
|
||||||
|
withAt,
|
||||||
insertText: `${withAt} `,
|
insertText: `${withAt} `,
|
||||||
detail: c.metadata.description,
|
detail: a.description,
|
||||||
range: new Range(1, 1, 1, 1),
|
range: new Range(1, 1, 1, 1),
|
||||||
|
command: { id: AssignSelectedAgentAction.ID, title: AssignSelectedAgentAction.ID, arguments: [{ agent: a, widget } satisfies AssignSelectedAgentActionArgs] },
|
||||||
kind: CompletionItemKind.Text, // The icons are disabled here anyway
|
kind: CompletionItemKind.Text, // The icons are disabled here anyway
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
|
@ -431,31 +439,37 @@ class AgentCompletions extends Disposable {
|
||||||
const justAgents: CompletionItem[] = agents
|
const justAgents: CompletionItem[] = agents
|
||||||
.filter(a => !a.isDefault)
|
.filter(a => !a.isDefault)
|
||||||
.map(agent => {
|
.map(agent => {
|
||||||
const agentLabel = `${chatAgentLeader}${agent.id}`;
|
const isDupe = !!agents.find(other => other.name === agent.name && other.id !== agent.id);
|
||||||
|
const detail = agent.description;
|
||||||
|
const agentLabel = `${chatAgentLeader}${agent.name} (${agent.id})`;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: { label: agentLabel, description: agent.metadata.description },
|
label: isDupe ?
|
||||||
filterText: `${chatSubcommandLeader}${agent.id}`,
|
{ label: agentLabel, description: agent.description, detail: ` (${agent.id})` } :
|
||||||
|
agentLabel,
|
||||||
|
detail,
|
||||||
|
filterText: `${chatSubcommandLeader}${agent.name}`,
|
||||||
insertText: `${agentLabel} `,
|
insertText: `${agentLabel} `,
|
||||||
range: new Range(1, 1, 1, 1),
|
range: new Range(1, 1, 1, 1),
|
||||||
kind: CompletionItemKind.Text,
|
kind: CompletionItemKind.Text,
|
||||||
sortText: `${chatSubcommandLeader}${agent.id}`,
|
sortText: `${chatSubcommandLeader}${agent.name}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
suggestions: justAgents.concat(
|
suggestions: justAgents.concat(
|
||||||
agents.flatMap(agent => agent.slashCommands.map((c, i) => {
|
agents.flatMap(agent => agent.slashCommands.map((c, i) => {
|
||||||
const agentLabel = `${chatAgentLeader}${agent.id}`;
|
const agentLabel = `${chatAgentLeader}${agent.name}`;
|
||||||
const withSlash = `${chatSubcommandLeader}${c.name}`;
|
const withSlash = `${chatSubcommandLeader}${c.name}`;
|
||||||
return {
|
return {
|
||||||
label: { label: withSlash, description: agentLabel },
|
label: { label: withSlash, description: agentLabel },
|
||||||
filterText: `${chatSubcommandLeader}${agent.id}${c.name}`,
|
filterText: `${chatSubcommandLeader}${agent.name}${c.name}`,
|
||||||
commitCharacters: [' '],
|
commitCharacters: [' '],
|
||||||
insertText: `${agentLabel} ${withSlash} `,
|
insertText: `${agentLabel} ${withSlash} `,
|
||||||
detail: `(${agentLabel}) ${c.description ?? ''}`,
|
detail: `(${agentLabel}) ${c.description ?? ''}`,
|
||||||
range: new Range(1, 1, 1, 1),
|
range: new Range(1, 1, 1, 1),
|
||||||
kind: CompletionItemKind.Text, // The icons are disabled here anyway
|
kind: CompletionItemKind.Text, // The icons are disabled here anyway
|
||||||
sortText: `${chatSubcommandLeader}${agent.id}${c.name}`,
|
sortText: `${chatSubcommandLeader}${agent.name}${c.name}`,
|
||||||
} satisfies CompletionItem;
|
} satisfies CompletionItem;
|
||||||
})))
|
})))
|
||||||
};
|
};
|
||||||
|
@ -465,6 +479,32 @@ class AgentCompletions extends Disposable {
|
||||||
}
|
}
|
||||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AgentCompletions, LifecyclePhase.Eventually);
|
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(AgentCompletions, LifecyclePhase.Eventually);
|
||||||
|
|
||||||
|
interface AssignSelectedAgentActionArgs {
|
||||||
|
agent: IChatAgentData;
|
||||||
|
widget: IChatWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssignSelectedAgentAction extends Action2 {
|
||||||
|
static readonly ID = 'workbench.action.chat.assignSelectedAgent';
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: AssignSelectedAgentAction.ID,
|
||||||
|
title: '' // not displayed
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(accessor: ServicesAccessor, ...args: any[]) {
|
||||||
|
const arg: AssignSelectedAgentActionArgs = args[0];
|
||||||
|
if (!arg || !arg.widget || !arg.agent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
arg.widget.lastSelectedAgent = arg.agent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
registerAction2(AssignSelectedAgentAction);
|
||||||
|
|
||||||
class BuiltinDynamicCompletions extends Disposable {
|
class BuiltinDynamicCompletions extends Disposable {
|
||||||
private static readonly VariableNameDef = new RegExp(`${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag
|
private static readonly VariableNameDef = new RegExp(`${chatVariableLeader}\\w*`, 'g'); // MUST be using `g`-flag
|
||||||
|
|
||||||
|
@ -592,12 +632,14 @@ class ChatTokenDeleter extends Disposable {
|
||||||
const parser = this.instantiationService.createInstance(ChatRequestParser);
|
const parser = this.instantiationService.createInstance(ChatRequestParser);
|
||||||
const inputValue = this.widget.inputEditor.getValue();
|
const inputValue = this.widget.inputEditor.getValue();
|
||||||
let previousInputValue: string | undefined;
|
let previousInputValue: string | undefined;
|
||||||
|
let previousSelectedAgent: IChatAgentData | undefined;
|
||||||
|
|
||||||
// A simple heuristic to delete the previous token when the user presses backspace.
|
// A simple heuristic to delete the previous token when the user presses backspace.
|
||||||
// The sophisticated way to do this would be to have a parse tree that can be updated incrementally.
|
// The sophisticated way to do this would be to have a parse tree that can be updated incrementally.
|
||||||
this._register(this.widget.inputEditor.onDidChangeModelContent(e => {
|
this._register(this.widget.inputEditor.onDidChangeModelContent(e => {
|
||||||
if (!previousInputValue) {
|
if (!previousInputValue) {
|
||||||
previousInputValue = inputValue;
|
previousInputValue = inputValue;
|
||||||
|
previousSelectedAgent = this.widget.lastSelectedAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't try to handle multicursor edits right now
|
// Don't try to handle multicursor edits right now
|
||||||
|
@ -605,7 +647,7 @@ class ChatTokenDeleter extends Disposable {
|
||||||
|
|
||||||
// If this was a simple delete, try to find out whether it was inside a token
|
// If this was a simple delete, try to find out whether it was inside a token
|
||||||
if (!change.text && this.widget.viewModel) {
|
if (!change.text && this.widget.viewModel) {
|
||||||
const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue);
|
const previousParsedValue = parser.parseChatRequest(this.widget.viewModel.sessionId, previousInputValue, { selectedAgent: previousSelectedAgent });
|
||||||
|
|
||||||
// For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping
|
// For dynamic variables, this has to happen in ChatDynamicVariableModel with the other bookkeeping
|
||||||
const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart);
|
const deletableTokens = previousParsedValue.parts.filter(p => p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart || p instanceof ChatRequestSlashCommandPart || p instanceof ChatRequestVariablePart);
|
||||||
|
@ -625,6 +667,7 @@ class ChatTokenDeleter extends Disposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
previousInputValue = this.widget.inputEditor.getValue();
|
previousInputValue = this.widget.inputEditor.getValue();
|
||||||
|
previousSelectedAgent = this.widget.lastSelectedAgent;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,19 +3,18 @@
|
||||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import { isNonEmptyArray, distinct } from 'vs/base/common/arrays';
|
|
||||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||||
import { Iterable } from 'vs/base/common/iterator';
|
import { Iterable } from 'vs/base/common/iterator';
|
||||||
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||||
import { ThemeIcon } from 'vs/base/common/themables';
|
import { ThemeIcon } from 'vs/base/common/themables';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { ProviderResult } from 'vs/editor/common/languages';
|
import { ProviderResult } from 'vs/editor/common/languages';
|
||||||
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IChatContributionService, IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
import { IRawChatCommandContribution, RawChatParticipantLocation } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||||
import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { IChatProgressResponseContent, IChatRequestVariableData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
|
import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
|
|
||||||
|
@ -47,6 +46,8 @@ export namespace ChatAgentLocation {
|
||||||
|
|
||||||
export interface IChatAgentData {
|
export interface IChatAgentData {
|
||||||
id: string;
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
extensionId: ExtensionIdentifier;
|
extensionId: ExtensionIdentifier;
|
||||||
/** The agent invoked when no agent is specified */
|
/** The agent invoked when no agent is specified */
|
||||||
isDefault?: boolean;
|
isDefault?: boolean;
|
||||||
|
@ -79,7 +80,6 @@ export interface IChatRequesterInformation {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IChatAgentMetadata {
|
export interface IChatAgentMetadata {
|
||||||
description?: string;
|
|
||||||
helpTextPrefix?: string | IMarkdownString;
|
helpTextPrefix?: string | IMarkdownString;
|
||||||
helpTextVariablesPrefix?: string | IMarkdownString;
|
helpTextVariablesPrefix?: string | IMarkdownString;
|
||||||
helpTextPostfix?: string | IMarkdownString;
|
helpTextPostfix?: string | IMarkdownString;
|
||||||
|
@ -118,86 +118,102 @@ export interface IChatAgentResult {
|
||||||
|
|
||||||
export const IChatAgentService = createDecorator<IChatAgentService>('chatAgentService');
|
export const IChatAgentService = createDecorator<IChatAgentService>('chatAgentService');
|
||||||
|
|
||||||
|
interface IChatAgentEntry {
|
||||||
|
data: IChatAgentData;
|
||||||
|
impl?: IChatAgentImplementation;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IChatAgentService {
|
export interface IChatAgentService {
|
||||||
_serviceBrand: undefined;
|
_serviceBrand: undefined;
|
||||||
/**
|
/**
|
||||||
* undefined when an agent was removed IChatAgent
|
* undefined when an agent was removed IChatAgent
|
||||||
*/
|
*/
|
||||||
readonly onDidChangeAgents: Event<IChatAgent | undefined>;
|
readonly onDidChangeAgents: Event<IChatAgent | undefined>;
|
||||||
registerAgent(name: string, agent: IChatAgentImplementation): IDisposable;
|
registerAgent(id: string, data: IChatAgentData): IDisposable;
|
||||||
|
registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable;
|
||||||
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable;
|
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable;
|
||||||
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
|
invokeAgent(agent: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult>;
|
||||||
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]>;
|
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]>;
|
||||||
getAgents(): IChatAgentData[];
|
|
||||||
getRegisteredAgents(): Array<IChatAgentData>;
|
|
||||||
getActivatedAgents(): Array<IChatAgent>;
|
|
||||||
getAgent(id: string): IChatAgentData | undefined;
|
getAgent(id: string): IChatAgentData | undefined;
|
||||||
|
getAgents(): IChatAgentData[];
|
||||||
|
getActivatedAgents(): Array<IChatAgent>;
|
||||||
|
getAgentsByName(name: string): IChatAgentData[];
|
||||||
getDefaultAgent(): IChatAgent | undefined;
|
getDefaultAgent(): IChatAgent | undefined;
|
||||||
getSecondaryAgent(): IChatAgentData | undefined;
|
getSecondaryAgent(): IChatAgentData | undefined;
|
||||||
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void;
|
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ChatAgentService extends Disposable implements IChatAgentService {
|
export class ChatAgentService implements IChatAgentService {
|
||||||
|
|
||||||
public static readonly AGENT_LEADER = '@';
|
public static readonly AGENT_LEADER = '@';
|
||||||
|
|
||||||
declare _serviceBrand: undefined;
|
declare _serviceBrand: undefined;
|
||||||
|
|
||||||
private readonly _agents = new Map<string, { data: IChatAgentData; impl?: IChatAgentImplementation }>();
|
private _agents: IChatAgentEntry[] = [];
|
||||||
|
|
||||||
private readonly _onDidChangeAgents = this._register(new Emitter<IChatAgent | undefined>());
|
private readonly _onDidChangeAgents = new Emitter<IChatAgent | undefined>();
|
||||||
readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event;
|
readonly onDidChangeAgents: Event<IChatAgent | undefined> = this._onDidChangeAgents.event;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IChatContributionService private chatContributionService: IChatContributionService,
|
@IContextKeyService private readonly contextKeyService: IContextKeyService
|
||||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
) { }
|
||||||
) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
override dispose(): void {
|
registerAgent(id: string, data: IChatAgentData): IDisposable {
|
||||||
super.dispose();
|
const existingAgent = this.getAgent(id);
|
||||||
this._agents.clear();
|
if (existingAgent) {
|
||||||
}
|
throw new Error(`Agent already registered: ${JSON.stringify(id)}`);
|
||||||
|
|
||||||
registerAgent(name: string, agentImpl: IChatAgentImplementation): IDisposable {
|
|
||||||
if (this._agents.has(name)) {
|
|
||||||
// TODO not keyed by name, dupes allowed between extensions
|
|
||||||
throw new Error(`Already registered an agent with id ${name}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = this.getAgent(name);
|
const that = this;
|
||||||
if (!data) {
|
const commands = data.slashCommands;
|
||||||
throw new Error(`Unknown agent: ${name}`);
|
data = {
|
||||||
|
...data,
|
||||||
|
get slashCommands() {
|
||||||
|
return commands.filter(c => !c.when || that.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(c.when)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const entry = { data };
|
||||||
|
this._agents.push(entry);
|
||||||
|
return toDisposable(() => {
|
||||||
|
this._agents = this._agents.filter(a => a !== entry);
|
||||||
|
this._onDidChangeAgents.fire(undefined);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerAgentImplementation(id: string, agentImpl: IChatAgentImplementation): IDisposable {
|
||||||
|
const entry = this._getAgentEntry(id);
|
||||||
|
if (!entry) {
|
||||||
|
throw new Error(`Unknown agent: ${JSON.stringify(id)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const agent = { data: data, impl: agentImpl };
|
if (entry.impl) {
|
||||||
this._agents.set(name, agent);
|
throw new Error(`Agent already has implementation: ${JSON.stringify(id)}`);
|
||||||
this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl));
|
}
|
||||||
|
|
||||||
|
entry.impl = agentImpl;
|
||||||
|
this._onDidChangeAgents.fire(new MergedChatAgent(entry.data, agentImpl));
|
||||||
|
|
||||||
return toDisposable(() => {
|
return toDisposable(() => {
|
||||||
if (this._agents.delete(name)) {
|
entry.impl = undefined;
|
||||||
this._onDidChangeAgents.fire(undefined);
|
this._onDidChangeAgents.fire(undefined);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable {
|
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable {
|
||||||
const agent = { data, impl: agentImpl };
|
const agent = { data, impl: agentImpl };
|
||||||
this._agents.set(data.id, agent);
|
this._agents.push(agent);
|
||||||
this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl));
|
this._onDidChangeAgents.fire(new MergedChatAgent(data, agentImpl));
|
||||||
|
|
||||||
return toDisposable(() => {
|
return toDisposable(() => {
|
||||||
if (this._agents.delete(data.id)) {
|
this._agents = this._agents.filter(a => a !== agent);
|
||||||
this._onDidChangeAgents.fire(undefined);
|
this._onDidChangeAgents.fire(undefined);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void {
|
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void {
|
||||||
const agent = this._agents.get(id);
|
const agent = this._getAgentEntry(id);
|
||||||
if (!agent?.impl) {
|
if (!agent?.impl) {
|
||||||
throw new Error(`No activated agent with id ${id} registered`);
|
throw new Error(`No activated agent with id ${JSON.stringify(id)} registered`);
|
||||||
}
|
}
|
||||||
agent.data.metadata = { ...agent.data.metadata, ...updateMetadata };
|
agent.data.metadata = { ...agent.data.metadata, ...updateMetadata };
|
||||||
this._onDidChangeAgents.fire(new MergedChatAgent(agent.data, agent.impl));
|
this._onDidChangeAgents.fire(new MergedChatAgent(agent.data, agent.impl));
|
||||||
|
@ -212,35 +228,19 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||||
return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data;
|
return Iterable.find(this._agents.values(), a => !!a.data.metadata.isSecondary)?.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRegisteredAgents(): Array<IChatAgentData> {
|
private _getAgentEntry(id: string): IChatAgentEntry | undefined {
|
||||||
const that = this;
|
return this._agents.find(a => a.data.id === id);
|
||||||
return this.chatContributionService.registeredParticipants.map(p => (
|
}
|
||||||
{
|
|
||||||
extensionId: p.extensionId,
|
getAgent(id: string): IChatAgentData | undefined {
|
||||||
id: p.name,
|
return this._getAgentEntry(id)?.data;
|
||||||
metadata: this._agents.has(p.name) ? this._agents.get(p.name)!.data.metadata : { description: p.description },
|
|
||||||
isDefault: p.isDefault,
|
|
||||||
defaultImplicitVariables: p.defaultImplicitVariables,
|
|
||||||
locations: isNonEmptyArray(p.locations) ? p.locations.map(ChatAgentLocation.fromRaw) : [ChatAgentLocation.Panel],
|
|
||||||
get slashCommands() {
|
|
||||||
const commands = p.commands ?? [];
|
|
||||||
return commands.filter(c => !c.when || that.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(c.when)));
|
|
||||||
}
|
|
||||||
} satisfies IChatAgentData));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns all agent datas that exist- static registered and dynamic ones.
|
* Returns all agent datas that exist- static registered and dynamic ones.
|
||||||
*/
|
*/
|
||||||
getAgents(): IChatAgentData[] {
|
getAgents(): IChatAgentData[] {
|
||||||
const registeredAgents = this.getRegisteredAgents();
|
return this._agents.map(entry => entry.data);
|
||||||
const dynamicAgents = Array.from(this._agents.values()).map(a => a.data);
|
|
||||||
const all = [
|
|
||||||
...registeredAgents,
|
|
||||||
...dynamicAgents
|
|
||||||
];
|
|
||||||
|
|
||||||
return distinct(all, a => a.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getActivatedAgents(): IChatAgent[] {
|
getActivatedAgents(): IChatAgent[] {
|
||||||
|
@ -249,12 +249,12 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||||
.map(a => new MergedChatAgent(a.data, a.impl!));
|
.map(a => new MergedChatAgent(a.data, a.impl!));
|
||||||
}
|
}
|
||||||
|
|
||||||
getAgent(id: string): IChatAgentData | undefined {
|
getAgentsByName(name: string): IChatAgentData[] {
|
||||||
return this.getAgents().find(a => a.id === id);
|
return this.getAgents().filter(a => a.name === name);
|
||||||
}
|
}
|
||||||
|
|
||||||
async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
|
async invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
|
||||||
const data = this._agents.get(id);
|
const data = this._getAgentEntry(id);
|
||||||
if (!data?.impl) {
|
if (!data?.impl) {
|
||||||
throw new Error(`No activated agent with id ${id}`);
|
throw new Error(`No activated agent with id ${id}`);
|
||||||
}
|
}
|
||||||
|
@ -263,7 +263,7 @@ export class ChatAgentService extends Disposable implements IChatAgentService {
|
||||||
}
|
}
|
||||||
|
|
||||||
async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]> {
|
async getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]> {
|
||||||
const data = this._agents.get(id);
|
const data = this._getAgentEntry(id);
|
||||||
if (!data?.impl) {
|
if (!data?.impl) {
|
||||||
throw new Error(`No activated agent with id ${id}`);
|
throw new Error(`No activated agent with id ${id}`);
|
||||||
}
|
}
|
||||||
|
@ -283,6 +283,8 @@ export class MergedChatAgent implements IChatAgent {
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
get id(): string { return this.data.id; }
|
get id(): string { return this.data.id; }
|
||||||
|
get name(): string { return this.data.name ?? ''; }
|
||||||
|
get description(): string { return this.data.description ?? ''; }
|
||||||
get extensionId(): ExtensionIdentifier { return this.data.extensionId; }
|
get extensionId(): ExtensionIdentifier { return this.data.extensionId; }
|
||||||
get isDefault(): boolean | undefined { return this.data.isDefault; }
|
get isDefault(): boolean | undefined { return this.data.isDefault; }
|
||||||
get metadata(): IChatAgentMetadata { return this.data.metadata; }
|
get metadata(): IChatAgentMetadata { return this.data.metadata; }
|
||||||
|
|
|
@ -19,10 +19,6 @@ export interface IChatContributionService {
|
||||||
registerChatProvider(provider: IChatProviderContribution): void;
|
registerChatProvider(provider: IChatProviderContribution): void;
|
||||||
deregisterChatProvider(providerId: string): void;
|
deregisterChatProvider(providerId: string): void;
|
||||||
getViewIdForProvider(providerId: string): string;
|
getViewIdForProvider(providerId: string): string;
|
||||||
|
|
||||||
readonly registeredParticipants: IChatParticipantContribution[];
|
|
||||||
registerChatParticipant(participant: IChatParticipantContribution): void;
|
|
||||||
deregisterChatParticipant(participant: IChatParticipantContribution): void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IRawChatProviderContribution {
|
export interface IRawChatProviderContribution {
|
||||||
|
@ -44,6 +40,7 @@ export interface IRawChatCommandContribution {
|
||||||
export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook';
|
export type RawChatParticipantLocation = 'panel' | 'terminal' | 'notebook';
|
||||||
|
|
||||||
export interface IRawChatParticipantContribution {
|
export interface IRawChatParticipantContribution {
|
||||||
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
isDefault?: boolean;
|
isDefault?: boolean;
|
||||||
|
|
|
@ -591,7 +591,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||||
const request = new ChatRequestModel(this, parsedRequest, variableData);
|
const request = new ChatRequestModel(this, parsedRequest, variableData);
|
||||||
if (raw.response || raw.result || (raw as any).responseErrorDetails) {
|
if (raw.response || raw.result || (raw as any).responseErrorDetails) {
|
||||||
const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format
|
const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format
|
||||||
revive<ISerializableChatAgentData>(raw.agent) : undefined;
|
this.reviveSerializedAgent(raw.agent) : undefined;
|
||||||
|
|
||||||
// Port entries from old format
|
// Port entries from old format
|
||||||
const result = 'responseErrorDetails' in raw ?
|
const result = 'responseErrorDetails' in raw ?
|
||||||
|
@ -613,6 +613,16 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private reviveSerializedAgent(raw: ISerializableChatAgentData): IChatAgentData {
|
||||||
|
const agent = 'name' in raw ?
|
||||||
|
raw :
|
||||||
|
{
|
||||||
|
...(raw as any),
|
||||||
|
name: (raw as any).id,
|
||||||
|
};
|
||||||
|
return revive(agent);
|
||||||
|
}
|
||||||
|
|
||||||
private getParsedRequestFromString(message: string): IParsedChatRequest {
|
private getParsedRequestFromString(message: string): IParsedChatRequest {
|
||||||
// TODO These offsets won't be used, but chat replies need to go through the parser as well
|
// TODO These offsets won't be used, but chat replies need to go through the parser as well
|
||||||
const parts = [new ChatRequestTextPart(new OffsetRange(0, message.length), { startColumn: 1, startLineNumber: 1, endColumn: 1, endLineNumber: 1 }, message)];
|
const parts = [new ChatRequestTextPart(new OffsetRange(0, message.length), { startColumn: 1, startLineNumber: 1, endColumn: 1, endLineNumber: 1 }, message)];
|
||||||
|
@ -703,7 +713,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||||
} else if (progress.kind === 'usedContext' || progress.kind === 'reference') {
|
} else if (progress.kind === 'usedContext' || progress.kind === 'reference') {
|
||||||
request.response.applyReference(progress);
|
request.response.applyReference(progress);
|
||||||
} else if (progress.kind === 'agentDetection') {
|
} else if (progress.kind === 'agentDetection') {
|
||||||
const agent = this.chatAgentService.getAgent(progress.agentName);
|
const agent = this.chatAgentService.getAgent(progress.agentId);
|
||||||
if (agent) {
|
if (agent) {
|
||||||
request.response.setAgent(agent, progress.command);
|
request.response.setAgent(agent, progress.command);
|
||||||
}
|
}
|
||||||
|
@ -802,7 +812,7 @@ export class ChatModel extends Disposable implements IChatModel {
|
||||||
vote: r.response?.vote,
|
vote: r.response?.vote,
|
||||||
agent: r.response?.agent ?
|
agent: r.response?.agent ?
|
||||||
// May actually be the full IChatAgent instance, just take the data props. slashCommands don't matter here.
|
// May actually be the full IChatAgent instance, just take the data props. slashCommands don't matter here.
|
||||||
{ id: r.response.agent.id, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata, slashCommands: [], locations: r.response.agent.locations, isDefault: r.response.agent.isDefault }
|
{ id: r.response.agent.id, name: r.response.agent.name, description: r.response.agent.description, extensionId: r.response.agent.extensionId, metadata: r.response.agent.metadata, slashCommands: [], locations: r.response.agent.locations, isDefault: r.response.agent.isDefault }
|
||||||
: undefined,
|
: undefined,
|
||||||
slashCommand: r.response?.slashCommand,
|
slashCommand: r.response?.slashCommand,
|
||||||
usedContext: r.response?.usedContext,
|
usedContext: r.response?.usedContext,
|
||||||
|
|
|
@ -75,7 +75,7 @@ export class ChatRequestAgentPart implements IParsedChatRequestPart {
|
||||||
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
|
constructor(readonly range: OffsetRange, readonly editorRange: IRange, readonly agent: IChatAgentData) { }
|
||||||
|
|
||||||
get text(): string {
|
get text(): string {
|
||||||
return `${chatAgentLeader}${this.agent.id}`;
|
return `${chatAgentLeader}${this.agent.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
get promptText(): string {
|
get promptText(): string {
|
||||||
|
@ -92,6 +92,8 @@ export class ChatRequestAgentPart implements IParsedChatRequestPart {
|
||||||
editorRange: this.editorRange,
|
editorRange: this.editorRange,
|
||||||
agent: {
|
agent: {
|
||||||
id: this.agent.id,
|
id: this.agent.id,
|
||||||
|
name: this.agent.name,
|
||||||
|
description: this.agent.description,
|
||||||
metadata: this.agent.metadata
|
metadata: this.agent.metadata
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -167,10 +169,19 @@ export function reviveParsedChatRequest(serialized: IParsedChatRequest): IParsed
|
||||||
(part as ChatRequestVariablePart).variableArg
|
(part as ChatRequestVariablePart).variableArg
|
||||||
);
|
);
|
||||||
} else if (part.kind === ChatRequestAgentPart.Kind) {
|
} else if (part.kind === ChatRequestAgentPart.Kind) {
|
||||||
|
let agent = (part as ChatRequestAgentPart).agent;
|
||||||
|
if (!('name' in agent)) {
|
||||||
|
// Port old format
|
||||||
|
agent = {
|
||||||
|
...(agent as any),
|
||||||
|
name: (agent as any).id
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return new ChatRequestAgentPart(
|
return new ChatRequestAgentPart(
|
||||||
new OffsetRange(part.range.start, part.range.endExclusive),
|
new OffsetRange(part.range.start, part.range.endExclusive),
|
||||||
part.editorRange,
|
part.editorRange,
|
||||||
(part as ChatRequestAgentPart).agent
|
agent
|
||||||
);
|
);
|
||||||
} else if (part.kind === ChatRequestAgentSubcommandPart.Kind) {
|
} else if (part.kind === ChatRequestAgentSubcommandPart.Kind) {
|
||||||
return new ChatRequestAgentSubcommandPart(
|
return new ChatRequestAgentSubcommandPart(
|
||||||
|
|
|
@ -6,10 +6,8 @@
|
||||||
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
import { OffsetRange } from 'vs/editor/common/core/offsetRange';
|
||||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||||
import { Range } from 'vs/editor/common/core/range';
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { IChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
|
||||||
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicVariablePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
|
||||||
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
||||||
import { IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService, IDynamicVariable } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
|
||||||
|
@ -17,18 +15,21 @@ const agentReg = /^@([\w_\-]+)(?=(\s|$|\b))/i; // An @-agent
|
||||||
const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2)
|
const variableReg = /^#([\w_\-]+)(:\d+)?(?=(\s|$|\b))/i; // A #-variable with an optional numeric : arg (@response:2)
|
||||||
const slashReg = /\/([\w_\-]+)(?=(\s|$|\b))/i; // A / command
|
const slashReg = /\/([\w_\-]+)(?=(\s|$|\b))/i; // A / command
|
||||||
|
|
||||||
|
export interface IChatParserContext {
|
||||||
|
/** Used only as a disambiguator, when the query references an agent that has a duplicate with the same name. */
|
||||||
|
selectedAgent?: IChatAgentData;
|
||||||
|
}
|
||||||
|
|
||||||
export class ChatRequestParser {
|
export class ChatRequestParser {
|
||||||
constructor(
|
constructor(
|
||||||
@IChatAgentService private readonly agentService: IChatAgentService,
|
@IChatAgentService private readonly agentService: IChatAgentService,
|
||||||
@IChatVariablesService private readonly variableService: IChatVariablesService,
|
@IChatVariablesService private readonly variableService: IChatVariablesService,
|
||||||
@IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService,
|
@IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService
|
||||||
@IChatService private readonly chatService: IChatService
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
parseChatRequest(sessionId: string, message: string): IParsedChatRequest {
|
parseChatRequest(sessionId: string, message: string, context?: IChatParserContext): IParsedChatRequest {
|
||||||
const parts: IParsedChatRequestPart[] = [];
|
const parts: IParsedChatRequestPart[] = [];
|
||||||
const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls
|
const references = this.variableService.getDynamicVariables(sessionId); // must access this list before any async calls
|
||||||
const model = this.chatService.getSession(sessionId)!;
|
|
||||||
|
|
||||||
let lineNumber = 1;
|
let lineNumber = 1;
|
||||||
let column = 1;
|
let column = 1;
|
||||||
|
@ -40,9 +41,9 @@ export class ChatRequestParser {
|
||||||
if (char === chatVariableLeader) {
|
if (char === chatVariableLeader) {
|
||||||
newPart = this.tryToParseVariable(message.slice(i), i, new Position(lineNumber, column), parts);
|
newPart = this.tryToParseVariable(message.slice(i), i, new Position(lineNumber, column), parts);
|
||||||
} else if (char === chatAgentLeader) {
|
} else if (char === chatAgentLeader) {
|
||||||
newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts);
|
newPart = this.tryToParseAgent(message.slice(i), message, i, new Position(lineNumber, column), parts, context);
|
||||||
} else if (char === chatSubcommandLeader) {
|
} else if (char === chatSubcommandLeader) {
|
||||||
newPart = this.tryToParseSlashCommand(model, message.slice(i), message, i, new Position(lineNumber, column), parts);
|
newPart = this.tryToParseSlashCommand(message.slice(i), message, i, new Position(lineNumber, column), parts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newPart) {
|
if (!newPart) {
|
||||||
|
@ -89,17 +90,23 @@ export class ChatRequestParser {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryToParseAgent(message: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
private tryToParseAgent(message: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>, context: IChatParserContext | undefined): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
||||||
const nextVariableMatch = message.match(agentReg);
|
const nextAgentMatch = message.match(agentReg);
|
||||||
if (!nextVariableMatch) {
|
if (!nextAgentMatch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const [full, name] = nextVariableMatch;
|
const [full, name] = nextAgentMatch;
|
||||||
const varRange = new OffsetRange(offset, offset + full.length);
|
const agentRange = new OffsetRange(offset, offset + full.length);
|
||||||
const varEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length);
|
const agentEditorRange = new Range(position.lineNumber, position.column, position.lineNumber, position.column + full.length);
|
||||||
|
|
||||||
const agent = this.agentService.getAgent(name);
|
const agents = this.agentService.getAgentsByName(name);
|
||||||
|
|
||||||
|
// If there is more than one agent with this name, and the user picked it from the suggest widget, then the selected agent should be in the
|
||||||
|
// context and we use that one. Otherwise just pick the first.
|
||||||
|
const agent = agents.length > 1 && context?.selectedAgent ?
|
||||||
|
context.selectedAgent :
|
||||||
|
agents[0];
|
||||||
if (!agent) {
|
if (!agent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +128,7 @@ export class ChatRequestParser {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ChatRequestAgentPart(varRange, varEditorRange, agent);
|
return new ChatRequestAgentPart(agentRange, agentEditorRange, agent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
private tryToParseVariable(message: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestAgentPart | ChatRequestVariablePart | undefined {
|
||||||
|
@ -142,7 +149,7 @@ export class ChatRequestParser {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private tryToParseSlashCommand(model: IChatModel, remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined {
|
private tryToParseSlashCommand(remainingMessage: string, fullMessage: string, offset: number, position: IPosition, parts: ReadonlyArray<IParsedChatRequestPart>): ChatRequestSlashCommandPart | ChatRequestAgentSubcommandPart | undefined {
|
||||||
const nextSlashMatch = remainingMessage.match(slashReg);
|
const nextSlashMatch = remainingMessage.match(slashReg);
|
||||||
if (!nextSlashMatch) {
|
if (!nextSlashMatch) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -15,6 +15,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
|
||||||
import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { IChatAgentCommand, IChatAgentData, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { ChatModel, IChatModel, IChatRequestVariableData, ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
|
import { IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||||
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
|
||||||
export interface IChat {
|
export interface IChat {
|
||||||
|
@ -85,7 +86,7 @@ export interface IChatContentInlineReference {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IChatAgentDetection {
|
export interface IChatAgentDetection {
|
||||||
agentName: string;
|
agentId: string;
|
||||||
command?: IChatAgentCommand;
|
command?: IChatAgentCommand;
|
||||||
kind: 'agentDetection';
|
kind: 'agentDetection';
|
||||||
}
|
}
|
||||||
|
@ -283,7 +284,7 @@ export interface IChatService {
|
||||||
/**
|
/**
|
||||||
* Returns whether the request was accepted.
|
* Returns whether the request was accepted.
|
||||||
*/
|
*/
|
||||||
sendRequest(sessionId: string, message: string, implicitVariablesEnabled?: boolean): Promise<IChatSendRequestData | undefined>;
|
sendRequest(sessionId: string, message: string, implicitVariablesEnabled?: boolean, parserContext?: IChatParserContext): Promise<IChatSendRequestData | undefined>;
|
||||||
removeRequest(sessionid: string, requestId: string): Promise<void>;
|
removeRequest(sessionid: string, requestId: string): Promise<void>;
|
||||||
cancelCurrentRequestForSession(sessionId: string): void;
|
cancelCurrentRequestForSession(sessionId: string): void;
|
||||||
clearSession(sessionId: string): void;
|
clearSession(sessionId: string): void;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import { ChatAgentLocation, IChatAgentRequest, IChatAgentResult, IChatAgentServi
|
||||||
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
|
||||||
import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableEntry, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageModel, IChatModel, IChatRequestVariableData, IChatRequestVariableEntry, ISerializableChatData, ISerializableChatsData, getHistoryEntriesFromModel, updateRanges } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, IParsedChatRequest, getPromptText } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
import { ChatRequestParser, IChatParserContext } from 'vs/workbench/contrib/chat/common/chatRequestParser';
|
||||||
import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
import { ChatCopyKind, IChat, IChatCompleteResponse, IChatDetail, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatSendRequestData, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
||||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
@ -435,7 +435,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||||
return this._startSession(data.providerId, data, CancellationToken.None);
|
return this._startSession(data.providerId, data, CancellationToken.None);
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendRequest(sessionId: string, request: string, implicitVariablesEnabled?: boolean): Promise<IChatSendRequestData | undefined> {
|
async sendRequest(sessionId: string, request: string, implicitVariablesEnabled?: boolean, parserContext?: IChatParserContext): Promise<IChatSendRequestData | undefined> {
|
||||||
this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`);
|
this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`);
|
||||||
if (!request.trim()) {
|
if (!request.trim()) {
|
||||||
this.trace('sendRequest', 'Rejected empty message');
|
this.trace('sendRequest', 'Rejected empty message');
|
||||||
|
@ -458,7 +458,7 @@ export class ChatService extends Disposable implements IChatService {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, request);
|
const parsedRequest = this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, request, parserContext);
|
||||||
const agent = parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart)?.agent ?? this.chatAgentService.getDefaultAgent()!;
|
const agent = parsedRequest.parts.find((r): r is ChatRequestAgentPart => r instanceof ChatRequestAgentPart)?.agent ?? this.chatAgentService.getDefaultAgent()!;
|
||||||
const agentSlashCommandPart = parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart);
|
const agentSlashCommandPart = parsedRequest.parts.find((r): r is ChatRequestAgentSubcommandPart => r instanceof ChatRequestAgentSubcommandPart);
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ suite('ChatVariables', function () {
|
||||||
instantiationService.stub(IExtensionService, new TestExtensionService());
|
instantiationService.stub(IExtensionService, new TestExtensionService());
|
||||||
instantiationService.stub(IChatVariablesService, service);
|
instantiationService.stub(IChatVariablesService, service);
|
||||||
instantiationService.stub(IChatService, new MockChatService());
|
instantiationService.stub(IChatService, new MockChatService());
|
||||||
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
|
instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ChatVariables - resolveVariables', async function () {
|
test('ChatVariables - resolveVariables', async function () {
|
||||||
|
|
|
@ -27,6 +27,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -27,6 +27,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -13,6 +13,12 @@
|
||||||
},
|
},
|
||||||
agent: {
|
agent: {
|
||||||
id: "agent",
|
id: "agent",
|
||||||
|
name: "agent",
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
locations: [ ],
|
||||||
metadata: { description: "" },
|
metadata: { description: "" },
|
||||||
slashCommands: [
|
slashCommands: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
{
|
||||||
|
requesterUsername: "test",
|
||||||
|
requesterAvatarIconUri: undefined,
|
||||||
|
responderUsername: "test",
|
||||||
|
responderAvatarIconUri: undefined,
|
||||||
|
welcomeMessage: undefined,
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
text: "@ChatProviderWithUsedContext test request",
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
kind: "agent",
|
||||||
|
range: {
|
||||||
|
start: 0,
|
||||||
|
endExclusive: 28
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 29
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
id: "ChatProviderWithUsedContext",
|
||||||
|
name: "ChatProviderWithUsedContext",
|
||||||
|
description: undefined,
|
||||||
|
metadata: { }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 28,
|
||||||
|
endExclusive: 41
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 29,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 42
|
||||||
|
},
|
||||||
|
text: " test request",
|
||||||
|
kind: "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
variableData: { variables: [ ] },
|
||||||
|
response: [ ],
|
||||||
|
result: { metadata: { metadataKey: "value" } },
|
||||||
|
followups: undefined,
|
||||||
|
isCanceled: false,
|
||||||
|
vote: undefined,
|
||||||
|
agent: {
|
||||||
|
id: "ChatProviderWithUsedContext",
|
||||||
|
name: "ChatProviderWithUsedContext",
|
||||||
|
description: undefined,
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
metadata: { },
|
||||||
|
slashCommands: [ ],
|
||||||
|
locations: [ 1 ],
|
||||||
|
isDefault: undefined
|
||||||
|
},
|
||||||
|
slashCommand: undefined,
|
||||||
|
usedContext: {
|
||||||
|
documents: [
|
||||||
|
{
|
||||||
|
uri: {
|
||||||
|
scheme: "file",
|
||||||
|
authority: "",
|
||||||
|
path: "/test/path/to/file",
|
||||||
|
query: "",
|
||||||
|
fragment: "",
|
||||||
|
_formatted: null,
|
||||||
|
_fsPath: null
|
||||||
|
},
|
||||||
|
version: 3,
|
||||||
|
ranges: [
|
||||||
|
{
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 2,
|
||||||
|
endColumn: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
kind: "usedContext"
|
||||||
|
},
|
||||||
|
contentReferences: [ ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
providerId: "testProvider"
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
requesterUsername: "test",
|
||||||
|
requesterAvatarIconUri: undefined,
|
||||||
|
responderUsername: "test",
|
||||||
|
responderAvatarIconUri: undefined,
|
||||||
|
welcomeMessage: undefined,
|
||||||
|
requests: [ ],
|
||||||
|
providerId: "testProvider"
|
||||||
|
}
|
|
@ -0,0 +1,102 @@
|
||||||
|
{
|
||||||
|
requesterUsername: "test",
|
||||||
|
requesterAvatarIconUri: undefined,
|
||||||
|
responderUsername: "test",
|
||||||
|
responderAvatarIconUri: undefined,
|
||||||
|
welcomeMessage: undefined,
|
||||||
|
requests: [
|
||||||
|
{
|
||||||
|
message: {
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
kind: "agent",
|
||||||
|
range: {
|
||||||
|
start: 0,
|
||||||
|
endExclusive: 28
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 29
|
||||||
|
},
|
||||||
|
agent: {
|
||||||
|
id: "ChatProviderWithUsedContext",
|
||||||
|
name: "ChatProviderWithUsedContext",
|
||||||
|
description: undefined,
|
||||||
|
metadata: {
|
||||||
|
requester: { name: "test" },
|
||||||
|
fullName: "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
start: 28,
|
||||||
|
endExclusive: 41
|
||||||
|
},
|
||||||
|
editorRange: {
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 29,
|
||||||
|
endLineNumber: 1,
|
||||||
|
endColumn: 42
|
||||||
|
},
|
||||||
|
text: " test request",
|
||||||
|
kind: "text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
text: "@ChatProviderWithUsedContext test request"
|
||||||
|
},
|
||||||
|
variableData: { variables: [ ] },
|
||||||
|
response: [ ],
|
||||||
|
result: { metadata: { metadataKey: "value" } },
|
||||||
|
followups: undefined,
|
||||||
|
isCanceled: false,
|
||||||
|
vote: undefined,
|
||||||
|
agent: {
|
||||||
|
id: "ChatProviderWithUsedContext",
|
||||||
|
name: "ChatProviderWithUsedContext",
|
||||||
|
description: undefined,
|
||||||
|
extensionId: {
|
||||||
|
value: "nullExtensionDescription",
|
||||||
|
_lower: "nullextensiondescription"
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
requester: { name: "test" },
|
||||||
|
fullName: "test"
|
||||||
|
},
|
||||||
|
slashCommands: [ ],
|
||||||
|
locations: [ 1 ],
|
||||||
|
isDefault: undefined
|
||||||
|
},
|
||||||
|
slashCommand: undefined,
|
||||||
|
usedContext: {
|
||||||
|
documents: [
|
||||||
|
{
|
||||||
|
uri: {
|
||||||
|
scheme: "file",
|
||||||
|
authority: "",
|
||||||
|
path: "/test/path/to/file",
|
||||||
|
query: "",
|
||||||
|
fragment: "",
|
||||||
|
_formatted: null,
|
||||||
|
_fsPath: null
|
||||||
|
},
|
||||||
|
version: 3,
|
||||||
|
ranges: [
|
||||||
|
{
|
||||||
|
startLineNumber: 1,
|
||||||
|
startColumn: 1,
|
||||||
|
endLineNumber: 2,
|
||||||
|
endColumn: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
kind: "usedContext"
|
||||||
|
},
|
||||||
|
contentReferences: [ ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
providerId: "testProvider"
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ suite('ChatModel', () => {
|
||||||
instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));
|
instantiationService.stub(IStorageService, testDisposables.add(new TestStorageService()));
|
||||||
instantiationService.stub(ILogService, new NullLogService());
|
instantiationService.stub(ILogService, new NullLogService());
|
||||||
instantiationService.stub(IExtensionService, new TestExtensionService());
|
instantiationService.stub(IExtensionService, new TestExtensionService());
|
||||||
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
|
instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Waits for initialization', async () => {
|
test('Waits for initialization', async () => {
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
||||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService';
|
import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService';
|
||||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||||
|
|
||||||
suite('ChatRequestParser', () => {
|
suite('ChatRequestParser', () => {
|
||||||
|
@ -31,7 +31,7 @@ suite('ChatRequestParser', () => {
|
||||||
instantiationService.stub(ILogService, new NullLogService());
|
instantiationService.stub(ILogService, new NullLogService());
|
||||||
instantiationService.stub(IExtensionService, new TestExtensionService());
|
instantiationService.stub(IExtensionService, new TestExtensionService());
|
||||||
instantiationService.stub(IChatService, new MockChatService());
|
instantiationService.stub(IChatService, new MockChatService());
|
||||||
instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService)));
|
instantiationService.stub(IChatAgentService, instantiationService.createInstance(ChatAgentService));
|
||||||
|
|
||||||
varService = mockObject<IChatVariablesService>()({});
|
varService = mockObject<IChatVariablesService>()({});
|
||||||
varService.getDynamicVariables.returns([]);
|
varService.getDynamicVariables.returns([]);
|
||||||
|
@ -112,12 +112,12 @@ suite('ChatRequestParser', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => {
|
const getAgentWithSlashCommands = (slashCommands: IChatAgentCommand[]) => {
|
||||||
return <IChatAgentData>{ id: 'agent', metadata: { description: '' }, slashCommands };
|
return <IChatAgentData>{ id: 'agent', name: 'agent', extensionId: nullExtensionDescription.identifier, locations: [], metadata: { description: '' }, slashCommands };
|
||||||
};
|
};
|
||||||
|
|
||||||
test('agent with subcommand after text', async () => {
|
test('agent with subcommand after text', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
@ -127,7 +127,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agents, subCommand', async () => {
|
test('agents, subCommand', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
@ -137,7 +137,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agent with question mark', async () => {
|
test('agent with question mark', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
@ -147,7 +147,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agent and subcommand with leading whitespace', async () => {
|
test('agent and subcommand with leading whitespace', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
@ -157,7 +157,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agent and subcommand after newline', async () => {
|
test('agent and subcommand after newline', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
@ -167,7 +167,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agent not first', async () => {
|
test('agent not first', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
parser = instantiationService.createInstance(ChatRequestParser);
|
parser = instantiationService.createInstance(ChatRequestParser);
|
||||||
|
@ -177,7 +177,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agents and variables and multiline', async () => {
|
test('agents and variables and multiline', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
varService.hasVariable.returns(true);
|
varService.hasVariable.returns(true);
|
||||||
|
@ -189,7 +189,7 @@ suite('ChatRequestParser', () => {
|
||||||
|
|
||||||
test('agents and variables and multiline, part2', async () => {
|
test('agents and variables and multiline, part2', async () => {
|
||||||
const agentsService = mockObject<IChatAgentService>()({});
|
const agentsService = mockObject<IChatAgentService>()({});
|
||||||
agentsService.getAgent.returns(getAgentWithSlashCommands([{ name: 'subCommand', description: '' }]));
|
agentsService.getAgentsByName.returns([getAgentWithSlashCommands([{ name: 'subCommand', description: '' }])]);
|
||||||
instantiationService.stub(IChatAgentService, agentsService as any);
|
instantiationService.stub(IChatAgentService, agentsService as any);
|
||||||
|
|
||||||
varService.hasVariable.returns(true);
|
varService.hasVariable.returns(true);
|
||||||
|
|
|
@ -20,7 +20,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
|
|
||||||
import { ChatAgentLocation, ChatAgentService, IChatAgent, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { ChatAgentLocation, ChatAgentService, IChatAgent, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
||||||
import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||||
|
@ -28,11 +27,11 @@ import { IChat, IChatFollowup, IChatProgress, IChatProvider, IChatRequest, IChat
|
||||||
import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
|
import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl';
|
||||||
import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands';
|
||||||
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables';
|
||||||
|
import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService';
|
||||||
import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables';
|
import { MockChatVariablesService } from 'vs/workbench/contrib/chat/test/common/mockChatVariables';
|
||||||
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
import { IExtensionService, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
||||||
|
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
|
||||||
import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
import { TestContextService, TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||||
import { MockChatService } from 'vs/workbench/contrib/chat/test/common/mockChatService';
|
|
||||||
import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService';
|
|
||||||
|
|
||||||
class SimpleTestProvider extends Disposable implements IChatProvider {
|
class SimpleTestProvider extends Disposable implements IChatProvider {
|
||||||
private static sessionId = 0;
|
private static sessionId = 0;
|
||||||
|
@ -57,6 +56,7 @@ class SimpleTestProvider extends Disposable implements IChatProvider {
|
||||||
const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext';
|
const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext';
|
||||||
const chatAgentWithUsedContext: IChatAgent = {
|
const chatAgentWithUsedContext: IChatAgent = {
|
||||||
id: chatAgentWithUsedContextId,
|
id: chatAgentWithUsedContextId,
|
||||||
|
name: chatAgentWithUsedContextId,
|
||||||
extensionId: nullExtensionDescription.identifier,
|
extensionId: nullExtensionDescription.identifier,
|
||||||
locations: [ChatAgentLocation.Panel],
|
locations: [ChatAgentLocation.Panel],
|
||||||
metadata: {},
|
metadata: {},
|
||||||
|
@ -82,7 +82,7 @@ const chatAgentWithUsedContext: IChatAgent = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
suite('Chat', () => {
|
suite('ChatService', () => {
|
||||||
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
|
const testDisposables = ensureNoDisposablesAreLeakedInTestSuite();
|
||||||
|
|
||||||
let storageService: IStorageService;
|
let storageService: IStorageService;
|
||||||
|
@ -104,13 +104,8 @@ suite('Chat', () => {
|
||||||
instantiationService.stub(IWorkspaceContextService, new TestContextService());
|
instantiationService.stub(IWorkspaceContextService, new TestContextService());
|
||||||
instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService)));
|
instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService)));
|
||||||
instantiationService.stub(IChatService, new MockChatService());
|
instantiationService.stub(IChatService, new MockChatService());
|
||||||
instantiationService.stub(IChatContributionService, new MockChatContributionService(
|
|
||||||
[
|
|
||||||
{ extensionId: nullExtensionDescription.identifier, name: 'testAgent', isDefault: true },
|
|
||||||
{ extensionId: nullExtensionDescription.identifier, name: chatAgentWithUsedContextId },
|
|
||||||
]));
|
|
||||||
|
|
||||||
chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService));
|
chatAgentService = instantiationService.createInstance(ChatAgentService);
|
||||||
instantiationService.stub(IChatAgentService, chatAgentService);
|
instantiationService.stub(IChatAgentService, chatAgentService);
|
||||||
|
|
||||||
const agent = {
|
const agent = {
|
||||||
|
@ -118,7 +113,9 @@ suite('Chat', () => {
|
||||||
return {};
|
return {};
|
||||||
},
|
},
|
||||||
} satisfies IChatAgentImplementation;
|
} satisfies IChatAgentImplementation;
|
||||||
testDisposables.add(chatAgentService.registerAgent('testAgent', agent));
|
testDisposables.add(chatAgentService.registerAgent('testAgent', { name: 'testAgent', id: 'testAgent', isDefault: true, extensionId: nullExtensionDescription.identifier, locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] }));
|
||||||
|
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContextId, { name: chatAgentWithUsedContextId, id: chatAgentWithUsedContextId, extensionId: nullExtensionDescription.identifier, locations: [ChatAgentLocation.Panel], metadata: {}, slashCommands: [] }));
|
||||||
|
testDisposables.add(chatAgentService.registerAgentImplementation('testAgent', agent));
|
||||||
chatAgentService.updateAgent('testAgent', { requester: { name: 'test' }, fullName: 'test' });
|
chatAgentService.updateAgent('testAgent', { requester: { name: 'test' }, fullName: 'test' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -209,7 +206,7 @@ suite('Chat', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('can serialize', async () => {
|
test('can serialize', async () => {
|
||||||
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext));
|
testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithUsedContextId, chatAgentWithUsedContext));
|
||||||
chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' }, fullName: 'test' });
|
chatAgentService.updateAgent(chatAgentWithUsedContextId, { requester: { name: 'test' }, fullName: 'test' });
|
||||||
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
|
const testService = testDisposables.add(instantiationService.createInstance(ChatService));
|
||||||
testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
|
testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider'))));
|
||||||
|
@ -230,7 +227,7 @@ suite('Chat', () => {
|
||||||
|
|
||||||
test('can deserialize', async () => {
|
test('can deserialize', async () => {
|
||||||
let serializedChatData: ISerializableChatData;
|
let serializedChatData: ISerializableChatData;
|
||||||
testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext.id, chatAgentWithUsedContext));
|
testDisposables.add(chatAgentService.registerAgentImplementation(chatAgentWithUsedContextId, chatAgentWithUsedContext));
|
||||||
|
|
||||||
// create the first service, send request, get response, and serialize the state
|
// create the first service, send request, get response, and serialize the state
|
||||||
{ // serapate block to not leak variables in outer scope
|
{ // serapate block to not leak variables in outer scope
|
||||||
|
|
|
@ -9,7 +9,6 @@ export class MockChatContributionService implements IChatContributionService {
|
||||||
_serviceBrand: undefined;
|
_serviceBrand: undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public readonly registeredParticipants: IChatParticipantContribution[] = []
|
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
registeredProviders: IChatProviderContribution[] = [];
|
registeredProviders: IChatProviderContribution[] = [];
|
||||||
|
|
|
@ -28,7 +28,10 @@ suite('VoiceChat', () => {
|
||||||
|
|
||||||
extensionId: ExtensionIdentifier = nullExtensionDescription.identifier;
|
extensionId: ExtensionIdentifier = nullExtensionDescription.identifier;
|
||||||
locations: ChatAgentLocation[] = [ChatAgentLocation.Panel];
|
locations: ChatAgentLocation[] = [ChatAgentLocation.Panel];
|
||||||
constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) { }
|
public readonly name: string;
|
||||||
|
constructor(readonly id: string, readonly slashCommands: IChatAgentCommand[]) {
|
||||||
|
this.name = id;
|
||||||
|
}
|
||||||
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error('Method not implemented.'); }
|
invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error('Method not implemented.'); }
|
||||||
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); }
|
provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString)[] | undefined> { throw new Error('Method not implemented.'); }
|
||||||
metadata = {};
|
metadata = {};
|
||||||
|
@ -47,17 +50,18 @@ suite('VoiceChat', () => {
|
||||||
class TestChatAgentService implements IChatAgentService {
|
class TestChatAgentService implements IChatAgentService {
|
||||||
_serviceBrand: undefined;
|
_serviceBrand: undefined;
|
||||||
readonly onDidChangeAgents = Event.None;
|
readonly onDidChangeAgents = Event.None;
|
||||||
registerAgent(name: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); }
|
registerAgentImplementation(id: string, agent: IChatAgentImplementation): IDisposable { throw new Error(); }
|
||||||
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { throw new Error('Method not implemented.'); }
|
registerDynamicAgent(data: IChatAgentData, agentImpl: IChatAgentImplementation): IDisposable { throw new Error('Method not implemented.'); }
|
||||||
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error(); }
|
invokeAgent(id: string, request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> { throw new Error(); }
|
||||||
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]> { throw new Error(); }
|
getFollowups(id: string, request: IChatAgentRequest, result: IChatAgentResult, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatFollowup[]> { throw new Error(); }
|
||||||
getRegisteredAgents(): Array<IChatAgent> { return agents; }
|
|
||||||
getActivatedAgents(): IChatAgent[] { return agents; }
|
getActivatedAgents(): IChatAgent[] { return agents; }
|
||||||
getAgents(): IChatAgent[] { return agents; }
|
getAgents(): IChatAgent[] { return agents; }
|
||||||
getAgent(id: string): IChatAgent | undefined { throw new Error(); }
|
|
||||||
getDefaultAgent(): IChatAgent | undefined { throw new Error(); }
|
getDefaultAgent(): IChatAgent | undefined { throw new Error(); }
|
||||||
getSecondaryAgent(): IChatAgent | undefined { throw new Error(); }
|
getSecondaryAgent(): IChatAgent | undefined { throw new Error(); }
|
||||||
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error(); }
|
registerAgent(id: string, data: IChatAgentData): IDisposable { throw new Error('Method not implemented.'); }
|
||||||
|
getAgent(id: string): IChatAgentData | undefined { throw new Error('Method not implemented.'); }
|
||||||
|
getAgentsByName(name: string): IChatAgentData[] { throw new Error('Method not implemented.'); }
|
||||||
|
updateAgent(id: string, updateMetadata: IChatAgentMetadata): void { throw new Error('Method not implemented.'); }
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestSpeechService implements ISpeechService {
|
class TestSpeechService implements ISpeechService {
|
||||||
|
|
|
@ -7,16 +7,20 @@ import * as assert from 'assert';
|
||||||
import { equals } from 'vs/base/common/arrays';
|
import { equals } from 'vs/base/common/arrays';
|
||||||
import { timeout } from 'vs/base/common/async';
|
import { timeout } from 'vs/base/common/async';
|
||||||
import { Emitter, Event } from 'vs/base/common/event';
|
import { Emitter, Event } from 'vs/base/common/event';
|
||||||
|
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
import { Schemas } from 'vs/base/common/network';
|
||||||
import { mock } from 'vs/base/test/common/mock';
|
import { mock } from 'vs/base/test/common/mock';
|
||||||
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
|
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
|
||||||
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
|
||||||
import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/testDiffProviderFactoryService';
|
|
||||||
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||||
import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService';
|
import { IDiffProviderFactoryService } from 'vs/editor/browser/widget/diffEditor/diffProviderFactoryService';
|
||||||
|
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||||
import { Range } from 'vs/editor/common/core/range';
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
import { ITextModel } from 'vs/editor/common/model';
|
import { ITextModel } from 'vs/editor/common/model';
|
||||||
|
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
||||||
import { IModelService } from 'vs/editor/common/services/model';
|
import { IModelService } from 'vs/editor/common/services/model';
|
||||||
|
import { TestDiffProviderFactoryService } from 'vs/editor/test/browser/diff/testDiffProviderFactoryService';
|
||||||
import { instantiateTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
import { instantiateTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
|
||||||
|
@ -30,24 +34,17 @@ import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||||
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
|
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
|
||||||
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
|
import { IAccessibleViewService } from 'vs/workbench/contrib/accessibility/browser/accessibleView';
|
||||||
import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat';
|
import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||||
|
import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
|
||||||
import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
|
import { InlineChatController, InlineChatRunOptions, State } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
|
||||||
import { IInlineChatSavingService } from '../../browser/inlineChatSavingService';
|
|
||||||
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
import { Session } from 'vs/workbench/contrib/inlineChat/browser/inlineChatSession';
|
||||||
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl';
|
|
||||||
import { IInlineChatSessionService } from '../../browser/inlineChatSessionService';
|
|
||||||
import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatEditResponse, IInlineChatRequest, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
import { CTX_INLINE_CHAT_USER_DID_EDIT, EditMode, IInlineChatEditResponse, IInlineChatRequest, IInlineChatService, InlineChatConfigKeys, InlineChatResponseType } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
|
||||||
import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl';
|
import { InlineChatServiceImpl } from 'vs/workbench/contrib/inlineChat/common/inlineChatServiceImpl';
|
||||||
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
import { workbenchInstantiationService } from 'vs/workbench/test/browser/workbenchTestServices';
|
||||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
import { IInlineChatSavingService } from '../../browser/inlineChatSavingService';
|
||||||
|
import { IInlineChatSessionService } from '../../browser/inlineChatSessionService';
|
||||||
|
import { InlineChatSessionServiceImpl } from '../../browser/inlineChatSessionServiceImpl';
|
||||||
import { TestWorkerService } from './testWorkerService';
|
import { TestWorkerService } from './testWorkerService';
|
||||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorker';
|
|
||||||
import { Schemas } from 'vs/base/common/network';
|
|
||||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
|
||||||
import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService';
|
|
||||||
import { MockChatContributionService } from 'vs/workbench/contrib/chat/test/common/mockChatContributionService';
|
|
||||||
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
|
|
||||||
import { ChatAgentService, IChatAgentImplementation, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
|
||||||
|
|
||||||
suite('InteractiveChatController', function () {
|
suite('InteractiveChatController', function () {
|
||||||
class TestController extends InlineChatController {
|
class TestController extends InlineChatController {
|
||||||
|
@ -117,8 +114,6 @@ suite('InteractiveChatController', function () {
|
||||||
const serviceCollection = new ServiceCollection(
|
const serviceCollection = new ServiceCollection(
|
||||||
[IEditorWorkerService, new SyncDescriptor(TestWorkerService)],
|
[IEditorWorkerService, new SyncDescriptor(TestWorkerService)],
|
||||||
[IContextKeyService, contextKeyService],
|
[IContextKeyService, contextKeyService],
|
||||||
[IChatContributionService, new MockChatContributionService(
|
|
||||||
[{ extensionId: nullExtensionDescription.identifier, name: 'testAgent', isDefault: true }])],
|
|
||||||
[IChatAgentService, new SyncDescriptor(ChatAgentService)],
|
[IChatAgentService, new SyncDescriptor(ChatAgentService)],
|
||||||
[IInlineChatService, inlineChatService],
|
[IInlineChatService, inlineChatService],
|
||||||
[IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)],
|
[IDiffProviderFactoryService, new SyncDescriptor(TestDiffProviderFactoryService)],
|
||||||
|
@ -152,15 +147,7 @@ suite('InteractiveChatController', function () {
|
||||||
}]
|
}]
|
||||||
);
|
);
|
||||||
|
|
||||||
instaService = store.add(workbenchInstantiationService(undefined, store).createChild(serviceCollection));
|
instaService = store.add((store.add(workbenchInstantiationService(undefined, store))).createChild(serviceCollection));
|
||||||
const chatAgentService = instaService.get(IChatAgentService);
|
|
||||||
const agent = {
|
|
||||||
async invoke(request, progress, history, token) {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
} satisfies IChatAgentImplementation;
|
|
||||||
store.add(chatAgentService.registerAgent('testAgent', agent));
|
|
||||||
|
|
||||||
inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService));
|
inlineChatSessionService = store.add(instaService.get(IInlineChatSessionService));
|
||||||
|
|
||||||
model = store.add(instaService.get(IModelService).createModel('Hello\nWorld\nHello Again\nHello World\n', null));
|
model = store.add(instaService.get(IModelService).createModel('Hello\nWorld\nHello Again\nHello World\n', null));
|
||||||
|
|
|
@ -14,14 +14,18 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
|
||||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||||
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
import { ResourceNotebookCellEdit } from 'vs/workbench/contrib/bulkEdit/browser/bulkCellEdits';
|
||||||
import { changeCellToKind, computeCellLinesContents, copyCellRange, joinCellsWithSurrounds, joinSelectedCells, moveCellRange } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
|
import { changeCellToKind, computeCellLinesContents, copyCellRange, joinCellsWithSurrounds, joinSelectedCells, moveCellRange } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
|
||||||
import { cellExecutionArgs, CellOverflowToolbarGroups, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookCellAction, NotebookMultiCellAction, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
|
import { cellExecutionArgs, CellOverflowToolbarGroups, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookCellAction, NotebookMultiCellAction, parseMultiCellExecutionArgs, findTargetCellEditor } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
|
||||||
import { CellFocusMode, EXPAND_CELL_INPUT_COMMAND_ID, EXPAND_CELL_OUTPUT_COMMAND_ID, ICellOutputViewModel, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
import { CellFocusMode, EXPAND_CELL_INPUT_COMMAND_ID, EXPAND_CELL_OUTPUT_COMMAND_ID, ICellOutputViewModel, ICellViewModel, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
|
||||||
import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
import { NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
||||||
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
|
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
|
||||||
import { CellEditType, CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
import { CellEditType, CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||||
|
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
|
||||||
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
|
import { CodeActionController } from 'vs/editor/contrib/codeAction/browser/codeActionController';
|
||||||
|
import { CodeActionKind, CodeActionTriggerSource } from 'vs/editor/contrib/codeAction/common/types';
|
||||||
|
|
||||||
//#region Move/Copy cells
|
//#region Move/Copy cells
|
||||||
const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp';
|
const MOVE_CELL_UP_COMMAND_ID = 'notebook.cell.moveUp';
|
||||||
|
@ -353,6 +357,7 @@ const COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.collapseAllCellOutpu
|
||||||
const EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.expandAllCellOutputs';
|
const EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.expandAllCellOutputs';
|
||||||
const TOGGLE_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.toggleOutputs';
|
const TOGGLE_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.toggleOutputs';
|
||||||
const TOGGLE_CELL_OUTPUT_SCROLLING = 'notebook.cell.toggleOutputScrolling';
|
const TOGGLE_CELL_OUTPUT_SCROLLING = 'notebook.cell.toggleOutputScrolling';
|
||||||
|
const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions';
|
||||||
|
|
||||||
registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
|
registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -579,6 +584,45 @@ registerAction2(class ToggleCellOutputScrolling extends NotebookMultiCellAction
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerAction2(class ExpandAllCellOutputsAction extends NotebookCellAction {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
id: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID,
|
||||||
|
title: localize2('notebookActions.cellFailureActions', "Show Cell Failure Actions"),
|
||||||
|
precondition: ContextKeyExpr.and(NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated()),
|
||||||
|
f1: true,
|
||||||
|
keybinding: {
|
||||||
|
when: ContextKeyExpr.and(NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS, NOTEBOOK_CELL_EDITOR_FOCUSED.toNegated()),
|
||||||
|
primary: KeyMod.CtrlCmd | KeyCode.Period,
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
|
||||||
|
if (context.cell instanceof CodeCellViewModel) {
|
||||||
|
const error = context.cell.cellErrorDetails;
|
||||||
|
if (error?.location) {
|
||||||
|
const location = Range.lift({
|
||||||
|
startLineNumber: error.location.startLineNumber + 1,
|
||||||
|
startColumn: error.location.startColumn + 1,
|
||||||
|
endLineNumber: error.location.endLineNumber + 1,
|
||||||
|
endColumn: error.location.endColumn + 1
|
||||||
|
});
|
||||||
|
context.notebookEditor.setCellEditorSelection(context.cell, Range.lift(location));
|
||||||
|
const editor = findTargetCellEditor(context, context.cell);
|
||||||
|
if (editor) {
|
||||||
|
const controller = CodeActionController.get(editor);
|
||||||
|
controller?.manualTriggerAtCurrentPosition(
|
||||||
|
localize('cellCommands.quickFix.noneMessage', "No code actions available"),
|
||||||
|
CodeActionTriggerSource.Default,
|
||||||
|
{ include: CodeActionKind.QuickFix });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
function forEachCell(editor: INotebookEditor, callback: (cell: ICellViewModel, index: number) => void) {
|
function forEachCell(editor: INotebookEditor, callback: (cell: ICellViewModel, index: number) => void) {
|
||||||
|
|
|
@ -70,7 +70,9 @@ export class CellDiagnostics extends Disposable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public clear() {
|
public clear() {
|
||||||
this.clearDiagnostics();
|
if (this.ErrorDetails) {
|
||||||
|
this.clearDiagnostics();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearDiagnostics() {
|
private clearDiagnostics() {
|
||||||
|
|
|
@ -13,7 +13,7 @@ import { CellContentPart } from 'vs/workbench/contrib/notebook/browser/view/cell
|
||||||
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
|
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
|
||||||
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
|
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
|
||||||
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||||
import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_GENERATED_BY_CHAT } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
import { NotebookCellExecutionStateContext, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_EDITOR_FOCUSED, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LINE_NUMBERS, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RESOURCE, NOTEBOOK_CELL_TYPE, NOTEBOOK_CELL_GENERATED_BY_CHAT, NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
||||||
import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
|
import { INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
|
||||||
|
|
||||||
export class CellContextKeyPart extends CellContentPart {
|
export class CellContextKeyPart extends CellContentPart {
|
||||||
|
@ -47,6 +47,7 @@ export class CellContextKeyManager extends Disposable {
|
||||||
private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>;
|
private cellLineNumbers!: IContextKey<'on' | 'off' | 'inherit'>;
|
||||||
private cellResource!: IContextKey<string>;
|
private cellResource!: IContextKey<string>;
|
||||||
private cellGeneratedByChat!: IContextKey<boolean>;
|
private cellGeneratedByChat!: IContextKey<boolean>;
|
||||||
|
private cellHasErrorDiagnostics!: IContextKey<boolean>;
|
||||||
|
|
||||||
private markdownEditMode!: IContextKey<boolean>;
|
private markdownEditMode!: IContextKey<boolean>;
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ export class CellContextKeyManager extends Disposable {
|
||||||
this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService);
|
this.cellLineNumbers = NOTEBOOK_CELL_LINE_NUMBERS.bindTo(this._contextKeyService);
|
||||||
this.cellGeneratedByChat = NOTEBOOK_CELL_GENERATED_BY_CHAT.bindTo(this._contextKeyService);
|
this.cellGeneratedByChat = NOTEBOOK_CELL_GENERATED_BY_CHAT.bindTo(this._contextKeyService);
|
||||||
this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService);
|
this.cellResource = NOTEBOOK_CELL_RESOURCE.bindTo(this._contextKeyService);
|
||||||
|
this.cellHasErrorDiagnostics = NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS.bindTo(this._contextKeyService);
|
||||||
|
|
||||||
if (element) {
|
if (element) {
|
||||||
this.updateForElement(element);
|
this.updateForElement(element);
|
||||||
|
@ -200,6 +202,10 @@ export class CellContextKeyManager extends Disposable {
|
||||||
this.cellRunState.set('idle');
|
this.cellRunState.set('idle');
|
||||||
this.cellExecuting.set(false);
|
this.cellExecuting.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.element instanceof CodeCellViewModel) {
|
||||||
|
this.cellHasErrorDiagnostics.set(!!this.element.cellErrorDetails);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateForEditState() {
|
private updateForEditState() {
|
||||||
|
|
|
@ -47,6 +47,9 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
||||||
private _outputCollection: number[] = [];
|
private _outputCollection: number[] = [];
|
||||||
|
|
||||||
private readonly _cellDiagnostics: CellDiagnostics;
|
private readonly _cellDiagnostics: CellDiagnostics;
|
||||||
|
get cellErrorDetails() {
|
||||||
|
return this._cellDiagnostics.ErrorDetails;
|
||||||
|
}
|
||||||
|
|
||||||
private _outputsTop: PrefixSumComputer | null = null;
|
private _outputsTop: PrefixSumComputer | null = null;
|
||||||
|
|
||||||
|
@ -171,7 +174,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
||||||
if (outputLayoutChange) {
|
if (outputLayoutChange) {
|
||||||
this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#model.onDidChangeOutputs');
|
this.layoutChange({ outputHeight: true }, 'CodeCellViewModel#model.onDidChangeOutputs');
|
||||||
}
|
}
|
||||||
if (this._outputCollection.length === 0 && this._cellDiagnostics.ErrorDetails) {
|
if (this._outputCollection.length === 0) {
|
||||||
this._cellDiagnostics.clear();
|
this._cellDiagnostics.clear();
|
||||||
}
|
}
|
||||||
dispose(removedOutputs);
|
dispose(removedOutputs);
|
||||||
|
@ -433,6 +436,7 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
||||||
|
|
||||||
protected onDidChangeTextModelContent(): void {
|
protected onDidChangeTextModelContent(): void {
|
||||||
if (this.getEditState() !== CellEditState.Editing) {
|
if (this.getEditState() !== CellEditState.Editing) {
|
||||||
|
this._cellDiagnostics.clear();
|
||||||
this.updateEditState(CellEditState.Editing, 'onDidChangeTextModelContent');
|
this.updateEditState(CellEditState.Editing, 'onDidChangeTextModelContent');
|
||||||
this._onDidChangeState.fire({ contentChanged: true });
|
this._onDidChangeState.fire({ contentChanged: true });
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,6 +47,7 @@ export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('noteboo
|
||||||
export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false);
|
export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false);
|
||||||
export const NOTEBOOK_CELL_RESOURCE = new RawContextKey<string>('notebookCellResource', '');
|
export const NOTEBOOK_CELL_RESOURCE = new RawContextKey<string>('notebookCellResource', '');
|
||||||
export const NOTEBOOK_CELL_GENERATED_BY_CHAT = new RawContextKey<boolean>('notebookCellGenerateByChat', false);
|
export const NOTEBOOK_CELL_GENERATED_BY_CHAT = new RawContextKey<boolean>('notebookCellGenerateByChat', false);
|
||||||
|
export const NOTEBOOK_CELL_HAS_ERROR_DIAGNOSTICS = new RawContextKey<boolean>('notebookCellHasErrorDiagnostics', false);
|
||||||
|
|
||||||
// Kernels
|
// Kernels
|
||||||
export const NOTEBOOK_KERNEL = new RawContextKey<string>('notebookKernel', undefined);
|
export const NOTEBOOK_KERNEL = new RawContextKey<string>('notebookKernel', undefined);
|
||||||
|
|
|
@ -12,8 +12,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
import { TerminalSettingId } from 'vs/platform/terminal/common/terminal';
|
||||||
import { IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService, GeneratingPhrase } from 'vs/workbench/contrib/chat/browser/chat';
|
import { GeneratingPhrase, IChatAccessibilityService, IChatCodeBlockContextProviderService, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
|
||||||
import { IChatAgentRequest, IChatAgentService, ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
|
import { ChatAgentLocation, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||||
import { ChatUserAction, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
import { ChatUserAction, IChatProgress, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||||
import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||||
|
@ -81,7 +81,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
||||||
readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store);
|
readonly onDidAcceptInput = Event.filter(this._messages.event, m => m === Message.ACCEPT_INPUT, this._store);
|
||||||
readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store);
|
readonly onDidCancelInput = Event.filter(this._messages.event, m => m === Message.CANCEL_INPUT || m === Message.CANCEL_SESSION, this._store);
|
||||||
|
|
||||||
private _terminalAgentId = 'terminal';
|
private _terminalAgentName = 'terminal';
|
||||||
|
private _terminalAgentId: string | undefined;
|
||||||
|
|
||||||
private _model: MutableDisposable<ChatModel> = this._register(new MutableDisposable());
|
private _model: MutableDisposable<ChatModel> = this._register(new MutableDisposable());
|
||||||
|
|
||||||
|
@ -97,7 +98,7 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
||||||
@IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService,
|
@IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService,
|
||||||
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
|
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService,
|
||||||
@IChatService private readonly _chatService: IChatService,
|
@IChatService private readonly _chatService: IChatService,
|
||||||
@IChatCodeBlockContextProviderService private readonly _chatCodeBlockContextProviderService: IChatCodeBlockContextProviderService
|
@IChatCodeBlockContextProviderService private readonly _chatCodeBlockContextProviderService: IChatCodeBlockContextProviderService,
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -113,14 +114,8 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this._chatAgentService.getAgent(this._terminalAgentId)) {
|
if (!this.initTerminalAgent()) {
|
||||||
this._register(this._chatAgentService.onDidChangeAgents(() => {
|
this._register(this._chatAgentService.onDidChangeAgents(() => this.initTerminalAgent()));
|
||||||
if (this._chatAgentService.getAgent(this._terminalAgentId)) {
|
|
||||||
this._terminalAgentRegisteredContextKey.set(true);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
} else {
|
|
||||||
this._terminalAgentRegisteredContextKey.set(true);
|
|
||||||
}
|
}
|
||||||
this._register(this._chatCodeBlockContextProviderService.registerProvider({
|
this._register(this._chatCodeBlockContextProviderService.registerProvider({
|
||||||
getCodeBlockContext: (editor) => {
|
getCodeBlockContext: (editor) => {
|
||||||
|
@ -141,6 +136,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
||||||
}, 'terminal'));
|
}, 'terminal'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private initTerminalAgent(): boolean {
|
||||||
|
const terminalAgent = this._chatAgentService.getAgentsByName(this._terminalAgentName)[0];
|
||||||
|
if (terminalAgent) {
|
||||||
|
this._terminalAgentId = terminalAgent.id;
|
||||||
|
this._terminalAgentRegisteredContextKey.set(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void {
|
xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void {
|
||||||
if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) {
|
if (!this._configurationService.getValue(TerminalSettingId.ExperimentalInlineChat)) {
|
||||||
return;
|
return;
|
||||||
|
@ -285,13 +291,13 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
||||||
const requestProps: IChatAgentRequest = {
|
const requestProps: IChatAgentRequest = {
|
||||||
sessionId: model.sessionId,
|
sessionId: model.sessionId,
|
||||||
requestId: this._currentRequest!.id,
|
requestId: this._currentRequest!.id,
|
||||||
agentId: this._terminalAgentId,
|
agentId: this._terminalAgentId!,
|
||||||
message: this._lastInput,
|
message: this._lastInput,
|
||||||
variables: { variables: [] },
|
variables: { variables: [] },
|
||||||
location: ChatAgentLocation.Terminal
|
location: ChatAgentLocation.Terminal
|
||||||
};
|
};
|
||||||
try {
|
try {
|
||||||
const task = this._chatAgentService.invokeAgent(this._terminalAgentId, requestProps, progressCallback, getHistoryEntriesFromModel(model), cancellationToken);
|
const task = this._chatAgentService.invokeAgent(this._terminalAgentId!, requestProps, progressCallback, getHistoryEntriesFromModel(model), cancellationToken);
|
||||||
this._chatWidget?.value.inlineChatWidget.updateChatMessage(undefined);
|
this._chatWidget?.value.inlineChatWidget.updateChatMessage(undefined);
|
||||||
this._chatWidget?.value.inlineChatWidget.updateFollowUps(undefined);
|
this._chatWidget?.value.inlineChatWidget.updateFollowUps(undefined);
|
||||||
this._chatWidget?.value.inlineChatWidget.updateProgress(true);
|
this._chatWidget?.value.inlineChatWidget.updateProgress(true);
|
||||||
|
|
|
@ -21,9 +21,9 @@ declare module 'vscode' {
|
||||||
readonly prompt: string;
|
readonly prompt: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the chat participant and contributing extension to which this request was directed.
|
* The id of the chat participant and contributing extension to which this request was directed.
|
||||||
*/
|
*/
|
||||||
readonly participant: { readonly extensionId: string; readonly name: string };
|
readonly participant: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the {@link ChatCommand command} that was selected for this request.
|
* The name of the {@link ChatCommand command} that was selected for this request.
|
||||||
|
@ -35,7 +35,7 @@ declare module 'vscode' {
|
||||||
*/
|
*/
|
||||||
readonly variables: ChatResolvedVariable[];
|
readonly variables: ChatResolvedVariable[];
|
||||||
|
|
||||||
private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: { extensionId: string; name: string });
|
private constructor(prompt: string, command: string | undefined, variables: ChatResolvedVariable[], participant: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,16 +54,16 @@ declare module 'vscode' {
|
||||||
readonly result: ChatResult;
|
readonly result: ChatResult;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the chat participant and contributing extension that this response came from.
|
* The id of the chat participant and contributing extension that this response came from.
|
||||||
*/
|
*/
|
||||||
readonly participant: { readonly extensionId: string; readonly name: string };
|
readonly participant: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the command that this response came from.
|
* The name of the command that this response came from.
|
||||||
*/
|
*/
|
||||||
readonly command?: string;
|
readonly command?: string;
|
||||||
|
|
||||||
private constructor(response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>, result: ChatResult, participant: { extensionId: string; name: string });
|
private constructor(response: ReadonlyArray<ChatResponseMarkdownPart | ChatResponseFileTreePart | ChatResponseAnchorPart | ChatResponseCommandButtonPart>, result: ChatResult, participant: string);
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatContext {
|
export interface ChatContext {
|
||||||
|
@ -158,7 +158,7 @@ declare module 'vscode' {
|
||||||
label?: string;
|
label?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant.
|
* By default, the followup goes to the same participant/command. But this property can be set to invoke a different participant by ID.
|
||||||
* Followups can only invoke a participant that was contributed by the same extension.
|
* Followups can only invoke a participant that was contributed by the same extension.
|
||||||
*/
|
*/
|
||||||
participant?: string;
|
participant?: string;
|
||||||
|
@ -192,9 +192,9 @@ declare module 'vscode' {
|
||||||
*/
|
*/
|
||||||
export interface ChatParticipant {
|
export interface ChatParticipant {
|
||||||
/**
|
/**
|
||||||
* The short name by which this participant is referred to in the UI, e.g `workspace`.
|
* A unique ID for this participant.
|
||||||
*/
|
*/
|
||||||
readonly name: string;
|
readonly id: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Icon for the participant shown in UI.
|
* Icon for the participant shown in UI.
|
||||||
|
@ -446,12 +446,11 @@ declare module 'vscode' {
|
||||||
/**
|
/**
|
||||||
* Create a new {@link ChatParticipant chat participant} instance.
|
* Create a new {@link ChatParticipant chat participant} instance.
|
||||||
*
|
*
|
||||||
* @param name Short name by which the participant is referred to in the UI. The name must be unique for the extension
|
* @param id A unique identifier for the participant.
|
||||||
* contributing the participant but can collide with names from other extensions.
|
|
||||||
* @param handler A request handler for the participant.
|
* @param handler A request handler for the participant.
|
||||||
* @returns A new chat participant
|
* @returns A new chat participant
|
||||||
*/
|
*/
|
||||||
export function createChatParticipant(name: string, handler: ChatRequestHandler): ChatParticipant;
|
export function createChatParticipant(id: string, handler: ChatRequestHandler): ChatParticipant;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -173,7 +173,9 @@ declare module 'vscode' {
|
||||||
/**
|
/**
|
||||||
* Create a chat participant with the extended progress type
|
* Create a chat participant with the extended progress type
|
||||||
*/
|
*/
|
||||||
export function createChatParticipant(name: string, handler: ChatExtendedRequestHandler): ChatParticipant;
|
export function createChatParticipant(id: string, handler: ChatExtendedRequestHandler): ChatParticipant;
|
||||||
|
|
||||||
|
export function createDynamicChatParticipant(id: string, name: string, description: string, handler: ChatExtendedRequestHandler): ChatParticipant;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -280,12 +282,4 @@ declare module 'vscode' {
|
||||||
*/
|
*/
|
||||||
resolve2?(name: string, context: ChatVariableContext, stream: ChatVariableResolverResponseStream, token: CancellationToken): ProviderResult<ChatVariableValue[]>;
|
resolve2?(name: string, context: ChatVariableContext, stream: ChatVariableResolverResponseStream, token: CancellationToken): ProviderResult<ChatVariableValue[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ChatParticipant {
|
|
||||||
/**
|
|
||||||
* A human-readable description explaining what this participant does.
|
|
||||||
* Only allow a static description for normal participants. Here where dynamic participants are allowed, the description must be able to be set as well.
|
|
||||||
*/
|
|
||||||
description?: string;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue