Skip to main content

krait/commands/
format.rs

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
10/// Format a file using the LSP `textDocument/formatting` request.
11///
12/// Re-opens the file to ensure the LSP has fresh on-disk content, then
13/// applies the returned `TextEdit` list atomically.
14///
15/// # Errors
16/// Returns an error if the LSP request fails or the file cannot be written.
17pub 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    // Re-open so the LSP sees current on-disk content
30    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    // Build a workspace_edit from the flat list of TextEdits
71    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}