Start moving clipboard kitten to kitty-tool

This commit is contained in:
Kovid Goyal 2022-11-25 12:58:10 +05:30
parent fbce5e7524
commit 4d3f3b5e91
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
9 changed files with 167 additions and 28 deletions

View File

@ -8,7 +8,7 @@ import subprocess
import sys import sys
from contextlib import contextmanager, suppress from contextlib import contextmanager, suppress
from functools import lru_cache from functools import lru_cache
from typing import Dict, Iterator, List, Set, Tuple, Union, Sequence from typing import Any, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Union
import kitty.constants as kc import kitty.constants as kc
from kittens.tui.operations import Mode from kittens.tui.operations import Mode
@ -50,10 +50,24 @@ def replace(template: str, **kw: str) -> str:
# Completions {{{ # Completions {{{
@lru_cache
def kitten_cli_docs(kitten: str) -> Any:
from kittens.runner import get_kitten_cli_docs
return get_kitten_cli_docs(kitten)
@lru_cache
def go_options_for_kitten(kitten: str) -> Tuple[Sequence[GoOption], Optional[CompletionSpec]]:
kcd = kitten_cli_docs(kitten)
if kcd:
ospec = kcd['options']
return (tuple(go_options_for_seq(parse_option_spec(ospec())[0])), kcd.get('args_completion'))
return (), None
def generate_kittens_completion() -> None: def generate_kittens_completion() -> None:
from kittens.runner import ( from kittens.runner import all_kitten_names, get_kitten_wrapper_of
all_kitten_names, get_kitten_cli_docs, get_kitten_wrapper_of,
)
for kitten in sorted(all_kitten_names()): for kitten in sorted(all_kitten_names()):
kn = 'kitten_' + kitten kn = 'kitten_' + kitten
print(f'{kn} := plus_kitten.AddSubCommand(&cli.Command{{Name:"{kitten}", Group: "Kittens"}})') print(f'{kn} := plus_kitten.AddSubCommand(&cli.Command{{Name:"{kitten}", Group: "Kittens"}})')
@ -62,12 +76,10 @@ def generate_kittens_completion() -> None:
print(f'{kn}.ArgCompleter = cli.CompletionForWrapper("{serialize_as_go_string(wof)}")') print(f'{kn}.ArgCompleter = cli.CompletionForWrapper("{serialize_as_go_string(wof)}")')
print(f'{kn}.OnlyArgsAllowed = true') print(f'{kn}.OnlyArgsAllowed = true')
continue continue
kcd = get_kitten_cli_docs(kitten) gopts, ac = go_options_for_kitten(kitten)
if kcd: if gopts or ac:
ospec = kcd['options'] for opt in gopts:
for opt in go_options_for_seq(parse_option_spec(ospec())[0]):
print(opt.as_option(kn)) print(opt.as_option(kn))
ac = kcd.get('args_completion')
if ac is not None: if ac is not None:
print(''.join(ac.as_go_code(kn + '.ArgCompleter', ' = '))) print(''.join(ac.as_go_code(kn + '.ArgCompleter', ' = ')))
else: else:
@ -265,6 +277,48 @@ def go_code_for_remote_command(name: str, cmd: RemoteCommand, template: str) ->
# }}} # }}}
# kittens {{{
@lru_cache
def wrapped_kittens() -> Sequence[str]:
with open('shell-integration/ssh/kitty') as f:
for line in f:
if line.startswith(' wrapped_kittens="'):
val = line.strip().partition('"')[2][:-1]
return tuple(sorted(filter(None, val.split())))
raise Exception('Failed to read wrapped kittens from kitty wrapper script')
def kitten_clis() -> None:
for kitten in wrapped_kittens():
with replace_if_needed(f'tools/cmd/{kitten}/cli_generated.go'):
kcd = kitten_cli_docs(kitten)
has_underscore = '_' in kitten
print(f'package {kitten}')
print('import "kitty/tools/cli"')
print('func create_cmd(root *cli.Command, run_func cli.RunFunc) {')
print('ans := root.AddSubCommand(&cli.Command{')
print(f'Name: "{kitten}",')
print(f'ShortDescription: "{serialize_as_go_string(kcd["short_desc"])}",')
print(f'Usage: "{serialize_as_go_string(kcd["usage"])}",')
print(f'HelpText: "{serialize_as_go_string(kcd["help_text"])}",')
print('Run: run_func,')
if has_underscore:
print('Hidden: true,')
print('})')
gopts, ac = go_options_for_kitten(kitten)
for opt in gopts:
print(opt.as_option('ans'))
if ac is not None:
print(''.join(ac.as_go_code('ans.ArgCompleter', ' = ')))
if has_underscore:
print("clone := root.AddClone(ans.Group, ans)")
print('clone.Hidden = false')
print(f'clone.Name = "{serialize_as_go_string(kitten.replace("_", "-"))}"')
print('}')
# }}}
# Constants {{{ # Constants {{{
def generate_spinners() -> str: def generate_spinners() -> str:
@ -335,7 +389,12 @@ var DocTitleMap = map[string]string{serialize_go_dict(ref_map['doc'])}
@contextmanager @contextmanager
def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringIO]: def replace_if_needed(path: str, show_diff: bool = False) -> Iterator[io.StringIO]:
buf = io.StringIO() buf = io.StringIO()
origb = sys.stdout
sys.stdout = buf
try:
yield buf yield buf
finally:
sys.stdout = origb
orig = '' orig = ''
with suppress(FileNotFoundError), open(path, 'r') as f: with suppress(FileNotFoundError), open(path, 'r') as f:
orig = f.read() orig = f.read()
@ -391,21 +450,15 @@ func add_rc_global_opts(cmd *cli.Command) {{
def update_completion() -> None: def update_completion() -> None:
orig = sys.stdout with replace_if_needed('tools/cmd/completion/kitty_generated.go'):
try:
with replace_if_needed('tools/cmd/completion/kitty_generated.go') as f:
sys.stdout = f
generate_completions_for_kitty() generate_completions_for_kitty()
with replace_if_needed('tools/cmd/edit_in_kitty/launch_generated.go') as f: with replace_if_needed('tools/cmd/edit_in_kitty/launch_generated.go'):
sys.stdout = f
print('package edit_in_kitty') print('package edit_in_kitty')
print('import "kitty/tools/cli"') print('import "kitty/tools/cli"')
print('func AddCloneSafeOpts(cmd *cli.Command) {') print('func AddCloneSafeOpts(cmd *cli.Command) {')
completion_for_launch_wrappers('cmd') completion_for_launch_wrappers('cmd')
print(''.join(CompletionSpec.from_string('type:file mime:text/* group:"Text files"').as_go_code('cmd.ArgCompleter', ' = '))) print(''.join(CompletionSpec.from_string('type:file mime:text/* group:"Text files"').as_go_code('cmd.ArgCompleter', ' = ')))
print('}') print('}')
finally:
sys.stdout = orig
def define_enum(package_name: str, type_name: str, items: str, underlying_type: str = 'uint') -> str: def define_enum(package_name: str, type_name: str, items: str, underlying_type: str = 'uint') -> str:
@ -496,6 +549,7 @@ def main() -> None:
f.write(generate_spinners()) f.write(generate_spinners())
update_completion() update_completion()
update_at_commands() update_at_commands()
kitten_clis()
print(json.dumps(changed, indent=2)) print(json.dumps(changed, indent=2))

View File

@ -147,3 +147,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['short_desc'] = 'Copy/paste with the system clipboard, even over SSH'

View File

@ -216,6 +216,23 @@ get_docs_ref_map(PyObject *self UNUSED, PyObject *args UNUSED) {
return PyBytes_FromStringAndSize(docs_ref_map, sizeof(docs_ref_map)); return PyBytes_FromStringAndSize(docs_ref_map, sizeof(docs_ref_map));
} }
#include "wrapped_kitten_names_generated.h"
static PyObject*
wrapped_kittens(PyObject *self UNUSED, PyObject *args UNUSED) {
PyObject *ans = PyFrozenSet_New(NULL);
if (ans != NULL) {
for (int i = 0; wrapped_kitten_names[i] != NULL; i++) {
PyObject *n = PyUnicode_FromString(wrapped_kitten_names[i]);
if (n == NULL) break;
if (PySet_Add(ans, n) != 0) { Py_DECREF(n); break; }
Py_DECREF(n);
}
}
if (PyErr_Occurred()) { Py_CLEAR(ans); }
return ans;
}
static PyMethodDef module_methods[] = { static PyMethodDef module_methods[] = {
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""}, {"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"get_docs_ref_map", (PyCFunction)get_docs_ref_map, METH_NOARGS, ""}, {"get_docs_ref_map", (PyCFunction)get_docs_ref_map, METH_NOARGS, ""},
@ -234,6 +251,7 @@ static PyMethodDef module_methods[] = {
{"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""}, {"locale_is_valid", (PyCFunction)locale_is_valid, METH_VARARGS, ""},
{"shm_open", (PyCFunction)py_shm_open, METH_VARARGS, ""}, {"shm_open", (PyCFunction)py_shm_open, METH_VARARGS, ""},
{"shm_unlink", (PyCFunction)py_shm_unlink, METH_VARARGS, ""}, {"shm_unlink", (PyCFunction)py_shm_unlink, METH_VARARGS, ""},
{"wrapped_kitten_names", (PyCFunction)wrapped_kittens, METH_NOARGS, ""},
#ifdef __APPLE__ #ifdef __APPLE__
METHODB(user_cache_dir, METH_NOARGS), METHODB(user_cache_dir, METH_NOARGS),
METHODB(process_group_map, METH_NOARGS), METHODB(process_group_map, METH_NOARGS),

View File

@ -1,8 +1,8 @@
import termios import termios
from ctypes import Array, c_ubyte from ctypes import Array, c_ubyte
from typing import ( from typing import (
Any, Callable, Dict, List, NewType, Optional, Tuple, TypedDict, Any, Callable, Dict, FrozenSet, Iterator, List, NewType, Optional, Tuple, TypedDict,
Union, Iterator Union,
) )
from kitty.boss import Boss from kitty.boss import Boss
@ -1493,3 +1493,4 @@ def get_clipboard_mime(ct: int, mime: Optional[str], callback: Callable[[bytes],
def run_with_activation_token(func: Callable[[str], None]) -> None: ... def run_with_activation_token(func: Callable[[str], None]) -> None: ...
def make_x11_window_a_dock_window(x11_window_id: int, strut: Tuple[int, int, int, int, int, int, int, int, int, int, int, int]) -> None: ... def make_x11_window_a_dock_window(x11_window_id: int, strut: Tuple[int, int, int, int, int, int, int, int, int, int, int, int]) -> None: ...
def unicode_database_version() -> Tuple[int, int, int]: ... def unicode_database_version() -> Tuple[int, int, int]: ...
def wrapped_kittens() -> FrozenSet[str]: ...

View File

@ -860,11 +860,37 @@ def build_ref_map() -> str:
return dest return dest
@lru_cache
def wrapped_kittens() -> Sequence[str]:
with open('shell-integration/ssh/kitty') as f:
for line in f:
if line.startswith(' wrapped_kittens="'):
val = line.strip().partition('"')[2][:-1]
return tuple(sorted(filter(None, val.split())))
raise Exception('Failed to read wrapped kittens from kitty wrapper script')
def build_wrapped_kittens() -> str:
h = 'static const char* wrapped_kitten_names[] = {\n'
h += ', '.join(f'"{x}"' for x in wrapped_kittens())
h += ', NULL'
h += '\n};\n'
dest = 'kitty/wrapped_kitten_names_generated.h'
q = ''
with suppress(FileNotFoundError), open(dest) as f:
q = f.read()
if q != h:
with open(dest, 'w') as f:
f.write(h)
return dest
def build(args: Options, native_optimizations: bool = True, call_init: bool = True) -> None: def build(args: Options, native_optimizations: bool = True, call_init: bool = True) -> None:
if call_init: if call_init:
init_env_from_args(args, native_optimizations) init_env_from_args(args, native_optimizations)
sources, headers = find_c_files() sources, headers = find_c_files()
headers.append(build_ref_map()) headers.append(build_ref_map())
headers.append(build_wrapped_kittens())
compile_c_extension( compile_c_extension(
kitty_env(), 'kitty/fast_data_types', args.compilation_database, sources, headers kitty_env(), 'kitty/fast_data_types', args.compilation_database, sources, headers
) )

View File

@ -22,6 +22,21 @@ exec_kitty() {
die "Failed to execute kitty" die "Failed to execute kitty"
} }
is_wrapped_kitten() {
wrapped_kittens="clipboard"
[ -n "$1" ] && {
case " $wrapped_kittens " in
*" $1 "*) printf "%s" "$1" ;;
esac
}
}
test "(" "$1" = "+kitten" -a -n "$(is_wrapped_kitten "$2")" ")" -o "(" "$1" = "+" -a "$2" = "kitten" -a "$(is_wrapped_kitten "$3")" ")" && {
if [ "$1" = "+kitten" ]; then shift "1"; else shift "2"; fi
exec kitty-tool "$@"
}
lock_dir="" lock_dir=""
script_path="$(command readlink -f "$0" 2> /dev/null)" script_path="$(command readlink -f "$0" 2> /dev/null)"
[ $? = 0 ] || script_path="$0" [ $? = 0 ] || script_path="$0"

View File

@ -14,6 +14,8 @@ import (
var _ = fmt.Print var _ = fmt.Print
type RunFunc = func(cmd *Command, args []string) (int, error)
type Command struct { type Command struct {
Name, Group string Name, Group string
Usage, ShortDescription, HelpText string Usage, ShortDescription, HelpText string
@ -26,7 +28,7 @@ type Command struct {
// If true subcommands are ignored unless they are the first non-option argument // If true subcommands are ignored unless they are the first non-option argument
SubCommandMustBeFirst bool SubCommandMustBeFirst bool
// The entry point for this command // The entry point for this command
Run func(cmd *Command, args []string) (int, error) Run RunFunc
// The completer for args // The completer for args
ArgCompleter CompletionFunc ArgCompleter CompletionFunc
// Stop completion processing at this arg num // Stop completion processing at this arg num

View File

@ -0,0 +1,18 @@
// License: GPLv3 Copyright: 2022, Kovid Goyal, <kovid at kovidgoyal.net>
package clipboard
import (
"fmt"
"kitty/tools/cli"
)
var _ = fmt.Print
func clipboard_main(cmd *cli.Command, args []string) (int, error) {
return 0, nil
}
func EntryPoint(parent *cli.Command) {
create_cmd(parent, clipboard_main)
}

View File

@ -4,8 +4,10 @@ package tool
import ( import (
"fmt" "fmt"
"kitty/tools/cli" "kitty/tools/cli"
"kitty/tools/cmd/at" "kitty/tools/cmd/at"
"kitty/tools/cmd/clipboard"
"kitty/tools/cmd/edit_in_kitty" "kitty/tools/cmd/edit_in_kitty"
"kitty/tools/cmd/update_self" "kitty/tools/cmd/update_self"
) )
@ -21,4 +23,6 @@ func KittyToolEntryPoints(root *cli.Command) {
update_self.EntryPoint(root) update_self.EntryPoint(root)
// edit-in-kitty // edit-in-kitty
edit_in_kitty.EntryPoint(root) edit_in_kitty.EntryPoint(root)
// clipboard
clipboard.EntryPoint(root)
} }