Wezterm: Allow opening the scrollback buffer in an external program

Created on 19 Jun 2020  路  10Comments  路  Source: wez/wezterm

My most used example for this would be to allow opening the scrollback buffer in vim, but there other programs would be situationally useful too

enhancement

Most helpful comment

master now allows this:

local wezterm = require 'wezterm';
local io = require 'io';
local os = require 'os';

wezterm.on("trigger-vim-with-scrollback", function(window, pane)
  -- Retrieve the current viewport's text.
  -- Pass an optional number of lines (eg: 2000) to retrieve
  -- that number of lines starting from the bottom of the viewport
  local scrollback = pane:get_lines_as_text();

  -- Create a temporary file to pass to vim
  local name = os.tmpname();
  local f = io.open(name, "w+");
  f:write(scrollback);
  f:flush();
  f:close();

  -- Open a new window running vim and tell it to open the file
  window:perform_action(wezterm.action{SpawnCommandInNewWindow={
    args={"vim", name}}
  }, pane)

  -- wait "enough" time for vim to read the file before we remove it.
  -- The window creation and process spawn are asynchronous
  -- wrt. running this script and are not awaitable, so we just pick
  -- a number.
  wezterm.sleep_ms(1000);
  os.remove(name);
end)

return {
  keys = {
    {key="E", mods="CTRL",
      action=wezterm.action{EmitEvent="trigger-vim-with-scrollback"}},
  }
}

All 10 comments

I think there are a couple of paths that could be taken to achieve this:

  1. Build on the APIs from https://github.com/wez/wezterm/issues/225 so that you can define an action that will capture the scrollback into a file and spawn a program in a new window. There are some details to work out around the lifetime and cleanup of that file, as well as the lifetime of the callback itself (there are some threading/blocking concerns).

  2. Expand the mux protocol and add a wezterm cli get-scrollback --tabid X command that will return the scrollback for the specified tab (you can use wezterm cli list today to enumerate windows/tabs in the mux). In this strategy you'd probably still want to use the foundation from https://github.com/wez/wezterm/issues/225 so that you can obtain the tabid and then pass that to the command that you want to spawn. The advantage here is that wezterm doesn't have as much complexity around managing the lifetime of the file holding the scrollback; it forces it all into the called program and that runs as any other spawn program.

  1. seems like it would be a little easier for the enduser. As to life-time and cleanup.... I kinda feel like wezterm can just punt on that? As if the scrollback buffer is dumped out into /tmp, the clean will happen next boot (which seems fine to me). I have no idea about mac or windows - but I guess they have some equivalent mechanism?

  2. Is there any contract on the order of the list in wezterm cli list? As in, if the order was in the order of most recently used (which I think the mux server could know...) then that would make it easier for the enduser?

Ultimately, what I would want if just a keybinding to dump the current wezterm tab/window out on a keybinding. So whatever wezterm could do to make that more convenient would seem nice. And (without having put too much thought into it...) it seems like wezterm should be able to know what the current active window. Maybe something like wezterm cli get-scrollback --tabid current?

Having said all this I don't want to come off too presumptuous. I鈥檓 pretty sure I can make either 1 or 2 work and were it me, all things being equal, I guess I'd go with the simplest solution codewise.

master now allows this:

local wezterm = require 'wezterm';
local io = require 'io';
local os = require 'os';

wezterm.on("trigger-vim-with-scrollback", function(window, pane)
  -- Retrieve the current viewport's text.
  -- Pass an optional number of lines (eg: 2000) to retrieve
  -- that number of lines starting from the bottom of the viewport
  local scrollback = pane:get_lines_as_text();

  -- Create a temporary file to pass to vim
  local name = os.tmpname();
  local f = io.open(name, "w+");
  f:write(scrollback);
  f:flush();
  f:close();

  -- Open a new window running vim and tell it to open the file
  window:perform_action(wezterm.action{SpawnCommandInNewWindow={
    args={"vim", name}}
  }, pane)

  -- wait "enough" time for vim to read the file before we remove it.
  -- The window creation and process spawn are asynchronous
  -- wrt. running this script and are not awaitable, so we just pick
  -- a number.
  wezterm.sleep_ms(1000);
  os.remove(name);
end)

return {
  keys = {
    {key="E", mods="CTRL",
      action=wezterm.action{EmitEvent="trigger-vim-with-scrollback"}},
  }
}

There's an example of this in the docs: https://wezfurlong.org/wezterm/config/lua/wezterm/on.html#custom-events

Right, just took this for a spin and it looks primo. Thank you Wez!

So one nit with this btw. The following config renders the "parent" wezterm unresponsive

