Refactor the kittens framework
Make it possible to perform arbitrary actions with the kittens output and also allow running kittens from standalone python files.
This commit is contained in:
parent
5755ba72b1
commit
2cf8c6aea7
59
kittens/runner.py
Normal file
59
kittens/runner.py
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# vim:fileencoding=utf-8
|
||||||
|
# License: GPL v3 Copyright: 2018, Kovid Goyal <kovid at kovidgoyal.net>
|
||||||
|
|
||||||
|
|
||||||
|
import importlib
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
|
||||||
|
def import_kitten_main_module(config_dir, kitten):
|
||||||
|
if kitten.endswith('.py'):
|
||||||
|
path = os.path.expanduser(kitten)
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
path = os.path.join(config_dir, path)
|
||||||
|
path = os.path.abspath(path)
|
||||||
|
if os.path.dirname(path):
|
||||||
|
sys.path.insert(0, os.path.dirname(path))
|
||||||
|
with open(path) as f:
|
||||||
|
src = f.read()
|
||||||
|
code = compile(src, path, 'exec')
|
||||||
|
g = {'__name__': 'kitten'}
|
||||||
|
exec(code, g)
|
||||||
|
return {'start': g['main'], 'end': g['handle_result']}
|
||||||
|
else:
|
||||||
|
m = importlib.import_module('kittens.{}.main'.format(kitten))
|
||||||
|
return {'start': m.main, 'end': m.handle_result}
|
||||||
|
|
||||||
|
|
||||||
|
def create_kitten_handler(kitten, orig_args):
|
||||||
|
from kitty.constants import config_dir
|
||||||
|
m = import_kitten_main_module(config_dir, kitten)
|
||||||
|
return partial(m['end'], [kitten] + orig_args)
|
||||||
|
|
||||||
|
|
||||||
|
def launch(args):
|
||||||
|
config_dir, kitten = args[:2]
|
||||||
|
del args[:2]
|
||||||
|
args = [kitten] + args
|
||||||
|
os.environ['KITTY_CONFIG_DIRECTORY'] = config_dir
|
||||||
|
from kittens.tui.operations import clear_screen, reset_mode
|
||||||
|
m = import_kitten_main_module(config_dir, kitten)
|
||||||
|
result = m['start'](args)
|
||||||
|
print(reset_mode('ALTERNATE_SCREEN') + clear_screen(), end='')
|
||||||
|
if result is not None:
|
||||||
|
import json
|
||||||
|
print('OK:', json.dumps(result))
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
args = sys.argv[1:]
|
||||||
|
launch(args)
|
||||||
|
except Exception:
|
||||||
|
print('Unhandled exception running kitten:')
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
|
input('Press Enter to quit...')
|
||||||
@ -5,7 +5,6 @@
|
|||||||
import os
|
import os
|
||||||
import string
|
import string
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from gettext import gettext as _
|
from gettext import gettext as _
|
||||||
|
|
||||||
@ -454,26 +453,24 @@ class UnicodeInput(Handler):
|
|||||||
self.refresh()
|
self.refresh()
|
||||||
|
|
||||||
|
|
||||||
def run_loop(args):
|
def main(args):
|
||||||
loop = Loop()
|
loop = Loop()
|
||||||
with cached_values_for('unicode-input') as cached_values:
|
with cached_values_for('unicode-input') as cached_values:
|
||||||
handler = UnicodeInput(cached_values)
|
handler = UnicodeInput(cached_values)
|
||||||
loop.loop(handler)
|
loop.loop(handler)
|
||||||
if handler.current_char and loop.return_code == 0:
|
if handler.current_char and loop.return_code == 0:
|
||||||
print('OK:', hex(ord(handler.current_char))[2:])
|
|
||||||
try:
|
try:
|
||||||
handler.recent.remove(ord(handler.current_char))
|
handler.recent.remove(ord(handler.current_char))
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
recent = [ord(handler.current_char)] + handler.recent
|
recent = [ord(handler.current_char)] + handler.recent
|
||||||
cached_values['recent'] = recent[:len(DEFAULT_SET)]
|
cached_values['recent'] = recent[:len(DEFAULT_SET)]
|
||||||
return loop.return_code
|
return handler.current_char
|
||||||
|
if loop.return_code != 0:
|
||||||
|
raise SystemExit(loop.return_code)
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def handle_result(args, current_char, target_window_id, boss):
|
||||||
try:
|
w = boss.window_id_map.get(target_window_id)
|
||||||
raise SystemExit(run_loop(args))
|
if w is not None:
|
||||||
except Exception:
|
w.paste(current_char)
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
input(_('Press Enter to quit.'))
|
|
||||||
|
|||||||
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import re
|
import re
|
||||||
import string
|
import string
|
||||||
import subprocess
|
|
||||||
import sys
|
import sys
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import lru_cache, partial
|
from functools import lru_cache, partial
|
||||||
@ -12,7 +11,6 @@ from gettext import gettext as _
|
|||||||
|
|
||||||
from kitty.cli import parse_args
|
from kitty.cli import parse_args
|
||||||
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
|
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
|
||||||
from kitty.utils import command_for_open
|
|
||||||
|
|
||||||
from ..tui.handler import Handler
|
from ..tui.handler import Handler
|
||||||
from ..tui.loop import Loop
|
from ..tui.loop import Loop
|
||||||
@ -178,16 +176,7 @@ def run_loop(args, lines, index_map):
|
|||||||
handler = URLHints(lines, index_map)
|
handler = URLHints(lines, index_map)
|
||||||
loop.loop(handler)
|
loop.loop(handler)
|
||||||
if handler.chosen and loop.return_code == 0:
|
if handler.chosen and loop.return_code == 0:
|
||||||
if args.in_kitty:
|
return {'url': handler.chosen, 'program': args.program}
|
||||||
import json
|
|
||||||
print('OK:', json.dumps({'url': handler.chosen, 'program': args.program, 'action': 'open_with'}))
|
|
||||||
else:
|
|
||||||
cmd = command_for_open(args.program)
|
|
||||||
ret = subprocess.Popen(cmd + [handler.chosen]).wait()
|
|
||||||
if ret != 0:
|
|
||||||
print('URL handler "{}" failed with return code: {}'.format(' '.join(cmd), ret), file=sys.stderr)
|
|
||||||
input('Press Enter to quit')
|
|
||||||
loop.return_code = ret
|
|
||||||
raise SystemExit(loop.return_code)
|
raise SystemExit(loop.return_code)
|
||||||
|
|
||||||
|
|
||||||
@ -215,7 +204,7 @@ def run(args, source_file=None):
|
|||||||
input(_('No URLs found, press Enter to abort.'))
|
input(_('No URLs found, press Enter to abort.'))
|
||||||
return
|
return
|
||||||
|
|
||||||
run_loop(args, lines, index_map)
|
return run_loop(args, lines, index_map)
|
||||||
|
|
||||||
|
|
||||||
OPTIONS = partial('''\
|
OPTIONS = partial('''\
|
||||||
@ -234,16 +223,10 @@ expression instead.
|
|||||||
default={0}
|
default={0}
|
||||||
Comma separated list of recognized URL prefixes. Defaults to:
|
Comma separated list of recognized URL prefixes. Defaults to:
|
||||||
{0}
|
{0}
|
||||||
|
|
||||||
|
|
||||||
--in-kitty
|
|
||||||
type=bool-set
|
|
||||||
Output the URL instead of opening it. Intended for use from within
|
|
||||||
kitty.
|
|
||||||
'''.format, ','.join(sorted(URL_PREFIXES)))
|
'''.format, ','.join(sorted(URL_PREFIXES)))
|
||||||
|
|
||||||
|
|
||||||
def main(args=sys.argv):
|
def main(args):
|
||||||
msg = 'Highlight URLs inside the specified text'
|
msg = 'Highlight URLs inside the specified text'
|
||||||
try:
|
try:
|
||||||
args, items = parse_args(args[1:], OPTIONS, '[path to file or omit to use stdin]', msg, 'url_hints')
|
args, items = parse_args(args[1:], OPTIONS, '[path to file or omit to use stdin]', msg, 'url_hints')
|
||||||
@ -251,9 +234,9 @@ def main(args=sys.argv):
|
|||||||
print(e.args[0], file=sys.stderr)
|
print(e.args[0], file=sys.stderr)
|
||||||
input(_('Press Enter to quit'))
|
input(_('Press Enter to quit'))
|
||||||
return 1
|
return 1
|
||||||
try:
|
return run(args, (items or [None])[0])
|
||||||
run(args, (items or [None])[0])
|
|
||||||
except Exception:
|
|
||||||
import traceback
|
def handle_result(args, data, target_window_id, boss):
|
||||||
traceback.print_exc()
|
program = data['program']
|
||||||
input(_('Press Enter to quit'))
|
boss.open_url(data['url'], None if program == 'default' else program)
|
||||||
|
|||||||
@ -15,7 +15,7 @@ from .cli import create_opts, parse_args
|
|||||||
from .config import (
|
from .config import (
|
||||||
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
|
MINIMUM_FONT_SIZE, initial_window_size, prepare_config_file_for_editing
|
||||||
)
|
)
|
||||||
from .constants import appname, editor, set_boss
|
from .constants import appname, editor, set_boss, config_dir
|
||||||
from .fast_data_types import (
|
from .fast_data_types import (
|
||||||
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
|
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
|
||||||
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
|
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
|
||||||
@ -471,14 +471,7 @@ class Boss:
|
|||||||
self.new_os_window(*cmd)
|
self.new_os_window(*cmd)
|
||||||
|
|
||||||
def input_unicode_character(self):
|
def input_unicode_character(self):
|
||||||
w = self.active_window
|
self.run_kitten('none', 'unicode_input')
|
||||||
tab = self.active_tab
|
|
||||||
if w is not None and tab is not None and w.overlay_for is None:
|
|
||||||
overlay_window = tab.new_special_window(
|
|
||||||
SpecialWindow(
|
|
||||||
['kitty', '+runpy', 'from kittens.unicode_input.main import main; main()'],
|
|
||||||
overlay_for=w.id))
|
|
||||||
overlay_window.action_on_close = partial(self.send_unicode_character, w.id)
|
|
||||||
|
|
||||||
def get_output(self, source_window, num_lines=1):
|
def get_output(self, source_window, num_lines=1):
|
||||||
output = ''
|
output = ''
|
||||||
@ -489,19 +482,6 @@ class Boss:
|
|||||||
output += str(s.linebuf.line(i))
|
output += str(s.linebuf.line(i))
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def send_unicode_character(self, target_window_id, source_window):
|
|
||||||
w = self.window_id_map.get(target_window_id)
|
|
||||||
if w is not None:
|
|
||||||
output = self.get_output(source_window)
|
|
||||||
if output.startswith('OK: '):
|
|
||||||
try:
|
|
||||||
text = chr(int(output.partition(' ')[2], 16))
|
|
||||||
except Exception:
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
else:
|
|
||||||
w.paste(text)
|
|
||||||
|
|
||||||
def set_tab_title(self):
|
def set_tab_title(self):
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
@ -524,34 +504,35 @@ class Boss:
|
|||||||
tab.set_title(title)
|
tab.set_title(title)
|
||||||
break
|
break
|
||||||
|
|
||||||
def run_simple_kitten(self, type_of_input, kitten, *args):
|
def run_kitten(self, type_of_input, kitten, *args):
|
||||||
import shlex
|
import shlex
|
||||||
w = self.active_window
|
w = self.active_window
|
||||||
tab = self.active_tab
|
tab = self.active_tab
|
||||||
if w is not None and tab is not None and w.overlay_for is None:
|
if w is not None and tab is not None and w.overlay_for is None:
|
||||||
cmdline = args[0] if args else ''
|
cmdline = args[0] if args else ''
|
||||||
args = shlex.split(cmdline) if cmdline else []
|
args = shlex.split(cmdline) if cmdline else []
|
||||||
if kitten == 'url_hints':
|
orig_args = args[:]
|
||||||
args[0:0] = ['--in-kitty', '--program', self.opts.open_url_with]
|
args[0:0] = [config_dir, kitten]
|
||||||
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
|
if type_of_input in ('text', 'history', 'ansi', 'ansi-history'):
|
||||||
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
|
data = w.as_text(as_ansi='ansi' in type_of_input, add_history='history' in type_of_input).encode('utf-8')
|
||||||
elif type_of_input == 'none':
|
elif type_of_input == 'none':
|
||||||
data = None
|
data = None
|
||||||
else:
|
else:
|
||||||
raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
|
raise ValueError('Unknown type_of_input: {}'.format(type_of_input))
|
||||||
|
from kittens.runner import create_kitten_handler
|
||||||
|
end_kitten = create_kitten_handler(kitten, orig_args)
|
||||||
overlay_window = tab.new_special_window(
|
overlay_window = tab.new_special_window(
|
||||||
SpecialWindow(
|
SpecialWindow(
|
||||||
['kitty', '+runpy', 'from kittens.{}.main import main; main()'.format(kitten)] + args,
|
['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
|
||||||
stdin=data,
|
stdin=data,
|
||||||
overlay_for=w.id))
|
overlay_for=w.id))
|
||||||
if kitten == 'url_hints':
|
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
|
||||||
overlay_window.action_on_close = self.open_hinted_url
|
|
||||||
|
|
||||||
def open_hinted_url(self, source_window):
|
def on_kitten_finish(self, target_window_id, end_kitten, source_window):
|
||||||
output = self.get_output(source_window, num_lines=None)
|
output = self.get_output(source_window, num_lines=None)
|
||||||
if output.startswith('OK: '):
|
if output.startswith('OK: '):
|
||||||
cmd = json.loads(output.partition(' ')[2].strip())
|
data = json.loads(output.partition(' ')[2].strip())
|
||||||
open_url(cmd['url'], cmd['program'])
|
end_kitten(data, target_window_id, self)
|
||||||
|
|
||||||
def kitty_shell(self, window_type):
|
def kitty_shell(self, window_type):
|
||||||
cmd = ['kitty', '@']
|
cmd = ['kitty', '@']
|
||||||
@ -580,12 +561,12 @@ class Boss:
|
|||||||
old_focus.focus_changed(False)
|
old_focus.focus_changed(False)
|
||||||
tab.active_window.focus_changed(True)
|
tab.active_window.focus_changed(True)
|
||||||
|
|
||||||
def open_url(self, url):
|
def open_url(self, url, program=None):
|
||||||
if url:
|
if url:
|
||||||
open_url(url, self.opts.open_url_with)
|
open_url(url, program or self.opts.open_url_with)
|
||||||
|
|
||||||
def open_url_lines(self, lines):
|
def open_url_lines(self, lines, program=None):
|
||||||
self.open_url(''.join(lines))
|
self.open_url(''.join(lines), program)
|
||||||
|
|
||||||
def destroy(self):
|
def destroy(self):
|
||||||
self.shutting_down = True
|
self.shutting_down = True
|
||||||
|
|||||||
@ -108,7 +108,9 @@ def parse_key_action(action):
|
|||||||
args = tuple(map(parse_key_action, filter(None, parts)))
|
args = tuple(map(parse_key_action, filter(None, parts)))
|
||||||
elif func == 'send_text':
|
elif func == 'send_text':
|
||||||
args = rest.split(' ', 1)
|
args = rest.split(' ', 1)
|
||||||
elif func == 'run_simple_kitten':
|
elif func in ('run_kitten', 'run_simple_kitten'):
|
||||||
|
if func == 'run_simple_kitten':
|
||||||
|
func = 'run_kitten'
|
||||||
args = rest.split(' ', 2)
|
args = rest.split(' ', 2)
|
||||||
elif func == 'goto_tab':
|
elif func == 'goto_tab':
|
||||||
args = (max(0, int(rest)), )
|
args = (max(0, int(rest)), )
|
||||||
@ -389,7 +391,7 @@ def parse_defaults(lines, check_keys=False):
|
|||||||
|
|
||||||
Options, defaults = init_config(default_config_path, parse_defaults)
|
Options, defaults = init_config(default_config_path, parse_defaults)
|
||||||
actions = frozenset(all_key_actions) | frozenset(
|
actions = frozenset(all_key_actions) | frozenset(
|
||||||
'combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
|
'run_simple_kitten combine send_text goto_tab goto_layout set_font_size new_tab_with_cwd new_window_with_cwd new_os_window_with_cwd'.
|
||||||
split()
|
split()
|
||||||
)
|
)
|
||||||
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
|
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})
|
||||||
|
|||||||
@ -435,8 +435,8 @@ map ctrl+shift+f2 edit_config_file
|
|||||||
# Open a currently visible URL using the keyboard. The program used to open the URL is specified in open_url_with.
|
# Open a currently visible URL using the keyboard. The program used to open the URL is specified in open_url_with.
|
||||||
# You can customize how the URLs are detected and opened by specifying command line options to
|
# You can customize how the URLs are detected and opened by specifying command line options to
|
||||||
# url_hints. For example:
|
# url_hints. For example:
|
||||||
# map ctrl+shift+e run_simple_kitten text url_hints --program firefox --regex "http://[^ ]+"
|
# map ctrl+shift+e run_kitten text url_hints --program firefox --regex "http://[^ ]+"
|
||||||
map ctrl+shift+e run_simple_kitten text url_hints
|
map ctrl+shift+e run_kitten text url_hints
|
||||||
# Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.
|
# Open the kitty shell in a new window/tab/overlay/os_window to control kitty using commands.
|
||||||
map ctrl+shift+escape kitty_shell window
|
map ctrl+shift+escape kitty_shell window
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user