aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/api/mod.rs13
-rw-r--r--src/api/schema.rs1
-rw-r--r--src/app/mod.rs1
-rw-r--r--src/cli.rs9
-rw-r--r--src/ghostty/mod.rs29
-rw-r--r--src/pane.rs42
6 files changed, 87 insertions, 8 deletions
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(&params.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<i32> {
fn pane_read(args: &[String]) -> std::io::Result<i32> {
let Some(raw_pane_id) = args.first() else {
- eprintln!("usage: herdr pane read <pane_id> [--source visible|recent] [--lines N]");
+ eprintln!("usage: herdr pane read <pane_id> [--source visible|recent|recent-unwrapped] [--lines N]");
return Ok(2);
};
@@ -763,7 +763,7 @@ fn integration_uninstall(args: &[String]) -> std::io::Result<i32> {
fn wait_output(args: &[String]) -> std::io::Result<i32> {
let Some(raw_pane_id) = args.first() else {
- eprintln!("usage: herdr wait output <pane_id> --match <text> [--source visible|recent] [--lines N] [--timeout MS] [--regex]");
+ eprintln!("usage: herdr wait output <pane_id> --match <text> [--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<ReadSource> {
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 <workspace_id>]");
eprintln!(" herdr pane get <pane_id>");
- eprintln!(" herdr pane read <pane_id> [--source visible|recent] [--lines N]");
+ eprintln!(" herdr pane read <pane_id> [--source visible|recent|recent-unwrapped] [--lines N]");
eprintln!(" herdr pane split <pane_id> --direction right|down [--cwd PATH] [--no-focus]");
eprintln!(" herdr pane close <pane_id>");
eprintln!(" herdr pane send-text <pane_id> <text>");
@@ -1080,7 +1081,7 @@ fn print_pane_help() {
fn print_wait_help() {
eprintln!("herdr wait commands:");
- eprintln!(" herdr wait output <pane_id> --match <text> [--source visible|recent] [--lines N] [--timeout MS] [--regex]");
+ eprintln!(" herdr wait output <pane_id> --match <text> [--source visible|recent|recent-unwrapped] [--lines N] [--timeout MS] [--regex]");
eprintln!(
" herdr wait agent-state <pane_id> --state <idle|working|blocked|unknown> [--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<String, Error> {
- 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<String, Error> {
+ 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<String, Error> {
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<String> {
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<String> {
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<String, crate::ghostty::Error> {
+ 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<String> {
self.terminal.extract_selection(selection)
}
@@ -1964,6 +1995,17 @@ mod tests {
}
#[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);
let terminal = crate::ghostty::Terminal::new(80, 24, 0).unwrap();