Add completion for the kittens

This commit is contained in:
Kovid Goyal 2022-09-17 11:59:41 +05:30
parent 25a7ec9a07
commit 7737369fc9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
12 changed files with 114 additions and 15 deletions

View File

@ -57,6 +57,23 @@ def generate_completion_for_rc(name: str) -> None:
print(opt.as_completion_option(name))
def generate_kittens_completion() -> None:
from kittens.runner import all_kitten_names, get_kitten_cli_docs
for kitten in all_kitten_names():
kn = 'kitten_' + kitten
print(f'{kn} := plus_kitten.add_command("{kitten}", "Kittens")')
kcd = get_kitten_cli_docs(kitten)
if kcd:
ospec = kcd['options']
for opt in go_options_for_seq(parse_option_spec(ospec())[0]):
print(opt.as_completion_option(kn))
ac = kcd.get('args_completion')
if ac is not None:
print(''.join(ac.as_go_code(kn)))
else:
print(f'{kn}.Description = ""')
def generate_completions_for_kitty() -> None:
print('package completion\n')
print('func kitty(root *Command) {')
@ -71,22 +88,27 @@ def generate_completions_for_kitty() -> None:
print(f'k.find_option("-o").Completion_for_arg = complete_kitty_override("Config directives", []string{{{conf_names}}})')
print('k.find_option("--listen-on").Completion_for_arg = complete_kitty_listen_on')
print('plus := k.add_command("+", "Entry point")')
print('plus := k.add_command("+", "Entry points")')
print('plus.Description = "Various special purpose tools and kittens"')
print('plus_launch := plus.add_command("launch", "Launch Python script")')
print('plus_launch := plus.add_command("launch", "Entry points")')
print('plus_launch.Completion_for_arg = complete_plus_launch')
print('k.add_clone("+launch", "Launch Python scripts", plus_launch)')
print('plus_runpy := plus.add_command("runpy", "Run python code")')
print('plus_runpy := plus.add_command("runpy", "Entry points")')
print('plus_runpy.Completion_for_arg = complete_plus_runpy')
print('k.add_clone("+runpy", "Run Python code", plus_runpy)')
print('plus_open := plus.add_command("open", "Open files and URLs")')
print('plus_open := plus.add_command("open", "Entry points")')
print('plus_open.Completion_for_arg = complete_plus_open')
print('plus_open.clone_options_from(k)')
print('k.add_clone("+open", "Open files and URLs", plus_open)')
print('plus_kitten := plus.add_command("kitten", "Kittens")')
print('plus_kitten.Subcommand_must_be_first = true')
generate_kittens_completion()
print('k.add_clone("+kitten", "Kittens", plus_kitten)')
print('at := k.add_command("@", "Remote control")')
print('at.Description = "Control kitty using commands"')
for go_name in all_command_names():

View File

@ -17,7 +17,7 @@ from typing import (
Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
)
from kitty.cli import CONFIG_HELP, parse_args
from kitty.cli import CONFIG_HELP, parse_args, CompletionSpec
from kitty.cli_stub import DiffCLIOptions
from kitty.conf.utils import KeyAction
from kitty.constants import appname
@ -577,6 +577,7 @@ number set in :file:`diff.conf`.
--config
type=list
completion=type:file ext:conf group:"Config files" kwds:none,NONE
{config_help}
@ -687,6 +688,7 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['args_completion'] = CompletionSpec.from_string('type:file mime:text/* mime:image/* group:"Text and image files"')
elif __name__ == '__conf__':
from .options.definition import definition
sys.options_definition = definition # type: ignore

View File

@ -15,7 +15,7 @@ from typing import (
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
)
from kitty.cli import parse_args
from kitty.cli import parse_args, CompletionSpec
from kitty.cli_stub import IcatCLIOptions
from kitty.constants import appname
from kitty.guess_mime_type import guess_type
@ -622,3 +622,4 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = options_spec
cd['help_text'] = help_text
cd['args_completion'] = CompletionSpec.from_string('type:file mime:image/* group:Images')

