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/input | |
| 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/input')
| -rw-r--r-- | src/app/input/mod.rs | 12 | ||||
| -rw-r--r-- | src/app/input/navigate.rs | 91 |
2 files changed, 101 insertions, 2 deletions
diff --git a/src/app/input/mod.rs b/src/app/input/mod.rs index 1b5dfefe..339f7293 100644 --- a/src/app/input/mod.rs +++ b/src/app/input/mod.rs @@ -341,6 +341,13 @@ impl App { }; self.last_pane_click = None; + match self.invoke_plugin_link_handler_for_url(&url, info.id) { + Ok(true) => return true, + Ok(false) => {} + Err(err) => { + tracing::warn!(err = %err, url = %url, "failed to invoke plugin link handler"); + } + } if let Err(err) = crate::platform::open_url(&url) { tracing::warn!(err = %err, url = %url, "failed to open pane URL"); } @@ -514,6 +521,7 @@ impl AppState { self.pane_scrollback_limit_bytes, self.host_terminal_theme, crate::pane::PaneShellConfig::new(&self.default_shell, self.shell_mode), + Vec::new(), ) { let new_id = new_pane.pane_id; terminal_runtimes.insert(new_pane.terminal.id.clone(), new_pane.runtime); @@ -622,7 +630,9 @@ fn wait_for_file(path: &std::path::Path) -> String { let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); while std::time::Instant::now() < deadline { if let Ok(content) = std::fs::read_to_string(path) { - return content; + if !content.is_empty() { + return content; + } } std::thread::sleep(std::time::Duration::from_millis(20)); } diff --git a/src/app/input/navigate.rs b/src/app/input/navigate.rs index b27b5cb9..7072f659 100644 --- a/src/app/input/navigate.rs +++ b/src/app/input/navigate.rs @@ -140,6 +140,9 @@ impl App { crate::config::CustomCommandAction::Pane => { self.spawn_pane_command(&binding.command, Vec::new()) } + crate::config::CustomCommandAction::PluginAction => self + .invoke_plugin_action_from_keybind(binding.command.clone()) + .map_err(std::io::Error::other), }; match result { Ok(()) => finish_custom_command_context(&mut self.state, context, previous_mode), @@ -317,7 +320,7 @@ impl App { new_cols, cwd, command, - &env, + env, self.state.pane_scrollback_limit_bytes, self.state.host_terminal_theme, )?; @@ -355,6 +358,92 @@ impl App { self.state.mode = Mode::Terminal; Ok(()) } + + pub(crate) fn spawn_overlay_argv_command( + &mut self, + argv: &[String], + cwd: Option<std::path::PathBuf>, + extra_env: Vec<(String, String)>, + temp_files: Vec<std::path::PathBuf>, + ) -> std::io::Result<(usize, crate::workspace::NewPane)> { + let Some(ws_idx) = self.state.active else { + return Err(std::io::Error::other("no active workspace")); + }; + let previous_focus_target = self.state.current_pane_focus_target(); + let (rows, cols) = self.state.estimate_pane_size(); + let new_rows = rows.max(4); + let new_cols = cols.max(10); + + let ws = self + .state + .workspaces + .get(ws_idx) + .ok_or_else(|| std::io::Error::other("active workspace disappeared"))?; + let previous_focus = ws + .focused_pane_id() + .ok_or_else(|| std::io::Error::other("no focused pane"))?; + let cwd = cwd.or_else(|| { + ws.active_tab().and_then(|tab| { + tab.cwd_for_pane( + previous_focus, + &self.state.terminals, + &self.terminal_runtimes, + ) + }) + }); + + let (tab_idx, new_pane, workspace_id) = { + let ws = self + .state + .workspaces + .get_mut(ws_idx) + .ok_or_else(|| std::io::Error::other("active workspace disappeared"))?; + let previous_zoomed = ws.active_tab().map(|tab| tab.zoomed).unwrap_or(false); + let result = ws.split_pane_argv_command( + previous_focus, + Direction::Horizontal, + new_rows, + new_cols, + cwd, + argv, + extra_env, + self.state.pane_scrollback_limit_bytes, + self.state.host_terminal_theme, + true, + ); + let (tab_idx, new_pane) = match result { + Some(Ok(result)) => result, + Some(Err(err)) => return Err(err), + None => return Err(std::io::Error::other("focused pane disappeared")), + }; + ws.tabs + .get_mut(tab_idx) + .ok_or_else(|| std::io::Error::other("plugin overlay tab disappeared"))? + .zoomed = true; + self.overlay_panes.insert( + new_pane.pane_id, + super::super::OverlayPaneState { + ws_idx, + tab_idx, + previous_focus, + previous_zoomed, + temp_files, + }, + ); + (tab_idx, new_pane, ws.id.clone()) + }; + + let new_focus_target = crate::app::state::PaneFocusTarget { + workspace_id, + pane_id: new_pane.pane_id, + }; + if previous_focus_target.as_ref() != Some(&new_focus_target) { + self.state.previous_pane_focus = previous_focus_target; + } + self.state.switch_workspace_tab(ws_idx, tab_idx); + self.state.mode = Mode::Terminal; + Ok((ws_idx, new_pane)) + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
