From 400ab584ac88aca3a515fa32bedd0b88291ea10d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Mon, 11 Nov 2019 21:26:42 +0530 Subject: [PATCH] hints kitten: Allow completely customizing the matching and actions performed by the kitten using your own script Fixes #2124 --- docs/changelog.rst | 3 +++ docs/kittens/hints.rst | 49 ++++++++++++++++++++++++++++++++++++++++++ kittens/hints/main.py | 38 +++++++++++++++++++++++++++----- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 5d3d3d472..2a9a9f529 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -16,6 +16,9 @@ To update |kitty|, :doc:`follow the instructions `. file with color definitions. See the :doc:`FAQ ` for details (:iss:`2083`) +- hints kitten: Allow completely customizing the matching and actions performed + by the kitten using your own script (:iss:`2124`) + - Wayland: Fix key repeat not being stopped when focus leaves window. This is expected behavior on Wayland, apparently (:iss:`2014`) diff --git a/docs/kittens/hints.rst b/docs/kittens/hints.rst index f68304d8b..9aaec0ba3 100644 --- a/docs/kittens/hints.rst +++ b/docs/kittens/hints.rst @@ -23,6 +23,55 @@ options and modes of operation, see below. You can use these options to create mappings in :file:`kitty.conf` to select various different text snippets. See :sc:`insert_selected_path` for examples. +Completely customizing the matching and actions of the kitten +--------------------------------------------------------------- + +The hints kitten supports writing simple python scripts that can be used to +completely customize how it finds matches and what happens when a match is +selected. This allows the hints kitten to provide the user interface, while +you can provide the logic for finding matches and performing actions on them. +This is best illustrated with an example. Create the file +:file:`custom-hints.py` in the kitty config directory with the following +contents: + +.. code-block:: python + + import re + + def mark(text, args, Mark, *a): + # This function is responsible for finding all + # matching text. + # We mark all individual word for potential selection + for idx, m in enumerate(re.finditer(r'\w+', text)): + start, end = m.span() + mark_text = text[start:end].replace('\n', '').replace('\0', '') + # The empty dictionary below will be available as groupdicts + # in handle_result() and can contain arbitrary data. + yield Mark(idx, start, end, mark_text, {}) + + + def handle_result(args, data, target_window_id, boss): + # This function is responsible for performing some + # action on the selected text. + # matches is a list of the selected entries and groupdicts contains + # the arbitrary data associated with each entry in mark() above + matches, groupdicts = [], [] + for m, g in zip(data['match'], data['groupdicts']): + if m: + matches.append(m), groupdicts.append(g) + for word, data in zip(matches, groupdicts): + # Lookup the word in a dictionary, the open_url function + # will open the provided url in the system browser + boss.open_url(f'https://www.google.com/search?q=define:{word}') + +Nor run kitty with:: + + kitty -o 'map f1 kitten hints --customize-processing custom-hints.py' + +and when you press the :kbd:`F1` key you will be able to select a word to +look it up in the Google dictionary. + + Command Line Interface ------------------------- diff --git a/kittens/hints/main.py b/kittens/hints/main.py index b1d57fb11..c24cf2843 100644 --- a/kittens/hints/main.py +++ b/kittens/hints/main.py @@ -262,9 +262,11 @@ def run_loop(args, text, all_marks, index_map): handler = Hints(text, all_marks, index_map, args) loop.loop(handler) if handler.chosen and loop.return_code == 0: - return {'match': handler.text_matches, 'programs': args.program, - 'multiple_joiner': args.multiple_joiner, - 'type': args.type, 'groupdicts': handler.groupdicts} + return { + 'match': handler.text_matches, 'programs': args.program, + 'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing, + 'type': args.type, 'groupdicts': handler.groupdicts + } raise SystemExit(loop.return_code) @@ -321,11 +323,25 @@ def parse_input(text): return convert_text(text, cols) +def load_custom_processor(customize_processing): + from kitty.constants import config_dir + custom_path = os.path.join(config_dir, customize_processing) + import runpy + return runpy.run_path(custom_path, run_name='__main__') + + def run(args, text): try: - pattern, post_processors = functions_for(args) text = parse_input(text) - all_marks = tuple(mark(pattern, post_processors, text, args)) + pattern, post_processors = functions_for(args) + if args.customize_processing: + m = load_custom_processor(args.customize_processing) + if 'mark' in m: + all_marks = tuple(m['mark'](text, args, Mark)) + else: + all_marks = tuple(mark(pattern, post_processors, text, args)) + else: + all_marks = tuple(mark(pattern, post_processors, text, args)) if not all_marks: input(_('No {} found, press Enter to quit.').format( 'URLs' if args.type == 'url' else 'matches' @@ -431,6 +447,13 @@ unless you specify the hints offset as zero the first match will be highlighted the second character you specify. +--customize-processing +Name of a python file in the kitty config directory which will be imported to provide +custom implementations for pattern finding and performing actions +on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html +for details. + + '''.format(','.join(sorted(URL_PREFIXES))).format help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.' usage = '' @@ -464,6 +487,11 @@ def main(args): def handle_result(args, data, target_window_id, boss): + if data['customize_processing']: + m = load_custom_processor(data['customize_processing']) + if 'handle_result' in m: + return m['handle_result'](args, data, target_window_id, boss) + programs = data['programs'] or ('default',) matches, groupdicts = [], [] for m, g in zip(data['match'], data['groupdicts']):