diff --git a/docs/changelog.rst b/docs/changelog.rst index d34232ec2..842c763f2 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -6,13 +6,17 @@ Changelog 0.12.2 [future] ------------------------------ -- Add a new ``last_used_layout`` function that can be mapped to a shortcut to +- A new ``last_used_layout`` function that can be mapped to a shortcut to switch to the previously used window layout (:iss:`870`) -- Add new ``neighboring_window`` and ``move_window`` functions to switch to +- New ``neighboring_window`` and ``move_window`` functions to switch to neighboring windows in the current layout, and move them around, similar to window movement in vim (:iss:`916`) +- A new ``pipe`` function that can be used to pipe the contents of the screen + and scrollback buffer to any desired program running in a new window, tab or + overlay window. (:iss:`933`) + - Add a new :option:`kitty --start-as` command line flag to start kitty fullscreen/maximized/minimized. This replaces the ``--start-in-fullscreen`` flag introduced in the previous release (:iss:`935`) diff --git a/kitty/boss.py b/kitty/boss.py index 81df7dbc6..69bae04d1 100644 --- a/kitty/boss.py +++ b/kitty/boss.py @@ -48,6 +48,23 @@ def listen_on(spec): return s.fileno() +def data_for_at(w, arg): + if arg == '@selection': + return w.text_for_selection() + if arg == '@ansi': + return w.as_text(as_ansi=True, add_history=True) + if arg == '@text': + return w.as_text(add_history=True) + if arg == '@screen': + return w.as_text() + if arg == '@ansi_screen': + return w.as_text(as_ansi=True) + if arg == '@alternate': + return w.as_text(alternate_screen=True) + if arg == '@ansi_alternate': + return w.as_text(as_ansi=True, alternate_screen=True) + + class DumpCommands: # {{{ def __init__(self, args): @@ -774,29 +791,50 @@ def previous_tab(self): if tm is not None: tm.next_tab(-1) + def special_window_for_cmd(self, cmd, window=None, stdin=None, cwd_from=None, as_overlay=False): + w = window or self.active_window + if stdin: + stdin = data_for_at(w, stdin) + if stdin is not None: + stdin = stdin.encode('utf-8') + cmdline = [] + for arg in cmd: + if arg == '@selection': + arg = data_for_at(w, arg) + if not arg: + continue + cmdline.append(arg) + overlay_for = w.id if as_overlay and w.overlay_for is None else None + return SpecialWindow(cmd, stdin, cwd_from=cwd_from, overlay_for=overlay_for) + + def pipe(self, source, dest, exe, *args): + cmd = [exe] + list(args) + window = self.active_window + cwd_from = window.child.pid if window else None + + def create_window(): + return self.special_window_for_cmd( + cmd, stdin=source, as_overlay=dest == 'overlay', cwd_from=cwd_from) + + if dest == 'overlay' or dest == 'window': + tab = self.active_tab + if tab is not None: + return tab.new_special_window(create_window()) + elif dest == 'tab': + tm = self.active_tab_manager + if tm is not None: + tm.new_tab(special_window=create_window(), cwd_from=cwd_from) + else: + import subprocess + subprocess.Popen(cmd) + def args_to_special_window(self, args, cwd_from=None): 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.as_text(as_ansi=True, add_history=True) - if arg == '@text': - return w.as_text(add_history=True) - if arg == '@screen': - return w.as_text() - if arg == '@ansi_screen': - return w.as_text(as_ansi=True) - if arg == '@alternate': - return w.as_text(alternate_screen=True) - if arg == '@ansi_alternate': - return w.as_text(as_ansi=True, alternate_screen=True) - if args[0].startswith('@') and args[0] != '@': - stdin = data_for_at(args[0]) or None + stdin = data_for_at(w, args[0]) or None if stdin is not None: stdin = stdin.encode('utf-8') del args[0] @@ -804,7 +842,7 @@ def data_for_at(arg): cmd = [] for arg in args: if arg == '@selection': - arg = data_for_at(arg) + arg = data_for_at(w, arg) if not arg: continue cmd.append(arg) diff --git a/kitty/config.py b/kitty/config.py index b1a0eb1cd..ff36fc0bc 100644 --- a/kitty/config.py +++ b/kitty/config.py @@ -163,6 +163,16 @@ def move_window(func, rest): return func, [rest] +@func_with_args('pipe') +def pipe(func, rest): + import shlex + rest = shlex.split(rest) + if len(rest) < 3: + log_error('Too few arguments to pipe function') + rest = ['none', 'none', 'true'] + return func, rest + + def parse_key_action(action): parts = action.split(' ', 1) func = parts[0] diff --git a/kitty/config_data.py b/kitty/config_data.py index cdd660134..73376e1b3 100644 --- a/kitty/config_data.py +++ b/kitty/config_data.py @@ -825,13 +825,23 @@ def macos_titlebar_color(x): k('scroll_home', 'kitty_mod+home', 'scroll_home', _('Scroll to top')) k('scroll_end', 'kitty_mod+end', 'scroll_end', _('Scroll to bottom')) k('show_scrollback', 'kitty_mod+h', 'show_scrollback', _('Browse scrollback buffer in less'), long_text=_(''' -You can send the contents of the current screen + history buffer as stdin to an arbitrary program using -the placeholders @text (which is the plain text) and @ansi (which includes text styling escape codes). -For only the current screen, use @screen or @ansi_screen. For the secondary screen, use -@alternate and @ansi_alternate. -For example, the following command opens the scrollback buffer in less in a new window:: - map kitty_mod+y new_window @ansi less +G -R +You can pipe the contents of the current screen + history buffer as +:file:`STDIN` to an arbitrary program using the ``pipe`` function. For example, +the following opens the scrollback buffer in less in an overlay window:: + + map f1 pipe @ansi overlay less +g -R + +Placeholders available are: @text (which is plain text) and @ansi (which +includes text styling escape codes). For only the current screen, use @screen +or @ansi_screen. For the secondary screen, use @alternate and @ansi_alternate. +Note that the secondary screen is not currently displayed. For example if you +run a fullscreen terminal application, the secondary screen will be the screen +you return to when quitting the application. + +To open in a new window or tab use ``window`` or ``tab`` respectively. You can +also use ``none`` in which case the data will be piped into the program without +creating any windows. '''))