From 5e05460816bbcea61a111bfa57be9907116e8ffc Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Tue, 7 Apr 2026 02:48:13 +0300 Subject: docs: clarify herdr pane split and wait output usage --- src/api/mod.rs | 13 +++++++++++-- src/api/schema.rs | 1 + src/app/mod.rs | 1 + src/cli.rs | 9 +++++---- src/ghostty/mod.rs | 29 +++++++++++++++++++++++++++-- src/pane.rs | 42 ++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 87 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/api/mod.rs b/src/api/mod.rs index 1a713323..27317403 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -248,6 +248,15 @@ fn handle_request(request: Request, api_tx: &ApiRequestSender) -> String { } } +fn output_match_read_source( + source: &crate::api::schema::ReadSource, +) -> crate::api::schema::ReadSource { + match source { + crate::api::schema::ReadSource::Recent => crate::api::schema::ReadSource::RecentUnwrapped, + other => other.clone(), + } +} + fn wait_for_output( request_id: String, params: crate::api::schema::PaneWaitForOutputParams, @@ -279,7 +288,7 @@ fn wait_for_output( id: format!("{request_id}:read"), method: Method::PaneRead(crate::api::schema::PaneReadParams { pane_id: params.pane_id.clone(), - source: params.source.clone(), + source: output_match_read_source(¶ms.source), lines: params.lines, strip_ansi: params.strip_ansi, }), @@ -587,7 +596,7 @@ impl ActiveOutputMatchedSubscription { let read = pane_read( format!("{}:read", self.request_prefix), &self.pane_id, - self.source.clone(), + output_match_read_source(&self.source), self.lines, self.strip_ansi, api_tx, diff --git a/src/api/schema.rs b/src/api/schema.rs index 5a27b01b..e266f0ff 100644 --- a/src/api/schema.rs +++ b/src/api/schema.rs @@ -197,6 +197,7 @@ pub struct PaneReleaseAgentParams { pub enum ReadSource { Visible, Recent, + RecentUnwrapped, } #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] diff --git a/src/app/mod.rs b/src/app/mod.rs index 9020658a..2c5d3855 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1566,6 +1566,7 @@ impl App { let text = match params.source { ReadSource::Visible => pane.visible_text(), ReadSource::Recent => pane.recent_text(requested_lines), + ReadSource::RecentUnwrapped => pane.recent_unwrapped_text(requested_lines), }; SuccessResponse { id: request.id, diff --git a/src/cli.rs b/src/cli.rs index 0516b3ce..cc364051 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -429,7 +429,7 @@ fn pane_get(args: &[String]) -> std::io::Result { fn pane_read(args: &[String]) -> std::io::Result { let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr pane read [--source visible|recent] [--lines N]"); + eprintln!("usage: herdr pane read [--source visible|recent|recent-unwrapped] [--lines N]"); return Ok(2); }; @@ -763,7 +763,7 @@ fn integration_uninstall(args: &[String]) -> std::io::Result { fn wait_output(args: &[String]) -> std::io::Result { let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr wait output --match [--source visible|recent] [--lines N] [--timeout MS] [--regex]"); + eprintln!("usage: herdr wait output --match [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--regex]"); return Ok(2); }; @@ -1016,6 +1016,7 @@ fn parse_read_source(value: &str) -> std::io::Result { match value { "visible" => Ok(ReadSource::Visible), "recent" => Ok(ReadSource::Recent), + "recent-unwrapped" | "recent_unwrapped" => Ok(ReadSource::RecentUnwrapped), _ => Err(std::io::Error::other(format!( "invalid read source: {value}" ))), @@ -1070,7 +1071,7 @@ fn print_pane_help() { eprintln!("herdr pane commands:"); eprintln!(" herdr pane list [--workspace ]"); eprintln!(" herdr pane get "); - eprintln!(" herdr pane read [--source visible|recent] [--lines N]"); + eprintln!(" herdr pane read [--source visible|recent|recent-unwrapped] [--lines N]"); eprintln!(" herdr pane split --direction right|down [--cwd PATH] [--no-focus]"); eprintln!(" herdr pane close "); eprintln!(" herdr pane send-text "); @@ -1080,7 +1081,7 @@ fn print_pane_help() { fn print_wait_help() { eprintln!("herdr wait commands:"); - eprintln!(" herdr wait output --match [--source visible|recent] [--lines N] [--timeout MS] [--regex]"); + eprintln!(" herdr wait output --match [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--regex]"); eprintln!( " herdr wait agent-state --state [--timeout MS]" ); diff --git a/src/ghostty/mod.rs b/src/ghostty/mod.rs index 297b0e6b..55bafebc 100644 --- a/src/ghostty/mod.rs +++ b/src/ghostty/mod.rs @@ -461,11 +461,27 @@ impl Terminal { end: (u16, u32), rectangle: bool, ) -> Result { - let selection = GhosttyTerminalSelection { + self.read_text_selection(GhosttyTerminalSelection { start: ghostty_viewport_point(start.0, start.1), end: ghostty_viewport_point(end.0, end.1), rectangle, - }; + }) + } + + pub fn read_text_screen( + &self, + start: (u16, u32), + end: (u16, u32), + rectangle: bool, + ) -> Result { + self.read_text_selection(GhosttyTerminalSelection { + start: ghostty_screen_point(start.0, start.1), + end: ghostty_screen_point(end.0, end.1), + rectangle, + }) + } + + fn read_text_selection(&self, selection: GhosttyTerminalSelection) -> Result { let mut out_ptr = ptr::null_mut(); let mut out_len = 0usize; unsafe { @@ -570,6 +586,15 @@ fn ghostty_viewport_point(x: u16, y: u32) -> ffi::GhosttyPoint { } } +fn ghostty_screen_point(x: u16, y: u32) -> ffi::GhosttyPoint { + ffi::GhosttyPoint { + tag: ffi::GhosttyPointTag_GHOSTTY_POINT_TAG_SCREEN, + value: ffi::GhosttyPointValue { + coordinate: ffi::GhosttyPointCoordinate { x, y }, + }, + } +} + pub struct RenderState { raw: ffi::GhosttyRenderState_ptr, } diff --git a/src/pane.rs b/src/pane.rs index e2afccdc..dcddabf4 100644 --- a/src/pane.rs +++ b/src/pane.rs @@ -345,6 +345,10 @@ impl PaneTerminal { self.ghostty.recent_text(lines) } + fn recent_unwrapped_text(&self, lines: usize) -> String { + self.ghostty.recent_unwrapped_text(lines) + } + fn extract_selection(&self, selection: &crate::selection::Selection) -> Option { self.ghostty.extract_selection(selection) } @@ -648,6 +652,14 @@ impl GhosttyPaneTerminal { .unwrap_or_default() } + fn recent_unwrapped_text(&self, lines: usize) -> String { + self.core + .lock() + .ok() + .and_then(|core| ghostty_recent_text_unwrapped(&core, lines).ok()) + .unwrap_or_default() + } + fn extract_selection(&self, selection: &crate::selection::Selection) -> Option { self.core .lock() @@ -1055,6 +1067,21 @@ fn ghostty_recent_text( Ok(recent_text_from_rows(&rows, lines)) } +fn ghostty_recent_text_unwrapped( + core: &GhosttyPaneCore, + lines: usize, +) -> Result { + let total_rows = core.terminal.total_rows()?; + let cols = core.terminal.cols()?; + if total_rows == 0 || cols == 0 { + return Ok(String::new()); + } + let start = total_rows.saturating_sub(lines) as u32; + let end = (total_rows.saturating_sub(1)) as u32; + core.terminal + .read_text_screen((0, start), (cols.saturating_sub(1), end), false) +} + fn ghostty_extract_selection( core: &mut GhosttyPaneCore, selection: &crate::selection::Selection, @@ -1685,6 +1712,10 @@ impl PaneRuntime { self.terminal.recent_text(lines) } + pub fn recent_unwrapped_text(&self, lines: usize) -> String { + self.terminal.recent_unwrapped_text(lines) + } + pub fn extract_selection(&self, selection: &crate::selection::Selection) -> Option { self.terminal.extract_selection(selection) } @@ -1963,6 +1994,17 @@ mod tests { assert!(pane.visible_text().contains("000000")); } + #[test] + fn recent_unwrapped_text_ignores_soft_wraps() { + let (tx, _rx) = mpsc::channel(4); + let mut terminal = crate::ghostty::Terminal::new(5, 3, 100).unwrap(); + terminal.write(b"ABCDEFGHIJ"); + let pane = GhosttyPaneTerminal::new(terminal, tx).unwrap(); + + assert_eq!(pane.recent_text(3), "ABCDE\nFGHIJ\n"); + assert_eq!(pane.recent_unwrapped_text(3), "ABCDEFGHIJ"); + } + #[test] fn synchronized_output_suppresses_intermediate_render_requests_until_batch_ends() { let (tx, _rx) = mpsc::channel(4); -- cgit v1.2.3