diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 47c2d61c6..d33954091 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -35,3 +35,7 @@ version 0.4.0 [future] - Allow combining multiple independent actions into a single shortcut - Add a new shortcut to pass the current selection to an external program + +- Allow creating shortcuts to open new windows running arbitrary commands. You + can also pass the current selection to the command as an arguments and the + contents of the screen + scrollback buffer as stdin to the command. diff --git a/kitty/boss.py b/kitty/boss.py index 390b57c23..6a82b8310 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -293,8 +293,45 @@ class Boss: def previous_tab(self): self.tab_manager.next_tab(-1) - def new_tab(self): - self.tab_manager.new_tab() + def args_to_special_window(self, args): + args = list(args) + stdin = None + w = self.active_window + + def data_for_at(arg): + if arg == '@selection': + return w.text_for_selection() + if arg == '@ansi': + return w.buffer_as_ansi() + elif arg == '@text': + return w.buffer_as_text() + + if args[0].startswith('@'): + stdin = data_for_at(args[0]).encode('utf-8') + del args[0] + + cmd = [] + for arg in args: + if arg == '@selection': + arg = data_for_at(arg) + if not arg: + continue + cmd.append(arg) + return SpecialWindow(cmd, stdin) + + def new_tab(self, *args): + special_window = None + if args: + special_window = self.args_to_special_window(args) + self.tab_manager.new_tab(special_window=special_window) + + def new_window(self, *args): + tab = self.active_tab + if tab is not None: + if args: + tab.new_special_window(self.args_to_special_window(args)) + else: + tab.new_window() def move_tab_forward(self): self.tab_manager.move_tab(1) diff --git a/kitty/config.py b/kitty/config.py index 040a657bb..97d76c018 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -94,7 +94,7 @@ def parse_shortcut(sc): KeyAction = namedtuple('KeyAction', 'func args') -shlex_actions = {'pass_selection_to_program'} +shlex_actions = {'pass_selection_to_program', 'new_window', 'new_tab'} def parse_key_action(action): diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 0581de3e0..4e0bcb83a 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -236,7 +236,7 @@ map ctrl+shift+end scroll_end map ctrl+shift+h show_scrollback # Window management -map ctrl+shift+enter new_window +map ctrl+shift+enter new_window map ctrl+shift+w close_window map ctrl+shift+] next_window map ctrl+shift+[ previous_window @@ -253,6 +253,15 @@ map ctrl+shift+7 seventh_window map ctrl+shift+8 eighth_window map ctrl+shift+9 ninth_window map ctrl+shift+0 tenth_window +# You can also open a new window running an arbitrary program, for example: +# map ctrl+shift+y new_window mutt +# You can also pass the current selection to the new program by using the @selection placeholder +# map ctrl+shift+y new_window less @selection +# Finally, you can even send the contents of the current screen + history buffer as stdin using +# the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes) +# For example, the following command opens the scrollback buffer in less in a new window. +# map ctrl+shift+y new_window @ansi less +G -R + # Tab management map ctrl+shift+right next_tab @@ -262,6 +271,9 @@ map ctrl+shift+q close_tab map ctrl+shift+l next_layout map ctrl+shift+. move_tab_forward map ctrl+shift+, move_tab_backward +# Just as with new_window above, you can also pass the name of arbitrary +# commands to run when using new_tab. + # Miscellaneous map ctrl+shift+equal increase_font_size diff --git a/kitty/window.py b/kitty/window.py index 094249ec4..625eceda5 100644 --- a/kitty/window.py +++ b/kitty/window.py @@ -234,14 +234,19 @@ class Window: remove_vao(self.gvao_id) self.vao_id = self.gvao_id = None - # actions {{{ - - def show_scrollback(self): + def buffer_as_ansi(self): data = [] self.screen.historybuf.as_ansi(data.append) self.screen.linebuf.as_ansi(data.append) - data = ''.join(data).encode('utf-8') - get_boss().display_scrollback(data) + return ''.join(data) + + def buffer_as_text(self): + return str(self.screen.historybuf) + '\n' + str(self.screen.linebuf) + + # actions {{{ + + def show_scrollback(self): + get_boss().display_scrollback(self.buffer_as_ansi().encode('utf-8')) def paste(self, text): if text and not self.destroyed: