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/server | |
| 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/server')
| -rw-r--r-- | src/server/headless.rs | 67 | ||||
| -rw-r--r-- | src/server/notifications.rs | 2 |
2 files changed, 65 insertions, 4 deletions
diff --git a/src/server/headless.rs b/src/server/headless.rs index ed81b015..750e9250 100644 --- a/src/server/headless.rs +++ b/src/server/headless.rs @@ -100,7 +100,7 @@ fn non_empty_body(value: &str) -> Option<String> { enum LoopEvent { Timer, Internal(AppEvent), - Api(api::ApiRequestMessage), + Api(Box<api::ApiRequestMessage>), ServerEvent(ServerEvent), RenderRequested, } @@ -637,7 +637,7 @@ impl HeadlessServer { let event = { tokio::select! { maybe_api = self.app.api_rx.recv() => match maybe_api { - Some(msg) => LoopEvent::Api(msg), + Some(msg) => LoopEvent::Api(Box::new(msg)), None => LoopEvent::Timer, }, maybe_ev = self.app.event_rx.recv() => match maybe_ev { @@ -662,7 +662,7 @@ impl HeadlessServer { } } LoopEvent::Api(msg) => { - if self.handle_api_request_with_shutdown_check(msg) { + if self.handle_api_request_with_shutdown_check(*msg) { needs_render = true; needs_full_render = true; } @@ -1603,6 +1603,39 @@ impl HeadlessServer { .unwrap_or_else(|_| "{}".to_string()) } + fn handle_client_window_title_api(&mut self, id: String, title: Option<String>) -> String { + use api::schema::{ClientWindowTitleReason, ResponseResult}; + + let title = match title { + Some(title) => match sanitize_window_title_text(&title, 200) { + Some(title) => Some(title), + None => { + return serde_json::to_string(&api::schema::ErrorResponse { + id, + error: api::schema::ErrorBody { + code: "invalid_params".into(), + message: "window title is empty".into(), + }, + }) + .unwrap_or_else(|_| "{}".to_string()); + } + }, + None => None, + }; + let set_title = title.is_some(); + let changed = self.send_to_foreground_client(ServerMessage::WindowTitle { title }); + let reason = match (changed, set_title) { + (true, true) => ClientWindowTitleReason::Set, + (true, false) => ClientWindowTitleReason::Cleared, + (false, _) => ClientWindowTitleReason::NoForegroundClient, + }; + serde_json::to_string(&api::schema::SuccessResponse { + id, + result: ResponseResult::ClientWindowTitle { changed, reason }, + }) + .unwrap_or_else(|_| "{}".to_string()) + } + fn forward_api_notification_sound(&mut self, sound: api::schema::NotificationShowSound) { let Some(sound) = sound.to_sound() else { return; @@ -2511,6 +2544,23 @@ impl HeadlessServer { return true; } + match &msg.request.method { + api::schema::Method::ClientWindowTitleSet(params) => { + let response = self.handle_client_window_title_api( + msg.request.id.clone(), + Some(params.title.clone()), + ); + let _ = msg.respond_to.send(response); + return true; + } + api::schema::Method::ClientWindowTitleClear(_) => { + let response = self.handle_client_window_title_api(msg.request.id.clone(), None); + let _ = msg.respond_to.send(response); + return true; + } + _ => {} + } + let mut changed = api::request_changes_ui(&msg.request); let skip_default_workspace = matches!( &msg.request.method, @@ -3544,6 +3594,17 @@ fn sanitize_notification_text(value: &str, max_chars: usize) -> Option<String> { (!sanitized.is_empty()).then_some(sanitized) } +fn sanitize_window_title_text(value: &str, max_chars: usize) -> Option<String> { + let sanitized = value + .chars() + .filter(|ch| !matches!(*ch, '\u{1b}' | '\u{7}' | '\u{9c}') && !ch.is_control()) + .take(max_chars) + .collect::<String>() + .trim() + .to_string(); + (!sanitized.is_empty()).then_some(sanitized) +} + fn server_config_diagnostic_summaries(diagnostics: &[String]) -> (Option<String>, Option<String>) { let without_keybindings = diagnostics .iter() diff --git a/src/server/notifications.rs b/src/server/notifications.rs index 63bfe944..79ff63ca 100644 --- a/src/server/notifications.rs +++ b/src/server/notifications.rs @@ -121,10 +121,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(); let deadline = std::time::Instant::now() + std::time::Duration::from_secs(2); |
