1use std::path::Path;
2
3use anyhow::Context;
4use serde_json::{json, Value};
5
6use crate::commands::workspace_edit::apply_workspace_edit;
7use crate::lsp::client::{path_to_uri, LspClient};
8use crate::lsp::files::FileTracker;
9
10pub async fn handle_format(
18 path: &Path,
19 client: &mut LspClient,
20 file_tracker: &mut FileTracker,
21 project_root: &Path,
22) -> anyhow::Result<Value> {
23 let abs_path = if path.is_absolute() {
24 path.to_path_buf()
25 } else {
26 project_root.join(path)
27 };
28
29 file_tracker
31 .reopen(&abs_path, client.transport_mut())
32 .await?;
33
34 let uri = path_to_uri(&abs_path)?;
35 let params = json!({
36 "textDocument": { "uri": uri.as_str() },
37 "options": { "tabSize": 4, "insertSpaces": true }
38 });
39
40 let request_id = client
41 .transport_mut()
42 .send_request("textDocument/formatting", params)
43 .await?;
44
45 let response = client
46 .wait_for_response_public(request_id)
47 .await
48 .context("textDocument/formatting request failed")?;
49
50 let edits: Vec<Value> = if let Value::Array(arr) = &response {
51 arr.clone()
52 } else {
53 vec![]
54 };
55
56 let n = edits.len();
57 let rel = abs_path
58 .strip_prefix(project_root)
59 .unwrap_or(&abs_path)
60 .to_string_lossy()
61 .to_string();
62
63 if n == 0 {
64 return Ok(json!({
65 "path": rel,
66 "edits_applied": 0,
67 }));
68 }
69
70 let mut changes = serde_json::Map::new();
72 changes.insert(uri.as_str().to_string(), serde_json::to_value(&edits)?);
73 let workspace_edit = json!({ "changes": changes });
74 apply_workspace_edit(&workspace_edit, project_root)?;
75
76 Ok(json!({
77 "path": rel,
78 "edits_applied": n,
79 }))
80}
81
82#[cfg(test)]
83mod tests {
84 use serde_json::json;
85
86 #[test]
87 fn format_response_shape() {
88 let data = json!({ "path": "src/lib.rs", "edits_applied": 5 });
89 assert_eq!(data["edits_applied"].as_u64(), Some(5));
90 }
91}