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:
parent
9e3a5ba8fa
commit
1609fd386b
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -5726,6 +5726,7 @@ dependencies = [
|
||||
"tempfile",
|
||||
"termios 0.3.3",
|
||||
"termwiz",
|
||||
"termwiz-funcs",
|
||||
"textwrap 0.16.0",
|
||||
"umask",
|
||||
"url",
|
||||
|
@ -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,
|
||||
|
@ -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
27
docs/cli/cli/get-text.md
Normal 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.
|
@ -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)?)
|
||||
}
|
||||
|
@ -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!(
|
||||
|
@ -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)?)
|
||||
}
|
||||
|
@ -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)))
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user