From 946d44c43f5b05172ca0d9929af831439e42e583 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Sep 2022 16:26:19 +0530 Subject: [PATCH] Implement completespec in go --- kitty/cli.py | 18 +++++++- tools/completion/files.go | 86 +++++++++++++++++++++++++++++++-------- tools/completion/types.go | 8 ++++ 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/kitty/cli.py b/kitty/cli.py index 28697687e..95903d7dd 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -71,7 +71,23 @@ class CompletionSpec: return self def as_go_code(self, go_name: str) -> Iterator[str]: - pass + completers = [] + if self.kwds: + kwds = (f'"{serialize_as_go_string(x)}"' for x in self.kwds) + g = (self.group if self.type is CompletionType.keyword else '') or "Keywords" + completers.append(f'names_completer("{serialize_as_go_string(g)}", ' + ', '.join(kwds) + ')') + relative_to = 'CONFIG' if self.relative_to is CompletionRelativeTo.config_dir else 'CWD' + if self.type is CompletionType.file: + g = serialize_as_go_string(self.group or 'Files') + if self.extensions: + pats = (f'"*.{ext}"' for ext in self.extensions) + completers.append(f'fnmatch_completer("{g}", {relative_to}, ' + ', '.join(pats) + ')') + if self.mime_patterns: + completers.append(f'mimepat_completer("{g}", {relative_to}, ' + ', '.join(f'"{p}"' for p in self.mime_patterns) + ')') + if len(completers) > 1: + yield f'{go_name}.Completion_for_arg = chain_completers(' + ', '.join(completers) + ')' + elif completers: + yield f'{go_name}.Completion_for_arg = {completers[0]}' class OptionDict(TypedDict): diff --git a/tools/completion/files.go b/tools/completion/files.go index 78681b125..e63d237bb 100644 --- a/tools/completion/files.go +++ b/tools/completion/files.go @@ -4,6 +4,7 @@ package completion import ( "fmt" + "mime" "os" "path/filepath" "strings" @@ -147,25 +148,14 @@ func is_dir_or_symlink_to_dir(entry os.DirEntry, path string) bool { return false } -func complete_by_fnmatch(prefix string, patterns []string) []string { +func fname_based_completer(prefix, cwd string, is_match func(string) bool) []string { ans := make([]string, 0, 1024) - - matches := func(name string) bool { - for _, pat := range patterns { - matched, err := filepath.Match(pat, name) - if err == nil && matched { - return true - } - } - return false - } - complete_files(prefix, func(entry *FileEntry) { if entry.is_dir && !entry.is_empty_dir { entries, err := os.ReadDir(entry.abspath) if err == nil { for _, e := range entries { - if matches(e.Name()) || is_dir_or_symlink_to_dir(e, filepath.Join(entry.abspath, e.Name())) { + if is_match(e.Name()) || is_dir_or_symlink_to_dir(e, filepath.Join(entry.abspath, e.Name())) { ans = append(ans, entry.completion_candidate) return } @@ -174,21 +164,75 @@ func complete_by_fnmatch(prefix string, patterns []string) []string { return } q := strings.ToLower(entry.name) - if matches(q) { + if is_match(q) { ans = append(ans, entry.completion_candidate) } - }, "") + }, cwd) return ans + } -func fnmatch_completer(title string, patterns ...string) completion_func { +func complete_by_fnmatch(prefix, cwd string, patterns []string) []string { + return fname_based_completer(prefix, cwd, func(name string) bool { + for _, pat := range patterns { + matched, err := filepath.Match(pat, name) + if err == nil && matched { + return true + } + } + return false + }) +} + +func complete_by_mimepat(prefix, cwd string, patterns []string) []string { + return fname_based_completer(prefix, cwd, func(name string) bool { + idx := strings.Index(name, ".") + if idx < 1 { + return false + } + ext := name[idx:] + mt := mime.TypeByExtension(ext) + if mt == "" { + ext = filepath.Ext(name) + mt = mime.TypeByExtension(ext) + } + if mt == "" { + return false + } + for _, pat := range patterns { + matched, err := filepath.Match(pat, mt) + if err == nil && matched { + return true + } + } + return false + }) +} + +type relative_to int + +const ( + CWD relative_to = iota + CONFIG +) + +func get_cwd_for_completion(relative_to relative_to) string { + switch relative_to { + case CONFIG: + return utils.ConfigDir() + } + return "" +} + +func make_completer(title string, relative_to relative_to, patterns []string, f func(string, string, []string) []string) completion_func { lpats := make([]string, 0, len(patterns)) for _, p := range patterns { lpats = append(lpats, strings.ToLower(p)) } + cwd := get_cwd_for_completion(relative_to) return func(completions *Completions, word string, arg_num int) { - q := complete_by_fnmatch(word, lpats) + q := f(word, cwd, lpats) if len(q) > 0 { mg := completions.add_match_group(title) mg.IsFiles = true @@ -198,3 +242,11 @@ func fnmatch_completer(title string, patterns ...string) completion_func { } } } + +func fnmatch_completer(title string, relative_to relative_to, patterns ...string) completion_func { + return make_completer(title, relative_to, patterns, complete_by_fnmatch) +} + +func mimepat_completer(title string, relative_to relative_to, patterns ...string) completion_func { + return make_completer(title, relative_to, patterns, complete_by_mimepat) +} diff --git a/tools/completion/types.go b/tools/completion/types.go index 2383acf03..e25e2b0fa 100644 --- a/tools/completion/types.go +++ b/tools/completion/types.go @@ -155,3 +155,11 @@ func names_completer(title string, names ...string) completion_func { } } } + +func chain_completers(completers ...completion_func) completion_func { + return func(completions *Completions, word string, arg_num int) { + for _, f := range completers { + f(completions, word, arg_num) + } + } +}