diff --git a/command/autocomplete.go b/command/autocomplete.go index b18e80465..cc1ad145b 100644 --- a/command/autocomplete.go +++ b/command/autocomplete.go @@ -2,6 +2,7 @@ package command import ( "github.com/posener/complete" + "github.com/posener/complete/match" ) // This file contains some re-usable predictors for auto-complete. The @@ -31,3 +32,48 @@ func (s completePredictSequence) Predict(a complete.Args) []string { return s[idx].Predict(a) } + +func (m *Meta) completePredictWorkspaceName() complete.Predictor { + return complete.PredictFunc(func(a complete.Args) []string { + // There are lot of things that can fail in here, so if we encounter + // any error then we'll just return nothing and not support autocomplete + // until whatever error is fixed. (The user can't actually see the error + // here, but other commands should produce a user-visible error before + // too long.) + + // We assume here that we want to autocomplete for the current working + // directory, since we don't have enough context to know where to + // find any config path argument, and it might be _after_ the argument + // we're trying to complete here anyway. + configPath, err := ModulePath(nil) + if err != nil { + return nil + } + + cfg, err := m.Config(configPath) + if err != nil { + return nil + } + + b, err := m.Backend(&BackendOpts{ + Config: cfg, + }) + if err != nil { + return nil + } + + names, _ := b.States() + + if a.Last != "" { + // filter for names that match the prefix only + filtered := make([]string, 0, len(names)) + for _, name := range names { + if match.Prefix(name, a.Last) { + filtered = append(filtered, name) + } + } + names = filtered + } + return names + }) +} diff --git a/command/autocomplete_test.go b/command/autocomplete_test.go new file mode 100644 index 000000000..7a799ac99 --- /dev/null +++ b/command/autocomplete_test.go @@ -0,0 +1,60 @@ +package command + +import ( + "io/ioutil" + "os" + "reflect" + "testing" + + "github.com/mitchellh/cli" + "github.com/posener/complete" +) + +func TestMetaCompletePredictWorkspaceName(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + os.MkdirAll(td, 0755) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + // make sure a vars file doesn't interfere + err := ioutil.WriteFile(DefaultVarsFilename, nil, 0644) + if err != nil { + t.Fatal(err) + } + + ui := new(cli.MockUi) + meta := &Meta{Ui: ui} + + predictor := meta.completePredictWorkspaceName() + + t.Run("no prefix", func(t *testing.T) { + got := predictor.Predict(complete.Args{ + Last: "", + }) + want := []string{"default"} + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) + } + }) + + t.Run("prefix that matches", func(t *testing.T) { + got := predictor.Predict(complete.Args{ + Last: "def", + }) + want := []string{"default"} + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) + } + }) + + t.Run("prefix that doesn't match", func(t *testing.T) { + got := predictor.Predict(complete.Args{ + Last: "x", + }) + want := []string{} + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) + } + }) +}