aboutsummaryrefslogtreecommitdiff
path: root/src/app/input
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-05-21 20:19:54 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-05-21 20:22:57 +0300
commit866aaf740e10fcfce169aaf81380ea0257fbe272 (patch)
treef815392b7c6b24a7c0de14e655bd635656383813 /src/app/input
parentca579c402d5668560ca13059f82c03178be18b2a (diff)
refactor: separate server client boundaries
Diffstat (limited to 'src/app/input')
-rw-r--r--src/app/input/mod.rs33
-rw-r--r--src/app/input/modal.rs19
-rw-r--r--src/app/input/mouse.rs194
-rw-r--r--src/app/input/navigate.rs68
-rw-r--r--src/app/input/selection.rs72
-rw-r--r--src/app/input/terminal.rs84
6 files changed, 339 insertions, 131 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs
index 245f935b..7c2bbe03 100644
--- a/src/app/input/mod.rs
+++ b/src/app/input/mod.rs
@@ -70,7 +70,13 @@ impl App {
}
Mode::Resize => handle_resize_key(&mut self.state, key),
Mode::ConfirmClose => handle_confirm_close_key(&mut self.state, key_event),
- Mode::ContextMenu => handle_context_menu_key(&mut self.state, key_event),
+ Mode::ContextMenu => {
+ handle_context_menu_key(
+ &mut self.state,
+ &mut self.terminal_runtimes,
+ key_event,
+ );
+ }
Mode::Settings => self.handle_settings_key(key_event),
Mode::GlobalMenu => handle_global_menu_key(&mut self.state, key_event),
Mode::KeybindHelp => handle_keybind_help_key(&mut self.state, key_event),
@@ -85,7 +91,10 @@ impl App {
return;
}
if let Some(ws_idx) = self.state.active {
- if let Some(rt) = self.state.focused_runtime_in_workspace(ws_idx) {
+ if let Some(rt) = self
+ .state
+ .focused_runtime_in_workspace(&self.terminal_runtimes, ws_idx)
+ {
let _ = rt.send_paste(text).await;
}
}
@@ -183,7 +192,7 @@ impl App {
let previous_agent_panel_scope = self.state.agent_panel_scope;
let previous_settings_section = self.state.settings.section;
- if let Some(action) = self.state.handle_mouse(mouse) {
+ if let Some(action) = self.state.handle_mouse(&mut self.terminal_runtimes, mouse) {
match action {
SettingsAction::SaveTheme(name) => self.save_theme(&name),
SettingsAction::SaveSound(enabled) => self.save_sound(enabled),
@@ -232,7 +241,11 @@ impl App {
// Note: split_pane needs runtime (event_tx for PTY spawn), so it lives on App
impl AppState {
- pub(crate) fn split_pane(&mut self, direction: Direction) {
+ pub(crate) fn split_pane(
+ &mut self,
+ terminal_runtimes: &mut crate::terminal::TerminalRuntimeRegistry,
+ direction: Direction,
+ ) {
// Actual PTY spawning happens in Workspace::split_focused
// which needs events channel — this is called from navigate_key
// where we don't have async context, so the workspace handles it
@@ -245,11 +258,7 @@ impl AppState {
.and_then(|i| self.workspaces.get(i))
.and_then(|ws| {
let tab = ws.active_tab()?;
- tab.cwd_for_pane(
- tab.layout.focused(),
- &self.terminals,
- &self.terminal_runtimes,
- )
+ tab.cwd_for_pane(tab.layout.focused(), &self.terminals, terminal_runtimes)
});
if let Some(ws) = self.active.and_then(|i| self.workspaces.get_mut(i)) {
@@ -263,8 +272,7 @@ impl AppState {
&self.default_shell,
) {
let new_id = new_pane.pane_id;
- self.terminal_runtimes
- .insert(new_pane.terminal.id.clone(), new_pane.runtime);
+ terminal_runtimes.insert(new_pane.terminal.id.clone(), new_pane.runtime);
self.terminals
.insert(new_pane.terminal.id.clone(), new_pane.terminal);
ws.layout.focus_pane(new_id);
@@ -332,10 +340,11 @@ fn numbered_lines_bytes(count: usize) -> Vec<u8> {
#[cfg(test)]
fn capture_snapshot(state: &AppState) -> crate::persist::SessionSnapshot {
+ let terminal_runtimes = crate::terminal::TerminalRuntimeRegistry::new();
crate::persist::capture(
&state.workspaces,
&state.terminals,
- &state.terminal_runtimes,
+ &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 084f44a9..868ee3d3 100644
--- a/src/app/input/modal.rs
+++ b/src/app/input/modal.rs
@@ -498,7 +498,12 @@ pub(crate) fn handle_confirm_close_key(state: &mut AppState, key: KeyEvent) {
}
}
-pub(super) fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuState, idx: usize) {
+pub(super) fn apply_context_menu_action(
+ state: &mut AppState,
+ terminal_runtimes: &mut crate::terminal::TerminalRuntimeRegistry,
+ menu: ContextMenuState,
+ idx: usize,
+) {
let item = menu.items().get(idx).copied();
match (menu.kind, item) {
(ContextMenuKind::Workspace { ws_idx }, Some("Rename")) => {
@@ -554,11 +559,11 @@ pub(super) fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuS
state.mode = Mode::Terminal;
}
(ContextMenuKind::Pane { .. }, Some("Split vertical")) => {
- state.split_pane(Direction::Horizontal);
+ state.split_pane(terminal_runtimes, Direction::Horizontal);
state.mode = Mode::Terminal;
}
(ContextMenuKind::Pane { .. }, Some("Split horizontal")) => {
- state.split_pane(Direction::Vertical);
+ state.split_pane(terminal_runtimes, Direction::Vertical);
state.mode = Mode::Terminal;
}
(ContextMenuKind::Pane { .. }, Some("Zoom")) => {
@@ -577,7 +582,11 @@ pub(super) fn apply_context_menu_action(state: &mut AppState, menu: ContextMenuS
}
}
-pub(crate) fn handle_context_menu_key(state: &mut AppState, key: KeyEvent) {
+pub(crate) fn handle_context_menu_key(
+ state: &mut AppState,
+ terminal_runtimes: &mut crate::terminal::TerminalRuntimeRegistry,
+ key: KeyEvent,
+) {
match key.code {
KeyCode::Esc => {
state.context_menu = None;
@@ -596,7 +605,7 @@ pub(crate) fn handle_context_menu_key(state: &mut AppState, key: KeyEvent) {
KeyCode::Enter => {
if let Some(menu) = state.context_menu.take() {
let idx = menu.list.highlighted;
- apply_context_menu_action(state, menu, idx);
+ apply_context_menu_action(state, terminal_runtimes, menu, idx);
}
}
_ => {}
diff --git a/src/app/input/mouse.rs b/src/app/input/mouse.rs
index e3f13d77..4d49a917 100644
--- a/src/app/input/mouse.rs
+++ b/src/app/input/mouse.rs
@@ -10,6 +10,7 @@ use crate::{
},
layout::{PaneInfo, SplitBorder},
selection::Selection,
+ terminal::TerminalRuntimeRegistry,
};
#[cfg(test)]
@@ -25,7 +26,11 @@ use super::{
};
impl AppState {
- pub(crate) fn handle_pane_mouse_only(&mut self, mouse: MouseEvent) {
+ pub(crate) fn handle_pane_mouse_only(
+ &mut self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ mouse: MouseEvent,
+ ) {
if self.mode != Mode::Terminal {
return;
}
@@ -38,16 +43,20 @@ impl AppState {
| MouseEventKind::ScrollDown
| MouseEventKind::ScrollLeft
| MouseEventKind::ScrollRight => {
- self.forward_pane_reported_wheel(&info, mouse);
+ self.forward_pane_reported_wheel(terminal_runtimes, &info, mouse);
}
MouseEventKind::Down(_) | MouseEventKind::Up(_) | MouseEventKind::Drag(_) => {
- self.forward_pane_mouse_button(&info, mouse);
+ self.forward_pane_mouse_button(terminal_runtimes, &info, mouse);
}
MouseEventKind::Moved => {}
}
}
- pub(super) fn handle_mouse(&mut self, mouse: MouseEvent) -> Option<SettingsAction> {
+ pub(super) fn handle_mouse(
+ &mut self,
+ terminal_runtimes: &mut TerminalRuntimeRegistry,
+ mouse: MouseEvent,
+ ) -> Option<SettingsAction> {
if self.mode == Mode::Onboarding {
self.handle_onboarding_mouse(mouse);
return None;
@@ -187,7 +196,7 @@ impl AppState {
let item_idx = self.context_menu_item_at(mouse.column, mouse.row);
if let Some(menu) = self.context_menu.take() {
if let Some(idx) = item_idx {
- apply_context_menu_action(self, menu, idx);
+ apply_context_menu_action(self, terminal_runtimes, menu, idx);
} else {
leave_modal(self);
}
@@ -224,7 +233,7 @@ impl AppState {
}
if let Some((pane_id, target)) =
- self.scrollbar_target_at(mouse.column, mouse.row)
+ self.scrollbar_target_at(terminal_runtimes, mouse.column, mouse.row)
{
self.focus_pane(pane_id);
match target {
@@ -237,7 +246,11 @@ impl AppState {
});
}
ScrollbarClickTarget::Track { offset_from_bottom } => {
- self.set_pane_scroll_offset(pane_id, offset_from_bottom);
+ self.set_pane_scroll_offset(
+ terminal_runtimes,
+ pane_id,
+ offset_from_bottom,
+ );
}
}
if self.mode != Mode::Terminal {
@@ -370,7 +383,7 @@ impl AppState {
self.mode = Mode::Terminal;
}
- if self.forward_pane_mouse_button(&info, mouse) {
+ if self.forward_pane_mouse_button(terminal_runtimes, &info, mouse) {
self.selection = None;
self.selection_autoscroll = None;
return None;
@@ -384,7 +397,7 @@ impl AppState {
info.id,
row,
col,
- self.pane_scroll_metrics(info.id),
+ self.pane_scroll_metrics(terminal_runtimes, info.id),
));
} else if let Some(info) = self.view.pane_infos.iter().find(|p| {
mouse.column >= p.rect.x
@@ -402,13 +415,13 @@ impl AppState {
MouseEventKind::Drag(MouseButton::Left) => {
if self.selection.is_some() {
- self.update_selection_drag(mouse.column, mouse.row);
+ self.update_selection_drag(terminal_runtimes, mouse.column, mouse.row);
return None;
}
if self.drag.is_none() {
if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() {
- if self.forward_pane_mouse_button(&info, mouse) {
+ if self.forward_pane_mouse_button(terminal_runtimes, &info, mouse) {
self.selection = None;
self.selection_autoscroll = None;
return None;
@@ -504,11 +517,16 @@ impl AppState {
grab_row_offset,
} => {
if let Some(offset_from_bottom) = self.scrollbar_offset_for_pane_row(
+ terminal_runtimes,
*pane_id,
mouse.row,
*grab_row_offset,
) {
- self.set_pane_scroll_offset(*pane_id, offset_from_bottom);
+ self.set_pane_scroll_offset(
+ terminal_runtimes,
+ *pane_id,
+ offset_from_bottom,
+ );
}
}
DragTarget::SidebarDivider => {
@@ -535,14 +553,14 @@ impl AppState {
self.selection = None;
self.selection_autoscroll = None;
} else {
- self.copy_selection();
+ self.copy_selection(terminal_runtimes);
}
return None;
}
if self.drag.is_none() {
if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() {
- if self.forward_pane_mouse_button(&info, mouse) {
+ if self.forward_pane_mouse_button(terminal_runtimes, &info, mouse) {
self.selection = None;
self.selection_autoscroll = None;
self.workspace_press = None;
@@ -597,7 +615,7 @@ impl AppState {
self.selection = None;
self.selection_autoscroll = None;
} else {
- self.copy_selection();
+ self.copy_selection(terminal_runtimes);
}
}
}
@@ -607,7 +625,7 @@ impl AppState {
if !in_sidebar =>
{
if let Some(info) = self.pane_mouse_target(mouse.column, mouse.row).cloned() {
- let _ = self.forward_pane_mouse_button(&info, mouse);
+ let _ = self.forward_pane_mouse_button(terminal_runtimes, &info, mouse);
}
}
@@ -622,12 +640,12 @@ impl AppState {
}
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown
- if !in_sidebar && self.scroll_selection_with_wheel(mouse) => {}
+ if !in_sidebar && self.scroll_selection_with_wheel(terminal_runtimes, mouse) => {}
MouseEventKind::ScrollUp | MouseEventKind::ScrollDown if !in_sidebar => {
self.selection = None;
self.selection_autoscroll = None;
- self.handle_terminal_wheel(mouse);
+ self.handle_terminal_wheel(terminal_runtimes, mouse);
}
MouseEventKind::ScrollUp if in_sidebar => {
@@ -1070,17 +1088,29 @@ impl AppState {
self.mode = Mode::Terminal;
}
- pub(crate) fn scroll_pane_up(&self, pane_id: crate::layout::PaneId, lines: usize) {
+ pub(crate) fn scroll_pane_up(
+ &self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ pane_id: crate::layout::PaneId,
+ lines: usize,
+ ) {
if let Some(ws_idx) = self.active {
- if let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, pane_id) {
+ if let Some(rt) = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, pane_id)
+ {
rt.scroll_up(lines);
}
}
}
- pub(crate) fn scroll_pane_down(&self, pane_id: crate::layout::PaneId, lines: usize) {
+ pub(crate) fn scroll_pane_down(
+ &self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ pane_id: crate::layout::PaneId,
+ lines: usize,
+ ) {
if let Some(ws_idx) = self.active {
- if let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, pane_id) {
+ if let Some(rt) = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, pane_id)
+ {
rt.scroll_down(lines);
}
}
@@ -1088,24 +1118,33 @@ impl AppState {
pub(crate) fn pane_scroll_metrics(
&self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
pane_id: crate::layout::PaneId,
) -> Option<crate::pane::ScrollMetrics> {
self.active
- .and_then(|i| self.runtime_for_pane_in_workspace(i, pane_id))
+ .and_then(|i| self.runtime_for_pane_in_workspace(terminal_runtimes, i, pane_id))
.and_then(crate::terminal::TerminalRuntime::scroll_metrics)
}
- pub(super) fn handle_terminal_wheel(&mut self, mouse: MouseEvent) {
+ pub(super) fn handle_terminal_wheel(
+ &mut self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ mouse: MouseEvent,
+ ) {
const LINES_PER_NOTCH: usize = 3;
if let Some(info) = self.pane_at(mouse.column, mouse.row).cloned() {
self.focus_pane(info.id);
- if self.forward_pane_wheel(&info, mouse) {
+ if self.forward_pane_wheel(terminal_runtimes, &info, mouse) {
return;
}
match mouse.kind {
- MouseEventKind::ScrollUp => self.scroll_pane_up(info.id, LINES_PER_NOTCH),
- MouseEventKind::ScrollDown => self.scroll_pane_down(info.id, LINES_PER_NOTCH),
+ MouseEventKind::ScrollUp => {
+ self.scroll_pane_up(terminal_runtimes, info.id, LINES_PER_NOTCH)
+ }
+ MouseEventKind::ScrollDown => {
+ self.scroll_pane_down(terminal_runtimes, info.id, LINES_PER_NOTCH)
+ }
_ => {}
}
return;
@@ -1114,15 +1153,19 @@ impl AppState {
if let Some(info) = self.pane_frame_at(mouse.column, mouse.row).cloned() {
self.focus_pane(info.id);
match mouse.kind {
- MouseEventKind::ScrollUp => self.scroll_pane_up(info.id, LINES_PER_NOTCH),
- MouseEventKind::ScrollDown => self.scroll_pane_down(info.id, LINES_PER_NOTCH),
+ MouseEventKind::ScrollUp => {
+ self.scroll_pane_up(terminal_runtimes, info.id, LINES_PER_NOTCH)
+ }
+ MouseEventKind::ScrollDown => {
+ self.scroll_pane_down(terminal_runtimes, info.id, LINES_PER_NOTCH)
+ }
_ => {}
}
return;
}
if let Some(ws_idx) = self.active {
- if let Some(rt) = self.focused_runtime_in_workspace(ws_idx) {
+ if let Some(rt) = self.focused_runtime_in_workspace(terminal_runtimes, ws_idx) {
match mouse.kind {
MouseEventKind::ScrollUp => rt.scroll_up(LINES_PER_NOTCH),
MouseEventKind::ScrollDown => rt.scroll_down(LINES_PER_NOTCH),
@@ -1132,11 +1175,17 @@ impl AppState {
}
}
- pub(super) fn forward_pane_mouse_button(&self, info: &PaneInfo, mouse: MouseEvent) -> bool {
+ pub(super) fn forward_pane_mouse_button(
+ &self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ info: &PaneInfo,
+ mouse: MouseEvent,
+ ) -> bool {
let Some(ws_idx) = self.active else {
return false;
};
- let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, info.id) else {
+ let Some(rt) = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id)
+ else {
return false;
};
let column = mouse.column.saturating_sub(info.inner_rect.x);
@@ -1151,11 +1200,17 @@ impl AppState {
true
}
- fn forward_pane_reported_wheel(&self, info: &PaneInfo, mouse: MouseEvent) -> bool {
+ fn forward_pane_reported_wheel(
+ &self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ info: &PaneInfo,
+ mouse: MouseEvent,
+ ) -> bool {
let Some(ws_idx) = self.active else {
return false;
};
- let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, info.id) else {
+ let Some(rt) = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id)
+ else {
return false;
};
if !rt
@@ -1177,11 +1232,17 @@ impl AppState {
true
}
- pub(super) fn forward_pane_wheel(&self, info: &PaneInfo, mouse: MouseEvent) -> bool {
+ pub(super) fn forward_pane_wheel(
+ &self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ info: &PaneInfo,
+ mouse: MouseEvent,
+ ) -> bool {
let Some(ws_idx) = self.active else {
return false;
};
- let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, info.id) else {
+ let Some(rt) = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id)
+ else {
return false;
};
match rt.wheel_routing() {
@@ -1215,11 +1276,13 @@ impl AppState {
pub(super) fn set_pane_scroll_offset(
&self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
pane_id: crate::layout::PaneId,
offset_from_bottom: usize,
) {
if let Some(ws_idx) = self.active {
- if let Some(rt) = self.runtime_for_pane_in_workspace(ws_idx, pane_id) {
+ if let Some(rt) = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, pane_id)
+ {
rt.set_scroll_offset_from_bottom(offset_from_bottom);
}
}
@@ -1227,6 +1290,7 @@ impl AppState {
pub(super) fn scrollbar_target_at(
&self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
col: u16,
row: u16,
) -> Option<(crate::layout::PaneId, ScrollbarClickTarget)> {
@@ -1239,7 +1303,7 @@ impl AppState {
&& row < track.y + track.height
})
})?;
- let rt = self.runtime_for_pane_in_workspace(ws_idx, info.id)?;
+ let rt = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, info.id)?;
let metrics = rt.scroll_metrics()?;
if metrics.max_offset_from_bottom == 0 {
return None;
@@ -1259,6 +1323,7 @@ impl AppState {
pub(super) fn scrollbar_offset_for_pane_row(
&self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
pane_id: crate::layout::PaneId,
row: u16,
grab_row_offset: u16,
@@ -1270,7 +1335,7 @@ impl AppState {
.iter()
.find(|info| info.id == pane_id)?;
let track = crate::ui::pane_scrollbar_rect(info)?;
- let rt = self.runtime_for_pane_in_workspace(ws_idx, pane_id)?;
+ let rt = self.runtime_for_pane_in_workspace(terminal_runtimes, ws_idx, pane_id)?;
let metrics = rt.scroll_metrics()?;
if metrics.max_offset_from_bottom == 0 {
return None;
@@ -1435,6 +1500,7 @@ mod tests {
app.state.mode = Mode::ContextMenu;
handle_context_menu_key(
&mut app.state,
+ &mut app.terminal_runtimes,
KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
);
assert_eq!(app.state.mode, Mode::ConfirmClose);
@@ -1458,6 +1524,56 @@ mod tests {
assert_eq!(app.state.workspaces[0].display_name(), "a");
}
+ #[tokio::test]
+ async fn keyboard_context_menu_split_keeps_new_runtime() {
+ let mut app = app_for_mouse_test();
+ app.state.default_shell = "/usr/bin/true".into();
+ let (workspace, terminal, runtime) = Workspace::new(
+ std::env::current_dir().unwrap_or_else(|_| "/".into()),
+ 24,
+ 80,
+ app.state.pane_scrollback_limit_bytes,
+ app.state.host_terminal_theme,
+ &app.state.default_shell,
+ app.event_tx.clone(),
+ app.render_notify.clone(),
+ app.render_dirty.clone(),
+ )
+ .expect("workspace should spawn");
+ app.state.workspaces = vec![workspace];
+ app.terminal_runtimes.insert(terminal.id.clone(), runtime);
+ app.state.terminals.insert(terminal.id.clone(), terminal);
+ app.state.active = Some(0);
+ app.state.selected = 0;
+ let pane_id = app.state.workspaces[0].tabs[0].root_pane;
+ let runtime_count = app.terminal_runtimes.len();
+ app.state.context_menu = Some(ContextMenuState {
+ kind: ContextMenuKind::Pane {
+ pane_id,
+ has_manual_label: false,
+ },
+ x: 2,
+ y: 2,
+ list: MenuListState::new(1),
+ });
+ app.state.mode = Mode::ContextMenu;
+
+ handle_context_menu_key(
+ &mut app.state,
+ &mut app.terminal_runtimes,
+ KeyEvent::new(KeyCode::Enter, KeyModifiers::empty()),
+ );
+
+ assert_eq!(app.state.mode, Mode::Terminal);
+ assert_eq!(app.state.workspaces[0].tabs[0].layout.pane_count(), 2);
+ assert_eq!(app.terminal_runtimes.len(), runtime_count + 1);
+
+ let runtimes: Vec<_> = app.terminal_runtimes.drain().collect();
+ for (_terminal_id, runtime) in runtimes {
+ runtime.shutdown();
+ }
+ }
+
#[test]
fn dragging_pane_split_updates_captured_layout_ratio() {
let mut app = app_for_mouse_test();
diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs
index 17180fed..9256bbbb 100644
--- a/src/app/input/navigate.rs
+++ b/src/app/input/navigate.rs
@@ -16,6 +16,7 @@ use crate::{
},
input::TerminalKey,
layout::NavDirection,
+ terminal::TerminalRuntimeRegistry,
};
pub(crate) fn terminal_direct_navigation_action(
@@ -55,7 +56,12 @@ impl App {
self.launch_focused_scrollback_editor();
finish_action_context(&mut self.state, ActionContext::Prefix, previous_mode);
} else {
- execute_navigate_action_in_context(&mut self.state, action, ActionContext::Prefix);
+ execute_navigate_action_in_context(
+ &mut self.state,
+ &mut self.terminal_runtimes,
+ action,
+ ActionContext::Prefix,
+ );
}
self.selection_autoscroll_deadline = None;
return;
@@ -88,6 +94,7 @@ impl App {
} else {
execute_navigate_action_in_context(
&mut self.state,
+ &mut self.terminal_runtimes,
action,
ActionContext::Navigate,
);
@@ -105,7 +112,10 @@ impl App {
let Some(ws_idx) = self.state.active else {
return false;
};
- let Some(rt) = self.state.focused_runtime_in_workspace(ws_idx) else {
+ let Some(rt) = self
+ .state
+ .focused_runtime_in_workspace(&self.terminal_runtimes, ws_idx)
+ else {
return false;
};
@@ -174,11 +184,7 @@ impl App {
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,
- &self.state.terminals,
- &self.state.terminal_runtimes,
- )
+ tab.cwd_for_pane(pane_id, &self.state.terminals, &self.terminal_runtimes)
}) {
env.push((
"HERDR_ACTIVE_PANE_CWD".to_string(),
@@ -245,7 +251,7 @@ impl App {
.ok_or_else(|| std::io::Error::other("no focused pane"))?;
let scrollback = self
.state
- .runtime_for_pane_in_workspace(ws_idx, pane_id)
+ .runtime_for_pane_in_workspace(&self.terminal_runtimes, ws_idx, pane_id)
.ok_or_else(|| std::io::Error::other("focused pane has no scrollback runtime"))?
.recent_text(usize::MAX);
@@ -298,7 +304,7 @@ impl App {
tab.cwd_for_pane(
previous_focus,
&self.state.terminals,
- &self.state.terminal_runtimes,
+ &self.terminal_runtimes,
)
});
let new_pane = ws.split_focused_command(
@@ -312,8 +318,7 @@ impl App {
self.state.host_terminal_theme,
)?;
let new_pane_id = new_pane.pane_id;
- self.state
- .terminal_runtimes
+ self.terminal_runtimes
.insert(new_pane.terminal.id.clone(), new_pane.runtime);
self.state
.terminals
@@ -428,6 +433,7 @@ pub(super) fn handle_navigate_reserved_key(state: &mut AppState, key: KeyEvent)
#[allow(dead_code)] // exercised in input unit tests; production uses App::handle_navigate_key
pub(crate) fn handle_navigate_key(state: &mut AppState, key: KeyEvent) {
+ let mut terminal_runtimes = TerminalRuntimeRegistry::new();
state.update_dismissed = true;
let terminal_key = TerminalKey::from(key);
@@ -441,7 +447,12 @@ pub(crate) fn handle_navigate_key(state: &mut AppState, key: KeyEvent) {
}
if let Some(action) = action_for_key(state, terminal_key, BindingDispatch::Prefix) {
- execute_navigate_action_in_context(state, action, ActionContext::Navigate);
+ execute_navigate_action_in_context(
+ state,
+ &mut terminal_runtimes,
+ action,
+ ActionContext::Navigate,
+ );
}
}
@@ -587,11 +598,18 @@ fn action_for_key(
#[cfg(test)]
pub(super) fn execute_navigate_action(state: &mut AppState, action: NavigateAction) {
- execute_navigate_action_in_context(state, action, ActionContext::Navigate);
+ let mut terminal_runtimes = TerminalRuntimeRegistry::new();
+ execute_navigate_action_in_context(
+ state,
+ &mut terminal_runtimes,
+ action,
+ ActionContext::Navigate,
+ );
}
pub(super) fn execute_navigate_action_in_context(
state: &mut AppState,
+ terminal_runtimes: &mut TerminalRuntimeRegistry,
action: NavigateAction,
context: ActionContext,
) {
@@ -694,11 +712,11 @@ pub(super) fn execute_navigate_action_in_context(
NavigateAction::FocusPaneUp => state.navigate_pane(NavDirection::Up),
NavigateAction::FocusPaneRight => state.navigate_pane(NavDirection::Right),
NavigateAction::SplitVertical => {
- state.split_pane(Direction::Horizontal);
+ state.split_pane(terminal_runtimes, Direction::Horizontal);
leave_navigate_mode(state);
}
NavigateAction::SplitHorizontal => {
- state.split_pane(Direction::Vertical);
+ state.split_pane(terminal_runtimes, Direction::Vertical);
leave_navigate_mode(state);
}
NavigateAction::ClosePane => {
@@ -1192,10 +1210,12 @@ mod tests {
#[test]
fn empty_state_new_tab_is_no_op() {
let mut state = crate::app::state::AppState::test_new();
+ let mut terminal_runtimes = TerminalRuntimeRegistry::new();
state.mode = Mode::Prefix;
execute_navigate_action_in_context(
&mut state,
+ &mut terminal_runtimes,
NavigateAction::NewTab,
ActionContext::Prefix,
);
@@ -1277,9 +1297,7 @@ mod tests {
)
.expect("workspace should spawn");
app.state.workspaces = vec![workspace];
- app.state
- .terminal_runtimes
- .insert(terminal.id.clone(), runtime);
+ app.terminal_runtimes.insert(terminal.id.clone(), runtime);
app.state.terminals.insert(terminal.id.clone(), terminal);
app.state.active = Some(0);
app.state.selected = 0;
@@ -1303,7 +1321,7 @@ mod tests {
.await;
assert_eq!(app.state.workspaces[0].tabs[0].layout.pane_count(), 2);
- assert_eq!(app.state.terminal_runtimes.len(), 2);
+ assert_eq!(app.terminal_runtimes.len(), 2);
assert!(app.state.workspaces[0].tabs[0].zoomed);
let _ = wait_for_file(&output_path);
@@ -1321,6 +1339,11 @@ mod tests {
assert!(!app.state.workspaces[0].tabs[0].zoomed);
assert_eq!(app.state.mode, Mode::Terminal);
let _ = std::fs::remove_file(output_path);
+
+ let runtimes: Vec<_> = app.terminal_runtimes.drain().collect();
+ for (_terminal_id, runtime) in runtimes {
+ runtime.shutdown();
+ }
}
#[tokio::test]
@@ -1337,7 +1360,12 @@ mod tests {
let root_pane = workspace.tabs[0].root_pane;
workspace.tabs[0].runtimes.insert(
root_pane,
- crate::pane::PaneRuntime::test_with_scrollback_bytes(20, 5, 4096, b"alpha\nbeta\n"),
+ crate::terminal::TerminalRuntime::test_with_scrollback_bytes(
+ 20,
+ 5,
+ 4096,
+ b"alpha\nbeta\n",
+ ),
);
app.state.workspaces = vec![workspace];
app.state.active = Some(0);
diff --git a/src/app/input/selection.rs b/src/app/input/selection.rs
index ad1dd33f..06de30b5 100644
--- a/src/app/input/selection.rs
+++ b/src/app/input/selection.rs
@@ -1,10 +1,14 @@
use crossterm::event::{MouseEvent, MouseEventKind};
-use crate::app::state::{AppState, SelectionAutoscroll, SelectionAutoscrollDirection};
+use crate::{
+ app::state::{AppState, SelectionAutoscroll, SelectionAutoscrollDirection},
+ terminal::TerminalRuntimeRegistry,
+};
impl AppState {
pub(crate) fn update_selection_cursor(
&mut self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
pane_id: crate::layout::PaneId,
screen_col: u16,
screen_row: u16,
@@ -12,7 +16,7 @@ impl AppState {
let Some(info) = self.pane_info_by_id(pane_id).cloned() else {
return;
};
- let metrics = self.pane_scroll_metrics(pane_id);
+ let metrics = self.pane_scroll_metrics(terminal_runtimes, pane_id);
if let Some(selection) = self.selection.as_mut() {
selection.drag(screen_col, screen_row, info.inner_rect, metrics);
}
@@ -22,7 +26,12 @@ impl AppState {
usize::from(distance).saturating_mul(3).clamp(3, 15)
}
- pub(super) fn update_selection_drag(&mut self, screen_col: u16, screen_row: u16) {
+ pub(super) fn update_selection_drag(
+ &mut self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ screen_col: u16,
+ screen_row: u16,
+ ) {
let Some(pane_id) = self.selection.as_ref().map(|selection| selection.pane_id) else {
return;
};
@@ -44,14 +53,16 @@ impl AppState {
// Anchor is stored in absolute row; for a simple screen
// comparison, check whether the mouse is on a different
// cell than the anchor's screen position.
- let (ar, ac) =
- s.anchor_screen_pos(info.inner_rect, self.pane_scroll_metrics(s.pane_id));
+ let (ar, ac) = s.anchor_screen_pos(
+ info.inner_rect,
+ self.pane_scroll_metrics(terminal_runtimes, s.pane_id),
+ );
ar != screen_row || ac != screen_col
});
let is_dragging = was_dragging || anchor_differs_from_mouse;
// Advance the selection cursor.
- self.update_selection_cursor(pane_id, screen_col, screen_row);
+ self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row);
// If the mouse is on a different cell than the anchor but drag()
// didn't transition (cursor clamped to edge == anchor), force
@@ -67,9 +78,13 @@ impl AppState {
if screen_row < top {
// Cursor above pane — immediate scroll + set autoscroll state
if is_dragging {
- self.scroll_pane_up(pane_id, Self::selection_edge_scroll_lines(top - screen_row));
+ self.scroll_pane_up(
+ terminal_runtimes,
+ pane_id,
+ Self::selection_edge_scroll_lines(top - screen_row),
+ );
// Re-advance cursor after scroll so it reflects the new viewport position
- self.update_selection_cursor(pane_id, screen_col, screen_row);
+ self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row);
self.selection_autoscroll = Some(SelectionAutoscroll {
direction: SelectionAutoscrollDirection::Up,
last_mouse_screen_col: screen_col,
@@ -81,11 +96,12 @@ impl AppState {
// Cursor below pane — immediate scroll + set autoscroll state
if is_dragging {
self.scroll_pane_down(
+ terminal_runtimes,
pane_id,
Self::selection_edge_scroll_lines(screen_row - bottom),
);
// Re-advance cursor after scroll so it reflects the new viewport position
- self.update_selection_cursor(pane_id, screen_col, screen_row);
+ self.update_selection_cursor(terminal_runtimes, pane_id, screen_col, screen_row);
self.selection_autoscroll = Some(SelectionAutoscroll {
direction: SelectionAutoscrollDirection::Down,
last_mouse_screen_col: screen_col,
@@ -123,7 +139,11 @@ impl AppState {
}
}
- pub(super) fn scroll_selection_with_wheel(&mut self, mouse: MouseEvent) -> bool {
+ pub(super) fn scroll_selection_with_wheel(
+ &mut self,
+ terminal_runtimes: &TerminalRuntimeRegistry,
+ mouse: MouseEvent,
+ ) -> bool {
const LINES_PER_NOTCH: usize = 3;
let Some(selection) = self.selection.as_ref() else {
@@ -135,11 +155,15 @@ impl AppState {
let pane_id = selection.pane_id;
self.focus_pane(pane_id);
match mouse.kind {
- MouseEventKind::ScrollUp => self.scroll_pane_up(pane_id, LINES_PER_NOTCH),
- MouseEventKind::ScrollDown => self.scroll_pane_down(pane_id, LINES_PER_NOTCH),
+ MouseEventKind::ScrollUp => {
+ self.scroll_pane_up(terminal_runtimes, pane_id, LINES_PER_NOTCH)
+ }
+ MouseEventKind::ScrollDown => {
+ self.scroll_pane_down(terminal_runtimes, pane_id, LINES_PER_NOTCH)
+ }
_ => return false,
}
- self.update_selection_cursor(pane_id, mouse.column, mouse.row);
+ self.update_selection_cursor(terminal_runtimes, pane_id, mouse.column, mouse.row);
true
}
}
@@ -148,6 +172,7 @@ impl AppState {
mod autoscroll_tests {
use super::*;
use crate::layout::PaneInfo;
+ use crate::terminal::TerminalRuntimeRegistry;
use crate::workspace::Workspace;
use ratatui::layout::Rect;
@@ -188,7 +213,8 @@ mod autoscroll_tests {
let mut sel = crate::selection::Selection::anchor(pane_id, 5, 10, None);
sel.drag(4, 5, Rect::new(0, 5, 80, 24), None);
state.selection = Some(sel);
- state.update_selection_drag(5, 4);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 5, 4);
let autoscroll = state.selection_autoscroll.as_ref().unwrap();
assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Up);
}
@@ -200,7 +226,8 @@ mod autoscroll_tests {
let mut sel = crate::selection::Selection::anchor(pane_id, 5, 10, None);
sel.drag(0, 0, Rect::new(0, 0, 80, 24), None);
state.selection = Some(sel);
- state.update_selection_drag(0, 0);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 0, 0);
let autoscroll = state.selection_autoscroll.as_ref().unwrap();
assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Up);
}
@@ -211,7 +238,8 @@ mod autoscroll_tests {
let (mut state, pane_id) = make_state_with_pane();
state.selection = Some(crate::selection::Selection::anchor(pane_id, 0, 0, None));
// Same-cell drag on top edge row — still anchored
- state.update_selection_drag(0, 0);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 0, 0);
assert!(state.selection_autoscroll.is_none());
}
@@ -222,7 +250,8 @@ mod autoscroll_tests {
let mut sel = crate::selection::Selection::anchor(pane_id, 0, 0, None);
sel.drag(23, 0, Rect::new(0, 0, 80, 24), None);
state.selection = Some(sel);
- state.update_selection_drag(0, 23);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 0, 23);
let autoscroll = state.selection_autoscroll.as_ref().unwrap();
assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Down);
}
@@ -234,7 +263,8 @@ mod autoscroll_tests {
// Anchor at bottom edge row
state.selection = Some(crate::selection::Selection::anchor(pane_id, 23, 0, None));
// Same-cell drag — still anchored
- state.update_selection_drag(0, 23);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 0, 23);
assert!(state.selection_autoscroll.is_none());
}
@@ -246,7 +276,8 @@ mod autoscroll_tests {
sel.drag(5, 5, Rect::new(0, 0, 80, 24), None);
state.selection = Some(sel);
// Drag cursor one row below the pane bottom
- state.update_selection_drag(0, 24);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 0, 24);
let autoscroll = state.selection_autoscroll.as_ref().unwrap();
assert_eq!(autoscroll.direction, SelectionAutoscrollDirection::Down);
}
@@ -266,7 +297,8 @@ mod autoscroll_tests {
inner_rect: Rect::new(0, 0, 80, 24),
});
// Move cursor into safe zone (middle of pane, not on edge rows)
- state.update_selection_drag(5, 12);
+ let terminal_runtimes = TerminalRuntimeRegistry::new();
+ state.update_selection_drag(&terminal_runtimes, 5, 12);
assert!(state.selection_autoscroll.is_none());
}
}
diff --git a/src/app/input/terminal.rs b/src/app/input/terminal.rs
index 41a62e82..eee5b863 100644
--- a/src/app/input/terminal.rs
+++ b/src/app/input/terminal.rs
@@ -47,6 +47,7 @@ impl App {
} else {
super::navigate::execute_navigate_action_in_context(
&mut self.state,
+ &mut self.terminal_runtimes,
action,
super::navigate::ActionContext::Direct,
);
@@ -88,7 +89,9 @@ 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 = self.state.runtime_for_pane_in_workspace(ws_idx, pane_id)?;
+ let rt =
+ self.state
+ .runtime_for_pane_in_workspace(&self.terminal_runtimes, 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
@@ -116,9 +119,11 @@ impl App {
.unwrap_or(10)
.max(1);
if key_event.code == KeyCode::PageUp {
- self.state.scroll_pane_up(pane_id, lines);
+ self.state
+ .scroll_pane_up(&self.terminal_runtimes, pane_id, lines);
} else {
- self.state.scroll_pane_down(pane_id, lines);
+ self.state
+ .scroll_pane_down(&self.terminal_runtimes, pane_id, lines);
}
debug!(
code = ?key_event.code,
@@ -223,7 +228,7 @@ mod tests {
let start_metrics = app
.state
- .runtime_for_pane(pane_id)
+ .runtime_for_pane(&app.terminal_runtimes, pane_id)
.and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("initial scroll metrics");
let start_row = info.inner_rect.y;
@@ -242,7 +247,7 @@ mod tests {
let end_metrics = app
.state
- .runtime_for_pane(pane_id)
+ .runtime_for_pane(&app.terminal_runtimes, pane_id)
.and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after drag");
assert_eq!(
@@ -329,7 +334,7 @@ mod tests {
let start_metrics = app
.state
- .runtime_for_pane(pane_id)
+ .runtime_for_pane(&app.terminal_runtimes, pane_id)
.and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("initial scroll metrics");
let top_row = info.inner_rect.y;
@@ -340,7 +345,7 @@ mod tests {
let end_metrics = app
.state
- .runtime_for_pane(pane_id)
+ .runtime_for_pane(&app.terminal_runtimes, pane_id)
.and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after wheel");
assert_eq!(
@@ -523,7 +528,12 @@ mod tests {
let root_pane = workspace.tabs[0].root_pane;
workspace.tabs[0].runtimes.insert(
root_pane,
- crate::pane::PaneRuntime::test_with_scrollback_bytes(20, 5, 4096, b"alpha\nbeta\n"),
+ crate::terminal::TerminalRuntime::test_with_scrollback_bytes(
+ 20,
+ 5,
+ 4096,
+ b"alpha\nbeta\n",
+ ),
);
app.state.workspaces = vec![workspace];
app.state.active = Some(0);
@@ -602,6 +612,7 @@ mod tests {
api_rx,
crate::api::EventHub::default(),
);
+ app.state.default_shell = "/usr/bin/true".into();
let (workspace, terminal, runtime) = Workspace::new(
std::env::current_dir().unwrap_or_else(|_| "/".into()),
24,
@@ -615,9 +626,7 @@ mod tests {
)
.expect("workspace should spawn");
app.state.workspaces = vec![workspace];
- app.state
- .terminal_runtimes
- .insert(terminal.id.clone(), runtime);
+ app.terminal_runtimes.insert(terminal.id.clone(), runtime);
app.state.terminals.insert(terminal.id.clone(), terminal);
app.state.active = Some(0);
app.state.selected = 0;
@@ -639,6 +648,11 @@ mod tests {
assert_eq!(app.state.workspaces[0].tabs[0].layout.pane_count(), 2);
assert!(app.state.workspaces[0].tabs[0].zoomed);
assert_eq!(app.state.mode, Mode::Terminal);
+
+ let runtimes: Vec<_> = app.terminal_runtimes.drain().collect();
+ for (_terminal_id, runtime) in runtimes {
+ runtime.shutdown();
+ }
}
#[tokio::test]
@@ -648,7 +662,7 @@ mod tests {
let pane_id = ws.tabs[0].root_pane;
let pane_infos = ws.tabs[0].layout.panes(Rect::new(0, 0, 80, 24));
let info = pane_infos[0].clone();
- let (runtime, mut rx) = crate::pane::PaneRuntime::test_with_channel(
+ let (runtime, mut rx) = crate::terminal::TerminalRuntime::test_with_channel(
info.inner_rect.width,
info.inner_rect.height,
);
@@ -677,7 +691,7 @@ mod tests {
let info = pane_infos[0].clone();
ws.tabs[0].runtimes.insert(
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,
@@ -693,8 +707,8 @@ mod tests {
let start_metrics = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("initial scroll metrics");
assert_eq!(start_metrics.offset_from_bottom, 0);
@@ -702,8 +716,8 @@ mod tests {
let end_metrics = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after PageUp");
assert_eq!(
end_metrics.offset_from_bottom,
@@ -720,7 +734,7 @@ mod tests {
let info = pane_infos[0].clone();
ws.tabs[0].runtimes.insert(
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,
@@ -737,8 +751,8 @@ mod tests {
app.handle_terminal_key_headless(TerminalKey::new(KeyCode::PageUp, KeyModifiers::empty()));
let after_up = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after PageUp");
assert!(after_up.offset_from_bottom > 0);
@@ -748,8 +762,8 @@ mod tests {
));
let after_down = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after PageDown");
assert_eq!(after_down.offset_from_bottom, 0);
}
@@ -763,7 +777,7 @@ mod tests {
let info = pane_infos[0].clone();
ws.tabs[0].runtimes.insert(
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,
@@ -780,8 +794,8 @@ mod tests {
app.handle_terminal_key_headless(TerminalKey::new(KeyCode::PageUp, KeyModifiers::empty()));
let after_press = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after PageUp press");
assert_eq!(
after_press.offset_from_bottom,
@@ -795,8 +809,8 @@ mod tests {
let after_release = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after PageUp release");
assert_eq!(
after_release.offset_from_bottom,
@@ -813,7 +827,7 @@ mod tests {
let info = pane_infos[0].clone();
ws.tabs[0].runtimes.insert(
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,
@@ -831,8 +845,8 @@ mod tests {
let metrics = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after modified PageUp");
assert_eq!(metrics.offset_from_bottom, 0);
}
@@ -848,7 +862,7 @@ mod tests {
bytes.extend_from_slice(&numbered_lines_bytes(64));
ws.tabs[0].runtimes.insert(
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,
@@ -864,8 +878,8 @@ mod tests {
let start_metrics = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("initial scroll metrics");
assert_eq!(start_metrics.offset_from_bottom, 0);
@@ -873,8 +887,8 @@ mod tests {
let end_metrics = app
.state
- .runtime_for_pane_in_workspace(0, pane_id)
- .and_then(crate::pane::PaneRuntime::scroll_metrics)
+ .runtime_for_pane_in_workspace(&app.terminal_runtimes, 0, pane_id)
+ .and_then(crate::terminal::TerminalRuntime::scroll_metrics)
.expect("scroll metrics after PageUp");
// Forwarded to pane, so test runtime doesn't process it — scroll stays at bottom.
assert_eq!(end_metrics.offset_from_bottom, 0);