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:
Kovid Goyal 2018-04-11 13:03:40 +05:30
parent 5755ba72b1
commit 2cf8c6aea7
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 98 additions and 76 deletions

59
kittens/runner.py Normal file
View 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...')

View File

@ -5,7 +5,6 @@
import os
import string
import subprocess
import sys
from functools import lru_cache
from gettext import gettext as _
@ -454,26 +453,24 @@ class UnicodeInput(Handler):
self.refresh()
def run_loop(args):
def main(args):
loop = Loop()
with cached_values_for('unicode-input') as cached_values:
handler = UnicodeInput(cached_values)
loop.loop(handler)
if handler.current_char and loop.return_code == 0:
print('OK:', hex(ord(handler.current_char))[2:])
try:
handler.recent.remove(ord(handler.current_char))
except Exception:
pass
recent = [ord(handler.current_char)] + handler.recent
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):
try:
raise SystemExit(run_loop(args))
except Exception:
import traceback
traceback.print_exc()
input(_('Press Enter to quit.'))
def handle_result(args, current_char, target_window_id, boss):
w = boss.window_id_map.get(target_window_id)
if w is not None:
w.paste(current_char)

View File

@ -4,7 +4,6 @@
import re
import string
import subprocess
import sys
from collections import namedtuple
from functools import lru_cache, partial
@ -12,7 +11,6 @@ from gettext import gettext as _
from kitty.cli import parse_args
from kitty.key_encoding import ESCAPE, backspace_key, enter_key
from kitty.utils import command_for_open
from ..tui.handler import Handler
from ..tui.loop import Loop
@ -178,16 +176,7 @@ def run_loop(args, lines, index_map):
handler = URLHints(lines, index_map)
loop.loop(handler)
if handler.chosen and loop.return_code == 0:
if args.in_kitty:
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
return {'url': handler.chosen, 'program': args.program}
raise SystemExit(loop.return_code)
@ -215,7 +204,7 @@ def run(args, source_file=None):
input(_('No URLs found, press Enter to abort.'))
return
run_loop(args, lines, index_map)
return run_loop(args, lines, index_map)
OPTIONS = partial('''\
@ -234,16 +223,10 @@ expression instead.
default={0}
Comma separated list of recognized URL prefixes. Defaults to:
{0}
--in-kitty
type=bool-set
Output the URL instead of opening it. Intended for use from within
kitty.
'''.format, ','.join(sorted(URL_PREFIXES)))
def main(args=sys.argv):
def main(args):
msg = 'Highlight URLs inside the specified text'
try:
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)
input(_('Press Enter to quit'))
return 1
try:
run(args, (items or [None])[0])
except Exception:
import traceback
traceback.print_exc()
input(_('Press Enter to quit'))
return run(args, (items or [None])[0])
def handle_result(args, data, target_window_id, boss):
program = data['program']
boss.open_url(data['url'], None if program == 'default' else program)

View File

@ -15,7 +15,7 @@ from .cli import create_opts, parse_args
from .config import (
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 (
ChildMonitor, create_os_window, current_os_window, destroy_global_data,
destroy_sprite_map, get_clipboard_string, glfw_post_empty_event,
@ -471,14 +471,7 @@ class Boss:
self.new_os_window(*cmd)
def input_unicode_character(self):
w = self.active_window
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)
self.run_kitten('none', 'unicode_input')
def get_output(self, source_window, num_lines=1):
output = ''
@ -489,19 +482,6 @@ class Boss:
output += str(s.linebuf.line(i))
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):
w = self.active_window
tab = self.active_tab
@ -524,34 +504,35 @@ class Boss:
tab.set_title(title)
break
def run_simple_kitten(self, type_of_input, kitten, *args):
def run_kitten(self, type_of_input, kitten, *args):
import shlex
w = self.active_window
tab = self.active_tab
if w is not None and tab is not None and w.overlay_for is None:
cmdline = args[0] if args else ''
args = shlex.split(cmdline) if cmdline else []
if kitten == 'url_hints':
args[0:0] = ['--in-kitty', '--program', self.opts.open_url_with]
orig_args = args[:]
args[0:0] = [config_dir, kitten]
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')
elif type_of_input == 'none':
data = None
else:
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(
SpecialWindow(
['kitty', '+runpy', 'from kittens.{}.main import main; main()'.format(kitten)] + args,
['kitty', '+runpy', 'from kittens.runner import main; main()'] + args,
stdin=data,
overlay_for=w.id))
if kitten == 'url_hints':
overlay_window.action_on_close = self.open_hinted_url
overlay_window.action_on_close = partial(self.on_kitten_finish, w.id, end_kitten)
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)
if output.startswith('OK: '):
cmd = json.loads(output.partition(' ')[2].strip())
open_url(cmd['url'], cmd['program'])
data = json.loads(output.partition(' ')[2].strip())
end_kitten(data, target_window_id, self)
def kitty_shell(self, window_type):
cmd = ['kitty', '@']
@ -580,12 +561,12 @@ class Boss:
old_focus.focus_changed(False)
tab.active_window.focus_changed(True)
def open_url(self, url):
def open_url(self, url, program=None):
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):
self.open_url(''.join(lines))
def open_url_lines(self, lines, program=None):
self.open_url(''.join(lines), program)
def destroy(self):
self.shutting_down = True

View File

@ -108,7 +108,9 @@ def parse_key_action(action):
args = tuple(map(parse_key_action, filter(None, parts)))
elif func == 'send_text':
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)
elif func == 'goto_tab':
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)
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()
)
no_op_actions = frozenset({'noop', 'no-op', 'no_op'})

View File

@ -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.
# You can customize how the URLs are detected and opened by specifying command line options to
# url_hints. For example:
# map ctrl+shift+e run_simple_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 --program firefox --regex "http://[^ ]+"
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.
map ctrl+shift+escape kitty_shell window