fix(tui): keep slash completion alive after a leading command

Typing a second slash command went dead whenever the message started with
one: `/work /cle` offered nothing while `do /work then /cle` completed
fine, which reads as an intermittent glitch rather than a rule.

Only the first slash can be an invocation, so detect the inline shape
first. The leading-command branch claimed the whole line and handed it to
the backend's completer, which has nothing to say about a slash sitting in
a command's argument tail. The inline trigger requires a whitespace-preceded
slash at the caret, so ordinary argument completion (`/cron ad`,
`/personality alic`) is untouched — it fires only where completion was
already dead.
This commit is contained in:
Brooklyn Nicholson 2026-08-01 01:17:41 -05:00
parent 609cd28b17
commit 40ec9834b2
2 changed files with 33 additions and 9 deletions

View File

@ -64,6 +64,28 @@ describe('completionRequestForInput — inline skill references', () => {
})
})
it('completes a second slash in a line that starts with a command', () => {
// Only the first slash is an invocation. Routing the whole line to the
// completer offered nothing, so `/work /cle` went dead while
// `do /work then /cle` completed fine.
expect(completionRequestForInput('/work /cle')).toMatchObject({
method: 'complete.slash',
params: { text: '/cle' },
replaceFrom: 7,
skillsOnly: true
})
})
it('leaves a command own arguments to the command', () => {
for (const input of ['/personality alic', '/cron ad', '/details ']) {
expect(completionRequestForInput(input)).toEqual({
method: 'complete.slash',
params: { text: input },
replaceFrom: 1
})
}
})
it('routes a real mid-message path to path completion, not skills', () => {
expect(completionRequestForInput('open src/foo/ba')).toMatchObject({ method: 'complete.path' })
expect(completionRequestForInput('open /usr/lo')).toMatchObject({ method: 'complete.path' })

View File

@ -41,15 +41,13 @@ export function completionRequestForInput(
return null
}
if (isSlashCommand) {
return { method: 'complete.slash', params: { text: input }, replaceFrom: 1 }
}
// A `/token` mid-message is a skill reference dropped into prose. It's only
// reachable here because the path branch below would otherwise claim it: a
// bare `/cle` matches TAB_PATH_RE as an absolute path. Skills win that tie —
// the moment a second `/` is typed the inline trigger stops matching and
// path completion takes back over.
// A `/token` mid-message is a skill reference dropped into prose. Detected
// BEFORE the leading-command shape because only the first slash can be an
// invocation — `/help /cle` is a command whose argument names a skill, and
// routing the whole line to the backend's completer offered nothing at all.
// It only matches a whitespace-preceded slash sitting at the caret, so
// ordinary argument completion (`/cron ad`, `/personality alic`) is
// untouched.
const inline = inlineSlashTrigger(input)
if (inline) {
@ -61,6 +59,10 @@ export function completionRequestForInput(
}
}
if (isSlashCommand) {
return { method: 'complete.slash', params: { text: input }, replaceFrom: 1 }
}
if (!pathWord) {
return null
}