local dump_scrollback_to_file = function(window, pane)
    -- Retrieve the current viewport's text.
    -- Pass an optional number of lines (eg: 2000) to retrieve
    -- that number of lines starting from the bottom of the viewport
    local scrollback = pane:get_lines_as_text();

    -- Create a temporary file to pass to vim
    local name = os.tmpname();
    local f = io.open(name, "w+");
    f:write(scrollback);
    f:flush();
    f:close();
    return name
end

wezterm.on("trigger-skim-with-scrollback", function(window, pane)
  local filename = dump_scrollback_to_file(window, pane)
  -- Open a new window running fzf to fuzzy search scrollback
  window:perform_action(wezterm.action{SpawnCommandInNewWindow={
    args={"fish", "-c", "fzf < " .. filename}}
  }, pane)
end)

What happens is the new window launches just fine and fzf launches there too just fine, but once I'm done with fzf (either by selecting something or by CTRL-C-ing it) the parent window becomes unresponsive.

The point of what ^ is trying to do slightly overlaps with scrollback search, but it's a bit different (filtering vs searching)

@sanga hmm, I tried your config and it seems ok to me locally on a linux system. I suspect that something else is contributing to this. Things to try:

  • Does opening a new window and closing it trigger the same behavior? (ctrl-shift-n should spawn a new window in the default key bindings).
  • Please start wezterm using RUST_LOG=trace wezterm 2>/tmp/doh.txt, reproduce the issue and attach that file here
  • When things are unresponsive, is wezterm using a lot of cpu or is it idle?

