github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/cmd/completion.go (about) 1 package cmd 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 10 "github.com/rclone/rclone/fs" 11 "github.com/rclone/rclone/fs/cache" 12 "github.com/rclone/rclone/fs/config" 13 "github.com/rclone/rclone/fs/fspath" 14 "github.com/spf13/cobra" 15 ) 16 17 // Make a debug message while doing the completion. 18 // 19 // These end up in the file specified by BASH_COMP_DEBUG_FILE 20 func compLogf(format string, a ...any) { 21 cobra.CompDebugln(fmt.Sprintf(format, a...), true) 22 } 23 24 // Add remotes to the completions being built up 25 func addRemotes(toComplete string, completions []string) []string { 26 remotes := config.FileSections() 27 for _, remote := range remotes { 28 remote += ":" 29 if strings.HasPrefix(remote, toComplete) { 30 completions = append(completions, remote) 31 } 32 } 33 return completions 34 } 35 36 // Add local files to the completions being built up 37 func addLocalFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) { 38 path := filepath.Clean(toComplete) 39 dir, file := filepath.Split(path) 40 if dir == "" { 41 dir = "." 42 } 43 if len(dir) > 0 && dir[0] != filepath.Separator && dir[0] != '/' { 44 dir = strings.TrimRight(dir, string(filepath.Separator)) 45 dir = strings.TrimRight(dir, "/") 46 } 47 fi, err := os.Stat(toComplete) 48 if err == nil { 49 if fi.IsDir() { 50 dir = toComplete 51 file = "" 52 } 53 } 54 fis, err := os.ReadDir(dir) 55 if err != nil { 56 compLogf("Failed to read directory %q: %v", dir, err) 57 return result, completions 58 } 59 for _, fi := range fis { 60 name := fi.Name() 61 if strings.HasPrefix(name, file) { 62 path := filepath.Join(dir, name) 63 if fi.IsDir() { 64 path += string(filepath.Separator) 65 result |= cobra.ShellCompDirectiveNoSpace 66 } 67 completions = append(completions, path) 68 } 69 } 70 return result, completions 71 } 72 73 // Add remote files to the completions being built up 74 func addRemoteFiles(toComplete string, result cobra.ShellCompDirective, completions []string) (cobra.ShellCompDirective, []string) { 75 ctx := context.Background() 76 parent, _, err := fspath.Split(toComplete) 77 if err != nil { 78 compLogf("Failed to split path %q: %v", toComplete, err) 79 return result, completions 80 } 81 f, err := cache.Get(ctx, parent) 82 if err == fs.ErrorIsFile { 83 completions = append(completions, toComplete) 84 return result, completions 85 } else if err != nil { 86 compLogf("Failed to make Fs %q: %v", parent, err) 87 return result, completions 88 } 89 fis, err := f.List(ctx, "") 90 if err != nil { 91 compLogf("Failed to list Fs %q: %v", parent, err) 92 return result, completions 93 } 94 for _, fi := range fis { 95 remote := fi.Remote() 96 path := parent + remote 97 if strings.HasPrefix(path, toComplete) { 98 if _, ok := fi.(fs.Directory); ok { 99 path += "/" 100 result |= cobra.ShellCompDirectiveNoSpace 101 } 102 completions = append(completions, path) 103 } 104 } 105 return result, completions 106 } 107 108 // Workaround doesn't seem to be needed for BashCompletionV2 109 const useColonWorkaround = false 110 111 // do command completion 112 // 113 // This is called by the command completion scripts using a hidden __complete or __completeNoDesc commands. 114 func validArgs(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 115 compLogf("ValidArgsFunction called with args=%q toComplete=%q", args, toComplete) 116 117 fixBug := -1 118 if useColonWorkaround { 119 // Work around what I think is a bug in cobra's bash 120 // completion which seems to be splitting the arguments on : 121 // Or there is something I don't understand - ncw 122 args = append(args, toComplete) 123 colonArg := -1 124 for i, arg := range args { 125 if arg == ":" { 126 colonArg = i 127 } 128 } 129 if colonArg > 0 { 130 newToComplete := strings.Join(args[colonArg-1:], "") 131 fixBug = len(newToComplete) - len(toComplete) 132 toComplete = newToComplete 133 } 134 compLogf("...shuffled args=%q toComplete=%q", args, toComplete) 135 } 136 137 result := cobra.ShellCompDirectiveDefault 138 completions := []string{} 139 140 // See whether we have a valid remote yet 141 _, err := fspath.Parse(toComplete) 142 parseOK := err == nil 143 hasColon := strings.ContainsRune(toComplete, ':') 144 validRemote := parseOK && hasColon 145 compLogf("valid remote = %v", validRemote) 146 147 // Add remotes for completion 148 if !validRemote { 149 completions = addRemotes(toComplete, completions) 150 } 151 152 // Add local files for completion 153 if !validRemote { 154 result, completions = addLocalFiles(toComplete, result, completions) 155 } 156 157 // Add remote files for completion 158 if validRemote { 159 result, completions = addRemoteFiles(toComplete, result, completions) 160 } 161 162 // If using bug workaround, adjust completions to start with : 163 if useColonWorkaround && fixBug >= 0 { 164 for i := range completions { 165 if len(completions[i]) >= fixBug { 166 completions[i] = completions[i][fixBug:] 167 } 168 } 169 } 170 171 return completions, result 172 }