Deliver shell completion query replies

This commit is contained in:
2026-11-03 04:06:05 +00:00
parent 4c47a0ca0b
commit d1f31d2931
7 changed files with 412 additions and 6 deletions

53
completion_query.go Normal file
View File

@@ -0,0 +1,53 @@
package commandkit
import (
"errors"
"io"
"strings"
internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
)
func completeRequest(snapshot *routingSnapshot, words []string) (commandmeta.CompletionResult, error) {
if len(words) == 0 {
return commandmeta.CompletionResult{}, errors.New("completion request has no current word")
}
context := resolveCompletionContext(snapshot, words[:len(words)-1], words[len(words)-1])
target := completionTargetFor(snapshot, context)
result := commandmeta.CompletionResult{Prefix: target.prefix}
switch target.kind {
case completionTargetCommands, completionTargetFlags:
result.Kind = commandmeta.CompletionChoices
result.Choices = target.choices
case completionTargetValue, completionTargetOperand:
result.Kind = target.hint.Kind
if target.hint.Kind == commandmeta.CompletionChoices {
for _, choice := range target.hint.Choices {
if strings.HasPrefix(choice.Value, target.valuePrefix) {
result.Choices = append(result.Choices, choice)
}
}
}
}
return result, nil
}
func (current *execution) complete(words []string) Outcome {
result, err := completeRequest(current.snapshot, words)
var reply string
if err == nil {
reply, err = internalcobra.CompletionReply(result)
}
if err != nil {
reply = internalcobra.CompletionErrorReply()
}
_ = current.reporter.RenderOutput(func(writer io.Writer) error {
_, writeErr := io.WriteString(writer, reply)
return writeErr
})
if err != nil {
return current.reporter.finalize(Failure("Could not complete the shell request.").WithCause(err), nil)
}
return current.reporter.finalize(Success(), nil)
}

117
completion_query_test.go Normal file
View File

