1package palette
2
3import (
4 "strings"
5 "unicode"
6
7 "github.com/bnema/vev/internal/usecase/command"
8)
9
10// Action is the exact command token and its unmodified, whitespace-delimited
11// arguments. It is deliberately independent of palette state.
12type Action struct {
13 Command command.Command
14 Args []string
15}
16
17// ParseAction recognizes an exact effective command result token. It accepts
18// outer and repeated whitespace but never treats a prefix or concatenated token
19// as a command. Commands with optional arguments accept zero or more arguments.
20func ParseAction(results []Result, input string) (Action, bool) {
21 fields := strings.Fields(input)
22 if len(fields) == 0 {
23 return Action{}, false
24 }
25 for _, result := range results {
26 cmd, ok := result.Command()
27 if !ok || !strings.EqualFold(fields[0], cmd.Code) {
28 continue
29 }
30 args := append([]string(nil), fields[1:]...)
31 switch cmd.Arguments {
32 case command.ArgumentsNone:
33 if len(args) != 0 {
34 return Action{}, false
35 }
36 case command.ArgumentsRequired:
37 if len(args) == 0 {
38 return Action{}, false
39 }
40 }
41 return Action{Command: cmd, Args: args}, true
42 }
43 return Action{}, false
44}
45
46// ExactCommandResult returns a command whose code exactly matches input.
47func ExactCommandResult(results []Result, input string) (Result, bool) {
48 if strings.Fields(input) == nil || strings.ContainsAny(strings.TrimSpace(input), " \t\n\r") {
49 return Result{}, false
50 }
51 for _, result := range results {
52 cmd, ok := result.Command()
53 if ok && strings.EqualFold(strings.TrimSpace(input), cmd.Code) {
54 return result, true
55 }
56 }
57 return Result{}, false
58}
59
60// ArgumentCommand returns the exact argument-capable command whose token is
61// being typed, including the whitespace-before-argument state.
62func ArgumentCommand(results []Result, input string) (command.Command, bool) {
63 _, cmd, ok := argumentResult(results, input)
64 return cmd, ok
65}
66
67// Preview returns the pure preview text for an exact argument-capable command.
68// It never consults mutable daemon state.
69func Preview(cmd command.Command, input string) string {
70 if cmd.Preview == nil {
71 return ""
72 }
73 fields := strings.Fields(input)
74 hasArgument := len(fields) > 1 || strings.TrimRightFunc(input, unicode.IsSpace) != input
75 return cmd.Preview(append([]string(nil), fields[1:]...), hasArgument)
76}
77
78// argumentResult finds the exact argument-taking command and its source row in
79// one pass so callers can reuse the immutable result without another scan.
80func argumentResult(results []Result, input string) (Result, command.Command, bool) {
81 fields := strings.Fields(input)
82 if len(fields) == 0 {
83 return Result{}, command.Command{}, false
84 }
85 for _, result := range results {
86 cmd, ok := result.Command()
87 if ok && cmd.Arguments != command.ArgumentsNone && strings.EqualFold(fields[0], cmd.Code) {
88 return result, cmd, true
89 }
90 }
91 return Result{}, command.Command{}, false
92}