From 0ff2446a1ac4db33396bb6b77dbde6a35017c139 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Fri, 16 Sep 2022 17:29:41 +0530 Subject: [PATCH] More completion work --- gen-go-code.py | 1 + kitty/cli.py | 15 ++++++++++++--- kitty_tests/completion.py | 3 +++ tools/completion/files.go | 17 +++++++++++++++++ tools/completion/kitty.go | 3 +++ tools/completion/parse-args.go | 4 ++-- tools/completion/types.go | 8 ++++++++ 7 files changed, 46 insertions(+), 5 deletions(-) diff --git a/gen-go-code.py b/gen-go-code.py index cd0637cc8..4966d21c0 100755 --- a/gen-go-code.py +++ b/gen-go-code.py @@ -61,6 +61,7 @@ def generate_completions_for_kitty() -> None: print('k := root.add_command("kitty", "")') print('k.First_arg_may_not_be_subcommand = true') print('k.Completion_for_arg = complete_kitty') + print('k.Subcommand_must_be_first = true') for opt in go_options_for_seq(parse_option_spec()[0]): print(opt.as_completion_option('k')) print('at := k.add_command("@", "Remote control")') diff --git a/kitty/cli.py b/kitty/cli.py index 95903d7dd..e6e427111 100644 --- a/kitty/cli.py +++ b/kitty/cli.py @@ -8,6 +8,7 @@ import socket import sys from collections import deque from enum import Enum, auto +from dataclasses import dataclass from typing import ( Any, Callable, Dict, FrozenSet, Iterator, List, Match, Optional, Sequence, Tuple, Type, TypeVar, Union, cast @@ -37,6 +38,7 @@ class CompletionRelativeTo(Enum): config_dir = auto() +@dataclass class CompletionSpec: type: CompletionType = CompletionType.none @@ -70,7 +72,7 @@ class CompletionSpec: raise KeyError(f'Unknown completion property: {ck}') return self - def as_go_code(self, go_name: str) -> Iterator[str]: + def as_go_code(self, go_name: str, sep: str = ' = ') -> Iterator[str]: completers = [] if self.kwds: kwds = (f'"{serialize_as_go_string(x)}"' for x in self.kwds) @@ -84,10 +86,15 @@ class CompletionSpec: 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 self.type is CompletionType.directory: + g = serialize_as_go_string(self.group or 'Directories') + completers.append(f'directory_completer("{g}", {relative_to})') + if go_name: + go_name += '.' if len(completers) > 1: - yield f'{go_name}.Completion_for_arg = chain_completers(' + ', '.join(completers) + ')' + yield f'{go_name}Completion_for_arg{sep}chain_completers(' + ', '.join(completers) + ')' elif completers: - yield f'{go_name}.Completion_for_arg = {completers[0]}' + yield f'{go_name}Completion_for_arg{sep}{completers[0]}' class OptionDict(TypedDict): @@ -191,6 +198,8 @@ class GoOption: if self.type == 'choices': cx = ', '.join(f'"{serialize_as_go_string(x)}"' for x in self.sorted_choices) ans += f'Completion_for_arg: names_completer("Choices for {self.long}", {cx}),' + elif self.obj_dict['completion'].type is not CompletionType.none: + ans += ''.join(self.obj_dict['completion'].as_go_code('', ': ')) + ',' return ans + '})' diff --git a/kitty_tests/completion.py b/kitty_tests/completion.py index 5e267eb85..271bddd5e 100644 --- a/kitty_tests/completion.py +++ b/kitty_tests/completion.py @@ -118,6 +118,9 @@ def completion(self: TestCompletion, tdir: str): add('kitty --s', has_words('--session', '--start-as')) add('kitty --start-as', all_words('--start-as')) add('kitty --start-as ', all_words('minimized', 'maximized', 'fullscreen', 'normal')) + add('kitty -1 ', does_not_have_words('@ls', '@')) + add('kitty --directory ', all_words('bin/', 'sub/')) + add('kitty -1d ', all_words('bin/', 'sub/')) for cmd, tests, result in zip(all_cmds, all_tests, run_tool()): self.current_cmd = cmd diff --git a/tools/completion/files.go b/tools/completion/files.go index e63d237bb..182bacd08 100644 --- a/tools/completion/files.go +++ b/tools/completion/files.go @@ -250,3 +250,20 @@ func fnmatch_completer(title string, relative_to relative_to, patterns ...string func mimepat_completer(title string, relative_to relative_to, patterns ...string) completion_func { return make_completer(title, relative_to, patterns, complete_by_mimepat) } + +func directory_completer(title string, relative_to relative_to) completion_func { + if title == "" { + title = "Directories" + } + cwd := get_cwd_for_completion(relative_to) + + return func(completions *Completions, word string, arg_num int) { + mg := completions.add_match_group(title) + mg.IsFiles = true + complete_files(word, func(entry *FileEntry) { + if entry.mode.IsDir() { + mg.add_match(entry.completion_candidate) + } + }, cwd) + } +} diff --git a/tools/completion/kitty.go b/tools/completion/kitty.go index d4e691313..8a01f2274 100644 --- a/tools/completion/kitty.go +++ b/tools/completion/kitty.go @@ -14,6 +14,9 @@ import ( var _ = fmt.Print func complete_kitty(completions *Completions, word string, arg_num int) { + if arg_num > 1 { + return + } exes := complete_executables_in_path(word) if len(exes) > 0 { mg := completions.add_match_group("Executables in PATH") diff --git a/tools/completion/parse-args.go b/tools/completion/parse-args.go index 755a103e8..eb11d7b4b 100644 --- a/tools/completion/parse-args.go +++ b/tools/completion/parse-args.go @@ -100,7 +100,7 @@ func complete_word(word string, completions *Completions, only_args_allowed bool } return } - if arg_num == 1 && cmd.has_subcommands() { + if cmd.has_subcommands() && cmd.sub_command_allowed_at(completions, arg_num) { for _, cg := range cmd.Groups { group := completions.add_match_group(cg.Title) if group.Title == "" { @@ -163,7 +163,7 @@ func (cmd *Command) parse_args(words []string, completions *Completions) { } continue } - if cmd.has_subcommands() && arg_num == 1 { + if cmd.has_subcommands() && cmd.sub_command_allowed_at(completions, arg_num) { sc := cmd.find_subcommand_with_name(word) if sc == nil { only_args_allowed = true diff --git a/tools/completion/types.go b/tools/completion/types.go index e25e2b0fa..ff305f17e 100644 --- a/tools/completion/types.go +++ b/tools/completion/types.go @@ -68,6 +68,7 @@ type Command struct { Completion_for_arg completion_func Stop_processing_at_arg int First_arg_may_not_be_subcommand bool + Subcommand_must_be_first bool } func (self *Command) add_group(name string) *CommandGroup { @@ -122,6 +123,13 @@ func (self *Command) has_subcommands() bool { return false } +func (self *Command) sub_command_allowed_at(completions *Completions, arg_num int) bool { + if self.Subcommand_must_be_first { + return arg_num == 1 && completions.current_word_idx == 0 + } + return arg_num == 1 +} + func (self *Command) add_option(opt *Option) { self.Options = append(self.Options, opt) }