Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 36 additions & 32 deletions packages/tui/src/component/prompt/autocomplete.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type AutocompleteOption = {
path?: string
absolute?: string
destructive?: { id: string; confirm: string; run: () => void }
kind?: "skill"
kind?: "skill" | "agent" | "file" | "directory" | "reference"
queueable?: boolean
}

Expand Down Expand Up @@ -376,6 +376,7 @@ export function Autocomplete(props: {
const { filename, part } = createFilePart(item, path.join(result.location.directory, item.path), lineRange)
return {
display: Locale.truncateMiddle(filename, width),
kind: item.type === "directory" ? "directory" : "file",
value: filename,
isDirectory: item.type === "directory",
path: item.path,
Expand Down Expand Up @@ -405,41 +406,13 @@ export function Autocomplete(props: {
return { options: [], failed: false, query: "", resolved: false }
})

const mcpResources = createMemo(() => {
if (store.visible !== "reference") return []

const options: AutocompleteOption[] = []
const width = props.anchor().width - 4

for (const res of data.location.mcp.resource.list(location.current) ?? []) {
options.push({
display: Locale.truncateMiddle(res.name, width),
// Match the name only; matching the URI caused unrelated fuzzy hits.
value: res.name,
description: res.description,
onSelect: () => {
insertPart(res.name, {
type: "file",
value: {
uri: res.uri,
name: res.name,
description: res.description,
mention: { start: 0, end: 0, text: "" },
},
})
},
})
}

return options
})

const agents = createMemo(() => {
return (data.location.agent.list() ?? [])
.filter((agent) => !agent.hidden && agent.mode !== "primary")
.map(
(agent): AutocompleteOption => ({
display: "@" + agent.id,
kind: "agent",
onSelect: () => {
insertPart(agent.id, {
type: "agent",
Expand Down Expand Up @@ -475,6 +448,7 @@ export function Autocomplete(props: {
.map(
(reference): AutocompleteOption => ({
display: "@" + reference.name,
kind: "reference",
description: ` ${reference.source.type === "git" ? reference.source.repository : reference.source.path}`,
onSelect: () => {
insertPart(reference.name, {
Expand Down Expand Up @@ -580,7 +554,7 @@ export function Autocomplete(props: {
const fileOptions: AutocompleteOption[] = store.visible === "reference" ? fileSearch.options : []
const nonFileOptions: AutocompleteOption[] =
store.visible === "reference"
? [...skillOptions(), ...referenceAliasesValue, ...agentsValue, ...mcpResources()]
? [...skillOptions(), ...referenceAliasesValue, ...agentsValue]
: store.index === 0
? [...commandsValue]
: []
Expand Down Expand Up @@ -882,6 +856,13 @@ export function Autocomplete(props: {
return "No matching files, agents, or references"
})
const emptyError = createMemo(() => store.visible === "reference" && !files.loading && visibleFiles().failed)
const labels = {
skill: "Skill",
agent: "Agent",
file: "File",
directory: "Dir",
reference: "Reference",
}

return (
<box
Expand Down Expand Up @@ -915,6 +896,14 @@ export function Autocomplete(props: {
>
{(option, index) => {
const destructive = () => option().destructive
const label = () => {
const kind = option().kind
return kind ? labels[kind] : undefined
}
const contentWidth = () => {
const text = label()
return Math.max(1, position().width - 4 - (text ? stringWidth(text) + 2 : 0))
}
const confirmingAction = () => {
const action = destructive()
return action !== undefined && action.id === confirming()
Expand Down Expand Up @@ -944,17 +933,32 @@ export function Autocomplete(props: {
: theme.text.default
}
flexShrink={0}
wrapMode="none"
>
{confirmingAction() ? destructive()?.confirm : option().display}
{Locale.truncateMiddle(
confirmingAction() ? (destructive()?.confirm ?? "") : option().display,
contentWidth(),
)}
</text>
<Show when={!confirmingAction() && option().description}>
<text
fg={index === store.selected ? theme.text.action.primary.focused : theme.text.subdued}
wrapMode="none"
flexShrink={1}
minWidth={0}
>
{" " + option().description?.replace(/\s+/g, " ").trim()}
</text>
</Show>
<Show when={!confirmingAction() && label()}>
<box flexGrow={1} minWidth={2} />
<text
flexShrink={0}
fg={index === store.selected ? theme.text.action.primary.focused : theme.text.subdued}
>
{label()}
</text>
</Show>
</box>
)
}}
Expand Down
Loading