diff options
| author | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-14 02:43:45 +0300 |
|---|---|---|
| committer | Ogulcan Celik <ogulcancelik@gmail.com> | 2026-06-14 03:47:07 +0300 |
| commit | fbd20ad6e488fb3b9efc1612fdffee6e672976c1 (patch) | |
| tree | d21daf93241786f8aa6fde23b41cdb9e15182b79 /src/app/actions.rs | |
| parent | d35c642ef36ee5ac2afe7c1b18ad5a717bc807d8 (diff) | |
feat: add plugin v1 system
Add manifest-driven plugin actions, event hooks, pane entrypoints, link handlers, and host APIs.
Support plugin link, GitHub install, build commands, installed plugin registry, pane movement, runtime context, and plugin docs.
Diffstat (limited to 'src/app/actions.rs')
| -rw-r--r-- | src/app/actions.rs | 75 |
1 files changed, 70 insertions, 5 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index 23b886d9..a7d99f35 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -1349,6 +1349,15 @@ impl AppState { .collect() } + pub(crate) fn pane_ids_for_workspace(&self, ws_idx: usize) -> Vec<PaneId> { + self.workspaces + .get(ws_idx) + .into_iter() + .flat_map(|ws| &ws.tabs) + .flat_map(|tab| tab.layout.pane_ids()) + .collect() + } + pub(crate) fn terminal_ids_for_tab( &self, ws_idx: usize, @@ -1363,6 +1372,14 @@ impl AppState { .collect() } + pub(crate) fn pane_ids_for_tab(&self, ws_idx: usize, tab_idx: usize) -> Vec<PaneId> { + self.workspaces + .get(ws_idx) + .and_then(|ws| ws.tabs.get(tab_idx)) + .map(|tab| tab.layout.pane_ids()) + .unwrap_or_default() + } + pub(crate) fn terminal_id_for_pane( &self, ws_idx: usize, @@ -1395,6 +1412,15 @@ impl AppState { } } + pub(crate) fn remove_plugin_pane_records( + &mut self, + pane_ids: impl IntoIterator<Item = PaneId>, + ) { + for pane_id in pane_ids { + self.plugin_panes.remove(&pane_id); + } + } + pub fn close_selected_workspace(&mut self) { if self.workspaces.is_empty() { return; @@ -1422,12 +1448,15 @@ impl AppState { .unwrap_or_else(|| vec![self.selected]); let mut terminal_ids = Vec::new(); + let mut pane_ids = Vec::new(); for idx in &close_indices { terminal_ids.extend(self.terminal_ids_for_workspace(*idx)); + pane_ids.extend(self.pane_ids_for_workspace(*idx)); if let Some(workspace_id) = self.workspaces.get(*idx).map(|ws| ws.id.clone()) { crate::logging::workspace_closed(&workspace_id); } } + self.remove_plugin_pane_records(pane_ids); for idx in close_indices.iter().rev() { self.workspaces.remove(*idx); } @@ -1761,9 +1790,14 @@ impl AppState { .and_then(|(i, pane_id)| self.terminal_id_for_pane(i, pane_id)) .into_iter() .collect::<Vec<_>>(); + let pane_ids = active + .and_then(|i| self.workspaces.get(i).and_then(|ws| ws.focused_pane_id())) + .into_iter() + .collect::<Vec<_>>(); let should_close_workspace = active .and_then(|i| self.workspaces.get_mut(i)) .is_some_and(|ws| ws.close_focused()); + self.remove_plugin_pane_records(pane_ids); if should_close_workspace { if let Some(active) = active { self.selected = active; @@ -1810,12 +1844,18 @@ impl AppState { .get(ws_idx) .map(|ws| self.terminal_ids_for_tab(ws_idx, ws.active_tab)) .unwrap_or_default(); + let pane_ids = self + .workspaces + .get(ws_idx) + .map(|ws| self.pane_ids_for_tab(ws_idx, ws.active_tab)) + .unwrap_or_default(); let Some(ws) = self.workspaces.get_mut(ws_idx) else { return false; }; let workspace_id = ws.id.clone(); let closing_tab_id = format!("{}:{}", workspace_id, ws.active_tab + 1); ws.close_active_tab(); + self.remove_plugin_pane_records(pane_ids); self.remove_unattached_terminal_ids(terminal_ids); crate::logging::tab_closed(&workspace_id, &closing_tab_id); self.tab_scroll_follow_active = true; @@ -2502,6 +2542,7 @@ impl AppState { } AppEvent::WorktreeAddFinished(_) => Vec::new(), AppEvent::WorktreeRemoveFinished(_) => Vec::new(), + AppEvent::PluginCommandFinished { .. } => Vec::new(), } } @@ -2791,6 +2832,7 @@ impl AppState { fn handle_pane_died(&mut self, pane_id: PaneId) { self.pending_agent_notifications.remove(&pane_id); + self.plugin_panes.remove(&pane_id); let ws_idx = self .workspaces .iter() @@ -3161,10 +3203,10 @@ mod tests { 0, crate::terminal_theme::TerminalTheme::default(), crate::pane::PaneShellConfig::new("/bin/sh", crate::config::ShellModeConfig::NonLogin), + &crate::pane::PaneLaunchEnv::default(), events, std::sync::Arc::new(tokio::sync::Notify::new()), std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)), - None, ) .unwrap(); @@ -4875,11 +4917,19 @@ mod tests { #[test] fn close_pane_removes_from_workspace() { let mut state = app_with_workspaces(&["test"]); - state.workspaces[0].test_split(Direction::Horizontal); + let closed = state.workspaces[0].test_split(Direction::Horizontal); assert_eq!(state.workspaces[0].panes.len(), 2); + state.plugin_panes.insert( + closed, + crate::app::state::PluginPaneRecord { + plugin_id: "example.pane".into(), + entrypoint: "board".into(), + }, + ); state.close_pane(); assert_eq!(state.workspaces[0].panes.len(), 1); + assert!(!state.plugin_panes.contains_key(&closed)); } #[test] @@ -4935,22 +4985,37 @@ mod tests { state.workspaces[0].switch_tab(tab_idx); let pane_id = state.workspaces[0].tabs[tab_idx].root_pane; let terminal_id = state.terminal_id_for_pane(0, pane_id).unwrap(); + state.plugin_panes.insert( + pane_id, + crate::app::state::PluginPaneRecord { + plugin_id: "example.pane".into(), + entrypoint: "board".into(), + }, + ); state.close_tab(); assert!(!state.terminals.contains_key(&terminal_id)); + assert!(!state.plugin_panes.contains_key(&pane_id)); } #[test] fn close_workspace_removes_unattached_terminal_states() { let mut state = app_with_workspaces(&["one", "two"]); - let terminal_id = state - .terminal_id_for_pane(0, state.workspaces[0].tabs[0].root_pane) - .unwrap(); + let pane_id = state.workspaces[0].tabs[0].root_pane; + let terminal_id = state.terminal_id_for_pane(0, pane_id).unwrap(); + state.plugin_panes.insert( + pane_id, + crate::app::state::PluginPaneRecord { + plugin_id: "example.pane".into(), + entrypoint: "board".into(), + }, + ); state.close_selected_workspace(); assert!(!state.terminals.contains_key(&terminal_id)); + assert!(!state.plugin_panes.contains_key(&pane_id)); } #[test] |
