From f93c53c5bacef868872e322834bd405ec3e433d4 Mon Sep 17 00:00:00 2001 From: Kevin Lyda <kevin@lyda.ie> Date: Sat, 24 May 2025 19:41:47 +0100 Subject: [PATCH] Add completer --- dclish/completer.go | 81 +++++++++++++++++++++++++++++++++++++++++++++ dclish/dclish.go | 5 +++ repl/repl.go | 8 +++-- 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 dclish/completer.go diff --git a/dclish/completer.go b/dclish/completer.go new file mode 100644 index 0000000..82d7f67 --- /dev/null +++ b/dclish/completer.go @@ -0,0 +1,81 @@ +package dclish + +import ( + "strings" + "unicode" +) + +// Completer command completer type. +type Completer struct { + commands []string + flags map[string][]string +} + +// NewCompleter creates a new completer. +func NewCompleter(commands Commands) Completer { + comps := []string{} + flags := map[string][]string{} + for c := range commands { + comps = append(comps, c) + if len(commands[c].Commands) > 0 { + subcommands := commands[c].Commands + for subc := range subcommands { + fullc := c + " " + subc + comps = append(comps, fullc) + flags[fullc] = []string{} + if subcommands[subc].Flags != nil { + for f := range subcommands[subc].Flags { + flags[fullc] = append(flags[fullc], f) + } + } + } + } + flags[c] = []string{} + if commands[c].Flags != nil { + for f := range commands[c].Flags { + flags[c] = append(flags[c], f) + } + } + } + return Completer{ + commands: comps, + flags: flags, + } +} + +// Do return a list of possible completions. +func (c Completer) Do(line []rune, pos int) ([][]rune, int) { + // Nothing typed in. + if pos == 0 { + newline := make([][]rune, len(c.commands)) + for i := range c.commands { + newline[i] = []rune(c.commands[i]) + } + return newline, pos + } + + // Command partially typed in. + newline := [][]rune{} + cmd := strings.ToUpper(string(line[0:pos])) + lower := false + if unicode.IsLower(line[0]) { + lower = true + } + for i := range c.commands { + if strings.HasPrefix(c.commands[i], cmd) { + rest := strings.Replace(c.commands[i], cmd, "", 1) + if lower { + newline = append(newline, []rune(strings.ToLower(rest))) + } else { + newline = append(newline, []rune(rest)) + } + } + } + if len(newline) > 0 { + return newline, pos + } + + // Command completely typed in. + // TODO: figure out flags. + return newline, pos +} diff --git a/dclish/dclish.go b/dclish/dclish.go index 7736360..c0cb79b 100644 --- a/dclish/dclish.go +++ b/dclish/dclish.go @@ -10,6 +10,10 @@ import ( // ActionFunc is the function that a command runs. type ActionFunc func(*Command) error +// CompleterFunc is a function to provide completions for arguments for +// a given command. +type CompleterFunc func() []string + // Flag is a flag for a command. type Flag struct { OptArg bool @@ -30,6 +34,7 @@ type Command struct { MinArgs int Commands Commands Action ActionFunc + Completer CompleterFunc Description string } diff --git a/repl/repl.go b/repl/repl.go index 7dba020..1f69333 100644 --- a/repl/repl.go +++ b/repl/repl.go @@ -8,6 +8,7 @@ import ( "strings" "unicode" + "git.lyda.ie/kevin/bulletin/dclish" "git.lyda.ie/kevin/bulletin/this" "github.com/adrg/xdg" "github.com/chzyer/readline" @@ -15,14 +16,15 @@ import ( // Loop is the main event loop. func Loop() error { + completer := dclish.NewCompleter(commands) histdir := path.Join(xdg.ConfigHome, "BULLETIN") os.MkdirAll(histdir, 0700) histfile := path.Join(histdir, fmt.Sprintf("%s.history", this.User.Login)) rl, err := readline.NewEx( &readline.Config{ - Prompt: "BULLETIN> ", - HistoryFile: histfile, - // TODO: AutoComplete: completer, + Prompt: "BULLETIN> ", + HistoryFile: histfile, + AutoComplete: completer, InterruptPrompt: "^C", EOFPrompt: "EXIT", HistorySearchFold: true, -- GitLab