1use std::path::Path;
4
5use crate::context::AppContext;
6use crate::edit;
7use crate::protocol::{RawRequest, Response};
8
9pub fn handle_write(req: &RawRequest, ctx: &AppContext) -> Response {
18 let file = match req.params.get("file").and_then(|v| v.as_str()) {
19 Some(f) => f,
20 None => {
21 return Response::error(
22 &req.id,
23 "invalid_request",
24 "write: missing required param 'file'",
25 );
26 }
27 };
28
29 let content = match req.params.get("content").and_then(|v| v.as_str()) {
30 Some(c) => c,
31 None => {
32 return Response::error(
33 &req.id,
34 "invalid_request",
35 "write: missing required param 'content'",
36 );
37 }
38 };
39
40 let create_dirs = req
41 .params
42 .get("create_dirs")
43 .and_then(|v| v.as_bool())
44 .unwrap_or(true);
45
46 if let Err(resp) = ctx.validate_path(&req.id, Path::new(file)) {
47 return resp;
48 }
49 let path = Path::new(file);
50 let existed = path.exists();
51
52 let original = if existed {
54 std::fs::read_to_string(path).unwrap_or_default()
55 } else {
56 String::new()
57 };
58
59 if edit::is_dry_run(&req.params) {
61 let dr = edit::dry_run_diff(&original, content, path);
62 return Response::success(
63 &req.id,
64 serde_json::json!({
65 "ok": true, "dry_run": true, "diff": dr.diff, "syntax_valid": dr.syntax_valid,
66 }),
67 );
68 }
69
70 let backup_id = match edit::auto_backup(ctx, path, "write: pre-write backup") {
72 Ok(id) => id,
73 Err(e) => {
74 return Response::error(&req.id, e.code(), e.to_string());
75 }
76 };
77
78 if create_dirs {
80 if let Some(parent) = path.parent() {
81 if !parent.exists() {
82 if let Err(e) = std::fs::create_dir_all(parent) {
83 return Response::error(
84 &req.id,
85 "invalid_request",
86 format!("write: failed to create directories: {}", e),
87 );
88 }
89 }
90 }
91 }
92
93 let mut write_result =
95 match edit::write_format_validate(path, content, &ctx.config(), &req.params) {
96 Ok(r) => r,
97 Err(e) => {
98 return Response::error(&req.id, e.code(), e.to_string());
99 }
100 };
101
102 if let Ok(final_content) = std::fs::read_to_string(path) {
103 write_result.lsp_diagnostics = ctx.lsp_post_write(path, &final_content, &req.params);
104 }
105
106 log::debug!("write: {}", file);
107
108 let mut result = serde_json::json!({
109 "file": file,
110 "created": !existed,
111 "formatted": write_result.formatted,
112 });
113
114 if let Some(valid) = write_result.syntax_valid {
115 result["syntax_valid"] = serde_json::json!(valid);
116 }
117
118 if let Some(ref reason) = write_result.format_skipped_reason {
119 result["format_skipped_reason"] = serde_json::json!(reason);
120 }
121
122 if write_result.validate_requested {
123 result["validation_errors"] = serde_json::json!(write_result.validation_errors);
124 }
125 if let Some(ref reason) = write_result.validate_skipped_reason {
126 result["validate_skipped_reason"] = serde_json::json!(reason);
127 }
128
129 if let Some(ref id) = backup_id {
130 result["backup_id"] = serde_json::json!(id);
131 }
132
133 write_result.append_lsp_diagnostics_to(&mut result);
134
135 if edit::wants_diff(&req.params) {
137 let final_content = std::fs::read_to_string(path).unwrap_or_else(|_| content.to_string());
138 result["diff"] = edit::compute_diff_info(&original, &final_content);
139 }
140
141 Response::success(&req.id, result)
142}