diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-15 15:48:38 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-07-16 22:47:10 +0300 |
| commit | 8dcb75a5c08b4dd1f225ea531a27824a9f41ae4a (patch) | |
| tree | 48b4b74e857c93ffc03ff1b790b2f9e76e0113ac /src/server | |
| parent | f43a6d4a5591a0286e01de737be7b3f42416898b (diff) | |
refactor: extract active tab surface rendering
Diffstat (limited to 'src/server')
| -rw-r--r-- | src/server/headless.rs | 64 | ||||
| -rw-r--r-- | src/server/headless/pane_graphics.rs | 1 | ||||
| -rw-r--r-- | src/server/render_stream.rs | 84 |
3 files changed, 59 insertions, 90 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs index 9c21aa1b..c5e8ca62 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -3384,6 +3384,7 @@ impl HeadlessServer { && crate::kitty_graphics::has_visible_pane_graphics( &self.app.state, &self.app.terminal_runtimes, + self.app.state.view.tab_surface(), *cell_size, ) { @@ -3670,6 +3671,7 @@ impl HeadlessServer { .extend(crate::kitty_graphics::encode_local_pane_graphics( &self.app.state, &self.app.terminal_runtimes, + self.app.state.view.tab_surface(), cell_size, &mut next_graphics_cache, )); @@ -7145,16 +7147,28 @@ next_tab = "" ); } - #[test] - fn render_and_stream_uses_each_client_terminal_size() { + #[tokio::test] + async fn render_and_stream_uses_each_client_terminal_size() { let mut server = test_headless_server(); - server.app.state.workspaces = vec![crate::workspace::Workspace::test_new("test")]; + let mut workspace = crate::workspace::Workspace::test_new("test"); + let active_pane = workspace.tabs[0].root_pane; + let background_tab = workspace.test_add_tab(Some("background")); + let background_pane = workspace.tabs[background_tab].root_pane; + workspace.tabs[0].runtimes.insert( + active_pane, + crate::terminal::TerminalRuntime::test_with_screen_bytes(80, 24, b"active"), + ); + workspace.tabs[background_tab].runtimes.insert( + background_pane, + crate::terminal::TerminalRuntime::test_with_screen_bytes(80, 24, b"background"), + ); + server.app.state.workspaces = vec![workspace]; server.app.state.active = Some(0); server.app.state.selected = 0; server.app.state.mode = crate::app::Mode::Terminal; let (desktop_tx, _desktop_control_rx, desktop_rx) = test_client_writer(); - let (phone_tx, _phone_control_rx, phone_rx) = test_client_writer(); + let (mobile_tx, _mobile_control_rx, mobile_rx) = test_client_writer(); server.clients.insert( 1, @@ -7171,13 +7185,13 @@ next_tab = "" server.clients.insert( 2, ClientConnection::new( - (80, 24), + (44, 20), crate::kitty_graphics::HostCellSize::default(), crate::terminal_theme::TerminalTheme::default(), None, 2, RenderEncoding::SemanticFrame, - Some(phone_tx), + Some(mobile_tx), ), ); server.foreground_client_id = Some(1); @@ -7187,10 +7201,44 @@ next_tab = "" server.render_and_stream(); let desktop_frame = read_server_frame(desktop_rx.recv().expect("desktop frame")); - let phone_frame = read_server_frame(phone_rx.recv().expect("phone frame")); + let mobile_frame = read_server_frame(mobile_rx.recv().expect("mobile frame")); assert_eq!((desktop_frame.width, desktop_frame.height), (120, 40)); - assert_eq!((phone_frame.width, phone_frame.height), (80, 24)); + assert_eq!((mobile_frame.width, mobile_frame.height), (44, 20)); + let mobile_text = frame_text(&mobile_frame); + let mut mobile_rows = mobile_text.lines(); + let mobile_header = mobile_rows.by_ref().take(2).collect::<String>(); + let mobile_surface = mobile_rows.collect::<String>(); + assert!(mobile_header.contains("test"), "header: {mobile_header:?}"); + assert!( + mobile_surface.contains("active"), + "surface: {mobile_surface:?}" + ); + assert!(!mobile_surface.contains("background")); + + let foreground_terminal_area = Rect::new(26, 1, 94, 39); + let expected_pane_size = ( + foreground_terminal_area.height, + foreground_terminal_area.width.saturating_sub(1), + ); + assert_eq!( + server.app.state.view.layout, + crate::app::state::ViewLayout::Desktop + ); + assert_eq!(server.app.state.view.mobile_header_rect, Rect::default()); + assert_eq!( + server.app.state.view.terminal_area, + foreground_terminal_area + ); + assert_eq!( + server.app.state.workspaces[0].tabs[0].runtimes[&active_pane].current_size(), + expected_pane_size + ); + assert_eq!( + server.app.state.workspaces[0].tabs[background_tab].runtimes[&background_pane] + .current_size(), + expected_pane_size + ); } #[tokio::test] diff --git a/src/server/headless/pane_graphics.rs b/src/server/headless/pane_graphics.rs index 18174f45..ed28eab0 100644 --- a/src/server/headless/pane_graphics.rs +++ b/src/server/headless/pane_graphics.rs @@ -121,6 +121,7 @@ impl HeadlessServer { frame_pane_graphics_for_client(crate::kitty_graphics::encode_local_pane_graphics( &self.app.state, &self.app.terminal_runtimes, + self.app.state.view.tab_surface(), cell_size, &mut next_graphics_cache, )); diff --git a/src/server/render_stream.rs b/src/server/render_stream.rs index facebd2c..8858bb6b 100644 --- a/src/server/render_stream.rs +++ b/src/server/render_stream.rs @@ -384,94 +384,14 @@ pub(crate) fn visible_hyperlinks( app_state: &AppState, terminal_runtimes: &TerminalRuntimeRegistry, ) -> Vec<((u16, u16), String, String)> { - let Some(ws_idx) = app_state.active else { - return Vec::new(); - }; - if app_state.workspaces.get(ws_idx).is_none() { - return Vec::new(); - } - - let mut links = Vec::new(); - for info in &app_state.view.pane_infos { - if let Some(runtime) = - app_state.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id) - { - links.extend(runtime.visible_hyperlinks(info.inner_rect)); - } - } - links + crate::ui::tab_surface_hyperlinks(app_state, terminal_runtimes, app_state.view.tab_surface()) } pub(crate) fn focused_terminal_cursor( app_state: &AppState, terminal_runtimes: &TerminalRuntimeRegistry, ) -> Option<CursorState> { - if app_state.mode != Mode::Terminal { - return None; - } - - let ws_idx = app_state.active?; - let info = app_state - .view - .pane_infos - .iter() - .find(|info| info.is_focused)?; - if !app_state.pane_exposes_host_cursor(ws_idx, info.id) { - return None; - } - let rt = app_state.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id)?; - if rt.synchronized_output_active() { - return None; - } - let scrolled_back = crate::ui::pane_is_scrolled_back(rt); - // Determine whether the IME-anchor reveal applies to this focused pane. - // The master switch must be on, and either no agent filter is configured - // (apply to any pane) or the focused pane's detected agent matches the - // allow-list. A configured list with no valid entries reveals nothing. - let reveal = app_state.reveal_hidden_cursor_for_cjk_ime - && (!app_state.cjk_ime_agent_filter_configured || { - let detected = app_state - .workspaces - .get(ws_idx) - .and_then(|ws| ws.terminal_id(info.id)) - .and_then(|tid| app_state.terminals.get(tid)) - .and_then(|t| t.detected_agent); - detected.is_some_and(|agent| app_state.cjk_ime_agents.contains(&agent)) - }); - - if let Some(cursor) = rt.cursor_state(info.inner_rect, true) { - // When the reveal applies, expose the cursor anchor regardless of the - // pane's `?25l` request so macOS IMEs keep tracking the candidate - // window when TUIs paint their own cursor. Scrollback suppression - // still applies. - let visible = if reveal { - !scrolled_back - } else { - cursor.visible && !scrolled_back - }; - Some(CursorState { - x: cursor.x, - y: cursor.y, - visible, - shape: if reveal && visible { - app_state.cjk_ime_cursor_shape - } else { - cursor.shape - }, - }) - } else if reveal && !scrolled_back { - // cursor_state() returned None — the viewport has no cursor position - // (can happen with complex TUIs). Fall back to the pane's top-left so - // the outer terminal still exposes a cursor anchor for IME tracking. - Some(CursorState { - x: info.inner_rect.x, - y: info.inner_rect.y, - visible: true, - shape: app_state.cjk_ime_cursor_shape, - }) - } else { - None - } + crate::ui::tab_surface_cursor(app_state, terminal_runtimes, app_state.view.tab_surface()) } fn focused_terminal_owns_host_cursor( |
