First stab at maglevc
This commit is contained in:
113
cmd/maglevc/complete.go
Normal file
113
cmd/maglevc/complete.go
Normal file
@@ -0,0 +1,113 @@
|
||||
// Copyright (c) 2026, Pim van Pelt <pim@ipng.ch>
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/chzyer/readline"
|
||||
|
||||
"git.ipng.ch/ipng/vpp-maglev/internal/grpcapi"
|
||||
)
|
||||
|
||||
const completeTimeout = 1 * time.Second
|
||||
|
||||
// Completer implements readline.AutoCompleter for the command tree.
|
||||
type Completer struct {
|
||||
root *Node
|
||||
client grpcapi.MaglevClient
|
||||
}
|
||||
|
||||
// Do implements readline.AutoCompleter.
|
||||
// line is the full current line; pos is the cursor position.
|
||||
// Returns (newLine [][]rune, length int) where length is how many rune bytes
|
||||
// before pos should be replaced by each candidate in newLine.
|
||||
func (co *Completer) Do(line []rune, pos int) (newLine [][]rune, length int) {
|
||||
before := string(line[:pos])
|
||||
tokens := splitTokens(before)
|
||||
|
||||
// Determine the partial token being completed.
|
||||
var partial string
|
||||
var prefix []string
|
||||
if len(tokens) == 0 || (len(before) > 0 && before[len(before)-1] == ' ') {
|
||||
// Cursor is after a space — completing a new token.
|
||||
prefix = tokens
|
||||
partial = ""
|
||||
} else {
|
||||
// Cursor is within the last token.
|
||||
prefix = tokens[:len(tokens)-1]
|
||||
partial = tokens[len(tokens)-1]
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), completeTimeout)
|
||||
defer cancel()
|
||||
|
||||
candidates := Candidates(co.root, prefix, partial, ctx, co.client)
|
||||
|
||||
var suffixes [][]rune
|
||||
for _, c := range candidates {
|
||||
suffix := c.Word[len(partial):]
|
||||
suffixes = append(suffixes, []rune(suffix+" "))
|
||||
}
|
||||
return suffixes, len([]rune(partial))
|
||||
}
|
||||
|
||||
// questionListener intercepts the '?' key and prints inline help.
|
||||
type questionListener struct {
|
||||
root *Node
|
||||
client grpcapi.MaglevClient
|
||||
rl *readline.Instance
|
||||
}
|
||||
|
||||
func (ql *questionListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
|
||||
if key != '?' {
|
||||
return line, pos, false
|
||||
}
|
||||
|
||||
// line[:pos] includes the '?' just typed — strip it before tokenizing.
|
||||
before := string(line[:pos])
|
||||
if len(before) > 0 && before[len(before)-1] == '?' {
|
||||
before = before[:len(before)-1]
|
||||
}
|
||||
tokens := splitTokens(before)
|
||||
|
||||
var partial string
|
||||
var prefix []string
|
||||
if len(before) == 0 || before[len(before)-1] == ' ' {
|
||||
prefix = tokens
|
||||
partial = ""
|
||||
} else if len(tokens) > 0 {
|
||||
prefix = tokens[:len(tokens)-1]
|
||||
partial = tokens[len(tokens)-1]
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), completeTimeout)
|
||||
defer cancel()
|
||||
|
||||
candidates := Candidates(ql.root, prefix, partial, ctx, ql.client)
|
||||
|
||||
fmt.Fprintf(ql.rl.Stderr(), "\r\n")
|
||||
if len(candidates) == 0 {
|
||||
fmt.Fprintf(ql.rl.Stderr(), " <no completions>\r\n")
|
||||
} else {
|
||||
for _, c := range candidates {
|
||||
if c.Help != "" {
|
||||
fmt.Fprintf(ql.rl.Stderr(), " %-20s %s\r\n", c.Word, c.Help)
|
||||
} else {
|
||||
fmt.Fprintf(ql.rl.Stderr(), " %s\r\n", c.Word)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the '?' from the line and return with cursor one step back.
|
||||
newLine = append(append([]rune{}, line[:pos-1]...), line[pos:]...)
|
||||
return newLine, pos - 1, true
|
||||
}
|
||||
|
||||
// splitTokens splits a string into whitespace-separated tokens.
|
||||
func splitTokens(s string) []string {
|
||||
return strings.Fields(s)
|
||||
}
|
||||
Reference in New Issue
Block a user