func getBucketSuggests() (s []prompt.Suggest) {
	for _, b := range shellutils.GetBucketList() {
		s = append(s, prompt.Suggest{Text: b})
	}
	return s
}
getBucketSuggests return suggest list which contains all buckets
package main
import (
	"context"
	"fmt"
	_ "net/http/pprof"
	"os"
	"os/signal"
	"strings"
	"time"
	"github.com/c-bata/go-prompt"
	"github.com/c-bata/go-prompt/completer"
	"github.com/cosiner/argv"
	"github.com/spf13/cobra"
	"github.com/spf13/pflag"
	"github.com/qingstor/qsctl/v2/cmd/qsctl/shellutils"
	"github.com/qingstor/qsctl/v2/cmd/qsctl/taskutils"
	cutils "github.com/qingstor/qsctl/v2/cmd/utils"
	"github.com/qingstor/qsctl/v2/constants"
	"github.com/qingstor/qsctl/v2/pkg/i18n"
	"github.com/qingstor/qsctl/v2/utils"
)
var ShellCommand = &cobra.Command{
	Use:   "shell",
	Short: i18n.Sprintf("start an interactive shell of qsctl"),
	Long:  i18n.Sprintf("qsctl shell can execute command interactively, input exit to quit"),
	Example: utils.AlignPrintWithColon(
		i18n.Sprintf("Start shell: qsctl shell"),
	),
	Args: cobra.NoArgs,
	Run:  shellRun,
	PreRunE: func(_ *cobra.Command, _ []string) error {
		if !cutils.IsInteractiveEnable() {
			return fmt.Errorf(i18n.Sprintf("not interactive shell, cannot call shell"))
		}
		return nil
	},
}
func executor(t string) {
	var err error
	if t == "" {
		return
	}
	if isExit(t) {
		os.Exit(0)
	}
	args, err := parseArgs(t)
	if err != nil {
		i18n.Printf("get args failed: %s\n", err)
		return
	}
	subCmdName := args[0]
	sh, err := shellHandlerFactory(subCmdName)
	if err != nil {
		i18n.Printf("%s\n", err)
		return
	}
	defer sh.postRun(err)
	err = sh.preRunE(args[1:])
	if err != nil {
		i18n.Printf("%s\n", err)
		return
	}
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	go monitorSignal(ctx, cancel, os.Interrupt)
	silenceUsage(rootCmd)  
	resetGlobalFlags()     
	rootCmd.SetArgs(args)
	rootCmd.SetOut(os.Stdout)
	handler, clearFunc := taskutils.NewHandler(ctx)
	defer clearFunc()
	go handler.StartProgress(time.Second)
	ctx = taskutils.ContextWithHandler(ctx, handler)
	_ = rootCmd.ExecuteContext(ctx)
	return
}
func completeFunc(d prompt.Document) (s []prompt.Suggest) {
	if !strings.Contains(d.CurrentLineBeforeCursor(), " ") {
		s := getCmdSuggests()
		return prompt.FilterHasPrefix(s, d.GetWordBeforeCursor(), true)
	}
	curWord := d.GetWordBeforeCursor()
	if curWord == "" {
		return
	}
	if strings.HasPrefix(curWord, "-") {
		s = getFlagSuggests(d)
		return prompt.FilterHasPrefix(s, curWord, true)
	}
	if utils.IsQsPath(curWord) {
		s = getBucketSuggests()
		return prompt.FilterHasPrefix(s, strings.TrimPrefix(curWord, "qs://"), true)
	}
	s = getFileSuggests(d)
	return s
}
func shellRun(c *cobra.Command, _ []string) {
	rootCmd.Help()
	i18n.Printf(`
To execute command, directly type command without "qsctl" at the beginning.
"Ctrl + D" or input "exit" to exit.
Version %s
`, constants.Version)
	go shellutils.InitBucketList(c.Context())
	p := prompt.New(executor, completeFunc,
		prompt.OptionPrefix(constants.Name+"> "),
		prompt.OptionTitle(constants.Name),
		prompt.OptionCompletionWordSeparator(completer.FilePathCompletionSeparator),
	)
	p.Run()
	return
}
type shellHandler interface {
	preRunE(args []string) error
	postRun(err error)
}
func shellHandlerFactory(cmd string) (shellHandler, error) {
	switch cmd {
	case CpCommand.Name():
		return cpShellHandler{}, nil
	case LsCommand.Name():
		return lsShellHandler{}, nil
	case MbCommand.Name():
		return &mbShellHandler{}, nil
	case MvCommand.Name():
		return mvShellHandler{}, nil
	case PresignCommand.Name():
		return presignShellHandler{}, nil
	case RbCommand.Name():
		return &rbShellHandler{}, nil
	case RmCommand.Name():
		return rmShellHandler{}, nil
	case StatCommand.Name():
		return statShellHandler{}, nil
	case SyncCommand.Name():
		return syncShellHandler{}, nil
	case "help":
		return blankShellHandler{}, nil
	default:
		return nil, constants.ErrCmdNotSupport
	}
}
func parseArgs(input string) ([]string, error) {
	args, err := argv.Argv(input, func(bq string) (string, error) {
		return bq, nil
	}, nil)
	if err != nil {
		return nil, err
	}
	if len(args) > 1 {
		i18n.Printf("pipe not supported in shell, input after %v would be abandoned\n", args[0])
	}
	return args[0], nil
}
func getCmdSuggests() (s []prompt.Suggest) {
	for _, command := range shellSubCommands() {
		s = append(s,
			prompt.Suggest{Text: command.Name(), Description: command.Short},
		)
	}
	return s
}
func getFlagSuggests(d prompt.Document) (s []prompt.Suggest) {
	for _, command := range shellSubCommands() {
		if input := d.TextBeforeCursor(); strings.HasPrefix(input, command.Name()) {
			command.LocalFlags().VisitAll(func(flag *pflag.Flag) {
				s = append(s,
					prompt.Suggest{Text: "--" + flag.Name, Description: flag.Usage},
				)
			})
			break
		}
	}
	rootCmd.PersistentFlags().VisitAll(func(flag *pflag.Flag) {
		s = append(s,
			prompt.Suggest{Text: "--" + flag.Name, Description: flag.Usage},
		)
	})
	return s
}
func getFileSuggests(d prompt.Document) []prompt.Suggest {
	var fileCompleter = completer.FilePathCompleter{
		IgnoreCase: true,
		Filter: func(fi os.FileInfo) bool {
			return true
		},
	}
	return fileCompleter.Complete(d)
}