refactor(tui): unify command registry and derive slash commands (#9115)
This commit is contained in:
@@ -332,16 +332,15 @@ export function Autocomplete(props: {
|
||||
)
|
||||
})
|
||||
|
||||
const session = createMemo(() => (props.sessionID ? sync.session.get(props.sessionID) : undefined))
|
||||
const commands = createMemo((): AutocompleteOption[] => {
|
||||
const results: AutocompleteOption[] = []
|
||||
const s = session()
|
||||
for (const command of sync.data.command) {
|
||||
const results: AutocompleteOption[] = [...command.slashes()]
|
||||
|
||||
for (const serverCommand of sync.data.command) {
|
||||
results.push({
|
||||
display: "/" + command.name + (command.mcp ? " (MCP)" : ""),
|
||||
description: command.description,
|
||||
display: "/" + serverCommand.name + (serverCommand.mcp ? " (MCP)" : ""),
|
||||
description: serverCommand.description,
|
||||
onSelect: () => {
|
||||
const newText = "/" + command.name + " "
|
||||
const newText = "/" + serverCommand.name + " "
|
||||
const cursor = props.input().logicalCursor
|
||||
props.input().deleteRange(0, 0, cursor.row, cursor.col)
|
||||
props.input().insertText(newText)
|
||||
@@ -349,138 +348,9 @@ export function Autocomplete(props: {
|
||||
},
|
||||
})
|
||||
}
|
||||
if (s) {
|
||||
results.push(
|
||||
{
|
||||
display: "/undo",
|
||||
description: "undo the last message",
|
||||
onSelect: () => {
|
||||
command.trigger("session.undo")
|
||||
},
|
||||
},
|
||||
{
|
||||
display: "/redo",
|
||||
description: "redo the last message",
|
||||
onSelect: () => command.trigger("session.redo"),
|
||||
},
|
||||
{
|
||||
display: "/compact",
|
||||
aliases: ["/summarize"],
|
||||
description: "compact the session",
|
||||
onSelect: () => command.trigger("session.compact"),
|
||||
},
|
||||
{
|
||||
display: "/unshare",
|
||||
disabled: !s.share,
|
||||
description: "unshare a session",
|
||||
onSelect: () => command.trigger("session.unshare"),
|
||||
},
|
||||
{
|
||||
display: "/rename",
|
||||
description: "rename session",
|
||||
onSelect: () => command.trigger("session.rename"),
|
||||
},
|
||||
{
|
||||
display: "/copy",
|
||||
description: "copy session transcript to clipboard",
|
||||
onSelect: () => command.trigger("session.copy"),
|
||||
},
|
||||
{
|
||||
display: "/export",
|
||||
description: "export session transcript to file",
|
||||
onSelect: () => command.trigger("session.export"),
|
||||
},
|
||||
{
|
||||
display: "/timeline",
|
||||
description: "jump to message",
|
||||
onSelect: () => command.trigger("session.timeline"),
|
||||
},
|
||||
{
|
||||
display: "/fork",
|
||||
description: "fork from message",
|
||||
onSelect: () => command.trigger("session.fork"),
|
||||
},
|
||||
{
|
||||
display: "/thinking",
|
||||
description: "toggle thinking visibility",
|
||||
onSelect: () => command.trigger("session.toggle.thinking"),
|
||||
},
|
||||
)
|
||||
if (sync.data.config.share !== "disabled") {
|
||||
results.push({
|
||||
display: "/share",
|
||||
disabled: !!s.share?.url,
|
||||
description: "share a session",
|
||||
onSelect: () => command.trigger("session.share"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results.push(
|
||||
{
|
||||
display: "/new",
|
||||
aliases: ["/clear"],
|
||||
description: "create a new session",
|
||||
onSelect: () => command.trigger("session.new"),
|
||||
},
|
||||
{
|
||||
display: "/models",
|
||||
description: "list models",
|
||||
onSelect: () => command.trigger("model.list"),
|
||||
},
|
||||
{
|
||||
display: "/agents",
|
||||
description: "list agents",
|
||||
onSelect: () => command.trigger("agent.list"),
|
||||
},
|
||||
{
|
||||
display: "/session",
|
||||
aliases: ["/resume", "/continue"],
|
||||
description: "list sessions",
|
||||
onSelect: () => command.trigger("session.list"),
|
||||
},
|
||||
{
|
||||
display: "/status",
|
||||
description: "show status",
|
||||
onSelect: () => command.trigger("opencode.status"),
|
||||
},
|
||||
{
|
||||
display: "/mcp",
|
||||
description: "toggle MCPs",
|
||||
onSelect: () => command.trigger("mcp.list"),
|
||||
},
|
||||
{
|
||||
display: "/theme",
|
||||
description: "toggle theme",
|
||||
onSelect: () => command.trigger("theme.switch"),
|
||||
},
|
||||
{
|
||||
display: "/editor",
|
||||
description: "open editor",
|
||||
onSelect: () => command.trigger("prompt.editor", "prompt"),
|
||||
},
|
||||
{
|
||||
display: "/connect",
|
||||
description: "connect to a provider",
|
||||
onSelect: () => command.trigger("provider.connect"),
|
||||
},
|
||||
{
|
||||
display: "/help",
|
||||
description: "show help",
|
||||
onSelect: () => command.trigger("help.show"),
|
||||
},
|
||||
{
|
||||
display: "/commands",
|
||||
description: "show all commands",
|
||||
onSelect: () => command.show(),
|
||||
},
|
||||
{
|
||||
display: "/exit",
|
||||
aliases: ["/quit", "/q"],
|
||||
description: "exit the app",
|
||||
onSelect: () => command.trigger("app.exit"),
|
||||
},
|
||||
)
|
||||
results.sort((a, b) => a.display.localeCompare(b.display))
|
||||
|
||||
const max = firstBy(results, [(x) => x.display.length, "desc"])?.display.length
|
||||
if (!max) return results
|
||||
return results.map((item) => ({
|
||||
@@ -494,9 +364,8 @@ export function Autocomplete(props: {
|
||||
const agentsValue = agents()
|
||||
const commandsValue = commands()
|
||||
|
||||
const mixed: AutocompleteOption[] = (
|
||||
const mixed: AutocompleteOption[] =
|
||||
store.visible === "@" ? [...agentsValue, ...(filesValue || []), ...mcpResources()] : [...commandsValue]
|
||||
).filter((x) => x.disabled !== true)
|
||||
|
||||
const currentFilter = filter()
|
||||
|
||||
|
||||
@@ -157,7 +157,7 @@ export function Prompt(props: PromptProps) {
|
||||
title: "Clear prompt",
|
||||
value: "prompt.clear",
|
||||
category: "Prompt",
|
||||
disabled: true,
|
||||
hidden: true,
|
||||
onSelect: (dialog) => {
|
||||
input.extmarks.clear()
|
||||
input.clear()
|
||||
@@ -167,9 +167,9 @@ export function Prompt(props: PromptProps) {
|
||||
{
|
||||
title: "Submit prompt",
|
||||
value: "prompt.submit",
|
||||
disabled: true,
|
||||
keybind: "input_submit",
|
||||
category: "Prompt",
|
||||
hidden: true,
|
||||
onSelect: (dialog) => {
|
||||
if (!input.focused) return
|
||||
submit()
|
||||
@@ -179,9 +179,9 @@ export function Prompt(props: PromptProps) {
|
||||
{
|
||||
title: "Paste",
|
||||
value: "prompt.paste",
|
||||
disabled: true,
|
||||
keybind: "input_paste",
|
||||
category: "Prompt",
|
||||
hidden: true,
|
||||
onSelect: async () => {
|
||||
const content = await Clipboard.read()
|
||||
if (content?.mime.startsWith("image/")) {
|
||||
@@ -197,8 +197,9 @@ export function Prompt(props: PromptProps) {
|
||||
title: "Interrupt session",
|
||||
value: "session.interrupt",
|
||||
keybind: "session_interrupt",
|
||||
disabled: status().type === "idle",
|
||||
category: "Session",
|
||||
hidden: true,
|
||||
enabled: status().type !== "idle",
|
||||
onSelect: (dialog) => {
|
||||
if (autocomplete.visible) return
|
||||
if (!input.focused) return
|
||||
@@ -229,7 +230,10 @@ export function Prompt(props: PromptProps) {
|
||||
category: "Session",
|
||||
keybind: "editor_open",
|
||||
value: "prompt.editor",
|
||||
onSelect: async (dialog, trigger) => {
|
||||
slash: {
|
||||
name: "editor",
|
||||
},
|
||||
onSelect: async (dialog) => {
|
||||
dialog.clear()
|
||||
|
||||
// replace summarized text parts with the actual text
|
||||
@@ -242,7 +246,7 @@ export function Prompt(props: PromptProps) {
|
||||
|
||||
const nonTextParts = store.prompt.parts.filter((p) => p.type !== "text")
|
||||
|
||||
const value = trigger === "prompt" ? "" : text
|
||||
const value = text
|
||||
const content = await Editor.open({ value, renderer })
|
||||
if (!content) return
|
||||
|
||||
@@ -432,7 +436,7 @@ export function Prompt(props: PromptProps) {
|
||||
title: "Stash prompt",
|
||||
value: "prompt.stash",
|
||||
category: "Prompt",
|
||||
disabled: !store.prompt.input,
|
||||
enabled: !!store.prompt.input,
|
||||
onSelect: (dialog) => {
|
||||
if (!store.prompt.input) return
|
||||
stash.push({
|
||||
@@ -450,7 +454,7 @@ export function Prompt(props: PromptProps) {
|
||||
title: "Stash pop",
|
||||
value: "prompt.stash.pop",
|
||||
category: "Prompt",
|
||||
disabled: stash.list().length === 0,
|
||||
enabled: stash.list().length > 0,
|
||||
onSelect: (dialog) => {
|
||||
const entry = stash.pop()
|
||||
if (entry) {
|
||||
@@ -466,7 +470,7 @@ export function Prompt(props: PromptProps) {
|
||||
title: "Stash list",
|
||||
value: "prompt.stash.list",
|
||||
category: "Prompt",
|
||||
disabled: stash.list().length === 0,
|
||||
enabled: stash.list().length > 0,
|
||||
onSelect: (dialog) => {
|
||||
dialog.replace(() => (
|
||||
<DialogStash
|
||||
|
||||
Reference in New Issue
Block a user