a few random comments:

  • it seems wayland-related. I haven't been able to trigger it yet with enable_wayland = false
  • I haven't either been able to trigger it by just spawning a new window via ctrl-shift-n
  • 1 point of note is that the original window doesn't repaint itself actually i.e. I use a tiling WM and when the other wezterm win closes, the first one should repaint to fill in the newly empty space. It doesn't so whatever fails, fails before that repaint happens.
  • the last few hundred lines of logs are here:
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('\u{e0b0}')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Reset))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=30 y=86 cell=Cell { text: "\u{e0b0}", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(237), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Reset
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Foreground(PaletteIndex(15))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Foreground(PaletteIndex(15))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Background(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Background(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print(' ')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('S')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('I')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('G')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('I')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('N')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('T')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print(' ')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Background(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=31 y=86 cell=Cell { text: " ", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=32 y=86 cell=Cell { text: "S", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=33 y=86 cell=Cell { text: "I", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=34 y=86 cell=Cell { text: "G", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=35 y=86 cell=Cell { text: "I", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=36 y=86 cell=Cell { text: "N", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=37 y=86 cell=Cell { text: "T", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=38 y=86 cell=Cell { text: " ", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Background(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Foreground(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Foreground(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('\u{e0b0}')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Reset))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=39 y=86 cell=Cell { text: "\u{e0b0}", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(161), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Reset
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Foreground(PaletteIndex(15))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Foreground(PaletteIndex(15))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Background(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Background(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print(' ')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('$')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print(' ')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Reset))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=40 y=86 cell=Cell { text: " ", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=41 y=86 cell=Cell { text: "$", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=42 y=86 cell=Cell { text: " ", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Reset
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Foreground(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Foreground(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('\u{e0b0}')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Reset))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=43 y=86 cell=Cell { text: "\u{e0b0}", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(161), background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Reset
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print(' ')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Control(CarriageReturn)
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=44 y=86 cell=Cell { text: " ", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Control(LineFeed)
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Foreground(PaletteIndex(15))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Foreground(PaletteIndex(15))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Background(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Background(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('$')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Reset))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=0 y=87 cell=Cell { text: "$", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(15), background: PaletteIndex(161), hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Reset
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Foreground(PaletteIndex(161))))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Foreground(PaletteIndex(161))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('\u{e0b0}')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Sgr(Reset))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=1 y=87 cell=Cell { text: "\u{e0b0}", attrs: CellAttributes { attributes: 0, foreground: PaletteIndex(161), background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > Reset
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print(' ')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Edit(EraseInDisplay(EraseToEndOfDisplay)))
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=2 y=87 cell=Cell { text: " ", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Cursor(Right(53)))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('1')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('.')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('2')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('7')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('9')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Print('s')
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Control(CarriageReturn)
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=56 y=87 cell=Cell { text: "1", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=57 y=87 cell=Cell { text: ".", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=58 y=87 cell=Cell { text: "2", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=59 y=87 cell=Cell { text: "7", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=60 y=87 cell=Cell { text: "9", attrs: CellAttributes { attributes: 0, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z TRACE wezterm_term::terminalstate         > print x=61 y=87 cell=Cell { text: "s", attrs: CellAttributes { attributes: 1024, foreground: Default, background: Default, hyperlink: None, image: None } }
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Cursor(Right(56)))
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform Control(CarriageReturn)
 2020-10-11T10:55:31.244Z DEBUG wezterm_term::terminalstate         > perform CSI(Cursor(Right(3)))
 2020-10-11T10:55:31.276Z DEBUG wezterm::font::ftwrap               > set_char_size 10 dpi=138
 2020-10-11T10:55:31.279Z DEBUG wezterm::gui::termwindow            > paint_tab_opengl elapsed=5.446169ms
 2020-10-11T10:55:31.280Z DEBUG wezterm::gui::termwindow            > paint_tab_opengl elapsed=1.075017ms
 2020-10-11T10:55:35.466Z DEBUG wezterm::gui::termwindow            > paint_tab_opengl elapsed=3.870082ms
 2020-10-11T10:55:35.797Z ERROR mux                                 > read_pty EOF: pane_id 1
 2020-10-11T10:55:35.806Z DEBUG wezterm::gui::termwindow            > paint_tab_opengl elapsed=12.992921ms
 2020-10-11T10:55:35.807Z DEBUG mux                                 > removing pane 1
 2020-10-11T10:55:35.807Z DEBUG mux                                 > killing pane 1
 2020-10-11T10:55:35.807Z DEBUG mux::localpane                      > killing process in pane 1
 2020-10-11T10:55:35.807Z ERROR mux::localpane                      > Pane id 1 is_dead
 2020-10-11T10:55:35.808Z DEBUG mux                                 > prune_dead_windows: window is now empty
 2020-10-11T10:55:35.808Z ERROR mux                                 > tab 1 is dead
 2020-10-11T10:55:35.808Z DEBUG mux                                 > remove_tab_internal tab 1
 2020-10-11T10:55:35.808Z DEBUG mux                                 > remove_window_internal 1
 2020-10-11T10:55:35.808Z DEBUG mux                                 > removing pane 1
 2020-10-11T10:55:35.817Z TRACE wezterm::gui::termwindow            > Setting focus to false
 2020-10-11T10:55:35.817Z TRACE wezterm::gui::termwindow            > Setting focus to true
 2020-10-11T10:55:35.817Z TRACE wezterm::gui::termwindow            > Setting focus to false
 2020-10-11T10:55:35.817Z TRACE wezterm::gui::termwindow            > Setting focus to true
 2020-10-11T10:55:35.817Z TRACE wezterm::gui::termwindow            > resize event, current cells: RowsAndCols { rows: 88, cols: 62 }, new dims: Dimensions { pixel_width: 1127, pixel_height: 2135, dpi: 96 }
 2020-10-11T10:55:35.817Z DEBUG wezterm::gui::renderstate           > compute_vertices 93x88 1127x2135 padding=0 0
 2020-10-11T10:55:35.827Z DEBUG wezterm_term::screen                > resize screen to 93x88
 2020-10-11T10:55:35.828Z DEBUG wezterm_term::screen                > resize screen to 93x88
 2020-10-11T10:55:35.835Z DEBUG wezterm::gui::termwindow            > paint_tab_opengl elapsed=6.779261ms

Hopefully, that is of use to you. That full file is 192mb. I don't particularly like posting logs to the internet from my work machine without vetting them first and 192 meg is a bit much to vet...

I think this does sound like a wayland related issue.
Using weston under X11 I could produce a broken keyboard modifier state with CTRL+SHIFT+N creating a new window. That would leave CTRL+SHIFT active in the original window and lead to surprising and broken behavior. dedfc4513b60fa1cd2cf04da94f81b992b304b83 addresses that, but in my exposure to wayland, there's a lot of variance depending on the compositor implementation so I'm not 100% sure if that is what you encountered, especially as you mentioned some resize interactions!

Please try a build of dedfc4513b60fa1cd2cf04da94f81b992b304b83 or later (that rev should show up in the "nightly" downloads in an hour or two).

I'd be interested to know:

  • Which compositor you are using (sway?) so that I can see if I can reproduce the issue
  • can you interact with the original window while the fzf window is still open? I wonder if we can nail down when it breaks

I don't think there is especially interesting wayland related trace logging present in the code today, so that big log file probably isn't useful right now.

Just checked it out now. It looks fixed to me now - thanks! For the record, you guessed correctly, I'm running Sway here.

Was this page helpful?
0 / 5 - 0 ratings