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  }