hints kitten: Allow pressing :sc:goto_file_line to quickly open the selected file at the selected line in vim

Fixes #2268
This commit is contained in:
Kovid Goyal 2020-01-10 10:01:03 +05:30
parent 6a8b7bf92f
commit 9bc1b5a2d9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 94 additions and 7 deletions

View File

@ -7,6 +7,9 @@ To update |kitty|, :doc:`follow the instructions <binary>`.
0.15.2 [future]
--------------------
- hints kitten: Allow pressing :sc:`goto_file_line` to quickly open
the selected file at the selected line in vim (:iss:`2268`)
- Allow choosing OpenType features for individual fonts via the
:opt:`font_features` option.

View File

@ -18,6 +18,11 @@ select anything that looks like a path or filename and then insert it into the
terminal, very useful for picking files from the output of a ``git`` or ``ls`` command and
adding them to the command line for the next command.
You can also press :sc:`goto_file_line` to select anything that looks
like a path or filename followed by a colon and a line number and open
the file in vim at the specified line number. The patterns and editor
to be used can be modified using options passed to the kitten.
The hints kitten is very powerful to see more detailed help on its various
options and modes of operation, see below. You can use these options to
create mappings in :file:`kitty.conf` to select various different text

View File

@ -21,6 +21,7 @@ from ..tui.operations import faint, styled
URL_PREFIXES = 'http https file ftp'.split()
DEFAULT_HINT_ALPHABET = string.digits + string.ascii_lowercase
DEFAULT_REGEX = r'(?m)^\s*(.+)\s*$'
screen_size = screen_size_function()
@ -265,7 +266,7 @@ def run_loop(args, text, all_marks, index_map, extra_cli_args=()):
return {
'match': handler.text_matches, 'programs': args.program,
'multiple_joiner': args.multiple_joiner, 'customize_processing': args.customize_processing,
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args
'type': args.type, 'groupdicts': handler.groupdicts, 'extra_cli_args': extra_cli_args, 'linenum_action': args.linenum_action
}
raise SystemExit(loop.return_code)
@ -323,11 +324,20 @@ def parse_input(text):
return convert_text(text, cols)
def linenum_marks(text, args, Mark, extra_cli_args, *a):
regex = args.regex
if regex == DEFAULT_REGEX:
regex = r'(?P<path>(?:\S*/\S+)|(?:\S+[.][a-zA-Z0-9]{2,7})):(?P<line>\d+)'
yield from mark(regex, [brackets, quotes], text, args)
def load_custom_processor(customize_processing):
if customize_processing.startswith('::import::'):
import importlib
m = importlib.import_module(customize_processing[len('::import::'):])
return {k: getattr(m, k) for k in dir(m)}
if customize_processing == '::linenum::':
return {'mark': linenum_marks, 'handle_result': linenum_handle_result}
from kitty.constants import config_dir
customize_processing = os.path.expandvars(os.path.expanduser(customize_processing))
if os.path.isabs(customize_processing):
@ -342,6 +352,8 @@ def run(args, text, extra_cli_args=()):
try:
text = parse_input(text)
pattern, post_processors = functions_for(args)
if args.type == 'linenum':
args.customize_processing = '::linenum::'
if args.customize_processing:
m = load_custom_processor(args.customize_processing)
if 'mark' in m:
@ -386,12 +398,15 @@ multiple times to run multiple programs.
--type
default=url
choices=url,regex,path,line,hash,word
The type of text to search for.
choices=url,regex,path,line,hash,word,linenum
The type of text to search for. A value of :code:`linenum` looks for error messages
using the pattern specified with :option:`--regex`, which must have the named groups,
path and line. If not specified, will look for :code:`path:line`.
The :option:`--linenum-action` option controls what to do with the selected error message.
--regex
default=(?m)^\s*(.+)\s*$
default={default_regex}
The regular expression to use when :option:`kitty +kitten hints --type`=regex.
The regular expression is in python syntax. If you specify a numbered group in
the regular expression only the group will be matched. This allow you to match
@ -403,8 +418,21 @@ the program will be passed arguments corresponding to each named group of
the form key=value.
--linenum-action
default=self
type=choice
choices=self,window,tab,os_window,background
The action to perform on the matched errors. The actual action is whatever
arguments are provided to the kitten, for example:
:code:`kitty + kitten hints --type=linenum vim +{line} {path}`
will open the matched path at the matched line number in vim. This option
controls where the action is executed: :code:`self` means the current window,
:code:`window` a new kitty window, :code:`tab` a new tab, :code:`os_window`
a new OS window and :code:`background` run in the background.
--url-prefixes
default={0}
default={url_prefixes}
Comma separated list of recognized URL prefixes.
@ -470,7 +498,10 @@ on selected matches. See https://sw.kovidgoyal.net/kitty/kittens/hints.html
for details. You can also specify absolute paths to load the script from elsewhere.
'''.format(','.join(sorted(URL_PREFIXES))).format
'''.format(
default_regex=DEFAULT_REGEX, url_prefixes=','.join(sorted(URL_PREFIXES)),
line='{{line}}', path='{{path}}'
).format
help_text = 'Select text from the screen using the keyboard. Defaults to searching for URLs.'
usage = ''
@ -496,12 +527,39 @@ def main(args):
print(e.args[0], file=sys.stderr)
input(_('Press Enter to quit'))
return
if items and not args.customize_processing:
if items and not (args.customize_processing or args.type == 'linenum'):
print('Extra command line arguments present: {}'.format(' '.join(items)), file=sys.stderr)
input(_('Press Enter to quit'))
return run(args, text, items)
def linenum_handle_result(args, data, target_window_id, boss, extra_cli_args, *a):
for m, g in zip(data['match'], data['groupdicts']):
if m:
path, line = g['path'], g['line']
path = path.split(':')[-1]
line = int(line)
break
else:
return
cmd = [x.format(path=path, line=line) for x in extra_cli_args or ('vim', '+{line}', '{path}')]
w = boss.window_id_map.get(target_window_id)
action = data['linenum_action']
if action == 'self':
if w is not None:
import shlex
w.paste_bytes(shlex.join(cmd) + '\r')
elif action == 'background':
import subprocess
subprocess.Popen(cmd)
else:
getattr(boss, {
'window': 'new_window_with_cwd', 'tab': 'new_tab_with_cwd', 'os_window': 'new_os_window_with_cwd'
}[action])(*cmd)
def handle_result(args, data, target_window_id, boss):
if data['customize_processing']:
m = load_custom_processor(data['customize_processing'])

