tabby/clients/vscode/src/commands.ts

307 lines
9.4 KiB
TypeScript

import {
ConfigurationTarget,
InputBoxValidationSeverity,
ProgressLocation,
Uri,
ThemeIcon,
ExtensionContext,
workspace,
window,
env,
commands,
} from "vscode";
import os from "os";
import { strict as assert } from "assert";
import { agent } from "./agent";
import { notifications } from "./notifications";
import { TabbyCompletionProvider } from "./TabbyCompletionProvider";
import { TabbyStatusBarItem } from "./TabbyStatusBarItem";
const configTarget = ConfigurationTarget.Global;
type Command = {
command: string;
callback: (...args: any[]) => any;
thisArg?: any;
};
const toggleInlineCompletionTriggerMode: Command = {
command: "tabby.toggleInlineCompletionTriggerMode",
callback: (value: "automatic" | "manual" | undefined) => {
const configuration = workspace.getConfiguration("tabby");
let target = value;
if (!target) {
const current = configuration.get("inlineCompletion.triggerMode", "automatic");
if (current === "automatic") {
target = "manual";
} else {
target = "automatic";
}
}
configuration.update("inlineCompletion.triggerMode", target, configTarget, false);
},
};
const setApiEndpoint: Command = {
command: "tabby.setApiEndpoint",
callback: () => {
const configuration = workspace.getConfiguration("tabby");
window
.showInputBox({
prompt: "Enter the URL of your Tabby Server",
value: configuration.get("api.endpoint", ""),
validateInput: (input: string) => {
try {
const url = new URL(input);
assert(url.protocol == "http:" || url.protocol == "https:");
} catch (_) {
return {
message: "Please enter a validate http or https URL.",
severity: InputBoxValidationSeverity.Error,
};
}
return null;
},
})
.then((url) => {
if (url) {
console.debug("Set Tabby Server URL: ", url);
configuration.update("api.endpoint", url, configTarget, false);
}
});
},
};
const openSettings: Command = {
command: "tabby.openSettings",
callback: () => {
commands.executeCommand("workbench.action.openSettings", "@ext:TabbyML.vscode-tabby");
},
};
const openTabbyAgentSettings: Command = {
command: "tabby.openTabbyAgentSettings",
callback: () => {
if (env.appHost !== "desktop") {
window.showWarningMessage("Tabby Agent config file is not supported on web.", { modal: true });
return;
}
const agentUserConfig = Uri.joinPath(Uri.file(os.homedir()), ".tabby-client", "agent", "config.toml");
workspace.fs.stat(agentUserConfig).then(
() => {
workspace.openTextDocument(agentUserConfig).then((document) => {
window.showTextDocument(document);
});
},
() => {
window.showWarningMessage("Tabby Agent config file not found.", { modal: true });
},
);
},
};
const openKeybindings: Command = {
command: "tabby.openKeybindings",
callback: () => {
commands.executeCommand("workbench.action.openGlobalKeybindings", "tabby.inlineCompletion");
},
};
const gettingStarted: Command = {
command: "tabby.gettingStarted",
callback: () => {
commands.executeCommand("workbench.action.openWalkthrough", "TabbyML.vscode-tabby#gettingStarted");
},
};
const openAuthPage: Command = {
command: "tabby.openAuthPage",
callback: (callbacks?: { onAuthStart?: () => void; onAuthEnd?: () => void }) => {
window.withProgress(
{
location: ProgressLocation.Notification,
title: "Tabby Server Authorization",
cancellable: true,
},
async (progress, token) => {
const abortController = new AbortController();
token.onCancellationRequested(() => {
abortController.abort();
});
const signal = abortController.signal;
try {
callbacks?.onAuthStart?.();
progress.report({ message: "Generating authorization url..." });
const authUrl = await agent().requestAuthUrl({ signal });
if (authUrl) {
env.openExternal(Uri.parse(authUrl.authUrl));
progress.report({ message: "Waiting for authorization from browser..." });
await agent().waitForAuthToken(authUrl.code, { signal });
assert(agent().getStatus() === "ready");
notifications.showInformationAuthSuccess();
} else if (agent().getStatus() === "ready") {
notifications.showInformationWhenStartAuthButAlreadyAuthorized();
} else {
notifications.showInformationWhenAuthFailed();
}
} catch (error: any) {
if (error.name === "AbortError") {
return;
}
console.debug("Error auth", { error });
notifications.showInformationWhenAuthFailed();
} finally {
callbacks?.onAuthEnd?.();
}
},
);
},
};
const applyCallback: Command = {
command: "tabby.applyCallback",
callback: (callback) => {
callback?.();
},
};
const triggerInlineCompletion: Command = {
command: "tabby.inlineCompletion.trigger",
callback: () => {
commands.executeCommand("editor.action.inlineSuggest.trigger");
},
};
const acceptInlineCompletion: Command = {
command: "tabby.inlineCompletion.accept",
callback: () => {
commands.executeCommand("editor.action.inlineSuggest.commit");
},
};
const acceptInlineCompletionNextWord = (completionProvider: TabbyCompletionProvider): Command => {
return {
command: "tabby.inlineCompletion.acceptNextWord",
callback: () => {
completionProvider.postEvent("accept_word");
commands.executeCommand("editor.action.inlineSuggest.acceptNextWord");
},
};
};
const acceptInlineCompletionNextLine = (completionProvider: TabbyCompletionProvider): Command => {
return {
command: "tabby.inlineCompletion.acceptNextLine",
callback: () => {
completionProvider.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");
},
};
};
const openOnlineHelp: Command = {
command: "tabby.openOnlineHelp",
callback: () => {
window
.showQuickPick([
{
label: "Online Documentation",
iconPath: new ThemeIcon("book"),
alwaysShow: true,
},
{
label: "Model Registry",
description: "Explore more recommend models from Tabby's model registry",
iconPath: new ThemeIcon("library"),
alwaysShow: true,
},
{
label: "Tabby Slack Community",
description: "Join Tabby's Slack community to get help or feed back",
iconPath: new ThemeIcon("comment-discussion"),
alwaysShow: true,
},
{
label: "Tabby GitHub Repository",
description: "View the source code for Tabby, and open issues",
iconPath: new ThemeIcon("github"),
alwaysShow: true,
},
])
.then((selection) => {
if (selection) {
switch (selection.label) {
case "Online Documentation":
env.openExternal(Uri.parse("https://tabby.tabbyml.com/"));
break;
case "Model Registry":
env.openExternal(Uri.parse("https://tabby.tabbyml.com/docs/models/"));
break;
case "Tabby Slack Community":
env.openExternal(
Uri.parse("https://join.slack.com/t/tabbycommunity/shared_invite/zt-1xeiddizp-bciR2RtFTaJ37RBxr8VxpA"),
);
break;
case "Tabby GitHub Repository":
env.openExternal(Uri.parse("https://github.com/tabbyml/tabby"));
break;
}
}
});
},
};
const muteNotifications = (context: ExtensionContext, statusBarItem: TabbyStatusBarItem): Command => {
return {
command: "tabby.notifications.mute",
callback: (type: string) => {
const notifications = context.globalState.get<string[]>("notifications.muted", []);
notifications.push(type);
context.globalState.update("notifications.muted", notifications);
statusBarItem.refresh();
},
};
};
const resetMutedNotifications = (context: ExtensionContext, statusBarItem: TabbyStatusBarItem): Command => {
return {
command: "tabby.notifications.resetMuted",
callback: (type?: string) => {
const notifications = context.globalState.get<string[]>("notifications.muted", []);
if (type) {
context.globalState.update(
"notifications.muted",
notifications.filter((t) => t !== type),
);
} else {
context.globalState.update("notifications.muted", []);
}
statusBarItem.refresh();
},
};
};
export const tabbyCommands = (
context: ExtensionContext,
completionProvider: TabbyCompletionProvider,
statusBarItem: TabbyStatusBarItem,
) =>
[
toggleInlineCompletionTriggerMode,
setApiEndpoint,
openSettings,
openTabbyAgentSettings,
openKeybindings,
gettingStarted,
openAuthPage,
applyCallback,
triggerInlineCompletion,
acceptInlineCompletion,
acceptInlineCompletionNextWord(completionProvider),
acceptInlineCompletionNextLine(completionProvider),
openOnlineHelp,
muteNotifications(context, statusBarItem),
resetMutedNotifications(context, statusBarItem),
].map((command) => commands.registerCommand(command.command, command.callback, command.thisArg));