Add completion for the kittens
This commit is contained in:
parent
25a7ec9a07
commit
7737369fc9
@ -57,6 +57,23 @@ def generate_completion_for_rc(name: str) -> None:
|
|||||||
print(opt.as_completion_option(name))
|
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:
|
def generate_completions_for_kitty() -> None:
|
||||||
print('package completion\n')
|
print('package completion\n')
|
||||||
print('func kitty(root *Command) {')
|
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(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('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.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('plus_launch.Completion_for_arg = complete_plus_launch')
|
||||||
print('k.add_clone("+launch", "Launch Python scripts", 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('plus_runpy.Completion_for_arg = complete_plus_runpy')
|
||||||
print('k.add_clone("+runpy", "Run Python code", 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.Completion_for_arg = complete_plus_open')
|
||||||
print('plus_open.clone_options_from(k)')
|
print('plus_open.clone_options_from(k)')
|
||||||
print('k.add_clone("+open", "Open files and URLs", plus_open)')
|
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 := k.add_command("@", "Remote control")')
|
||||||
print('at.Description = "Control kitty using commands"')
|
print('at.Description = "Control kitty using commands"')
|
||||||
for go_name in all_command_names():
|
for go_name in all_command_names():
|
||||||
|
|||||||
@ -17,7 +17,7 @@ from typing import (
|
|||||||
Any, DefaultDict, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
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.cli_stub import DiffCLIOptions
|
||||||
from kitty.conf.utils import KeyAction
|
from kitty.conf.utils import KeyAction
|
||||||
from kitty.constants import appname
|
from kitty.constants import appname
|
||||||
@ -577,6 +577,7 @@ number set in :file:`diff.conf`.
|
|||||||
|
|
||||||
--config
|
--config
|
||||||
type=list
|
type=list
|
||||||
|
completion=type:file ext:conf group:"Config files" kwds:none,NONE
|
||||||
{config_help}
|
{config_help}
|
||||||
|
|
||||||
|
|
||||||
@ -687,6 +688,7 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
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__':
|
elif __name__ == '__conf__':
|
||||||
from .options.definition import definition
|
from .options.definition import definition
|
||||||
sys.options_definition = definition # type: ignore
|
sys.options_definition = definition # type: ignore
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from typing import (
|
|||||||
Dict, Generator, List, NamedTuple, Optional, Pattern, Tuple, Union
|
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.cli_stub import IcatCLIOptions
|
||||||
from kitty.constants import appname
|
from kitty.constants import appname
|
||||||
from kitty.guess_mime_type import guess_type
|
from kitty.guess_mime_type import guess_type
|
||||||
@ -622,3 +622,4 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = options_spec
|
cd['options'] = options_spec
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['args_completion'] = CompletionSpec.from_string('type:file mime:image/* group:Images')
|
||||||
|
|||||||
@ -8,17 +8,20 @@ import os
|
|||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
import zipfile
|
import zipfile
|
||||||
from contextlib import suppress
|
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.error import HTTPError
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
from kitty.config import atomic_save, parse_config
|
from kitty.config import atomic_save, parse_config
|
||||||
from kitty.constants import cache_dir, config_dir
|
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.fast_data_types import Color
|
||||||
|
from kitty.options.types import Options as KittyOptions
|
||||||
from kitty.utils import reload_conf_in_all_kitties
|
from kitty.utils import reload_conf_in_all_kitties
|
||||||
|
|
||||||
from ..choose.match import match
|
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.load_from_dir(os.path.join(config_dir, 'themes'))
|
||||||
ans.index_map = tuple(ans.themes)
|
ans.index_map = tuple(ans.themes)
|
||||||
return ans
|
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()
|
||||||
|
|||||||
@ -11,12 +11,12 @@ from typing import (
|
|||||||
Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union
|
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.cli_stub import ThemesCLIOptions
|
||||||
from kitty.config import cached_values_for
|
from kitty.config import cached_values_for
|
||||||
from kitty.options.types import Options as KittyOptions
|
|
||||||
from kitty.constants import config_dir
|
from kitty.constants import config_dir
|
||||||
from kitty.fast_data_types import truncate_point_for_length, wcswidth
|
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.rgb import color_as_sharp, color_from_int
|
||||||
from kitty.typing import KeyEventType
|
from kitty.typing import KeyEventType
|
||||||
from kitty.utils import ScreenSize
|
from kitty.utils import ScreenSize
|
||||||
@ -616,3 +616,4 @@ elif __name__ == '__doc__':
|
|||||||
cd['usage'] = usage
|
cd['usage'] = usage
|
||||||
cd['options'] = OPTIONS
|
cd['options'] = OPTIONS
|
||||||
cd['help_text'] = help_text
|
cd['help_text'] = help_text
|
||||||
|
cd['args_completion'] = CompletionSpec.from_string('type:special group:complete_themes')
|
||||||
|
|||||||
@ -30,6 +30,7 @@ class CompletionType(Enum):
|
|||||||
file = auto()
|
file = auto()
|
||||||
directory = auto()
|
directory = auto()
|
||||||
keyword = auto()
|
keyword = auto()
|
||||||
|
special = auto()
|
||||||
none = auto()
|
none = auto()
|
||||||
|
|
||||||
|
|
||||||
@ -89,6 +90,8 @@ class CompletionSpec:
|
|||||||
if self.type is CompletionType.directory:
|
if self.type is CompletionType.directory:
|
||||||
g = serialize_as_go_string(self.group or 'Directories')
|
g = serialize_as_go_string(self.group or 'Directories')
|
||||||
completers.append(f'directory_completer("{g}", {relative_to})')
|
completers.append(f'directory_completer("{g}", {relative_to})')
|
||||||
|
if self.type is CompletionType.special:
|
||||||
|
completers.append(self.group)
|
||||||
if go_name:
|
if go_name:
|
||||||
go_name += '.'
|
go_name += '.'
|
||||||
if len(completers) > 1:
|
if len(completers) > 1:
|
||||||
@ -113,7 +116,9 @@ def serialize_as_go_string(x: str) -> str:
|
|||||||
return x.replace('\\', '\\\\').replace('\n', '\\n').replace('"', '\\"')
|
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 = {
|
go_getter_map = {
|
||||||
'bool-set': 'GetBool', 'bool-reset': 'GetBool', 'int': 'GetInt', 'float': 'GetFloat64', '': 'GetString',
|
'bool-set': 'GetBool', 'bool-reset': 'GetBool', 'int': 'GetInt', 'float': 'GetFloat64', '': 'GetString',
|
||||||
'list': 'GetStringArray', 'choices': 'GetString'
|
'list': 'GetStringArray', 'choices': 'GetString'
|
||||||
@ -460,6 +465,8 @@ def parse_option_spec(spec: Optional[str] = None) -> Tuple[OptionSpecSeq, Option
|
|||||||
if k == 'default':
|
if k == 'default':
|
||||||
current_cmd['default'] = v
|
current_cmd['default'] = v
|
||||||
elif k == 'type':
|
elif k == 'type':
|
||||||
|
if v == 'choice':
|
||||||
|
v = 'choices'
|
||||||
current_cmd['type'] = v
|
current_cmd['type'] = v
|
||||||
elif k == 'dest':
|
elif k == 'dest':
|
||||||
current_cmd['dest'] = v
|
current_cmd['dest'] = v
|
||||||
|
|||||||
@ -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 @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()):
|
for cmd, tests, result in zip(all_cmds, all_tests, run_tool()):
|
||||||
self.current_cmd = cmd
|
self.current_cmd = cmd
|
||||||
for test in tests:
|
for test in tests:
|
||||||
|
|||||||
@ -185,7 +185,17 @@ func complete_by_fnmatch(prefix, cwd string, patterns []string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func complete_by_mimepat(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 {
|
return fname_based_completer(prefix, cwd, func(name string) bool {
|
||||||
|
if all_allowed {
|
||||||
|
return true
|
||||||
|
}
|
||||||
idx := strings.Index(name, ".")
|
idx := strings.Index(name, ".")
|
||||||
if idx < 1 {
|
if idx < 1 {
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -3,8 +3,12 @@
|
|||||||
package completion
|
package completion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"kitty/tools/utils"
|
||||||
"os"
|
"os"
|
||||||
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"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) {
|
func complete_plus_open(completions *Completions, word string, arg_num int) {
|
||||||
fnmatch_completer("Files", CWD, "*")(completions, word, arg_num)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -139,6 +139,7 @@ func (cmd *Command) parse_args(words []string, completions *Completions) {
|
|||||||
for i, word := range words {
|
for i, word := range words {
|
||||||
cmd = completions.current_cmd
|
cmd = completions.current_cmd
|
||||||
completions.current_word_idx = i
|
completions.current_word_idx = i
|
||||||
|
completions.current_word_idx_in_parent++
|
||||||
is_last_word := i == len(words)-1
|
is_last_word := i == len(words)-1
|
||||||
if expecting_arg_for == nil && !strings.HasPrefix(word, "-") {
|
if expecting_arg_for == nil && !strings.HasPrefix(word, "-") {
|
||||||
arg_num++
|
arg_num++
|
||||||
@ -172,6 +173,7 @@ func (cmd *Command) parse_args(words []string, completions *Completions) {
|
|||||||
completions.current_cmd = sc
|
completions.current_cmd = sc
|
||||||
cmd = sc
|
cmd = sc
|
||||||
arg_num = 0
|
arg_num = 0
|
||||||
|
completions.current_word_idx_in_parent = 0
|
||||||
only_args_allowed = false
|
only_args_allowed = false
|
||||||
} else if cmd.Stop_processing_at_arg > 0 && arg_num >= cmd.Stop_processing_at_arg {
|
} else if cmd.Stop_processing_at_arg > 0 && arg_num >= cmd.Stop_processing_at_arg {
|
||||||
return
|
return
|
||||||
|
|||||||
@ -31,9 +31,10 @@ func (self *MatchGroup) add_prefix_to_all_matches(prefix string) {
|
|||||||
type Completions struct {
|
type Completions struct {
|
||||||
Groups []*MatchGroup `json:"groups,omitempty"`
|
Groups []*MatchGroup `json:"groups,omitempty"`
|
||||||
|
|
||||||
current_cmd *Command
|
current_cmd *Command
|
||||||
all_words []string // all words passed to parse_args()
|
all_words []string // all words passed to parse_args()
|
||||||
current_word_idx int // index of current word in all_words
|
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) {
|
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 {
|
func (self *Command) sub_command_allowed_at(completions *Completions, arg_num int) bool {
|
||||||
if self.Subcommand_must_be_first {
|
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
|
return arg_num == 1
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Sep = string(os.PathSeparator)
|
var Sep = string(os.PathSeparator)
|
||||||
@ -56,6 +58,15 @@ func Abspath(path string) string {
|
|||||||
|
|
||||||
var config_dir 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 {
|
func ConfigDir() string {
|
||||||
if config_dir != "" {
|
if config_dir != "" {
|
||||||
return config_dir
|
return config_dir
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user