Skip to content

Commit

Permalink
feat: completion for interactive mode (#2729)
Browse files Browse the repository at this point in the history
fixes: #2708
  • Loading branch information
stuartwdouglas authored Sep 19, 2024
1 parent 9f15b42 commit 2b0d47a
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 6 deletions.
87 changes: 84 additions & 3 deletions frontend/cli/cmd_interactive.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@ import (

"github.com/alecthomas/kong"
"github.com/chzyer/readline"
kongcompletion "github.com/jotaen/kong-completion"
"github.com/kballard/go-shellquote"
"github.com/posener/complete"

"github.com/TBD54566975/ftl/internal/projectconfig"
)

var _ readline.AutoCompleter = &FTLCompletion{}

type interactiveCmd struct {
}

func (i *interactiveCmd) Run(ctx context.Context, k *kong.Kong, projectConfig projectconfig.Config) error {
func (i *interactiveCmd) Run(ctx context.Context, k *kong.Kong, projectConfig projectconfig.Config, app *kong.Kong) error {
l, err := readline.NewEx(&readline.Config{
Prompt: "\033[32m>\033[0m ",
InterruptPrompt: "^C",
EOFPrompt: "exit",
AutoComplete: &FTLCompletion{app: app},
})
if err != nil {
return fmt.Errorf("init readline: %w", err)
Expand All @@ -38,6 +43,9 @@ func (i *interactiveCmd) Run(ctx context.Context, k *kong.Kong, projectConfig pr
break
}
line = strings.TrimSpace(line)
if line == "" {
continue
}
args, err := shellquote.Split(line)
if err != nil {
errorf("%s", err)
Expand All @@ -48,11 +56,11 @@ func (i *interactiveCmd) Run(ctx context.Context, k *kong.Kong, projectConfig pr
errorf("%s", err)
continue
}
subctx := bindContext(ctx, kctx, projectConfig)
subctx := bindContext(ctx, kctx, projectConfig, app)

err = kctx.Run(subctx)
if err != nil {
errorf("%s", err)
errorf("error: %s", err)
continue
}
}
Expand All @@ -62,3 +70,76 @@ func (i *interactiveCmd) Run(ctx context.Context, k *kong.Kong, projectConfig pr
func errorf(format string, args ...any) {
fmt.Printf("\033[31m%s\033[0m\n", fmt.Sprintf(format, args...))
}

type FTLCompletion struct {
app *kong.Kong
}

func (f *FTLCompletion) Do(line []rune, pos int) ([][]rune, int) {
parser := f.app
if parser == nil {
return nil, 0
}
all := []string{}
completed := []string{}
last := ""
lastCompleted := ""
lastSpace := false
// We don't care about anything past pos
// this completer can't handle completing in the middle of things
if pos < len(line) {
line = line[:pos]
}
current := 0
for i, arg := range line {
if i == pos {
break
}
if arg == ' ' {
lastWord := string(line[current:i])
all = append(all, lastWord)
completed = append(completed, lastWord)
current = i + 1
lastSpace = true
} else {
lastSpace = false
}
}
if pos > 0 {
if lastSpace {
lastCompleted = all[len(all)-1]
} else {
if current < len(line) {
last = string(line[current:])
all = append(all, last)
}
if len(all) > 0 {
lastCompleted = all[len(all)-1]
}
}
}

args := complete.Args{
Completed: completed,
All: all,
Last: last,
LastCompleted: lastCompleted,
}

command, err := kongcompletion.Command(parser)
if err != nil {
// TODO handle error
println(err.Error())
}
result := command.Predict(args)
runes := [][]rune{}
for _, s := range result {
if !strings.HasPrefix(s, last) || s == "interactive" {
continue
}
s = s[len(last):]
str := []rune(s)
runes = append(runes, str)
}
return runes, pos
}
5 changes: 3 additions & 2 deletions frontend/cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,14 +132,15 @@ func main() {
if err != nil && !errors.Is(err, os.ErrNotExist) {
kctx.FatalIfErrorf(err)
}
ctx = bindContext(ctx, kctx, config)
ctx = bindContext(ctx, kctx, config, app)

err = kctx.Run(ctx)
kctx.FatalIfErrorf(err)
}

func bindContext(ctx context.Context, kctx *kong.Context, projectConfig projectconfig.Config) context.Context {
func bindContext(ctx context.Context, kctx *kong.Context, projectConfig projectconfig.Config, app *kong.Kong) context.Context {
kctx.Bind(projectConfig)
kctx.Bind(app)

controllerServiceClient := rpc.Dial(ftlv1connect.NewControllerServiceClient, cli.Endpoint.String(), log.Error)
ctx = rpc.ContextWithClient(ctx, controllerServiceClient)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ require (
github.com/mattn/go-isatty v0.0.20
github.com/multiformats/go-base36 v0.2.0
github.com/otiai10/copy v1.14.0
github.com/posener/complete v1.2.3
github.com/radovskyb/watcher v1.0.7
github.com/rs/cors v1.11.1
github.com/santhosh-tekuri/jsonschema/v5 v5.3.1
Expand Down Expand Up @@ -127,7 +128,6 @@ require (
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkoukk/tiktoken-go v0.1.6 // indirect
github.com/posener/complete v1.2.3 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
Expand Down

0 comments on commit 2b0d47a

Please sign in to comment.