@@ -0,0 +1,117 @@
package commandkit
import (
"bytes"
"context"
"errors"
"io"
"strings"
"testing"
internalcobra "gitea.maximumdirect.net/eric/CommandKit/internal/cobra"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
)
func TestCompletionQueryDispatch(t *testing.T) {
var factories, converters, handlers int
command := NewCommand(CommandSpec[struct{}]{
Name: "run", Summary: "Run a command.",
NewInput: func() struct{} { factories++; return struct{}{} },
Flags: []Flag[struct{}]{
ValueFlag(FlagMeta{Name: "color", Short: "c", Completion: CompleteChoices(CompletionChoice{Value: "blue", Description: "cool\ncolor"}, CompletionChoice{Value: "green"}, CompletionChoice{Value: "foo=bar"})}, (*string)(nil),
func(value string) (string, error) { converters++; return value, nil }, func(*struct{}, Supplied[string]) {}),
ValueFlag(FlagMeta{Name: "file", Short: "f", Completion: CompleteFiles()}, (*string)(nil),
func(value string) (string, error) { converters++; return value, nil }, func(*struct{}, Supplied[string]) {}),
},
Run: func(Invocation, struct{}) Outcome { handlers++; return Success() },
})
app, err := New(Identity{Name: "example"}, command, Completion("example"), Alias("shortcut", "run"), FlagAliasFor("run", "--colour", "--color"))
if err != nil {
t.Fatal(err)
}
emptyReply, err := internalcobra.CompletionReply(commandmeta.CompletionResult{})
if err != nil {
t.Fatal(err)
}
for _, tc := range []struct {
name string
args []string
want string
empty bool
}{
{"empty word", []string{"__complete", ""}, "run\tRun a command.", false},
{"alias value", []string{"__complete", "shortcut", "--colour=bl"}, "blue\tcool color\n", false},
{"quiet is data", []string{"__complete", "run", "--quiet", "--color=bl"}, "blue\tcool color\n", false},
{"debug is data", []string{"__complete", "run", "--debug", "--color=bl"}, "blue\tcool color\n", false},
{"short attached choice", []string{"__complete", "run", "-cfoo=ba"}, "foo=bar\n", false},
{"unknown route", []string{"__complete", "missing", ""}, emptyReply, true},
} {
t.Run(tc.name, func(t *testing.T) {
var output, diagnostic bytes.Buffer
outcome := app.Execute(context.Background(), tc.args, Streams{Out: &output, Err: &diagnostic})
if outcome.Status != 0 || diagnostic.Len() != 0 || (tc.empty && output.String() != tc.want) || (!tc.empty && !strings.Contains(output.String(), tc.want)) {
t.Fatalf("outcome=%#v output=%q diagnostics=%q", outcome, output.String(), diagnostic.String())
}
})
}
fileReply, err := internalcobra.CompletionReply(commandmeta.CompletionResult{Kind: commandmeta.CompletionFiles, Prefix: "-f"})
if err != nil {
t.Fatal(err)
}
var fileOutput bytes.Buffer
fileOutcome := app.Execute(context.Background(), []string{"__complete", "run", "-f./part"}, Streams{Out: &fileOutput})
if fileOutcome.Status != 0 || fileOutput.String() != fileReply {
t.Fatalf("file query outcome=%#v reply=%q", fileOutcome, fileOutput.String())
}
if factories != 0 || converters != 0 || handlers != 0 {
t.Fatalf("query invoked application callbacks: %d %d %d", factories, converters, handlers)
}
if err := app.Register(simpleRunnable("later")); err != nil {
t.Fatal(err)
}
var output bytes.Buffer
if outcome := app.Execute(context.Background(), []string{"__complete", "lat"}, Streams{Out: &output}); outcome.Status != 0 || !strings.Contains(output.String(), "later") {
t.Fatalf("later command outcome=%#v reply=%q", outcome, output.String())
}
}
func TestCompletionQueryErrorsAndDelivery(t *testing.T) {
app, err := New(Identity{Name: "example"}, Completion("example"))
if err != nil {
t.Fatal(err)
}
var output, diagnostics bytes.Buffer
outcome := app.Execute(context.Background(), []string{"__complete"}, Streams{Out: &output, Err: &diagnostics})
if outcome.Status != 1 || outcome.Cause == nil || output.String() != internalcobra.CompletionErrorReply() || diagnostics.Len() == 0 {
t.Fatalf("malformed query outcome=%#v reply=%q diagnostics=%q", outcome, output.String(), diagnostics.String())
}
writeErr := errors.New("reply destination failed")
writer := &completionCountingWriter{err: writeErr}
outcome = app.Execute(context.Background(), []string{"__complete", ""}, Streams{Out: writer, Err: io.Discard})
if outcome.Status != 1 || !errors.Is(outcome.Presentation.Output.WriteError, writeErr) || writer.calls != 1 {
t.Fatalf("reply write failure: %#v", outcome)
}
}
type completionCountingWriter struct {
err error
calls int
}
func (writer *completionCountingWriter) Write([]byte) (int, error) {
writer.calls++
return 0, writer.err
}
func TestCompletionQueryDiscovery(t *testing.T) {
app, err := New(Identity{Name: "example"}, Completion("example"),
Group("workspace", "", simpleRunnable("inspect")), Alias("legacy inspect", "workspace inspect"), FlagAlias("--silent", "--quiet"))
if err != nil {
t.Fatal(err)
}
var output bytes.Buffer
outcome := app.Execute(context.Background(), []string{"__complete", "legacy", "--"}, Streams{Out: &output})
if outcome.Status != 0 || !strings.Contains(output.String(), "--quiet") || strings.Contains(output.String(), "inspect") || strings.Contains(output.String(), "--silent") {
t.Fatalf("discovery outcome=%#v reply=%q", outcome, output.String())
}
}

View File

@@ -1,6 +1,6 @@
# Shell completion implementation plan
Status: Stages 1–5 complete. Stage 6 is next.
Status: Stages 1–6 complete. Stage 7 is next.
Implement the [shell completion roadmap](completion.md) from baseline
`2301872`. Follow the stages in order in one sprint. Intermediate stages may
@@ -229,6 +229,8 @@ with `GOWORK=off` and `GOTOOLCHAIN=local`.
## Stage 6: Assemble candidates and deliver query replies
Status: complete.
1. Produce a neutral result of candidate replacements, optional descriptions,
or a filesystem strategy with value context. Keep prefix preservation
distinct from matching; the adapter translates the neutral replacement
@@ -259,6 +261,17 @@ with `GOWORK=off` and `GOTOOLCHAIN=local`.
Use counter fixtures to prove target handlers, converters, factories,
and providers are untouched. Verify no special dispatch without installation.
Completion: connected the installed query before normal routing and reporting
selection, assembled neutral candidates and filesystem strategies, and routed
protocol output through managed reporting. Invalid transport requests return an
error directive and a separate diagnostic; write failures remain presentation
evidence. The adapter preserves short attached prefixes through a private cue
consumed by the generated scripts, while retaining literal choice values and
normalizing descriptions. Query tests cover empty and malformed requests,
aliases and discovery, later registration, callback isolation, reporting flags
as data, and writer failure. Root and adapter package suites and the full
interactive Bash, Zsh, and Fish transport matrix pass.
## Stage 7: Annotate maintained commands and examples
1. Annotate static format/scope flags where their choices already come from