View File

@ -1274,6 +1274,11 @@ k('insert_selected_hash', 'kitty_mod+p>h', 'kitten hints --type hash --program -
Select something that looks like a hash and insert it into the terminal.
Useful with git, which uses sha1 hashes to identify commits'''))
k('goto_file_line', 'kitty_mod+p>n', 'kitten hints --type linenum', _('Open the selected file at the selected line'), long_text=_('''
Select something that looks like :code:`filename:linenum` and open it in vim at
the specified line number.'''))
# }}}
g('shortcuts.misc') # {{{

View File

@ -2243,12 +2243,21 @@ send_escape_code_to_child(Screen *self, PyObject *args) {
static PyObject*
paste(Screen *self, PyObject *bytes) {
if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_START);
write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
if (self->modes.mBRACKETED_PASTE) write_escape_code_to_child(self, CSI, BRACKETED_PASTE_END);
Py_RETURN_NONE;
}
static PyObject*
paste_bytes(Screen *self, PyObject *bytes) {
if (!PyBytes_Check(bytes)) { PyErr_SetString(PyExc_TypeError, "Must paste() bytes"); return NULL; }
write_to_child(self, PyBytes_AS_STRING(bytes), PyBytes_GET_SIZE(bytes));
Py_RETURN_NONE;
}
WRAP2(cursor_position, 1, 1)
#define COUNT_WRAP(name) WRAP1(name, 1)
@ -2329,6 +2338,7 @@ static PyMethodDef methods[] = {
MND(toggle_alt_screen, METH_NOARGS)
MND(reset_callbacks, METH_NOARGS)
MND(paste, METH_O)
MND(paste_bytes, METH_O)
MND(copy_colors_from, METH_O)
{"select_graphic_rendition", (PyCFunction)_select_graphic_rendition, METH_VARARGS, ""},

View File

@ -535,6 +535,12 @@ class Window:
cmd = [x.replace('INPUT_LINE_NUMBER', str(data['input_line_number'])) for x in self.opts.scrollback_pager]
get_boss().display_scrollback(self, data['text'], cmd)
def paste_bytes(self, text):
# paste raw bytes without any processing
if isinstance(text, str):
text = text.encode('utf-8')
self.screen.paste_bytes(text)
def paste(self, text):
if text and not self.destroyed:
if isinstance(text, str):