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 let backup_id = if existed {
78 match edit::auto_backup(
79 ctx,
80 req.session(),
81 path.as_path(),
82 "write: pre-write backup",
83 Some(&op_id),
84 ) {
85 Ok(id) => id,
86 Err(e) => {
87 return Response::error(&req.id, e.code(), e.to_string());
88 }
89 }
90 } else {
91 match ctx.backup().borrow_mut().snapshot_op_tombstone(
92 req.session(),
93 &op_id,
94 path.as_path(),
95 "write: file created by write",
96 ) {
97 Ok(id) => Some(id),
98 Err(e) => return Response::error(&req.id, e.code(), e.to_string()),
99 }
100 };
101
102 if create_dirs {
104 if let Some(parent) = path.parent() {
105 if !parent.exists() {
106 if let Err(e) = std::fs::create_dir_all(parent) {
107 if !existed {
108 ctx.backup()
109 .borrow_mut()
110 .discard_operation_entries(req.session(), &op_id);
111 }
112 return Response::error(
113 &req.id,
114 "invalid_request",
115 format!("write: failed to create directories: {}", e),
116 );
117 }
118 }
119 }
120 }
121
122 let mut write_result =
124 match edit::write_format_validate(path.as_path(), content, &ctx.config(), &req.params) {
125 Ok(r) => r,
126 Err(e) => {
127 if !existed {
128 ctx.backup()
129 .borrow_mut()
130 .discard_operation_entries(req.session(), &op_id);
131 }
132 return Response::error(&req.id, e.code(), e.to_string());
133 }
134 };
135
136 if write_result.rolled_back {
137 ctx.backup()
138 .borrow_mut()
139 .discard_operation_entries(req.session(), &op_id);
140 }
141
142 if let Ok(final_content) = std::fs::read_to_string(path.as_path()) {
143 let config_change_type = if existed {
144 FileChangeType::CHANGED
145 } else {
146 FileChangeType::CREATED
147 };
148 ctx.lsp_notify_watched_config_file(path.as_path(), config_change_type);
149 write_result.lsp_outcome = ctx.lsp_post_write(path.as_path(), &final_content, &req.params);
150 }
151
152 log::debug!("write: {}", file);
153
154 let mut result = serde_json::json!({
155 "file": file,
156 "created": !existed,
157 "formatted": write_result.formatted,
158 });
159
160 if let Some(valid) = write_result.syntax_valid {
161 result["syntax_valid"] = serde_json::json!(valid);
162 }
163
164 if let Some(ref reason) = write_result.format_skipped_reason {
165 result["format_skipped_reason"] = serde_json::json!(reason);
166 }
167
168 if write_result.validate_requested {
169 result["validation_errors"] = serde_json::json!(write_result.validation_errors);
170 }
171 if let Some(ref reason) = write_result.validate_skipped_reason {
172 result["validate_skipped_reason"] = serde_json::json!(reason);
173 }
174
175 if let Some(ref id) = backup_id {
176 result["backup_id"] = serde_json::json!(id);
177 }
178
179 write_result.append_lsp_diagnostics_to(&mut result);
180
181 let final_content =
187 std::fs::read_to_string(path.as_path()).unwrap_or_else(|_| content.to_string());
188 if existed && original == final_content {
189 result["no_op"] = serde_json::json!(true);
190 }
191
192 if edit::wants_diff(&req.params) {
193 result["diff"] = edit::compute_diff_for_response(&req.params, &original, &final_content);
194 }
195
196 Response::success(&req.id, result)
197}