//! `workspace/executeCommand` dispatcher. //! //! Phase 13 lands the router + the first command. Phases 14–17 register //! their own commands here. Each handler returns the `WorkspaceEdit` it //! wants applied; the `Backend` calls `client.apply_edit` after the call. //! //! Phase 13 surface: //! - `nuwiki.file.delete` — args: `{ "uri": }` use serde::Deserialize; use tower_lsp::lsp_types::{Url, WorkspaceEdit}; use crate::edits::{op_delete, WorkspaceEditBuilder}; use crate::Backend; /// All commands the server advertises in `ExecuteCommandOptions.commands`. /// Keep this in lock-step with the match arms in `execute` below — adding /// here without a handler will fail at runtime; adding a handler without /// listing here means the client won't see it as available. pub const COMMANDS: &[&str] = &["nuwiki.file.delete"]; /// Dispatch an `executeCommand` request. Returns `Ok(None)` for commands /// that don't produce a `WorkspaceEdit` (e.g. read-only queries); returns /// `Err` with a human-readable message for unknown commands or bad args /// — the LSP backend logs it and returns `null` to the client. pub(crate) async fn execute( _backend: &Backend, command: &str, args: Vec, ) -> Result, String> { match command { "nuwiki.file.delete" => file_delete(args), other => Err(format!("unknown nuwiki command: {other}")), } } fn file_delete(args: Vec) -> Result, String> { #[derive(Deserialize)] struct Args { uri: Url, } let raw = args .into_iter() .next() .ok_or_else(|| "nuwiki.file.delete: missing arguments".to_string())?; let parsed: Args = serde_json::from_value(raw) .map_err(|e| format!("nuwiki.file.delete: invalid args — {e}"))?; let mut builder = WorkspaceEditBuilder::new(); builder.file_op(op_delete(parsed.uri)); Ok(Some(builder.build())) }