View File

@@ -30,6 +30,9 @@ func (a *App) Execute(ctx context.Context, args []string, streams Streams) Outco
err := errors.New("invocation context must not be nil")
return current.finalize(Failure(err.Error()).WithCause(err), nil, false)
}
if current.snapshot != nil && current.snapshot.completion && len(args) > 0 && args[0] == internalcobra.CompletionQueryToken {
return current.complete(args[1:])
}
routedSnapshot := current.snapshot
current.route = resolveEnteredRoute(args, routedSnapshot)
current.recordRouteEvents()

View File

@@ -6,12 +6,57 @@ import (
"io"
"strings"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
spfcobra "github.com/spf13/cobra"
)
// CompletionQueryToken is the hidden request understood by generated scripts.
const CompletionQueryToken = spfcobra.ShellCompRequestCmd
const completionPathPrefixMarker = "__commandkit_path_prefix__"
// CompletionReply translates neutral suggestions to the shell line protocol.
func CompletionReply(result commandmeta.CompletionResult) (string, error) {
var directive spfcobra.ShellCompDirective
switch result.Kind {
case commandmeta.CompletionNone, commandmeta.CompletionChoices:
directive = spfcobra.ShellCompDirectiveNoFileComp
case commandmeta.CompletionFiles:
directive = spfcobra.ShellCompDirectiveDefault
case commandmeta.CompletionDirectories:
directive = spfcobra.ShellCompDirectiveFilterDirs
default:
return "", fmt.Errorf("unsupported completion suggestion strategy")
}
var output strings.Builder
if result.Kind != commandmeta.CompletionNone && result.Prefix != "" && !strings.Contains(result.Prefix, "=") {
output.WriteString(completionPathPrefixMarker)
output.WriteString(result.Prefix)
output.WriteByte('\n')
}
if result.Kind == commandmeta.CompletionChoices {
for _, choice := range result.Choices {
if choice.Value == "" || strings.ContainsAny(choice.Value, "\x00\r\n\t") {
continue
}
// Generated scripts restore the attached prefix when needed.
output.WriteString(choice.Value)
if choice.Description != "" {
output.WriteByte('\t')
output.WriteString(strings.NewReplacer("\x00", " ", "\r", " ", "\n", " ", "\t", " ").Replace(choice.Description))
}
output.WriteByte('\n')
}
}
fmt.Fprintf(&output, ":%d\n", directive)
return output.String(), nil
}
// CompletionErrorReply is the protocol response for a malformed query.
func CompletionErrorReply() string {
return fmt.Sprintf(":%d\n", spfcobra.ShellCompDirectiveError)
}
// CompletionScript generates a shell script for the installed executable.
func CompletionScript(w io.Writer, executable, shell string) error {
root := &spfcobra.Command{Use: executable}
@@ -32,11 +77,10 @@ func CompletionScript(w io.Writer, executable, shell string) error {
}
script := generated.String()
if shell == "zsh" {
script, err = replaceCompletionText(script, `words=("${=words[1,CURRENT]}")`, `words=("${(@)words[1,CURRENT]}")`)
if err == nil {
script, err = replaceCompletionText(script, `=~ '-.*='`, `=~ '-[^=]*='`)
}
if shell == "bash" {
script, err = adjustBashCompletion(script, root.Name())
} else if shell == "zsh" {
script, err = adjustZshCompletion(script)
} else if shell == "fish" {
script, err = adjustFishCompletion(script, root.Name())
}
@@ -50,6 +94,82 @@ func CompletionScript(w io.Writer, executable, shell string) error {
return err
}
func adjustBashCompletion(script, name string) (string, error) {
var err error
script, err = replaceCompletionText(script, `local out directive`, `local out directive completionPrefix completionOriginalCur`)
if err != nil {
return "", err
}
errorDebug := fmt.Sprintf(`__%s_debug "Received error from custom completion go code"`, name)
script, err = replaceCompletionText(script, errorDebug, errorDebug+"\n compopt +o default")
if err != nil {
return "", err
}
parsePrefix := `if [[ ${out} == ` + completionPathPrefixMarker + `-* ]]; then
completionPrefix=${out%%$'\n'*}
completionPrefix=${completionPrefix#` + completionPathPrefixMarker + `}
completionOriginalCur=${words[${#words[@]}-1]}
if [[ $completionPrefix != --* && $completionOriginalCur == "$completionPrefix"* ]]; then
out=${out#*$'\n'}
cur=${completionOriginalCur#"$completionPrefix"}
else
completionPrefix=""
fi
fi
# Separate activeHelp from normal completions`
script, err = replaceCompletionText(script, `# Separate activeHelp from normal completions`, parsePrefix)
if err != nil {
return "", err
}
script, err = replaceCompletionText(script,
`if (((directive & shellCompDirectiveFilterFileExt) != 0)); then`,
`if [[ -n $completionPrefix ]] && (((directive & shellCompDirectiveFilterDirs) == 0)) && (((directive & shellCompDirectiveNoFileComp) == 0)); then
compopt +o default
_filedir
elif (((directive & shellCompDirectiveFilterFileExt) != 0)); then`)
if err != nil {
return "", err
}
old := fmt.Sprintf("__%[1]s_handle_special_char \"$cur\" :\n __%[1]s_handle_special_char \"$cur\" =", name)
replacement := fmt.Sprintf(`if [[ -n $completionPrefix ]]; then
compopt +o default
local i
for i in "${!COMPREPLY[@]}"; do COMPREPLY[$i]="${completionPrefix}${COMPREPLY[$i]}"; done
cur=$completionOriginalCur
fi
__%[1]s_handle_special_char "$cur" :
__%[1]s_handle_special_char "$cur" =`, name)
return replaceCompletionText(script, old, replacement)
}
func adjustZshCompletion(script string) (string, error) {
var err error
script, err = replaceCompletionText(script, `words=("${=words[1,CURRENT]}")`, `words=("${(@)words[1,CURRENT]}")`)
if err != nil {
return "", err
}
script, err = replaceCompletionText(script, `=~ '-.*='`, `=~ '-[^=]*='`)
if err != nil {
return "", err
}
return replaceCompletionText(script, `local activeHelpMarker="_activeHelp_ "`, `if [[ "$out" == `+completionPathPrefixMarker+`-* ]]; then
local pathPrefix="${out%%$'\n'*}"
pathPrefix="${pathPrefix#`+completionPathPrefixMarker+`}"
if [[ "$pathPrefix" != --* && "$lastParam" == "$pathPrefix"* ]]; then
out="${out#*$'\n'}"
if [ $((directive & shellCompDirectiveNoFileComp)) -ne 0 ]; then
flagPrefix="-P ${pathPrefix}"
else
flagPrefix=""
compset -P "${(b)pathPrefix}"
fi
fi
fi
local activeHelpMarker="_activeHelp_ "`)
}
func replaceCompletionText(script, old, replacement string) (string, error) {
if strings.Count(script, old) != 1 {
return "", fmt.Errorf("unsupported generated completion script")
@@ -77,10 +197,62 @@ func adjustFishCompletion(script, name string) (string, error) {
return "", err
}
script, err = replaceCompletionText(script, `string match -r -- '-.*=' "$lastArg"`, `string match -r -- '-[^=]*=' "$lastArg"`)
if err != nil {
return "", err
}
script, err = replaceCompletionText(script, `for comp in $comps`, `if set -q comps[1]; and string match -q -- '`+completionPathPrefixMarker+`-*' "$comps[1]"
set -l candidatePrefix (string replace -- '`+completionPathPrefixMarker+`' '' "$comps[1]")
if not string match -q -- '--*' "$candidatePrefix"; and test (string sub -l (string length -- "$candidatePrefix") -- "$lastArg") = "$candidatePrefix"
set flagPrefix "$candidatePrefix"
set -e comps[1]
printf "%s%s\n" '`+completionPathPrefixMarker+`' "$flagPrefix"
end
end
for comp in $comps`)
if err != nil {
return "", err
}
variable := "__" + variableName + "_comp_results"
errorBranch := fmt.Sprintf(`__%[1]s_debug "Received error directive: aborting."
# Might as well do file completion, in case it helps
return 1`, variableName)
errorReplacement := fmt.Sprintf(`__%[1]s_debug "Received error directive: aborting."
return 0`, variableName)
script, err = replaceCompletionText(script, errorBranch, errorReplacement)
if err != nil {
return "", err
}
pathContext := fmt.Sprintf(`set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) %% 2)
if test (count $%[1]s) -gt 0; and string match -q -- '%[2]s-*' "$%[1]s[1]"
set -l pathPrefix (string replace -- '%[2]s' '' "$%[1]s[1]")
set -l token (string unescape -- (commandline -ct))
if not string match -q -- '--*' "$pathPrefix"; and test (string sub -l (string length -- "$pathPrefix") -- "$token") = "$pathPrefix"
set -e %[1]s[1]
set -l nofiles (math (math --scale 0 $directive / $shellCompDirectiveNoFileComp) %% 2)
if test $nofiles -eq 1
return 0
end
set -l value (string sub -s (math (string length -- "$pathPrefix") + 1) -- "$token")
set -q value[1]; or set value ""
set -l escaped (string escape --no-quoted -- "$value" | string replace -a -- '=' '\\=')
set --global %[1]s
if test $dirfilter -eq 1
for entry in (__fish_complete_directories "$escaped")
set -a %[1]s "$pathPrefix$entry"
end
else
for entry in (complete -C "'' $escaped")
set -a %[1]s "$pathPrefix$entry"
end
end
__%[3]s_debug "Path completions: $%[1]s"
return 0
end
end`, variable, completionPathPrefixMarker, variableName)
script, err = replaceCompletionText(script, `set -l dirfilter (math (math --scale 0 $directive / $shellCompDirectiveFilterDirs) % 2)`, pathContext)
if err != nil {
return "", err
}
directories := fmt.Sprintf(`if test $dirfilter -eq 1
set -l flagPrefix (string match -r -- '^-[^=]*=' (commandline -ct))
set --global %[1]s

View File

@@ -12,6 +12,7 @@ import (
"strings"
"testing"
commandmeta "gitea.maximumdirect.net/eric/CommandKit/internal/commandmeta"
spfcobra "github.com/spf13/cobra"
)
@@ -33,6 +34,20 @@ func TestCompletionScriptGeneration(t *testing.T) {
}
}
func TestCompletionReplyFiltersUnrepresentableValues(t *testing.T) {
result := commandmeta.CompletionResult{Kind: commandmeta.CompletionChoices, Prefix: "-c", Choices: []commandmeta.CompletionChoice{
{Value: "good", Description: "one\ntwo\tthree"}, {Value: ""}, {Value: "bad\nline"}, {Value: "bad\tfield"}, {Value: "bad\x00byte"},
}}
reply, err := CompletionReply(result)
want := fmt.Sprintf("%s-c\ngood\tone two three\n:%d\n", completionPathPrefixMarker, spfcobra.ShellCompDirectiveNoFileComp)
if err != nil || reply != want {
t.Fatalf("reply=%q error=%v", reply, err)
}
if _, err := CompletionReply(commandmeta.CompletionResult{Kind: 255}); err == nil {
t.Fatal("unsupported strategy accepted")
}
}
type shortCompletionWriter struct{}
func (shortCompletionWriter) Write(p []byte) (int, error) { return len(p) - 1, nil }
@@ -61,9 +76,26 @@ sys.stdout.write(os.environ['PROBE_REPLY'])
if err := os.WriteFile(filepath.Join(dir, "partial file"), nil, 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "foo=bar"), nil, 0o644); err != nil {
t.Fatal(err)
}
reply := func(candidates string, directive spfcobra.ShellCompDirective) string {
return fmt.Sprintf("%s:%d\n", candidates, directive)
}
pathReply := func(kind commandmeta.CompletionKind, prefix string) string {
result, err := CompletionReply(commandmeta.CompletionResult{Kind: kind, Prefix: prefix})
if err != nil {
t.Fatal(err)
}
return result
}
choiceReply := func(prefix, value string) string {
result, err := CompletionReply(commandmeta.CompletionResult{Kind: commandmeta.CompletionChoices, Prefix: prefix, Choices: []commandmeta.CompletionChoice{{Value: value, Description: "value"}}})
if err != nil {
t.Fatal(err)
}
return result
}
tests := []struct {
name, line, reply string
query, executed []string
@@ -74,8 +106,17 @@ sys.stdout.write(os.environ['PROBE_REPLY'])
{"attached file", `probe --config=./partial\ f`, reply("", spfcobra.ShellCompDirectiveDefault), []string{"__complete", "--config=./partial f"}, []string{"--config=./partial file"}},
{"directory", "probe ./partial", reply("", spfcobra.ShellCompDirectiveFilterDirs), []string{"__complete", "./partial"}, []string{"./partial dir"}},
{"attached directory", "probe --config=./partial", reply("", spfcobra.ShellCompDirectiveFilterDirs), []string{"__complete", "--config=./partial"}, []string{"--config=./partial dir"}},
{"short attached file", `probe -c./partial\ f`, pathReply(commandmeta.CompletionFiles, "-c"), []string{"__complete", "-c./partial f"}, []string{"-c./partial file"}},
{"short cluster directory", "probe -sc./partial", pathReply(commandmeta.CompletionDirectories, "-sc"), []string{"__complete", "-sc./partial"}, []string{"-sc./partial dir"}},
{"short path with equals", "probe -cfoo=ba", pathReply(commandmeta.CompletionFiles, "-c"), []string{"__complete", "-cfoo=ba"}, []string{"-cfoo=bar"}},
{"short attached choice", "probe -cbl", choiceReply("-c", "blue"), []string{"__complete", "-cbl"}, []string{"-cblue"}},
{"short choice with equals", "probe -cfoo=ba", choiceReply("-c", "foo=bar"), []string{"__complete", "-cfoo=ba"}, []string{"-cfoo=bar"}},
{"short no suggestions", "probe -cnomatch", pathReply(commandmeta.CompletionChoices, "-c"), []string{"__complete", "-cnomatch"}, []string{"-cnomatch"}},
{"literal prefix marker", "probe __commandkit_path_pre", choiceReply("", completionPathPrefixMarker+"-c"), []string{"__complete", "__commandkit_path_pre"}, []string{completionPathPrefixMarker + "-c"}},
{"literal marker attached", "probe --config=__commandkit_path_pre", choiceReply("--config=", completionPathPrefixMarker+"-c"), []string{"__complete", "--config=__commandkit_path_pre"}, []string{"--config=" + completionPathPrefixMarker + "-c"}},
{"empty", "probe ", reply("", spfcobra.ShellCompDirectiveNoFileComp), []string{"__complete", ""}, []string{}},
{"no file fallback", `probe ./partial\ f`, reply("", spfcobra.ShellCompDirectiveNoFileComp), []string{"__complete", "./partial f"}, []string{"./partial f"}},
{"error no fallback", `probe ./partial\ f`, reply("", spfcobra.ShellCompDirectiveError), []string{"__complete", "./partial f"}, []string{"./partial f"}},
{"cluster", "probe -ab", reply("-abc\tflag\n", spfcobra.ShellCompDirectiveNoFileComp), []string{"__complete", "-ab"}, []string{"-abc"}},
{"quoted argument", "probe 'alpha beta' --config=./par", reply("./partial file\tdescription\n", spfcobra.ShellCompDirectiveNoFileComp), []string{"__complete", "alpha beta", "--config=./par"}, []string{"alpha beta", "--config=./partial file"}},
}

View File

@@ -67,6 +67,13 @@ type CompletionHint struct {
Choices []CompletionChoice
}
// CompletionResult contains neutral suggestions and the entered value prefix.
type CompletionResult struct {
Kind CompletionKind
Choices []CompletionChoice
Prefix string
}
var reportingFlags = [...]Flag{
{Name: "quiet", Usage: "suppress substantive output and optional messages; keep failures visible", Default: "false", Boolean: true},
{Name: "verbose", Usage: "show warning and informational messages", Default: "false", Boolean: true},