rclone/cmd/completion.go

173 lines
4.6 KiB
Go
Raw Normal View History

package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/cache"
"github.com/rclone/rclone/fs/config"
"github.com/rclone/rclone/fs/fspath"
"github.com/spf13/cobra"
)
// Make a debug message while doing the completion.
//
// These end up in the file specified by BASH_COMP_DEBUG_FILE
func compLogf(format string, a ...any) {
cobra.CompDebugln(fmt.Sprintf(format, a...), true)
}
// Add remotes to the completions being built up
func addRemotes(toComplete string, completions []string) []string {
remotes := config.FileSections()
for _, remote := range remotes {
remote += ":"
if strings.HasPrefix(remote, toComplete) {
completions = append(completions, remote)
}
}
return completions
}
// Add local files to the completions being built up
func addLocalFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) {
path := filepath.Clean(toComplete)
dir, file := filepath.Split(path)
if dir == "" {
dir = "."
}
if len(dir) > 0 && dir[0] != filepath.Separator && dir[0] != '/' {
dir = strings.TrimRight(dir, string(filepath.Separator))
dir = strings.TrimRight(dir, "/")
}
fi, err := os.Stat(toComplete)
if err == nil {
if fi.IsDir() {
dir = toComplete
file = ""
}
}
fis, err := os.ReadDir(dir)
if err != nil {
compLogf("Failed to read directory %q: %v", dir, err)
return result, completions
}
for _, fi := range fis {
name := fi.Name()
if strings.HasPrefix(name, file) {
path := filepath.Join(dir, name)
if fi.IsDir() {
path += string(filepath.Separator)
result |= cobra.ShellCompDirectiveNoSpace
}
completions = append(completions, path)
}
}
return result, completions
}
// Add remote files to the completions being built up
func addRemoteFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) {
ctx := context.Background()
parent, _, err := fspath.Split(toComplete)
if err != nil {
compLogf("Failed to split path %q: %v", toComplete, err)
return result, completions
}
f, err := cache.Get(ctx, parent)
if err == fs.ErrorIsFile {
completions = append(completions, toComplete)
return result, completions
} else if err != nil {
compLogf("Failed to make Fs %q: %v", parent, err)
return result, completions
}
fis, err := f.List(ctx, "")
if err != nil {
compLogf("Failed to list Fs %q: %v", parent, err)
return result, completions
}
for _, fi := range fis {
remote := fi.Remote()
path := parent + remote
if strings.HasPrefix(path, toComplete) {
if _, ok := fi.(fs.Directory); ok {
path += "/"
result |= cobra.ShellCompDirectiveNoSpace
}
completions = append(completions, path)
}
}
return result, completions
}
// Workaround doesn't seem to be needed for BashCompletionV2
const useColonWorkaround = false
// do command completion
//
// This is called by the command completion scripts using a hidden __complete or __completeNoDesc commands.
func validArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
compLogf("ValidArgsFunction called with args=%q toComplete=%q", args, toComplete)
fixBug := -1
if useColonWorkaround {
// Work around what I think is a bug in cobra's bash
// completion which seems to be splitting the arguments on :
// Or there is something I don't understand - ncw
args = append(args, toComplete)
colonArg := -1
for i, arg := range args {
if arg == ":" {
colonArg = i
}
}
if colonArg > 0 {
newToComplete := strings.Join(args[colonArg-1:], "")
fixBug = len(newToComplete) - len(toComplete)
toComplete = newToComplete
}
compLogf("...shuffled args=%q toComplete=%q", args, toComplete)
}
result := cobra.ShellCompDirectiveDefault
completions := []string{}
// See whether we have a valid remote yet
_, err := fspath.Parse(toComplete)
parseOK := err == nil
hasColon := strings.ContainsRune(toComplete, ':')
validRemote := parseOK && hasColon
compLogf("valid remote = %v", validRemote)
// Add remotes for completion
if !validRemote {
completions = addRemotes(toComplete, completions)
}
// Add local files for completion
if !validRemote {
result, completions = addLocalFiles(toComplete, result, completions)
}
// Add remote files for completion
if validRemote {
result, completions = addRemoteFiles(toComplete, result, completions)
}
// If using bug workaround, adjust completions to start with :
if useColonWorkaround && fixBug >= 0 {
for i := range completions {
if len(completions[i]) >= fixBug {
completions[i] = completions[i][fixBug:]
}
}
}
return completions, result
}