View File

@ -8,17 +8,20 @@ import os
import re
import shutil
import signal
import sys
import tempfile
import zipfile
from contextlib import suppress
from typing import Any, Callable, Dict, Iterator, Match, Optional, Tuple, Union, Type
from typing import (
Any, Callable, Dict, Iterator, Match, Optional, Tuple, Type, Union
)
from urllib.error import HTTPError
from urllib.request import Request, urlopen
from kitty.config import atomic_save, parse_config
from kitty.constants import cache_dir, config_dir
from kitty.options.types import Options as KittyOptions
from kitty.fast_data_types import Color
from kitty.options.types import Options as KittyOptions
from kitty.utils import reload_conf_in_all_kitties
from ..choose.match import match
@ -647,3 +650,13 @@ def load_themes(cache_age: float = 1., ignore_no_cache: bool = False) -> Themes:
ans.load_from_dir(os.path.join(config_dir, 'themes'))
ans.index_map = tuple(ans.themes)
return ans
def print_theme_names() -> None:
found = False
for theme in load_themes(cache_age=-1, ignore_no_cache=True):
print(theme.name)
found = True
if not found:
print('Default')
sys.stdout.flush()

View File

@ -11,12 +11,12 @@ from typing import (
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
)
from kitty.cli import create_default_opts, parse_args
from kitty.cli import CompletionSpec, create_default_opts, parse_args
from kitty.cli_stub import ThemesCLIOptions
from kitty.config import cached_values_for
from kitty.options.types import Options as KittyOptions
from kitty.constants import config_dir
from kitty.fast_data_types import truncate_point_for_length, wcswidth
from kitty.options.types import Options as KittyOptions
from kitty.rgb import color_as_sharp, color_from_int
from kitty.typing import KeyEventType
from kitty.utils import ScreenSize
@ -616,3 +616,4 @@ elif __name__ == '__doc__':
cd['usage'] = usage
cd['options'] = OPTIONS
cd['help_text'] = help_text
cd['args_completion'] = CompletionSpec.from_string('type:special group:complete_themes')

View File

