fix(vscode): completion view/select event. (#471)
* fix(vscode): completion view/select event. * chore: bump tabby-agent version 0.3.0-dev.release-0.2
parent
63309d6d6e
commit
cc83e4d269
|
|
@ -10,6 +10,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cpy-cli": "^4.2.0",
|
"cpy-cli": "^4.2.0",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"tabby-agent": "0.2.0"
|
"tabby-agent": "0.3.0-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "tabby-agent",
|
"name": "tabby-agent",
|
||||||
"version": "0.2.0",
|
"version": "0.3.0-dev",
|
||||||
"description": "Generic client agent for Tabby AI coding assistant IDE extensions.",
|
"description": "Generic client agent for Tabby AI coding assistant IDE extensions.",
|
||||||
"repository": "https://github.com/TabbyML/tabby",
|
"repository": "https://github.com/TabbyML/tabby",
|
||||||
"main": "./dist/index.js",
|
"main": "./dist/index.js",
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,9 @@ export type CompletionRequest = {
|
||||||
|
|
||||||
export type CompletionResponse = ApiComponents["schemas"]["CompletionResponse"];
|
export type CompletionResponse = ApiComponents["schemas"]["CompletionResponse"];
|
||||||
|
|
||||||
export type LogEventRequest = ApiComponents["schemas"]["LogEventRequest"];
|
export type LogEventRequest = ApiComponents["schemas"]["LogEventRequest"] & {
|
||||||
|
select_kind?: "line";
|
||||||
|
};
|
||||||
|
|
||||||
export type AbortSignalOption = { signal: AbortSignal };
|
export type AbortSignalOption = { signal: AbortSignal };
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -429,7 +429,19 @@ export class TabbyAgent extends EventEmitter implements Agent {
|
||||||
if (this.status === "notInitialized") {
|
if (this.status === "notInitialized") {
|
||||||
throw new Error("Agent is not initialized");
|
throw new Error("Agent is not initialized");
|
||||||
}
|
}
|
||||||
await this.post("/v1/events", { body: request, parseAs: "text" }, options);
|
await this.post(
|
||||||
|
"/v1/events",
|
||||||
|
{
|
||||||
|
body: request,
|
||||||
|
params: {
|
||||||
|
query: {
|
||||||
|
select_kind: request.select_kind,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
parseAs: "text",
|
||||||
|
},
|
||||||
|
options,
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,6 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cpy-cli": "^4.2.0",
|
"cpy-cli": "^4.2.0",
|
||||||
"rimraf": "^5.0.1",
|
"rimraf": "^5.0.1",
|
||||||
"tabby-agent": "0.2.0"
|
"tabby-agent": "0.3.0-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,6 @@ Once you have installed the Tabby VSCode extension, you can easily get started b
|
||||||
1. **Setup the Tabby server**: You have two options to set up your Tabby server. You can either get a Tabby Cloud hosted server [here](https://app.tabbyml.com) or build your own self-hosted Tabby server following [this guide](https://tabby.tabbyml.com/docs/installation).
|
1. **Setup the Tabby server**: You have two options to set up your Tabby server. You can either get a Tabby Cloud hosted server [here](https://app.tabbyml.com) or build your own self-hosted Tabby server following [this guide](https://tabby.tabbyml.com/docs/installation).
|
||||||
2. **Connect the extension to your Tabby server**: Use the command `Tabby: Specify API Endpoint of Tabby` to connect the extension to your Tabby server. If you are using a Tabby Cloud server endpoint, follow the instructions provided in the popup messages to complete the authorization process.
|
2. **Connect the extension to your Tabby server**: Use the command `Tabby: Specify API Endpoint of Tabby` to connect the extension to your Tabby server. If you are using a Tabby Cloud server endpoint, follow the instructions provided in the popup messages to complete the authorization process.
|
||||||
|
|
||||||
Once the setup is complete, Tabby will automatically provide inline suggestions. You can accept the suggestions by simply pressing the `Tab` key. Hovering over the inline suggestion text will display additional useful actions, such as partially accepting suggestions by word or by line.
|
Once the setup is complete, Tabby will automatically provide inline suggestions. You can accept the suggestions by simply pressing the `Tab` key.
|
||||||
|
|
||||||
If you prefer to trigger code completion manually, you can select the manual trigger option in the settings. After that, use the shortcut `Alt + \` to trigger code completion. To access the settings page, use the command `Tabby: Open Settings`.
|
If you prefer to trigger code completion manually, you can select the manual trigger option in the settings. After that, use the shortcut `Alt + \` to trigger code completion. To access the settings page, use the command `Tabby: Open Settings`.
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,6 @@ Tabby will show inline suggestions when you stop typing, and you can accept sugg
|
||||||
|
|
||||||
If you select manual trigger in the [settings](command:tabby.openSettings), you can trigger code completion by pressing `Alt + \`.
|
If you select manual trigger in the [settings](command:tabby.openSettings), you can trigger code completion by pressing `Alt + \`.
|
||||||
|
|
||||||
## Cycling Through Choices
|
|
||||||
|
|
||||||
When multiple choices are available, you can cycle through them by pressing `Alt + [` and `Alt + ]`.
|
|
||||||
|
|
||||||
## Keybindings
|
## Keybindings
|
||||||
|
|
||||||
You can select a keybinding profile in the [settings](command:tabby.openSettings), or customize your own [keybindings](command:tabby.openKeybindings).
|
You can select a keybinding profile in the [settings](command:tabby.openSettings), or customize your own [keybindings](command:tabby.openKeybindings).
|
||||||
|
|
|
||||||
|
|
@ -216,6 +216,6 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@xstate/fsm": "^2.0.1",
|
"@xstate/fsm": "^2.0.1",
|
||||||
"tabby-agent": "0.2.0"
|
"tabby-agent": "0.3.0-dev"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,15 +10,24 @@ import {
|
||||||
workspace,
|
workspace,
|
||||||
} from "vscode";
|
} from "vscode";
|
||||||
import { EventEmitter } from "events";
|
import { EventEmitter } from "events";
|
||||||
import { CompletionResponse } from "tabby-agent";
|
import { CompletionRequest, CompletionResponse, LogEventRequest } from "tabby-agent";
|
||||||
import { agent } from "./agent";
|
import { agent } from "./agent";
|
||||||
|
|
||||||
export class TabbyCompletionProvider extends EventEmitter implements InlineCompletionItemProvider {
|
export class TabbyCompletionProvider extends EventEmitter implements InlineCompletionItemProvider {
|
||||||
|
static instance: TabbyCompletionProvider;
|
||||||
|
static getInstance(): TabbyCompletionProvider {
|
||||||
|
if (!TabbyCompletionProvider.instance) {
|
||||||
|
TabbyCompletionProvider.instance = new TabbyCompletionProvider();
|
||||||
|
}
|
||||||
|
return TabbyCompletionProvider.instance;
|
||||||
|
}
|
||||||
|
|
||||||
private triggerMode: "automatic" | "manual" | "disabled" = "automatic";
|
private triggerMode: "automatic" | "manual" | "disabled" = "automatic";
|
||||||
private onGoingRequestAbortController: AbortController | null = null;
|
private onGoingRequestAbortController: AbortController | null = null;
|
||||||
private loading: boolean = false;
|
private loading: boolean = false;
|
||||||
|
private latestCompletions: CompletionResponse | null = null;
|
||||||
|
|
||||||
constructor() {
|
private constructor() {
|
||||||
super();
|
super();
|
||||||
this.updateConfiguration();
|
this.updateConfiguration();
|
||||||
workspace.onDidChangeConfiguration((event) => {
|
workspace.onDidChangeConfiguration((event) => {
|
||||||
|
|
@ -57,13 +66,13 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
|
||||||
}
|
}
|
||||||
|
|
||||||
if (token?.isCancellationRequested) {
|
if (token?.isCancellationRequested) {
|
||||||
console.debug("Cancellation was requested.");
|
console.debug("Completion request is canceled before agent request.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replaceRange = this.calculateReplaceRange(document, position);
|
const replaceRange = this.calculateReplaceRange(document, position);
|
||||||
|
|
||||||
const request = {
|
const request: CompletionRequest = {
|
||||||
filepath: document.uri.fsPath,
|
filepath: document.uri.fsPath,
|
||||||
language: document.languageId, // https://code.visualstudio.com/docs/languages/identifiers
|
language: document.languageId, // https://code.visualstudio.com/docs/languages/identifiers
|
||||||
text: document.getText(),
|
text: document.getText(),
|
||||||
|
|
@ -71,10 +80,12 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
|
||||||
manually: context.triggerKind === InlineCompletionTriggerKind.Invoke,
|
manually: context.triggerKind === InlineCompletionTriggerKind.Invoke,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.latestCompletions = null;
|
||||||
|
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
this.onGoingRequestAbortController = abortController;
|
this.onGoingRequestAbortController = abortController;
|
||||||
token?.onCancellationRequested(() => {
|
token?.onCancellationRequested(() => {
|
||||||
console.debug("Cancellation requested.");
|
console.debug("Completion request is canceled.");
|
||||||
abortController.abort();
|
abortController.abort();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -84,7 +95,30 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
|
||||||
const result = await agent().provideCompletions(request, { signal: abortController.signal });
|
const result = await agent().provideCompletions(request, { signal: abortController.signal });
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
this.emit("loadingStatusUpdated");
|
this.emit("loadingStatusUpdated");
|
||||||
return this.toInlineCompletions(result, replaceRange);
|
|
||||||
|
if (token?.isCancellationRequested) {
|
||||||
|
console.debug("Completion request is canceled after agent request.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assume only one choice is provided, do not support multiple choices for now
|
||||||
|
if (result.choices.length > 0) {
|
||||||
|
this.latestCompletions = result;
|
||||||
|
|
||||||
|
this.postEvent("show");
|
||||||
|
|
||||||
|
return [
|
||||||
|
new InlineCompletionItem(result.choices[0].text, replaceRange, {
|
||||||
|
title: "",
|
||||||
|
command: "tabby.applyCallback",
|
||||||
|
arguments: [
|
||||||
|
() => {
|
||||||
|
this.postEvent("accept");
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
if (this.onGoingRequestAbortController === abortController) {
|
if (this.onGoingRequestAbortController === abortController) {
|
||||||
// the request was not replaced by a new request, set loading to false safely
|
// the request was not replaced by a new request, set loading to false safely
|
||||||
|
|
@ -99,6 +133,35 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public postEvent(event: "show" | "accept" | "accept_word" | "accept_line") {
|
||||||
|
const completion = this.latestCompletions;
|
||||||
|
if (completion && completion.choices.length > 0) {
|
||||||
|
let postBody: LogEventRequest = {
|
||||||
|
type: event === "show" ? "view" : "select",
|
||||||
|
completion_id: completion.id,
|
||||||
|
// Assume only one choice is provided for now
|
||||||
|
choice_index: completion.choices[0].index,
|
||||||
|
};
|
||||||
|
switch (event) {
|
||||||
|
case "accept_word":
|
||||||
|
// select_kind should be "word" but not supported by Tabby Server yet, use "line" instead
|
||||||
|
postBody = { ...postBody, select_kind: "line" };
|
||||||
|
break;
|
||||||
|
case "accept_line":
|
||||||
|
postBody = { ...postBody, select_kind: "line" };
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.debug(`Post event ${event}`, { postBody });
|
||||||
|
try {
|
||||||
|
agent().postEvent(postBody);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.debug("Error when posting event", { error });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateConfiguration() {
|
private updateConfiguration() {
|
||||||
if (!workspace.getConfiguration("editor").get("inlineSuggest.enabled", true)) {
|
if (!workspace.getConfiguration("editor").get("inlineSuggest.enabled", true)) {
|
||||||
this.triggerMode = "disabled";
|
this.triggerMode = "disabled";
|
||||||
|
|
@ -109,23 +172,6 @@ export class TabbyCompletionProvider extends EventEmitter implements InlineCompl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private toInlineCompletions(tabbyCompletion: CompletionResponse | null, range: Range): InlineCompletionItem[] {
|
|
||||||
return (
|
|
||||||
tabbyCompletion?.choices?.map((choice: any) => {
|
|
||||||
let event = {
|
|
||||||
type: "select",
|
|
||||||
completion_id: tabbyCompletion.id,
|
|
||||||
choice_index: choice.index,
|
|
||||||
};
|
|
||||||
return new InlineCompletionItem(choice.text, range, {
|
|
||||||
title: "",
|
|
||||||
command: "tabby.emitEvent",
|
|
||||||
arguments: [event],
|
|
||||||
});
|
|
||||||
}) || []
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
private hasSuffixParen(document: TextDocument, position: Position) {
|
private hasSuffixParen(document: TextDocument, position: Position) {
|
||||||
const suffix = document.getText(
|
const suffix = document.getText(
|
||||||
new Range(position.line, position.character, position.line, position.character + 1),
|
new Range(position.line, position.character, position.line, position.character + 1),
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import {
|
||||||
import { strict as assert } from "assert";
|
import { strict as assert } from "assert";
|
||||||
import { agent } from "./agent";
|
import { agent } from "./agent";
|
||||||
import { notifications } from "./notifications";
|
import { notifications } from "./notifications";
|
||||||
|
import { TabbyCompletionProvider } from "./TabbyCompletionProvider";
|
||||||
|
|
||||||
const configTarget = ConfigurationTarget.Global;
|
const configTarget = ConfigurationTarget.Global;
|
||||||
|
|
||||||
|
|
@ -109,14 +110,6 @@ const gettingStarted: Command = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const emitEvent: Command = {
|
|
||||||
command: "tabby.emitEvent",
|
|
||||||
callback: (event) => {
|
|
||||||
console.debug("Emit Event: ", event);
|
|
||||||
agent().postEvent(event);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const openAuthPage: Command = {
|
const openAuthPage: Command = {
|
||||||
command: "tabby.openAuthPage",
|
command: "tabby.openAuthPage",
|
||||||
callback: (callbacks?: { onAuthStart?: () => void; onAuthEnd?: () => void }) => {
|
callback: (callbacks?: { onAuthStart?: () => void; onAuthEnd?: () => void }) => {
|
||||||
|
|
@ -185,7 +178,7 @@ const acceptInlineCompletion: Command = {
|
||||||
const acceptInlineCompletionNextWord: Command = {
|
const acceptInlineCompletionNextWord: Command = {
|
||||||
command: "tabby.inlineCompletion.acceptNextWord",
|
command: "tabby.inlineCompletion.acceptNextWord",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
// FIXME: sent event when partially accept?
|
TabbyCompletionProvider.getInstance().postEvent("accept_word");
|
||||||
commands.executeCommand("editor.action.inlineSuggest.acceptNextWord");
|
commands.executeCommand("editor.action.inlineSuggest.acceptNextWord");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -193,7 +186,8 @@ const acceptInlineCompletionNextWord: Command = {
|
||||||
const acceptInlineCompletionNextLine: Command = {
|
const acceptInlineCompletionNextLine: Command = {
|
||||||
command: "tabby.inlineCompletion.acceptNextLine",
|
command: "tabby.inlineCompletion.acceptNextLine",
|
||||||
callback: () => {
|
callback: () => {
|
||||||
// FIXME: sent event when partially accept?
|
TabbyCompletionProvider.getInstance().postEvent("accept_line");
|
||||||
|
// FIXME: this command move cursor to next line, but we want to move cursor to the end of current line
|
||||||
commands.executeCommand("editor.action.inlineSuggest.acceptNextLine");
|
commands.executeCommand("editor.action.inlineSuggest.acceptNextLine");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -206,7 +200,6 @@ export const tabbyCommands = () =>
|
||||||
openTabbyAgentSettings,
|
openTabbyAgentSettings,
|
||||||
openKeybindings,
|
openKeybindings,
|
||||||
gettingStarted,
|
gettingStarted,
|
||||||
emitEvent,
|
|
||||||
openAuthPage,
|
openAuthPage,
|
||||||
applyCallback,
|
applyCallback,
|
||||||
triggerInlineCompletion,
|
triggerInlineCompletion,
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import { TabbyStatusBarItem } from "./TabbyStatusBarItem";
|
||||||
export async function activate(context: ExtensionContext) {
|
export async function activate(context: ExtensionContext) {
|
||||||
console.debug("Activating Tabby extension", new Date());
|
console.debug("Activating Tabby extension", new Date());
|
||||||
await createAgentInstance(context);
|
await createAgentInstance(context);
|
||||||
const completionProvider = new TabbyCompletionProvider();
|
const completionProvider = TabbyCompletionProvider.getInstance();
|
||||||
const statusBarItem = new TabbyStatusBarItem(completionProvider);
|
const statusBarItem = new TabbyStatusBarItem(completionProvider);
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
languages.registerInlineCompletionItemProvider({ pattern: "**" }, completionProvider),
|
languages.registerInlineCompletionItemProvider({ pattern: "**" }, completionProvider),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue