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 if super::github_comments::is_github_resource_path(file) {
44 return super::github_comments::handle_comment_write(req, ctx, file, content);
45 }
46
47 let create_dirs = req
48 .params
49 .get("create_dirs")
50 .and_then(|v| v.as_bool())
51 .unwrap_or(true);
52
53 let path = match ctx.validate_path(&req.id, Path::new(file)) {
54 Ok(path) => path,
55 Err(resp) => return resp,
56 };
57 let existed = path.exists();
58
59 let original = if existed {
64 match std::fs::read_to_string(path.as_path()) {
65 Ok(content) => content,
66 Err(error) => {
67 crate::slog_warn!(
68 "write: failed to read existing file before diff for {}: {}",
69 file,
70 error
71 );
72 String::new()
73 }
74 }
75 } else {
76 String::new()
77 };
78
79 if edit::wants_preview(&req.params) {
80 let mut result = serde_json::json!({
81 "file": file,
82 "created": !existed,
83 "formatted": false,
84 });
85 if existed && original == content {
86 result["no_op"] = serde_json::json!(true);
87 }
88 edit::attach_preview_diff(&mut result, &req.params, file, &original, content);
89 return Response::success(&req.id, result);
90 }
91
92 let backup_id = if existed {
95 match edit::auto_backup(
96 ctx,
97 req.session(),
98 path.as_path(),
99 "write: pre-write backup",
100 Some(&op_id),
101 ) {
102 Ok(id) => id,
103 Err(e) => {
104 return Response::error(&req.id, e.code(), e.to_string());
105 }
106 }
107 } else {
108 match ctx.backup().lock().snapshot_op_tombstone(
109 req.session(),
110 &op_id,
111 path.as_path(),
112 "write: file created by write",
113 ) {
114 Ok(id) => id,
115 Err(e) => return Response::error(&req.id, e.code(), e.to_string()),
116 }
117 };
118
119 if create_dirs {
121 if let Some(parent) = path.parent() {
122 if !parent.exists() {
123 if let Err(e) = std::fs::create_dir_all(parent) {
124 if !existed {
125 ctx.backup()
126 .lock()
127 .discard_operation_entries(req.session(), &op_id);
128 }
129 return Response::error(
130 &req.id,
131 "invalid_request",
132 format!("write: failed to create directories: {}", e),
133 );
134 }
135 }
136 }
137 }
138
139 let mut write_result =
141 match edit::write_format_validate(path.as_path(), content, &ctx.config(), &req.params) {
142 Ok(r) => r,
143 Err(e) => {
144 if !existed {
145 ctx.backup()
146 .lock()
147 .discard_operation_entries(req.session(), &op_id);
148 }
149 return Response::error(&req.id, e.code(), e.to_string());
150 }
151 };
152
153 if write_result.rolled_back {
154 ctx.backup()
155 .lock()
156 .discard_operation_entries(req.session(), &op_id);
157 }
158
159 if let Ok(final_content) = std::fs::read_to_string(path.as_path()) {
160 let config_change_type = if existed {
161 FileChangeType::CHANGED
162 } else {
163 FileChangeType::CREATED
164 };
165 ctx.lsp_notify_watched_config_file(path.as_path(), config_change_type);
166 write_result.lsp_outcome = ctx.lsp_post_write(path.as_path(), &final_content, &req.params);
167 }
168
169 log::debug!("write: {}", file);
170
171 let mut result = serde_json::json!({
172 "file": file,
173 "created": !existed,
174 "formatted": write_result.formatted,
175 });
176
177 if let Some(valid) = write_result.syntax_valid {
178 result["syntax_valid"] = serde_json::json!(valid);
179 }
180
181 if let Some(ref reason) = write_result.format_skipped_reason {
182 result["format_skipped_reason"] = serde_json::json!(reason);
183 }
184
185 if write_result.validate_requested {
186 result["validation_errors"] = serde_json::json!(write_result.validation_errors);
187 }
188 if let Some(ref reason) = write_result.validate_skipped_reason {
189 result["validate_skipped_reason"] = serde_json::json!(reason);
190 }
191
192 if let Some(ref id) = backup_id {
193 result["backup_id"] = serde_json::json!(id);
194 }
195 edit::attach_backup_skipped_reason(
196 &mut result,
197 ctx,
198 req.session(),
199 &op_id,
200 Some(path.as_path()),
201 );
202
203 write_result.append_lsp_diagnostics_to(&mut result);
204 write_result.append_reformatted_excerpt_to(&mut result);
205
206 let final_content =
212 std::fs::read_to_string(path.as_path()).unwrap_or_else(|_| content.to_string());
213 if existed && original == final_content {
214 result["no_op"] = serde_json::json!(true);
215 }
216
217 if edit::wants_diff(&req.params) {
218 result["diff"] = edit::compute_diff_for_response(&req.params, &original, &final_content);
219 }
220
221 Response::success(&req.id, result)
222}