diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-05-17 01:41:16 +0300 |
| commit | a36da363f14639085bec91d7a378d6c2eeaa6c58 (patch) | |
| tree | 2c86155b115f8c79db2b8826fd8b2199d153f7bf /src/app/input | |
| parent | e4de58b37f58d59d146158239ed218a8accd5c3c (diff) | |
refactor: extract terminal state and runtime from panes
Diffstat (limited to 'src/app/input')
| -rw-r--r-- | src/app/input/mod.rs | 21 | ||||
| -rw-r--r-- | src/app/input/modal.rs | 29 | ||||
| -rw-r--r-- | src/app/input/mouse.rs | 67 | ||||
| -rw-r--r-- | src/app/input/navigate.rs | 37 | ||||
| -rw-r--r-- | src/app/input/sidebar.rs | 183 | ||||
| -rw-r--r-- | src/app/input/terminal.rs | 58 |
6 files changed, 246 insertions, 149 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 575b373d..fea88b55 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -82,8 +82,8 @@ impl App { if self.state.mode != Mode::Terminal { return; } - if let Some(ws) = self.state.active.and_then(|i| self.state.workspaces.get(i)) { - if let Some(rt) = ws.focused_runtime() { + if let Some(ws_idx) = self.state.active { + if let Some(rt) = self.state.focused_runtime_in_workspace(ws_idx) { let _ = rt.send_paste(text).await; } } @@ -197,11 +197,17 @@ impl AppState { let cwd = self .active .and_then(|i| self.workspaces.get(i)) - .and_then(|ws| ws.focused_runtime()) - .and_then(|rt| rt.cwd()); + .and_then(|ws| { + let tab = ws.active_tab()?; + tab.cwd_for_pane( + tab.layout.focused(), + &self.terminals, + &self.terminal_runtimes, + ) + }); if let Some(ws) = self.active.and_then(|i| self.workspaces.get_mut(i)) { - if let Ok(new_id) = ws.split_focused( + if let Ok(new_pane) = ws.split_focused( direction, new_rows, new_cols, @@ -209,6 +215,9 @@ impl AppState { self.pane_scrollback_limit_bytes, self.host_terminal_theme, ) { + let new_id = new_pane.pane_id; + self.terminals + .insert(new_pane.terminal.id.clone(), new_pane.terminal); ws.layout.focus_pane(new_id); self.mark_session_dirty(); self.mode = Mode::Terminal; @@ -277,6 +286,8 @@ fn numbered_lines_bytes(count: usize) -> Vec<u8> { fn capture_snapshot(state: &AppState) -> crate::persist::SessionSnapshot { crate::persist::capture( &state.workspaces, + &state.terminals, + &state.terminal_runtimes, state.active, state.selected, state.agent_panel_scope, diff --git a/src/app/input/modal.rs b/src/app/input/modal.rs index 367e7e84..6de349cb 100644 --- a/src/app/input/modal.rs +++ b/src/app/input/modal.rs @@ -190,11 +190,14 @@ pub(super) fn open_rename_pane(state: &mut AppState, pane_id: crate::layout::Pan let Some(pane) = ws.pane_state(pane_id) else { return; }; + let terminal = state.terminals.get(&pane.attached_terminal_id); state.creating_new_tab = false; state.requested_new_tab_name = None; state.rename_pane_target = Some(pane_id); - state.name_input = pane.manual_label.clone().unwrap_or_default(); - state.name_input_replace_on_type = pane.manual_label.is_none(); + state.name_input = terminal + .and_then(|t| t.manual_label.clone()) + .unwrap_or_default(); + state.name_input_replace_on_type = terminal.and_then(|t| t.manual_label.as_ref()).is_none(); state.mode = Mode::RenamePane; } @@ -316,10 +319,13 @@ pub(super) fn apply_rename_action(state: &mut AppState, action: ModalAction) { Mode::RenamePane => { if let (Some(ws_idx), Some(pane_id)) = (state.active, state.rename_pane_target) { - if let Some(ws) = state.workspaces.get_mut(ws_idx) { - if let Some(pane) = ws.pane_state_mut(pane_id) { - pane.set_manual_label(new_name); - state.mark_session_dirty(); + if let Some(ws) = state.workspaces.get(ws_idx) { + if let Some(pane) = ws.pane_state(pane_id) { + let terminal_id = pane.attached_terminal_id.clone(); + if let Some(terminal) = state.terminals.get_mut(&terminal_id) { + terminal.set_manual_label(new_name); + state.mark_session_dirty(); + } } } } @@ -468,10 +474,13 @@ pub(super) fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuS } (ContextMenuKind::Pane { pane_id, .. }, Some("Clear pane name")) => { if let Some(ws_idx) = state.active { - if let Some(ws) = state.workspaces.get_mut(ws_idx) { - if let Some(pane) = ws.pane_state_mut(pane_id) { - pane.clear_manual_label(); - state.mark_session_dirty(); + if let Some(ws) = state.workspaces.get(ws_idx) { + if let Some(pane) = ws.pane_state(pane_id) { + let terminal_id = pane.attached_terminal_id.clone(); + if let Some(terminal) = state.terminals.get_mut(&terminal_id) { + terminal.clear_manual_label(); + state.mark_session_dirty(); + } } } } diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs index a7a2159a..10ef9c18 100644 --- a/src/app/input/mouse.rs +++ b/src/app/input/mouse.rs @@ -712,7 +712,8 @@ impl AppState { .active .and_then(|ws_idx| self.workspaces.get(ws_idx)) .and_then(|ws| ws.pane_state(info.id)) - .and_then(|pane| pane.manual_label.as_ref()) + .and_then(|pane| self.terminals.get(&pane.attached_terminal_id)) + .and_then(|terminal| terminal.manual_label.as_ref()) .is_some(); self.context_menu = Some(ContextMenuState { kind: ContextMenuKind::Pane { @@ -1061,16 +1062,16 @@ impl AppState { } pub(super) fn scroll_pane_up(&self, pane_id: crate::layout::PaneId, lines: usize) { - if let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) { - if let Some(rt) = ws.runtimes.get(&pane_id) { + if let Some(ws_idx) = self.active { + if let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, pane_id) { rt.scroll_up(lines); } } } pub(super) fn scroll_pane_down(&self, pane_id: crate::layout::PaneId, lines: usize) { - if let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) { - if let Some(rt) = ws.runtimes.get(&pane_id) { + if let Some(ws_idx) = self.active { + if let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, pane_id) { rt.scroll_down(lines); } } @@ -1081,9 +1082,8 @@ impl AppState { pane_id: crate::layout::PaneId, ) -> Option<crate::pane::ScrollMetrics> { self.active - .and_then(|i| self.workspaces.get(i)) - .and_then(|ws| ws.runtime(pane_id)) - .and_then(crate::pane::PaneRuntime::scroll_metrics) + .and_then(|i| self.runtime_for_pane_in_workspace(i, pane_id)) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) } pub(super) fn handle_terminal_wheel(&mut self, mouse: MouseEvent) { @@ -1112,8 +1112,8 @@ impl AppState { return; } - if let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) { - if let Some(rt) = ws.focused_runtime() { + if let Some(ws_idx) = self.active { + if let Some(rt) = self.focused_runtime_in_workspace(ws_idx) { match mouse.kind { MouseEventKind::ScrollUp => rt.scroll_up(LINES_PER_NOTCH), MouseEventKind::ScrollDown => rt.scroll_down(LINES_PER_NOTCH), @@ -1124,10 +1124,10 @@ impl AppState { } pub(super) fn forward_pane_mouse_button(&self, info: &PaneInfo, mouse: MouseEvent) -> bool { - let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) else { + let Some(ws_idx) = self.active else { return false; }; - let Some(rt) = ws.runtimes.get(&info.id) else { + let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, info.id) else { return false; }; let column = mouse.column.saturating_sub(info.inner_rect.x); @@ -1169,10 +1169,10 @@ impl AppState { } pub(super) fn forward_pane_wheel(&self, info: &PaneInfo, mouse: MouseEvent) -> bool { - let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) else { + let Some(ws_idx) = self.active else { return false; }; - let Some(rt) = ws.runtimes.get(&info.id) else { + let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, info.id) else { return false; }; match rt.wheel_routing() { @@ -1209,8 +1209,8 @@ impl AppState { pane_id: crate::layout::PaneId, offset_from_bottom: usize, ) { - if let Some(ws) = self.active.and_then(|i| self.workspaces.get(i)) { - if let Some(rt) = ws.runtimes.get(&pane_id) { + if let Some(ws_idx) = self.active { + if let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, pane_id) { rt.set_scroll_offset_from_bottom(offset_from_bottom); } } @@ -1221,7 +1221,7 @@ impl AppState { col: u16, row: u16, ) -> Option<(crate::layout::PaneId, ScrollbarClickTarget)> { - let ws = self.active.and_then(|i| self.workspaces.get(i))?; + let ws_idx = self.active?; let info = self.view.pane_infos.iter().find(|info| { crate::ui::pane_scrollbar_rect(info).is_some_and(|track| { col >= track.x @@ -1230,7 +1230,7 @@ impl AppState { && row < track.y + track.height }) })?; - let rt = ws.runtimes.get(&info.id)?; + let rt = self.runtime_for_pane_in_workspace(ws_idx, info.id)?; let metrics = rt.scroll_metrics()?; if metrics.max_offset_from_bottom == 0 { return None; @@ -1254,14 +1254,14 @@ impl AppState { row: u16, grab_row_offset: u16, ) -> Option<usize> { - let ws = self.active.and_then(|i| self.workspaces.get(i))?; + let ws_idx = self.active?; let info = self .view .pane_infos .iter() .find(|info| info.id == pane_id)?; let track = crate::ui::pane_scrollbar_rect(info)?; - let rt = ws.runtimes.get(&pane_id)?; + let rt = self.runtime_for_pane_in_workspace(ws_idx, pane_id)?; let metrics = rt.scroll_metrics()?; if metrics.max_offset_from_bottom == 0 { return None; @@ -1345,12 +1345,19 @@ mod tests { background.tabs[0].layout.focus_pane(first_pane); app.state.workspaces = vec![active, background]; + app.state.ensure_test_terminals(); app.state.active = Some(0); app.state.selected = 0; app.state.toast_config.delivery = crate::config::ToastDelivery::Herdr; - app.state.workspaces[1] + let target_terminal_id = app.state.workspaces[1] .panes - .get_mut(&target_pane) + .get(&target_pane) + .unwrap() + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&target_terminal_id) .unwrap() .state = AgentState::Working; @@ -1606,17 +1613,17 @@ mod tests { .expect("second pane info") .clone(); - app.state.workspaces[0].tabs[0].runtimes.insert( + app.state.insert_test_runtime( first_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( first_info.inner_rect.width.max(1), first_info.inner_rect.height.max(1), b"\x1b[?1002h", ), ); - app.state.workspaces[0].tabs[0].runtimes.insert( + app.state.insert_test_runtime( second_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( second_info.inner_rect.width.max(1), second_info.inner_rect.height.max(1), b"\x1b[?1002h", @@ -1675,17 +1682,17 @@ mod tests { .expect("second pane info") .clone(); - app.state.workspaces[0].tabs[0].runtimes.insert( + app.state.insert_test_runtime( first_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( first_info.inner_rect.width.max(1), first_info.inner_rect.height.max(1), b"\x1b[?1002h", ), ); - app.state.workspaces[0].tabs[0].runtimes.insert( + app.state.insert_test_runtime( second_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( second_info.inner_rect.width.max(1), second_info.inner_rect.height.max(1), b"\x1b[?1002h", diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index f5a0bcaa..9fc11605 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -126,10 +126,10 @@ impl App { } fn pass_through_key_to_focused_pane(&mut self, key: TerminalKey) -> bool { - let Some(ws) = self.state.active.and_then(|i| self.state.workspaces.get(i)) else { + let Some(ws_idx) = self.state.active else { return false; }; - let Some(rt) = ws.focused_runtime() else { + let Some(rt) = self.state.focused_runtime_in_workspace(ws_idx) else { return false; }; @@ -191,10 +191,13 @@ impl App { if let Some(public_pane_id) = self.public_pane_id(ws_idx, pane_id) { env.push(("HERDR_ACTIVE_PANE_ID".to_string(), public_pane_id)); } - if let Some(pane_cwd) = workspace - .active_tab() - .and_then(|tab| tab.cwd_for_pane(pane_id)) - { + if let Some(pane_cwd) = workspace.active_tab().and_then(|tab| { + tab.cwd_for_pane( + pane_id, + &self.state.terminals, + &self.state.terminal_runtimes, + ) + }) { env.push(( "HERDR_ACTIVE_PANE_CWD".to_string(), pane_cwd.display().to_string(), @@ -308,10 +311,14 @@ impl App { .focused_pane_id() .ok_or_else(|| std::io::Error::other("no focused pane"))?; let previous_zoomed = ws.active_tab().map(|tab| tab.zoomed).unwrap_or(false); - let cwd = ws - .active_tab() - .and_then(|tab| tab.cwd_for_pane(previous_focus)); - let new_pane_id = ws.split_focused_command( + let cwd = ws.active_tab().and_then(|tab| { + tab.cwd_for_pane( + previous_focus, + &self.state.terminals, + &self.state.terminal_runtimes, + ) + }); + let new_pane = ws.split_focused_command( Direction::Horizontal, new_rows, new_cols, @@ -321,6 +328,10 @@ impl App { self.state.pane_scrollback_limit_bytes, self.state.host_terminal_theme, )?; + let new_pane_id = new_pane.pane_id; + self.state + .terminals + .insert(new_pane.terminal.id.clone(), new_pane.terminal); ws.active_tab_mut() .expect("workspace must have an active tab") .layout @@ -1072,7 +1083,7 @@ mod tests { api_rx, crate::api::EventHub::default(), ); - let workspace = Workspace::new( + let (workspace, terminal, runtime) = Workspace::new( std::env::current_dir().unwrap_or_else(|_| "/".into()), 24, 80, @@ -1084,6 +1095,10 @@ mod tests { ) .expect("workspace should spawn"); app.state.workspaces = vec![workspace]; + app.state + .terminal_runtimes + .insert(terminal.id.clone(), runtime); + app.state.terminals.insert(terminal.id.clone(), terminal); app.state.active = Some(0); app.state.selected = 0; app.state.mode = Mode::Terminal; diff --git a/src/app/input/sidebar.rs b/src/app/input/sidebar.rs index 8f6c7792..e8e1e094 100644 --- a/src/app/input/sidebar.rs +++ b/src/app/input/sidebar.rs @@ -351,7 +351,7 @@ impl AppState { let ws_idx = self.collapsed_detail_workspace_idx()?; let ws = self.workspaces.get(ws_idx)?; let detail_idx = (row - detail_content_area.y) as usize; - let details = ws.pane_details(); + let details = ws.pane_details(&self.terminals); let detail = details.get(detail_idx)?; Some((ws_idx, detail.tab_idx, detail.pane_id)) } @@ -633,19 +633,26 @@ mod tests { let mut ws = Workspace::test_new("test"); ws.tabs[0].set_custom_name("main".into()); let first_pane = ws.tabs[0].root_pane; - ws.tabs[0] - .panes - .get_mut(&first_pane) + let first_tab = ws.test_add_tab(Some("logs")); + let second_pane = ws.tabs[first_tab].root_pane; + app.state.workspaces = vec![ws]; + app.state.ensure_test_terminals(); + let first_terminal_id = app.state.workspaces[0].tabs[0].panes[&first_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&first_terminal_id) .unwrap() .detected_agent = Some(Agent::Pi); - let second_tab = ws.test_add_tab(Some("logs")); - let second_pane = ws.tabs[second_tab].root_pane; - ws.tabs[second_tab] - .panes - .get_mut(&second_pane) + let second_terminal_id = app.state.workspaces[0].tabs[first_tab].panes[&second_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&second_terminal_id) .unwrap() .detected_agent = Some(Agent::Claude); - app.state.workspaces = vec![ws]; app.state.active = Some(0); app.state.selected = 0; app.state.mode = Mode::Terminal; @@ -659,9 +666,9 @@ mod tests { ); assert_eq!(app.state.mode, Mode::Terminal); let snapshot = capture_snapshot(&app.state); - assert_eq!(snapshot.workspaces[0].active_tab, second_tab); + assert_eq!(snapshot.workspaces[0].active_tab, first_tab); assert_eq!( - snapshot.workspaces[0].tabs[second_tab].focused, + snapshot.workspaces[0].tabs[first_tab].focused, Some(second_pane.raw()) ); } @@ -701,23 +708,30 @@ mod tests { #[test] fn clicking_all_workspaces_agent_row_switches_to_correct_workspace() { let mut app = app_for_mouse_test(); - let mut first = Workspace::test_new("one"); + let first = Workspace::test_new("one"); let first_pane = first.tabs[0].root_pane; - first.tabs[0] - .panes - .get_mut(&first_pane) - .unwrap() - .detected_agent = Some(Agent::Pi); - let mut second = Workspace::test_new("two"); + let second = Workspace::test_new("two"); let second_pane = second.tabs[0].root_pane; - second.tabs[0] - .panes - .get_mut(&second_pane) - .unwrap() - .detected_agent = Some(Agent::Claude); app.state.workspaces = vec![first, second]; + app.state.ensure_test_terminals(); + let first_terminal_id = app.state.workspaces[0].tabs[0].panes[&first_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&first_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Pi); + let second_terminal_id = app.state.workspaces[1].tabs[0].panes[&second_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&second_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Claude); app.state.active = Some(0); app.state.selected = 0; app.state.mode = Mode::Terminal; @@ -747,12 +761,8 @@ mod tests { let mut app = app_for_mouse_test(); let mut ws = Workspace::test_new("test"); let first_pane = ws.tabs[0].root_pane; - ws.tabs[0] - .panes - .get_mut(&first_pane) - .unwrap() - .detected_agent = Some(Agent::Pi); + let mut tabs = Vec::new(); for (tab_name, agent) in [ ("logs", Agent::Claude), ("review", Agent::Codex), @@ -760,14 +770,29 @@ mod tests { ] { let tab_idx = ws.test_add_tab(Some(tab_name)); let pane_id = ws.tabs[tab_idx].root_pane; - ws.tabs[tab_idx] - .panes - .get_mut(&pane_id) - .unwrap() - .detected_agent = Some(agent); + tabs.push((tab_idx, pane_id, agent)); } app.state.workspaces = vec![ws]; + app.state.ensure_test_terminals(); + let first_terminal_id = app.state.workspaces[0].tabs[0].panes[&first_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&first_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Pi); + for (tab_idx, pane_id, agent) in tabs { + let terminal_id = app.state.workspaces[0].tabs[tab_idx].panes[&pane_id] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&terminal_id) + .unwrap() + .detected_agent = Some(agent); + } app.state.active = Some(0); app.state.selected = 0; app.state.mode = Mode::Terminal; @@ -792,31 +817,43 @@ mod tests { let mut app = app_for_mouse_test(); let mut ws = Workspace::test_new("test"); let first_pane = ws.tabs[0].root_pane; - ws.tabs[0] - .panes - .get_mut(&first_pane) - .unwrap() - .detected_agent = Some(Agent::Pi); - let second_tab = ws.test_add_tab(Some("logs")); let second_pane = ws.tabs[second_tab].root_pane; - ws.tabs[second_tab] - .panes - .get_mut(&second_pane) - .unwrap() - .detected_agent = Some(Agent::Claude); - + let mut extra_tabs = Vec::new(); for (tab_name, agent) in [("review", Agent::Codex), ("ops", Agent::Gemini)] { let tab_idx = ws.test_add_tab(Some(tab_name)); let pane_id = ws.tabs[tab_idx].root_pane; - ws.tabs[tab_idx] - .panes - .get_mut(&pane_id) - .unwrap() - .detected_agent = Some(agent); + extra_tabs.push((tab_idx, pane_id, agent)); } app.state.workspaces = vec![ws]; + app.state.ensure_test_terminals(); + let first_terminal_id = app.state.workspaces[0].tabs[0].panes[&first_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&first_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Pi); + let second_terminal_id = app.state.workspaces[0].tabs[second_tab].panes[&second_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&second_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Claude); + for (tab_idx, pane_id, agent) in extra_tabs { + let terminal_id = app.state.workspaces[0].tabs[tab_idx].panes[&pane_id] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&terminal_id) + .unwrap() + .detected_agent = Some(agent); + } app.state.active = Some(0); app.state.selected = 0; app.state.mode = Mode::Terminal; @@ -843,19 +880,26 @@ mod tests { let mut app = app_for_mouse_test(); let mut ws = Workspace::test_new("test"); let first_pane = ws.tabs[0].root_pane; - ws.tabs[0] - .panes - .get_mut(&first_pane) - .unwrap() - .detected_agent = Some(Agent::Pi); let second_tab = ws.test_add_tab(Some("logs")); let second_pane = ws.tabs[second_tab].root_pane; - ws.tabs[second_tab] - .panes - .get_mut(&second_pane) + app.state.workspaces = vec![ws]; + app.state.ensure_test_terminals(); + let first_terminal_id = app.state.workspaces[0].tabs[0].panes[&first_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&first_terminal_id) + .unwrap() + .detected_agent = Some(Agent::Pi); + let second_terminal_id = app.state.workspaces[0].tabs[second_tab].panes[&second_pane] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&second_terminal_id) .unwrap() .detected_agent = Some(Agent::Claude); - app.state.workspaces = vec![ws]; app.state.active = Some(0); app.state.selected = 0; app.state.mode = Mode::Terminal; @@ -1128,20 +1172,27 @@ mod tests { let mut first = Workspace::test_new("a"); let first_root = first.tabs[0].root_pane; first.identity_cwd = first_repo.clone(); - first.tabs[0] - .pane_cwds - .insert(first_root, first_repo.clone()); first.refresh_git_ahead_behind(); let mut second = Workspace::test_new("b"); let second_root = second.tabs[0].root_pane; second.identity_cwd = second_repo.clone(); - second.tabs[0] - .pane_cwds - .insert(second_root, second_repo.clone()); second.refresh_git_ahead_behind(); app.state.workspaces = vec![first, second]; + app.state.ensure_test_terminals(); + let first_terminal_id = app.state.workspaces[0].tabs[0].panes[&first_root] + .attached_terminal_id + .clone(); + app.state.terminals.get_mut(&first_terminal_id).unwrap().cwd = first_repo.clone(); + let second_terminal_id = app.state.workspaces[1].tabs[0].panes[&second_root] + .attached_terminal_id + .clone(); + app.state + .terminals + .get_mut(&second_terminal_id) + .unwrap() + .cwd = second_repo.clone(); crate::ui::compute_view(&mut app.state, Rect::new(0, 0, 106, 20)); assert_eq!(app.state.workspace_drop_index_at_row(0), Some(0)); diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs index 22cf3169..3f0bff2c 100644 --- a/src/app/input/terminal.rs +++ b/src/app/input/terminal.rs @@ -64,7 +64,7 @@ impl App { let ws_idx = self.state.active?; let ws = self.state.workspaces.get(ws_idx)?; let pane_id = ws.focused_pane_id()?; - let rt = ws.runtimes.get(&pane_id)?; + let rt = self.state.runtime_for_pane_in_workspace(ws_idx, pane_id)?; // Intercept plain PageUp/PageDown presses for pane scrollback when the // focused pane doesn't handle its own scrolling (e.g., a plain shell @@ -179,9 +179,9 @@ mod tests { let pane_id = ws.tabs[0].root_pane; let pane_infos = ws.tabs[0].layout.panes(Rect::new(26, 2, 80, 18)); let info = pane_infos[0].clone(); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( pane_id, - crate::pane::PaneRuntime::test_with_scrollback_bytes( + crate::terminal::TerminalRuntime::test_with_scrollback_bytes( info.inner_rect.width, info.inner_rect.height, 16 * 1024, @@ -195,9 +195,10 @@ mod tests { app.state.mode = Mode::Terminal; app.state.view.pane_infos = pane_infos; - let start_metrics = app.state.workspaces[0] - .runtime(pane_id) - .and_then(crate::pane::PaneRuntime::scroll_metrics) + let start_metrics = app + .state + .runtime_for_pane(pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) .expect("initial scroll metrics"); let start_row = info.inner_rect.y; let start_col = info.inner_rect.x + 2; @@ -213,9 +214,10 @@ mod tests { info.inner_rect.y.saturating_sub(1), )); - let end_metrics = app.state.workspaces[0] - .runtime(pane_id) - .and_then(crate::pane::PaneRuntime::scroll_metrics) + let end_metrics = app + .state + .runtime_for_pane(pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) .expect("scroll metrics after drag"); assert_eq!( end_metrics.offset_from_bottom, @@ -243,9 +245,9 @@ mod tests { let pane_id = ws.tabs[0].root_pane; let pane_infos = ws.tabs[0].layout.panes(Rect::new(26, 2, 80, 18)); let info = pane_infos[0].clone(); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( pane_id, - crate::pane::PaneRuntime::test_with_scrollback_bytes( + crate::terminal::TerminalRuntime::test_with_scrollback_bytes( info.inner_rect.width, info.inner_rect.height, 16 * 1024, @@ -283,9 +285,9 @@ mod tests { let pane_id = ws.tabs[0].root_pane; let pane_infos = ws.tabs[0].layout.panes(Rect::new(26, 2, 80, 18)); let info = pane_infos[0].clone(); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( pane_id, - crate::pane::PaneRuntime::test_with_scrollback_bytes( + crate::terminal::TerminalRuntime::test_with_scrollback_bytes( info.inner_rect.width, info.inner_rect.height, 16 * 1024, @@ -299,9 +301,10 @@ mod tests { app.state.mode = Mode::Terminal; app.state.view.pane_infos = pane_infos; - let start_metrics = app.state.workspaces[0] - .runtime(pane_id) - .and_then(crate::pane::PaneRuntime::scroll_metrics) + let start_metrics = app + .state + .runtime_for_pane(pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) .expect("initial scroll metrics"); let top_row = info.inner_rect.y; let col = info.inner_rect.x + 2; @@ -309,9 +312,10 @@ mod tests { app.handle_mouse(mouse(MouseEventKind::Down(MouseButton::Left), col, top_row)); app.handle_mouse(mouse(MouseEventKind::ScrollUp, col, top_row)); - let end_metrics = app.state.workspaces[0] - .runtime(pane_id) - .and_then(crate::pane::PaneRuntime::scroll_metrics) + let end_metrics = app + .state + .runtime_for_pane(pane_id) + .and_then(crate::terminal::TerminalRuntime::scroll_metrics) .expect("scroll metrics after wheel"); assert_eq!( end_metrics.offset_from_bottom, @@ -352,17 +356,17 @@ mod tests { .unwrap() .clone(); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( first_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( first_info.inner_rect.width.max(1), first_info.inner_rect.height.max(1), b"", ), ); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( second_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( second_info.inner_rect.width.max(1), second_info.inner_rect.height.max(1), b"\x1b[?1002h", @@ -410,17 +414,17 @@ mod tests { .unwrap() .clone(); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( first_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( first_info.inner_rect.width.max(1), first_info.inner_rect.height.max(1), b"", ), ); - ws.tabs[0].runtimes.insert( + ws.insert_test_runtime( second_pane, - crate::pane::PaneRuntime::test_with_screen_bytes( + crate::terminal::TerminalRuntime::test_with_screen_bytes( second_info.inner_rect.width.max(1), second_info.inner_rect.height.max(1), b"\x1b[?1002h", |
