package main import ( "os" "path/filepath" "strings" "github.com/chzyer/readline" ) // completer wires up Tab completion. Command names complete at the start of the // line; cd/open/view complete local project names; clone/show complete // repository names cached from the git server; checkout/tag complete branch and // tag names; dist completes filesystem paths. func completer() *readline.PrefixCompleter { return readline.NewPrefixCompleter( readline.PcItem("cd", readline.PcItemDynamic(dynLocalProjects)), readline.PcItem("open", readline.PcItemDynamic(dynLocalProjects)), readline.PcItem("view", readline.PcItemDynamic(dynLocalProjects)), readline.PcItem("clone", readline.PcItem("-a", readline.PcItemDynamic(dynServerArchives)), readline.PcItemDynamic(dynServerRepos), ), readline.PcItem("cloneall"), readline.PcItem("show", readline.PcItemDynamic(dynServerRepos)), readline.PcItem("list", readline.PcItem("-a")), readline.PcItem("push"), readline.PcItem("pushremote"), readline.PcItem("pull"), readline.PcItem("fetch"), readline.PcItem("status", readline.PcItem("-a")), readline.PcItem("overview"), readline.PcItem("diff"), readline.PcItem("init"), readline.PcItem("edit"), readline.PcItem("checkout", readline.PcItemDynamic(dynCheckout)), readline.PcItem("log"), readline.PcItem("archive"), readline.PcItem("dist", readline.PcItemDynamic(dynPaths)), readline.PcItem("login"), readline.PcItem("count"), readline.PcItem("tag", readline.PcItem("add"), readline.PcItem("checkout", readline.PcItemDynamic(dynTags)), readline.PcItem("delete", readline.PcItemDynamic(dynTags)), ), readline.PcItem("alias", readline.PcItemDynamic(dynAliasNames)), readline.PcItem("unalias", readline.PcItemDynamic(dynAliasNames)), readline.PcItem("rescan"), readline.PcItem("help"), readline.PcItem("quit"), readline.PcItem("exit"), ) } // dynLocalProjects lists project directories under BASE (already name-sorted). func dynLocalProjects(string) []string { entries, err := os.ReadDir(BASE) if err != nil { return nil } var out []string for _, e := range entries { if e.IsDir() && !strings.HasPrefix(e.Name(), ".") { out = append(out, e.Name()) } } return out } var ( serverRepos []string serverArchives []string serverFetched bool ) // fetchServerRepos queries the server once and caches the repository/archive // names for Tab completion. func fetchServerRepos() { if serverFetched { return } serverFetched = true if lines, err := sshOut("/bin/ls ."); err == nil { for _, ln := range lines { if m := gitDirRe.FindStringSubmatch(strings.TrimSpace(ln)); m != nil { serverRepos = append(serverRepos, m[1]) } } } if lines, err := sshOut("/bin/ls archive"); err == nil { for _, ln := range lines { t := strings.TrimSpace(ln) if strings.HasSuffix(t, ".git.tar.gz") { serverArchives = append(serverArchives, strings.TrimSuffix(t, ".git.tar.gz")) } } } } // rescanServer clears the cached server listing so the next completion (or use) // re-fetches it. func rescanServer() { serverFetched = false serverRepos = nil serverArchives = nil } func dynServerRepos(string) []string { fetchServerRepos(); return serverRepos } func dynServerArchives(string) []string { fetchServerRepos(); return serverArchives } // dynBranches / dynTags list the active project's local branches / tags. func dynBranches(string) []string { if !isDir(DIR + "/.git") { return nil } out, _ := gitCapture(DIR, "for-each-ref", "--format=%(refname:short)", "refs/heads") return splitLines(out) } func dynTags(string) []string { if !isDir(DIR + "/.git") { return nil } out, _ := gitCapture(DIR, "tag", "-l") return splitLines(out) } // dynCheckout offers both branches and tags for `checkout`. func dynCheckout(line string) []string { return append(dynBranches(line), dynTags(line)...) } // dynPaths completes filesystem paths for the last token on the line. func dynPaths(line string) []string { partial := "" if !strings.HasSuffix(line, " ") { if f := strings.Fields(line); len(f) > 0 { partial = f[len(f)-1] } } dir := "." if partial != "" { if strings.HasSuffix(partial, "/") { dir = partial } else { dir = filepath.Dir(partial) } } entries, err := os.ReadDir(dir) if err != nil { return nil } var out []string for _, e := range entries { if strings.HasPrefix(e.Name(), ".") { continue } p := filepath.Join(dir, e.Name()) if e.IsDir() { p += "/" } out = append(out, p) } return out }