@ -30,6 +30,7 @@ class CompletionType(Enum):
file = auto()
directory = auto()
keyword = auto()
special = auto()
none = auto()
@ -89,6 +90,8 @@ class CompletionSpec:
if self.type is CompletionType.directory:
g = serialize_as_go_string(self.group or 'Directories')
completers.append(f'directory_completer("{g}", {relative_to})')
if self.type is CompletionType.special:
completers.append(self.group)
if go_name:
go_name += '.'
if len(completers) > 1:
@ -113,7 +116,9 @@ def serialize_as_go_string(x: str) -> str:
return x.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
go_type_map = {'bool-set': 'bool', 'bool-reset': 'bool', 'int': 'int', 'float': 'float64', '': 'string', 'list': '[]string', 'choices': 'string'}
go_type_map = {
'bool-set': 'bool', 'bool-reset': 'bool', 'int': 'int', 'float': 'float64',
'': 'string', 'list': '[]string', 'choices': 'string', 'str': 'string'}
go_getter_map = {
'bool-set': 'GetBool', 'bool-reset': 'GetBool', 'int': 'GetInt', 'float': 'GetFloat64', '': 'GetString',
'list': 'GetStringArray', 'choices': 'GetString'
@ -460,6 +465,8 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option
if k == 'default':
current_cmd['default'] = v
elif k == 'type':
if v == 'choice':
v = 'choices'
current_cmd['type'] = v
elif k == 'dest':
current_cmd['dest'] = v

View File

@ -136,6 +136,14 @@ def completion(self: TestCompletion, tdir: str):
add('kitty @launch --logo ', all_words('exe-not3.png'))
add('kitty @launch --logo ~', all_words('~/exe-not3.png'))
add('kitty + ', has_words('launch', 'kitten'))
add('kitty + kitten ', has_words('icat', 'diff'))
add('kitty +kitten icat ', has_words('sub/', 'exe-not2.jpeg'))
add('kitty + kitten icat --pr', has_words('--print-window-size'))
add('kitty + kitten diff ', has_words('exe-not2.jpeg'))
add('kitty + kitten themes --', has_words('--cache-age'))
add('kitty + kitten themes D', has_words('Default'))
for cmd, tests, result in zip(all_cmds, all_tests, run_tool()):
self.current_cmd = cmd
for test in tests:

View File

@ -185,7 +185,17 @@ func complete_by_fnmatch(prefix, cwd string, patterns []string) []string {
}
func complete_by_mimepat(prefix, cwd string, patterns []string) []string {
all_allowed := false
for _, p := range patterns {
if p == "*" {
all_allowed = true
break
}
}
return fname_based_completer(prefix, cwd, func(name string) bool {
if all_allowed {
return true
}
idx := strings.Index(name, ".")
if idx < 1 {
return false

View File

@ -3,8 +3,12 @@
package completion
import (
"bufio"
"bytes"
"fmt"
"kitty/tools/utils"
"os"
"os/exec"
"path/filepath"
"strings"
@ -97,3 +101,20 @@ func complete_plus_runpy(completions *Completions, word string, arg_num int) {
func complete_plus_open(completions *Completions, word string, arg_num int) {
fnmatch_completer("Files", CWD, "*")(completions, word, arg_num)
}
func complete_themes(completions *Completions, word string, arg_num int) {
kitty, err := utils.KittyExe()
if err == nil {
out, err := exec.Command(kitty, "+runpy", "from kittens.themes.collection import *; print_theme_names()").Output()
if err == nil {
mg := completions.add_match_group("Themes")
scanner := bufio.NewScanner(bytes.NewReader(out))
for scanner.Scan() {
theme_name := strings.TrimSpace(scanner.Text())
if theme_name != "" && strings.HasPrefix(theme_name, word) {
mg.add_match(theme_name)
}
}
}
}
}

View File

@ -139,6 +139,7 @@ func (cmd *Command) parse_args(words []string, completions *Completions) {
for i, word := range words {
cmd = completions.current_cmd
completions.current_word_idx = i
completions.current_word_idx_in_parent++
is_last_word := i == len(words)-1
if expecting_arg_for == nil && !strings.HasPrefix(word, "-") {
arg_num++
@ -172,6 +173,7 @@ func (cmd *Command) parse_args(words []string, completions *Completions) {
completions.current_cmd = sc
cmd = sc
arg_num = 0
completions.current_word_idx_in_parent = 0
only_args_allowed = false
} else if cmd.Stop_processing_at_arg > 0 && arg_num >= cmd.Stop_processing_at_arg {
return

View File

@ -31,9 +31,10 @@ func (self *MatchGroup) add_prefix_to_all_matches(prefix string) {
type Completions struct {
Groups []*MatchGroup `json:"groups,omitempty"`
current_cmd *Command
all_words []string // all words passed to parse_args()
current_word_idx int // index of current word in all_words
current_cmd *Command
all_words []string // all words passed to parse_args()
current_word_idx int // index of current word in all_words
current_word_idx_in_parent int // index of current word in parents command line 1 for first word after parent
}
func (self *Completions) add_prefix_to_all_matches(prefix string) {
@ -141,7 +142,7 @@ func (self *Command) has_subcommands() bool {
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 && completions.current_word_idx_in_parent == 1
}
return arg_num == 1
}

View File

@ -9,6 +9,8 @@ import (
"path/filepath"
"runtime"
"strings"
"golang.org/x/sys/unix"
)
var Sep = string(os.PathSeparator)
@ -56,6 +58,15 @@ func Abspath(path string) string {
var config_dir string
func KittyExe() (string, error) {
exe, err := os.Executable()
if err != nil {
return "", err
}
ans := filepath.Join(filepath.Dir(exe), "kitty")
return ans, unix.Access(ans, unix.X_OK)
}
func ConfigDir() string {
if config_dir != "" {
return config_dir