1
1
mirror of https://github.com/wez/wezterm.git synced 2024-09-11 22:37:11 +03:00

add wezterm cli get-text command

closes: https://github.com/wez/wezterm/pull/2729
This commit is contained in:
Wez Furlong 2023-02-05 09:05:48 -07:00
parent 9e3a5ba8fa
commit 1609fd386b
No known key found for this signature in database
GPG Key ID: 7A7F66A31EC9B387
10 changed files with 208 additions and 42 deletions

1
Cargo.lock generated
View File

@ -5726,6 +5726,7 @@ dependencies = [
"tempfile",
"termios 0.3.3",
"termwiz",
"termwiz-funcs",
"textwrap 0.16.0",
"umask",
"url",

View File

@ -417,7 +417,7 @@ macro_rules! pdu {
/// The overall version of the codec.
/// This must be bumped when backwards incompatible changes
/// are made to the types and protocol.
pub const CODEC_VERSION: usize = 31;
pub const CODEC_VERSION: usize = 32;
// Defines the Pdu enum.
// Each struct has an explicit identifying number.
@ -466,6 +466,8 @@ pdu! {
MovePaneToNewTab: 48,
MovePaneToNewTabResponse: 49,
ActivatePaneDirection: 50,
GetPaneRenderableDimensions: 51,
GetPaneRenderableDimensionsResponse: 52,
}
impl Pdu {
@ -780,6 +782,18 @@ pub struct GetPaneRenderChanges {
pub pane_id: PaneId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetPaneRenderableDimensions {
pub pane_id: PaneId,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct GetPaneRenderableDimensionsResponse {
pub pane_id: PaneId,
pub cursor_position: StableCursorPosition,
pub dimensions: RenderableDimensions,
}
#[derive(Deserialize, Serialize, PartialEq, Debug)]
pub struct LivenessResponse {
pub pane_id: PaneId,

View File

@ -62,6 +62,7 @@ As features stabilize some brief notes about them will accumulate here.
* [wezterm.config_builder()](config/lua/wezterm/config_builder.md)
* [gui-attached](config/lua/gui-events/gui-attached.md) event provides some
more flexibility at startup.
* [wezterm cli get-text](cli/cli/get-text.md) command for capturing the content of a pane.
#### Fixed
* X11: hanging or killing the IME could hang wezterm

27
docs/cli/cli/get-text.md Normal file
View File

@ -0,0 +1,27 @@
# `wezterm cli get-text`
*Since: nightly builds only*
*Run `wezterm cli get-text --help` to see more help*
Retrieves the textual content of a pane and output it to stdout.
For example:
```
$ wezterm cli get-text > /tmp/myscreen.txt
```
will capture the main (non-scrollback) portion of the current pane to `/tmp/myscreen.txt`.
By default, just the raw text is captured without any color or styling escape sequences.
You may pass `--escapes` to include those:
```
$ wezterm cli get-text --escapes > /tmp/myscreen-with-colors.txt
```
The default capture region is the main terminal screen, not including the scrollback.
You may use the `--start-line` and `--end-line` parameters to set the range.
Both of these accept integer values, where `0` refers to the top of the non-scrollback
screen area, and negative numbers index backwards into the scrollback.

View File

@ -9,6 +9,7 @@ use termwiz::color::{AnsiColor, ColorAttribute, ColorSpec, SrgbaTuple};
use termwiz::input::Modifiers;
use termwiz::render::terminfo::TerminfoRenderer;
use termwiz::surface::change::Change;
use termwiz::surface::Line;
use wezterm_dynamic::{FromDynamic, ToDynamic};
pub fn register(lua: &Lua) -> anyhow::Result<()> {
@ -262,3 +263,40 @@ lazy_static::lazy_static! {
pub fn new_wezterm_terminfo_renderer() -> TerminfoRenderer {
TerminfoRenderer::new(CAPS.clone())
}
pub fn lines_to_escapes(lines: Vec<Line>) -> anyhow::Result<String> {
let mut changes = vec![];
let mut attr = CellAttributes::blank();
for line in lines {
changes.append(&mut line.changes(&attr));
changes.push(Change::Text("\r\n".to_string()));
if let Some(a) = line.visible_cells().last().map(|cell| cell.attrs().clone()) {
attr = a;
}
}
changes.push(Change::AllAttributes(CellAttributes::blank()));
let mut renderer = new_wezterm_terminfo_renderer();
struct Target {
target: Vec<u8>,
}
impl std::io::Write for Target {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
std::io::Write::write(&mut self.target, buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl termwiz::render::RenderTty for Target {
fn get_size_in_cells(&mut self) -> termwiz::Result<(usize, usize)> {
Ok((80, 24))
}
}
let mut target = Target { target: vec![] };
renderer.render_to(&changes, &mut target)?;
Ok(String::from_utf8(target.target)?)
}

View File

@ -1204,6 +1204,11 @@ impl Client {
LivenessResponse
);
rpc!(get_lines, GetLines, GetLinesResponse);
rpc!(
get_dimensions,
GetPaneRenderableDimensions,
GetPaneRenderableDimensionsResponse
);
rpc!(get_codec_version, GetCodecVersion, GetCodecVersionResponse);
rpc!(get_tls_creds, GetTlsCreds = (), GetTlsCredsResponse);
rpc!(

View File

@ -9,9 +9,7 @@ use mux::pane::PaneId;
use mux::window::WindowId as MuxWindowId;
use mux::Mux;
use mux_lua::MuxPane;
use termwiz::cell::CellAttributes;
use termwiz::surface::{Change, Line};
use termwiz_funcs::new_wezterm_terminfo_renderer;
use termwiz_funcs::lines_to_escapes;
use wezterm_dynamic::{FromDynamic, ToDynamic};
use wezterm_toast_notification::ToastNotification;
use window::{Connection, ConnectionOps, DeadKeyStatus, WindowOps, WindowState};
@ -312,40 +310,3 @@ impl UserData for GuiWin {
);
}
}
fn lines_to_escapes(lines: Vec<Line>) -> anyhow::Result<String> {
let mut changes = vec![];
let mut attr = CellAttributes::blank();
for line in lines {
changes.append(&mut line.changes(&attr));
changes.push(Change::Text("\r\n".to_string()));
if let Some(a) = line.visible_cells().last().map(|cell| cell.attrs().clone()) {
attr = a;
}
}
changes.push(Change::AllAttributes(CellAttributes::blank()));
let mut renderer = new_wezterm_terminfo_renderer();
struct Target {
target: Vec<u8>,
}
impl std::io::Write for Target {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
std::io::Write::write(&mut self.target, buf)
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl termwiz::render::RenderTty for Target {
fn get_size_in_cells(&mut self) -> termwiz::Result<(usize, usize)> {
Ok((80, 24))
}
}
let mut target = Target { target: vec![] };
renderer.render_to(&changes, &mut target)?;
Ok(String::from_utf8(target.target)?)
}

View File

@ -582,6 +582,30 @@ impl SessionHandler {
.detach();
}
Pdu::GetPaneRenderableDimensions(GetPaneRenderableDimensions { pane_id }) => {
spawn_into_main_thread(async move {
catch(
move || {
let mux = Mux::get();
let pane = mux
.get_pane(pane_id)
.ok_or_else(|| anyhow!("no such pane {}", pane_id))?;
let cursor_position = pane.get_cursor_position();
let dimensions = pane.get_dimensions();
Ok(Pdu::GetPaneRenderableDimensionsResponse(
GetPaneRenderableDimensionsResponse {
pane_id,
cursor_position,
dimensions,
},
))
},
send_response,
)
})
.detach();
}
Pdu::GetPaneRenderChanges(GetPaneRenderChanges { pane_id, .. }) => {
let sender = self.to_write_tx.clone();
let per_pane = self.per_pane(pane_id);
@ -724,6 +748,7 @@ impl SessionHandler {
| Pdu::PaneRemoved { .. }
| Pdu::GetImageCellResponse { .. }
| Pdu::MovePaneToNewTabResponse { .. }
| Pdu::GetPaneRenderableDimensionsResponse { .. }
| Pdu::ErrorResponse { .. } => {
send_response(Err(anyhow!("expected a request, got {:?}", decoded.pdu)))
}

View File

@ -32,6 +32,7 @@ smol = "1.2"
tabout = { path = "../tabout" }
tempfile = "3.3"
termwiz = { path = "../termwiz" }
termwiz-funcs = { path = "../lua-api-crates/termwiz-funcs" }
textwrap = "0.16"
umask = { path = "../umask" }
url = "2"

View File

@ -16,10 +16,11 @@ use std::ffi::OsString;
use std::io::{Read, Write};
use std::sync::Arc;
use tabout::{tabulate_output, Alignment, Column};
use termwiz_funcs::lines_to_escapes;
use umask::UmaskSaver;
use wezterm_client::client::{unix_connect_with_retry, Client};
use wezterm_gui_subcommands::*;
use wezterm_term::TerminalSize;
use wezterm_term::{ScrollbackOrVisibleRowIndex, StableRowIndex, TerminalSize};
mod asciicast;
@ -369,6 +370,37 @@ Outputs the pane-id for the newly created pane on success"
text: Option<String>,
},
/// Retrieves the textual content of a pane and output it to stdout
#[command(name = "get-text", rename_all = "kebab")]
GetText {
/// Specify the target pane.
/// The default is to use the current pane based on the
/// environment variable WEZTERM_PANE.
#[arg(long)]
pane_id: Option<PaneId>,
/// The starting line number.
/// 0 is the first line of terminal screen.
/// Negative numbers proceed backwards into the scrollback.
/// The default value is unspecified is 0, the first line of
/// the terminal screen.
#[arg(long)]
start_line: Option<ScrollbackOrVisibleRowIndex>,
/// The ending line number.
/// 0 is the first line of terminal screen.
/// Negative numbers proceed backwards into the scrollback.
/// The default value if unspecified is the bottom of the
/// the terminal screen.
#[arg(long)]
end_line: Option<ScrollbackOrVisibleRowIndex>,
/// Include escape sequences that color and style the text.
/// If omitted, unattributed text will be returned.
#[arg(long)]
escapes: bool,
},
/// Activate an adjacent pane in the specified direction.
#[command(name = "activate-pane-direction", rename_all = "kebab")]
ActivatePaneDirection {
@ -1081,6 +1113,67 @@ async fn run_cli_async(config: config::ConfigHandle, cli: CliCommand) -> anyhow:
.await?;
}
}
CliSubCommand::GetText {
pane_id,
start_line,
end_line,
escapes,
} => {
let pane_id = resolve_pane_id(&client, pane_id).await?;
let info = client
.get_dimensions(codec::GetPaneRenderableDimensions { pane_id })
.await?;
let start_line = match start_line {
None => info.dimensions.physical_top,
Some(n) if n >= 0 => info.dimensions.physical_top + n as StableRowIndex,
Some(n) => {
let line = info.dimensions.physical_top as isize + n as isize;
if line < info.dimensions.scrollback_top as isize {
info.dimensions.scrollback_top
} else {
line as StableRowIndex
}
}
};
let end_line = match end_line {
None => {
info.dimensions.physical_top + info.dimensions.viewport_rows as StableRowIndex
}
Some(n) if n >= 0 => info.dimensions.physical_top + n as StableRowIndex,
Some(n) => {
let line = info.dimensions.physical_top as isize + n as isize;
if line < info.dimensions.scrollback_top as isize {
info.dimensions.scrollback_top
} else {
line as StableRowIndex
}
}
};
let lines = client
.get_lines(codec::GetLines {
pane_id: pane_id.into(),
lines: vec![start_line..end_line + 1],
})
.await?;
let lines = lines
.lines
.extract_data()
.0
.into_iter()
.map(|(_idx, line)| line)
.collect();
if escapes {
println!("{}", lines_to_escapes(lines)?);
} else {
lines.iter().for_each(|line| println!("{}", line.as_str()));
}
}
CliSubCommand::SpawnCommand {
cwd,
prog,