1use std::path::Path;
4
5use lsp_types::FileChangeType;
6
7use crate::context::AppContext;
8use crate::edit;
9use crate::protocol::{RawRequest, Response};
10
11pub fn handle_write(req: &RawRequest, ctx: &AppContext) -> Response {
20 let op_id = crate::backup::new_op_id();
21 let file = match req.params.get("file").and_then(|v| v.as_str()) {
22 Some(f) => f,
23 None => {
24 return Response::error(
25 &req.id,
26 "invalid_request",
27 "write: missing required param 'file'",
28 );
29 }
30 };
31
32 let content = match req.params.get("content").and_then(|v| v.as_str()) {
33 Some(c) => c,
34 None => {
35 return Response::error(
36 &req.id,
37 "invalid_request",
38 "write: missing required param 'content'",
39 );
40 }
41 };
42
43 let create_dirs = req
44 .params
45 .get("create_dirs")
46 .and_then(|v| v.as_bool())
47 .unwrap_or(true);
48
49 let path = match ctx.validate_path(&req.id, Path::new(file)) {
50 Ok(path) => path,
51 Err(resp) => return resp,
52 };
53 let existed = path.exists();
54
55 let original = if existed {
60 match std::fs::read_to_string(path.as_path()) {
61 Ok(content) => content,
62 Err(error) => {
63 crate::slog_warn!(
64 "write: failed to read existing file before diff for {}: {}",
65 file,
66 error
67 );
68 String::new()
69 }
70 }
71 } else {
72 String::new()
73 };
74
75 if edit::wants_preview(&req.params) {
76 let mut result = serde_json::json!({
77 "file": file,
78 "created": !existed,
79 "formatted": false,
80 });
81 if existed && original == content {
82 result["no_op"] = serde_json::json!(true);
83 }
84 edit::attach_preview_diff(&mut result, &req.params, file, &original, content);
85 return Response::success(&req.id, result);
86 }
87
88 let backup_id = if existed {
91 match edit::auto_backup(
92 ctx,
93 req.session(),
94 path.as_path(),
95 "write: pre-write backup",
96 Some(&op_id),
97 ) {
98 Ok(id) => id,
99 Err(e) => {
100 return Response::error(&req.id, e.code(), e.to_string());
101 }
102 }
103 } else {
104 match ctx.backup().lock().snapshot_op_tombstone(
105 req.session(),
106 &op_id,
107 path.as_path(),
108 "write: file created by write",
109 ) {
110 Ok(id) => id,
111 Err(e) => return Response::error(&req.id, e.code(), e.to_string()),
112 }
113 };
114
115 if create_dirs {
117 if let Some(parent) = path.parent() {
118 if !parent.exists() {
119 if let Err(e) = std::fs::create_dir_all(parent) {
120 if !existed {
121 ctx.backup()
122 .lock()
123 .discard_operation_entries(req.session(), &op_id);
124 }
125 return Response::error(
126 &req.id,
127 "invalid_request",
128 format!("write: failed to create directories: {}", e),
129 );
130 }
131 }
132 }
133 }
134
135 let mut write_result =
137 match edit::write_format_validate(path.as_path(), content, &ctx.config(), &req.params) {
138 Ok(r) => r,
139 Err(e) => {
140 if !existed {
141 ctx.backup()
142 .lock()
143 .discard_operation_entries(req.session(), &op_id);
144 }
145 return Response::error(&req.id, e.code(), e.to_string());
146 }
147 };
148
149 if write_result.rolled_back {
150 ctx.backup()
151 .lock()
152 .discard_operation_entries(req.session(), &op_id);
153 }
154
155 if let Ok(final_content) = std::fs::read_to_string(path.as_path()) {
156 let config_change_type = if existed {
157 FileChangeType::CHANGED
158 } else {
159 FileChangeType::CREATED
160 };
161 ctx.lsp_notify_watched_config_file(path.as_path(), config_change_type);
162 write_result.lsp_outcome = ctx.lsp_post_write(path.as_path(), &final_content, &req.params);
163 }
164
165 log::debug!("write: {}", file);
166
167 let mut result = serde_json::json!({
168 "file": file,
169 "created": !existed,
170 "formatted": write_result.formatted,
171 });
172
173 if let Some(valid) = write_result.syntax_valid {
174 result["syntax_valid"] = serde_json::json!(valid);
175 }
176
177 if let Some(ref reason) = write_result.format_skipped_reason {
178 result["format_skipped_reason"] = serde_json::json!(reason);
179 }
180
181 if write_result.validate_requested {
182 result["validation_errors"] = serde_json::json!(write_result.validation_errors);
183 }
184 if let Some(ref reason) = write_result.validate_skipped_reason {
185 result["validate_skipped_reason"] = serde_json::json!(reason);
186 }
187
188 if let Some(ref id) = backup_id {
189 result["backup_id"] = serde_json::json!(id);
190 }
191
192 write_result.append_lsp_diagnostics_to(&mut result);
193 write_result.append_reformatted_excerpt_to(&mut result);
194
195 let final_content =
201 std::fs::read_to_string(path.as_path()).unwrap_or_else(|_| content.to_string());
202 if existed && original == final_content {
203 result["no_op"] = serde_json::json!(true);
204 }
205
206 if edit::wants_diff(&req.params) {
207 result["diff"] = edit::compute_diff_for_response(&req.params, &original, &final_content);
208 }
209
210 Response::success(&req.id, result)
211}