fix(palette): validate previews and completion input
- commit
- 26071daeaddc0c29423c6fba21dc613db86332d9
- author
- brice · · 2026-08-11 15:02 UTC
- parent
- bdf6220
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +8 -0
M internal/usecase/command/registry.go +3 -0
| @@ -80,6 +80,9 @@ func paletteOptionalOne(slug, code, name, desc string, target TargetKind, prompt | |||
| 80 | 80 | } | |
| 81 | 81 | return desc | |
| 82 | 82 | } | |
| 83 | + | if len(args) > 1 { | |
| 84 | + | return "Use one name" | |
| 85 | + | } | |
| 83 | 86 | switch slug { | |
| 84 | 87 | case "new-session": | |
| 85 | 88 | return "Create session “" + args[0] + "”" | |
| @@ -80,6 +80,9 @@ func paletteOptionalOne(slug, code, name, desc string, target TargetKind, prompt | |||||
| 80 | } | 80 | } | ||
| 81 | return desc | 81 | return desc | ||
| 82 | } | 82 | } | ||
| 83 | + | if len(args) > 1 { | |||
| 84 | + | return "Use one name" | |||
| 85 | + | } | |||
| 83 | switch slug { | 86 | switch slug { | ||
| 84 | case "new-session": | 87 | case "new-session": | ||
| 85 | return "Create session “" + args[0] + "”" | 88 | return "Create session “" + args[0] + "”" | ||
M internal/usecase/palette/model.go +2 -0
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package palette | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "strings" | |
| 4 | 5 | "unicode" | |
| 5 | 6 | "unicode/utf8" | |
| 6 | 7 | ||
| @@ -124,6 +125,7 @@ func (m *Model) CompleteSelected() bool { | |||
| 124 | 125 | query := m.Query() | |
| 125 | 126 | completed := cmd.Code | |
| 126 | 127 | if cmd.Arguments != command.ArgumentsNone { | |
| 128 | + | query = strings.TrimLeftFunc(query, unicode.IsSpace) | |
| 127 | 129 | token, args, hasSeparator := completionParts(query) | |
| 128 | 130 | if token == cmd.Code && hasSeparator && args != "" { | |
| 129 | 131 | return false | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package palette | 1 | package palette | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "strings" | |||
| 4 | "unicode" | 5 | "unicode" | ||
| 5 | "unicode/utf8" | 6 | "unicode/utf8" | ||
| 6 | 7 | ||||
| @@ -124,6 +125,7 @@ func (m *Model) CompleteSelected() bool { | |||||
| 124 | query := m.Query() | 125 | query := m.Query() | ||
| 125 | completed := cmd.Code | 126 | completed := cmd.Code | ||
| 126 | if cmd.Arguments != command.ArgumentsNone { | 127 | if cmd.Arguments != command.ArgumentsNone { | ||
| 128 | + | query = strings.TrimLeftFunc(query, unicode.IsSpace) | |||
| 127 | token, args, hasSeparator := completionParts(query) | 129 | token, args, hasSeparator := completionParts(query) | ||
| 128 | if token == cmd.Code && hasSeparator && args != "" { | 130 | if token == cmd.Code && hasSeparator && args != "" { | ||
| 129 | return false | 131 | return false | ||
M internal/usecase/palette/model_test.go +3 -0
| @@ -396,6 +396,8 @@ func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) | |||
| 396 | 396 | func TestModelCompleteSelected(t *testing.T) { | |
| 397 | 397 | jrs := cmd("JRS", "Jump", "Jump to recent session") | |
| 398 | 398 | jrs.Arguments = command.ArgumentsRequired | |
| 399 | + | optional := cmd("CNS", "New session", "Create named session") | |
| 400 | + | optional.Arguments = command.ArgumentsOptional | |
| 399 | 401 | registryFirst := command.Registry()[0] | |
| 400 | 402 | ||
| 401 | 403 | tests := []struct { | |
| @@ -419,6 +421,7 @@ func TestModelCompleteSelected(t *testing.T) { | |||
| 419 | 421 | {name: "argument command appends required space", commands: []command.Command{jrs}, query: "jrs", want: "JRS ", wantChange: true}, | |
| 420 | 422 | {name: "partial token preserves unicode whitespace argument bytes", commands: []command.Command{jrs}, query: "jr\u2003 \tα β", want: "JRS α β", wantChange: true}, | |
| 421 | 423 | {name: "exact code and argument is unchanged", commands: []command.Command{jrs}, query: "JRS\u2003 α β", want: "JRS\u2003 α β", wantChange: false}, | |
| 424 | + | {name: "leading whitespace optional argument does not duplicate token", commands: []command.Command{optional}, query: "\u2003CNS name", want: "\u2003CNS name", wantChange: false}, | |
| 422 | 425 | {name: "no match", commands: []command.Command{cmd("CPY", "Copy", "Enter copy mode")}, query: "zzz", want: "zzz", wantChange: false}, | |
| 423 | 426 | {name: "effective override code", commands: []command.Command{{Slug: "new-tab", Code: "NT", Desc: "Create tab"}}, query: "n", want: "NT", wantChange: true}, | |
| 424 | 427 | } | |
| @@ -396,6 +396,8 @@ func TestPaletteDescriptionKeepsInactiveRowSurfaceAcrossFallbacks(t *testing.T) | |||||
| 396 | func TestModelCompleteSelected(t *testing.T) { | 396 | func TestModelCompleteSelected(t *testing.T) { | ||
| 397 | jrs := cmd("JRS", "Jump", "Jump to recent session") | 397 | jrs := cmd("JRS", "Jump", "Jump to recent session") | ||
| 398 | jrs.Arguments = command.ArgumentsRequired | 398 | jrs.Arguments = command.ArgumentsRequired | ||
| 399 | + | optional := cmd("CNS", "New session", "Create named session") | |||
| 400 | + | optional.Arguments = command.ArgumentsOptional | |||
| 399 | registryFirst := command.Registry()[0] | 401 | registryFirst := command.Registry()[0] | ||
| 400 | 402 | ||||
| 401 | tests := []struct { | 403 | tests := []struct { | ||
| @@ -419,6 +421,7 @@ func TestModelCompleteSelected(t *testing.T) { | |||||
| 419 | {name: "argument command appends required space", commands: []command.Command{jrs}, query: "jrs", want: "JRS ", wantChange: true}, | 421 | {name: "argument command appends required space", commands: []command.Command{jrs}, query: "jrs", want: "JRS ", wantChange: true}, | ||
| 420 | {name: "partial token preserves unicode whitespace argument bytes", commands: []command.Command{jrs}, query: "jr\u2003 \tα β", want: "JRS α β", wantChange: true}, | 422 | {name: "partial token preserves unicode whitespace argument bytes", commands: []command.Command{jrs}, query: "jr\u2003 \tα β", want: "JRS α β", wantChange: true}, | ||
| 421 | {name: "exact code and argument is unchanged", commands: []command.Command{jrs}, query: "JRS\u2003 α β", want: "JRS\u2003 α β", wantChange: false}, | 423 | {name: "exact code and argument is unchanged", commands: []command.Command{jrs}, query: "JRS\u2003 α β", want: "JRS\u2003 α β", wantChange: false}, | ||
| 424 | + | {name: "leading whitespace optional argument does not duplicate token", commands: []command.Command{optional}, query: "\u2003CNS name", want: "\u2003CNS name", wantChange: false}, | |||
| 422 | {name: "no match", commands: []command.Command{cmd("CPY", "Copy", "Enter copy mode")}, query: "zzz", want: "zzz", wantChange: false}, | 425 | {name: "no match", commands: []command.Command{cmd("CPY", "Copy", "Enter copy mode")}, query: "zzz", want: "zzz", wantChange: false}, | ||
| 423 | {name: "effective override code", commands: []command.Command{{Slug: "new-tab", Code: "NT", Desc: "Create tab"}}, query: "n", want: "NT", wantChange: true}, | 426 | {name: "effective override code", commands: []command.Command{{Slug: "new-tab", Code: "NT", Desc: "Create tab"}}, query: "n", want: "NT", wantChange: true}, | ||
| 424 | } | 427 | } | ||