diff --git a/.gitignore b/.gitignore index 72836cfe06..31df79818f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ cmd/carafmt/carafmt cmd/caralint/caralint cmd/carapace/carapace cmd/carapace/cmd/completers.go +cmd/carapace/cmd/completers/name.go +cmd/carapace/cmd/completers/description.go cmd/carapace/cmd/completers_release.go cmd/carapace/cmd/macros.go cmd/caraparse/caraparse diff --git a/cmd/carapace/cmd/root.go b/cmd/carapace/cmd/root.go index 255a6826f2..63da3bc95b 100644 --- a/cmd/carapace/cmd/root.go +++ b/cmd/carapace/cmd/root.go @@ -12,6 +12,7 @@ import ( "strconv" "strings" + "github.com/rsteube/carapace-bin/cmd/carapace/cmd/completers" spec "github.com/rsteube/carapace-spec" "github.com/rsteube/carapace/pkg/ps" "github.com/rsteube/carapace/pkg/style" @@ -75,7 +76,7 @@ var rootCmd = &cobra.Command{ Specs are loaded from [%v/carapace/specs]. `, suppressErr(os.UserCacheDir), suppressErr(os.UserConfigDir), suppressErr(os.UserConfigDir)), Args: cobra.MinimumNArgs(1), - ValidArgs: completers, + ValidArgs: completers.Names, Run: func(cmd *cobra.Command, args []string) { // since flag parsing is disabled do this manually switch args[0] { @@ -119,25 +120,25 @@ var rootCmd = &cobra.Command{ } switch shell { case "bash": - fmt.Println(bash_lazy(completers)) + fmt.Println(bash_lazy(completers.Names)) case "bash-ble": - fmt.Println(bash_ble_lazy(completers)) + fmt.Println(bash_ble_lazy(completers.Names)) case "elvish": - fmt.Println(elvish_lazy(completers)) + fmt.Println(elvish_lazy(completers.Names)) case "fish": - fmt.Println(fish_lazy(completers)) + fmt.Println(fish_lazy(completers.Names)) case "nushell": - fmt.Println(nushell_lazy(completers)) + fmt.Println(nushell_lazy(completers.Names)) case "oil": - fmt.Println(oil_lazy(completers)) + fmt.Println(oil_lazy(completers.Names)) case "powershell": - fmt.Println(powershell_lazy(completers)) + fmt.Println(powershell_lazy(completers.Names)) case "tcsh": - fmt.Println(tcsh_lazy(completers)) + fmt.Println(tcsh_lazy(completers.Names)) case "xonsh": - fmt.Println(xonsh_lazy(completers)) + fmt.Println(xonsh_lazy(completers.Names)) case "zsh": - fmt.Println(zsh_lazy(completers)) + fmt.Println(zsh_lazy(completers.Names)) default: fmt.Fprintln(os.Stderr, "could not determine shell") } @@ -159,14 +160,14 @@ func suppressErr(f func() (string, error)) string { s, _ := f(); return s } func printCompleters() { maxlen := 0 - for _, name := range completers { + for _, name := range completers.Names { if len := len(name); len > maxlen { maxlen = len } } - for _, name := range completers { - fmt.Printf("%-"+strconv.Itoa(maxlen)+"v %v\n", name, descriptions[name]) + for _, name := range completers.Names { + fmt.Printf("%-"+strconv.Itoa(maxlen)+"v %v\n", name, completers.Descriptions[name]) } } diff --git a/cmd/generate/gen.go b/cmd/generate/gen.go index 683145172d..601825fdfb 100644 --- a/cmd/generate/gen.go +++ b/cmd/generate/gen.go @@ -56,20 +56,12 @@ func main() { import ( %v) -var completers = []string{ -%v} - -var descriptions = map[string]string{ -%v} - func executeCompleter(completer string) { switch completer { %v } } `, fmt.Sprintln(strings.Join(imports, "\n")), - fmt.Sprintln(strings.Join(formattedNames, "\n")), - fmt.Sprintln(strings.Join(formattedDescriptions, "\n")), fmt.Sprintln(strings.Join(cases, "\n")), ) @@ -79,7 +71,10 @@ func executeCompleter(completer string) { os.Exit(1) } + os.Mkdir(root+"/cmd/carapace/cmd/completers", 0755) os.WriteFile(root+"/cmd/carapace/cmd/completers.go", []byte("//go:build !release\n\n"+content), 0644) + os.WriteFile(root+"/cmd/carapace/cmd/completers/name.go", []byte(fmt.Sprintf("package completers\n\nvar Names = []string{\n%v\n}\n", strings.Join(formattedNames, "\n"))), 0644) + os.WriteFile(root+"/cmd/carapace/cmd/completers/description.go", []byte(fmt.Sprintf("package completers\n\nvar Descriptions = map[string]string{\n%v\n}\n", strings.Join(formattedDescriptions, "\n"))), 0644) os.WriteFile(root+"/cmd/carapace/cmd/completers_release.go", []byte("//go:build release\n\n"+strings.Replace(content, "/completers/", "/completers_release/", -1)), 0644) os.RemoveAll(root + "/completers_release") execabs.Command("cp", "-r", root+"/completers", root+"/completers_release").Run() diff --git a/pkg/actions/bridge/carapace.go b/pkg/actions/bridge/carapace.go index acb64eea07..bf67d3a105 100644 --- a/pkg/actions/bridge/carapace.go +++ b/pkg/actions/bridge/carapace.go @@ -1,6 +1,12 @@ package bridge -import "github.com/rsteube/carapace" +import ( + "fmt" + "os" + + "github.com/rsteube/carapace" + spec "github.com/rsteube/carapace-spec" +) // ActionCarapace bridges rsteube/carapace completion func ActionCarapace(cmd string) carapace.Action { @@ -19,6 +25,15 @@ func ActionCarapace(cmd string) carapace.Action { // ActionCarapaceBin bridges a completer provided by carapace-bin func ActionCarapaceBin(completer string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if a, err := actionSpec(completer); err == nil { + return a + } + return actionCarapaceBin(completer) + }) +} + +func actionCarapaceBin(completer string) carapace.Action { return carapace.ActionCallback(func(c carapace.Context) carapace.Action { args := []string{completer, "export", ""} args = append(args, c.Args...) @@ -31,3 +46,15 @@ func ActionCarapaceBin(completer string) carapace.Action { }) }) } + +func actionSpec(completer string) (carapace.Action, error) { + configDir, err := os.UserConfigDir() + if err != nil { + return carapace.ActionValues(), err + } + path := fmt.Sprintf("%v/carapace/specs/%v.yaml", configDir, completer) + if _, err := os.Stat(path); os.IsNotExist(err) { + return carapace.ActionValues(), err + } + return spec.ActionSpec(path), nil +} diff --git a/pkg/actions/os/executable.go b/pkg/actions/os/executable.go new file mode 100644 index 0000000000..1de88953d6 --- /dev/null +++ b/pkg/actions/os/executable.go @@ -0,0 +1,73 @@ +package os + +import ( + "fmt" + "os" + "strings" + + "github.com/rsteube/carapace" + "github.com/rsteube/carapace-bin/cmd/carapace/cmd/completers" + "github.com/rsteube/carapace/pkg/style" + "gopkg.in/yaml.v3" +) + +// ActionPathExecutables completes executable files from PATH +// +// nvim +// chmod +func ActionPathExecutables() carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + batch := carapace.Batch() + dirs := strings.Split(os.Getenv("PATH"), string(os.PathListSeparator)) + for i := len(dirs) - 1; i >= 0; i-- { + batch = append(batch, actionDirectoryExecutables(dirs[i], c.CallbackValue)) + } + return batch.ToA() + }) +} + +func actionDirectoryExecutables(dir string, prefix string) carapace.Action { + return carapace.ActionCallback(func(c carapace.Context) carapace.Action { + if files, err := os.ReadDir(dir); err == nil { + vals := make([]string, 0) + for _, f := range files { + if strings.HasPrefix(f.Name(), prefix) { + if info, err := f.Info(); err == nil && !f.IsDir() && isExecAny(info.Mode()) { + if description, err := specDescription(f.Name()); err != nil { + vals = append(vals, f.Name(), completers.Descriptions[f.Name()], style.ForPath(dir+"/"+f.Name())) + } else { + vals = append(vals, f.Name(), description, style.ForPath(dir+"/"+f.Name())) + } + } + } + } + return carapace.ActionStyledValuesDescribed(vals...) + } + return carapace.ActionValues() + }) +} + +func isExecAny(mode os.FileMode) bool { + return mode&0111 != 0 +} + +func specDescription(name string) (string, error) { + confDir, err := os.UserConfigDir() + if err != nil { + return "", err + } + + content, err := os.ReadFile(fmt.Sprintf("%v/carapace/specs/%v.yaml", confDir, name)) + if err != nil { + return "", err + } + var s struct { + Description string + } + + err = yaml.Unmarshal(content, &s) + if err != nil { + return "", err + } + return s.Description, nil +} diff --git a/pkg/actions/os/os.go b/pkg/actions/os/os.go index 3ebde20b30..f840aa6ed1 100644 --- a/pkg/actions/os/os.go +++ b/pkg/actions/os/os.go @@ -2,11 +2,9 @@ package os import ( - "os" "strings" "github.com/rsteube/carapace" - "github.com/rsteube/carapace/pkg/style" ) // ActionShells completes available terminal shells @@ -22,38 +20,6 @@ func ActionShells() carapace.Action { }) } -// ActionPathExecutables completes executable files from PATH -// -// nvim -// chmod -func ActionPathExecutables() carapace.Action { - return carapace.ActionCallback(func(c carapace.Context) carapace.Action { - executables := make(map[string]string) - - for _, folder := range strings.Split(os.Getenv("PATH"), string(os.PathListSeparator)) { - if files, err := os.ReadDir(folder); err == nil { - for _, f := range files { - if _, ok := executables[f.Name()]; !ok { - if info, err := f.Info(); err == nil && !f.IsDir() && isExecAny(info.Mode()) { - executables[f.Name()] = style.ForPath(folder + "/" + f.Name()) - } - } - } - } - } - - vals := make([]string, 0) - for executable, _style := range executables { - vals = append(vals, executable, _style) - } - return carapace.ActionStyledValues(vals...) - }) -} - -func isExecAny(mode os.FileMode) bool { - return mode&0111 != 0 -} - // ActionCgroups completes cgroup names // // blkio