aboutsummaryrefslogtreecommitdiff
path: root/src/server/headless.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-07 21:41:13 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-07 21:48:50 +0300
commitb0aa7b2cddc57a55a1c0285a1ea82b59ac9783bd (patch)
tree866e4bd3322e69c4916457cde420c7e4b74792ef /src/server/headless.rs
parentdcfe11a7588fcd42471159453810abc9b7720a57 (diff)
fix: preserve osc8 hyperlinks in client rendering
fixes #73
Diffstat (limited to 'src/server/headless.rs')
-rw-r--r--src/server/headless.rs40
1 files changed, 39 insertions, 1 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs
index 11b9d606..5fc7a544 100644
--- a/src/server/headless.rs
+++ b/src/server/headless.rs
@@ -368,6 +368,27 @@ fn render_virtual(
(buffer, cursor)
}
+fn visible_hyperlinks(app_state: &AppState) -> Vec<((u16, u16), String, String)> {
+ let Some(ws_idx) = app_state.active else {
+ return Vec::new();
+ };
+ let Some(tab) = app_state
+ .workspaces
+ .get(ws_idx)
+ .and_then(crate::workspace::Workspace::active_tab)
+ else {
+ return Vec::new();
+ };
+
+ let mut links = Vec::new();
+ for info in &app_state.view.pane_infos {
+ if let Some(runtime) = tab.runtimes.get(&info.id) {
+ links.extend(runtime.visible_hyperlinks(info.inner_rect));
+ }
+ }
+ links
+}
+
fn focused_terminal_cursor(app_state: &AppState) -> Option<CursorState> {
if app_state.mode != Mode::Terminal {
return None;
@@ -1120,6 +1141,13 @@ impl HeadlessServer {
fn frame_server_message(msg: &ServerMessage) -> Result<Vec<u8>, protocol::FramingError> {
let mut framed = Vec::new();
protocol::write_message(&mut framed, msg)?;
+ let payload_len = framed.len().saturating_sub(4);
+ if payload_len > MAX_FRAME_SIZE {
+ return Err(protocol::FramingError::Oversized {
+ claimed: payload_len,
+ max: MAX_FRAME_SIZE,
+ });
+ }
Ok(framed)
}
@@ -1568,7 +1596,9 @@ impl HeadlessServer {
for (client_id, (cols, rows), is_foreground) in render_targets {
let area = Rect::new(0, 0, cols, rows);
let (buffer, cursor) = render_virtual(&mut self.app.state, area, is_foreground);
- let frame = FrameData::from_ratatui_buffer(&buffer, cursor);
+ let hyperlinks = visible_hyperlinks(&self.app.state);
+ let frame =
+ FrameData::from_ratatui_buffer_with_hyperlinks(&buffer, cursor, &hyperlinks);
let Some(client) = self.clients.get_mut(&client_id) else {
continue;
@@ -1584,6 +1614,14 @@ impl HeadlessServer {
let message = ServerMessage::Frame(frame.clone());
let serialized = match Self::frame_server_message(&message) {
Ok(framed) => framed,
+ Err(protocol::FramingError::Oversized { claimed, max }) => {
+ warn!(
+ client_id,
+ claimed, max, "skipping oversized frame for client"
+ );
+ client.last_frame = Some(frame);
+ continue;
+ }
Err(err) => {
warn!(client_id, err = %err, "failed to serialize frame for client");
broken_clients.push(client_id);