diff options
author | Zac Pullar-Strecker <[email protected]> | 2020-07-31 03:12:44 +0100 |
---|---|---|
committer | Zac Pullar-Strecker <[email protected]> | 2020-07-31 03:12:44 +0100 |
commit | f05d7b41a719d848844b054a16477b29d0f063c6 (patch) | |
tree | 0a8a0946e8aef2ce64d4c13d0035ba41cce2daf3 /crates/rust-analyzer/src/main_loop.rs | |
parent | 73ff610e41959e3e7c78a2b4b25b086883132956 (diff) | |
parent | 6b7cb8b5ab539fc4333ce34bc29bf77c976f232a (diff) |
Merge remote-tracking branch 'upstream/master' into 503-hover-doc-links
Hasn't fixed tests yet.
Diffstat (limited to 'crates/rust-analyzer/src/main_loop.rs')
-rw-r--r-- | crates/rust-analyzer/src/main_loop.rs | 218 |
1 files changed, 155 insertions, 63 deletions
diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index 9fd16ef3b..0ace4cb45 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs | |||
@@ -1,13 +1,14 @@ | |||
1 | //! The main loop of `rust-analyzer` responsible for dispatching LSP | 1 | //! The main loop of `rust-analyzer` responsible for dispatching LSP |
2 | //! requests/replies and notifications back to the client. | 2 | //! requests/replies and notifications back to the client. |
3 | use std::{ | 3 | use std::{ |
4 | borrow::Cow, | ||
4 | env, fmt, panic, | 5 | env, fmt, panic, |
5 | time::{Duration, Instant}, | 6 | time::{Duration, Instant}, |
6 | }; | 7 | }; |
7 | 8 | ||
8 | use crossbeam_channel::{never, select, Receiver}; | 9 | use crossbeam_channel::{select, Receiver}; |
9 | use lsp_server::{Connection, Notification, Request, Response}; | 10 | use lsp_server::{Connection, Notification, Request, Response}; |
10 | use lsp_types::notification::Notification as _; | 11 | use lsp_types::{notification::Notification as _, DidChangeTextDocumentParams}; |
11 | use ra_db::VfsPath; | 12 | use ra_db::VfsPath; |
12 | use ra_ide::{Canceled, FileId}; | 13 | use ra_ide::{Canceled, FileId}; |
13 | use ra_prof::profile; | 14 | use ra_prof::profile; |
@@ -15,12 +16,15 @@ use ra_prof::profile; | |||
15 | use crate::{ | 16 | use crate::{ |
16 | config::Config, | 17 | config::Config, |
17 | dispatch::{NotificationDispatcher, RequestDispatcher}, | 18 | dispatch::{NotificationDispatcher, RequestDispatcher}, |
19 | document::DocumentData, | ||
18 | from_proto, | 20 | from_proto, |
19 | global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, | 21 | global_state::{file_id_to_url, url_to_file_id, GlobalState, Status}, |
20 | handlers, lsp_ext, | 22 | handlers, lsp_ext, |
21 | lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, | 23 | lsp_utils::{apply_document_changes, is_canceled, notification_is, Progress}, |
22 | Result, | 24 | Result, |
23 | }; | 25 | }; |
26 | use ra_project_model::ProjectWorkspace; | ||
27 | use vfs::ChangeKind; | ||
24 | 28 | ||
25 | pub fn main_loop(config: Config, connection: Connection) -> Result<()> { | 29 | pub fn main_loop(config: Config, connection: Connection) -> Result<()> { |
26 | log::info!("initial config: {:#?}", config); | 30 | log::info!("initial config: {:#?}", config); |
@@ -58,6 +62,7 @@ enum Event { | |||
58 | pub(crate) enum Task { | 62 | pub(crate) enum Task { |
59 | Response(Response), | 63 | Response(Response), |
60 | Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>), | 64 | Diagnostics(Vec<(FileId, Vec<lsp_types::Diagnostic>)>), |
65 | Workspaces(Vec<anyhow::Result<ProjectWorkspace>>), | ||
61 | Unit, | 66 | Unit, |
62 | } | 67 | } |
63 | 68 | ||
@@ -94,24 +99,49 @@ impl fmt::Debug for Event { | |||
94 | } | 99 | } |
95 | 100 | ||
96 | impl GlobalState { | 101 | impl GlobalState { |
97 | fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> { | 102 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { |
98 | select! { | 103 | if self.config.linked_projects.is_empty() && self.config.notifications.cargo_toml_not_found |
99 | recv(inbox) -> msg => | 104 | { |
100 | msg.ok().map(Event::Lsp), | 105 | self.show_message( |
101 | 106 | lsp_types::MessageType::Error, | |
102 | recv(self.task_pool.receiver) -> task => | 107 | "rust-analyzer failed to discover workspace".to_string(), |
103 | Some(Event::Task(task.unwrap())), | 108 | ); |
109 | }; | ||
104 | 110 | ||
105 | recv(self.loader.receiver) -> task => | 111 | let save_registration_options = lsp_types::TextDocumentSaveRegistrationOptions { |
106 | Some(Event::Vfs(task.unwrap())), | 112 | include_text: Some(false), |
113 | text_document_registration_options: lsp_types::TextDocumentRegistrationOptions { | ||
114 | document_selector: Some(vec![ | ||
115 | lsp_types::DocumentFilter { | ||
116 | language: None, | ||
117 | scheme: None, | ||
118 | pattern: Some("**/*.rs".into()), | ||
119 | }, | ||
120 | lsp_types::DocumentFilter { | ||
121 | language: None, | ||
122 | scheme: None, | ||
123 | pattern: Some("**/Cargo.toml".into()), | ||
124 | }, | ||
125 | lsp_types::DocumentFilter { | ||
126 | language: None, | ||
127 | scheme: None, | ||
128 | pattern: Some("**/Cargo.lock".into()), | ||
129 | }, | ||
130 | ]), | ||
131 | }, | ||
132 | }; | ||
107 | 133 | ||
108 | recv(self.flycheck.as_ref().map_or(&never(), |it| &it.receiver)) -> task => | 134 | let registration = lsp_types::Registration { |
109 | Some(Event::Flycheck(task.unwrap())), | 135 | id: "textDocument/didSave".to_string(), |
110 | } | 136 | method: "textDocument/didSave".to_string(), |
111 | } | 137 | register_options: Some(serde_json::to_value(save_registration_options).unwrap()), |
138 | }; | ||
139 | self.send_request::<lsp_types::request::RegisterCapability>( | ||
140 | lsp_types::RegistrationParams { registrations: vec![registration] }, | ||
141 | |_, _| (), | ||
142 | ); | ||
112 | 143 | ||
113 | fn run(mut self, inbox: Receiver<lsp_server::Message>) -> Result<()> { | 144 | self.fetch_workspaces(); |
114 | self.reload(); | ||
115 | 145 | ||
116 | while let Some(event) = self.next_event(&inbox) { | 146 | while let Some(event) = self.next_event(&inbox) { |
117 | if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { | 147 | if let Event::Lsp(lsp_server::Message::Notification(not)) = &event { |
@@ -125,6 +155,22 @@ impl GlobalState { | |||
125 | Err("client exited without proper shutdown sequence")? | 155 | Err("client exited without proper shutdown sequence")? |
126 | } | 156 | } |
127 | 157 | ||
158 | fn next_event(&self, inbox: &Receiver<lsp_server::Message>) -> Option<Event> { | ||
159 | select! { | ||
160 | recv(inbox) -> msg => | ||
161 | msg.ok().map(Event::Lsp), | ||
162 | |||
163 | recv(self.task_pool.receiver) -> task => | ||
164 | Some(Event::Task(task.unwrap())), | ||
165 | |||
166 | recv(self.loader.receiver) -> task => | ||
167 | Some(Event::Vfs(task.unwrap())), | ||
168 | |||
169 | recv(self.flycheck_receiver) -> task => | ||
170 | Some(Event::Flycheck(task.unwrap())), | ||
171 | } | ||
172 | } | ||
173 | |||
128 | fn handle_event(&mut self, event: Event) -> Result<()> { | 174 | fn handle_event(&mut self, event: Event) -> Result<()> { |
129 | let loop_start = Instant::now(); | 175 | let loop_start = Instant::now(); |
130 | // NOTE: don't count blocking select! call as a loop-turn time | 176 | // NOTE: don't count blocking select! call as a loop-turn time |
@@ -136,7 +182,7 @@ impl GlobalState { | |||
136 | log::info!("queued count = {}", queue_count); | 182 | log::info!("queued count = {}", queue_count); |
137 | } | 183 | } |
138 | 184 | ||
139 | let mut became_ready = false; | 185 | let prev_status = self.status; |
140 | match event { | 186 | match event { |
141 | Event::Lsp(msg) => match msg { | 187 | Event::Lsp(msg) => match msg { |
142 | lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, | 188 | lsp_server::Message::Request(req) => self.on_request(loop_start, req)?, |
@@ -153,39 +199,54 @@ impl GlobalState { | |||
153 | self.diagnostics.set_native_diagnostics(file_id, diagnostics) | 199 | self.diagnostics.set_native_diagnostics(file_id, diagnostics) |
154 | } | 200 | } |
155 | } | 201 | } |
202 | Task::Workspaces(workspaces) => self.switch_workspaces(workspaces), | ||
156 | Task::Unit => (), | 203 | Task::Unit => (), |
157 | } | 204 | } |
158 | self.analysis_host.maybe_collect_garbage(); | 205 | self.analysis_host.maybe_collect_garbage(); |
159 | } | 206 | } |
160 | Event::Vfs(task) => match task { | 207 | Event::Vfs(mut task) => { |
161 | vfs::loader::Message::Loaded { files } => { | 208 | let _p = profile("GlobalState::handle_event/vfs"); |
162 | let vfs = &mut self.vfs.write().0; | 209 | loop { |
163 | for (path, contents) in files { | 210 | match task { |
164 | let path = VfsPath::from(path); | 211 | vfs::loader::Message::Loaded { files } => { |
165 | if !self.mem_docs.contains(&path) { | 212 | let vfs = &mut self.vfs.write().0; |
166 | vfs.set_file_contents(path, contents) | 213 | for (path, contents) in files { |
214 | let path = VfsPath::from(path); | ||
215 | if !self.mem_docs.contains_key(&path) { | ||
216 | vfs.set_file_contents(path, contents) | ||
217 | } | ||
218 | } | ||
219 | } | ||
220 | vfs::loader::Message::Progress { n_total, n_done } => { | ||
221 | if n_total == 0 { | ||
222 | self.transition(Status::Invalid); | ||
223 | } else { | ||
224 | let state = if n_done == 0 { | ||
225 | self.transition(Status::Loading); | ||
226 | Progress::Begin | ||
227 | } else if n_done < n_total { | ||
228 | Progress::Report | ||
229 | } else { | ||
230 | assert_eq!(n_done, n_total); | ||
231 | self.transition(Status::Ready); | ||
232 | Progress::End | ||
233 | }; | ||
234 | self.report_progress( | ||
235 | "roots scanned", | ||
236 | state, | ||
237 | Some(format!("{}/{}", n_done, n_total)), | ||
238 | Some(Progress::percentage(n_done, n_total)), | ||
239 | ) | ||
240 | } | ||
167 | } | 241 | } |
168 | } | 242 | } |
243 | // Coalesce many VFS event into a single loop turn | ||
244 | task = match self.loader.receiver.try_recv() { | ||
245 | Ok(task) => task, | ||
246 | Err(_) => break, | ||
247 | } | ||
169 | } | 248 | } |
170 | vfs::loader::Message::Progress { n_total, n_done } => { | 249 | } |
171 | let state = if n_done == 0 { | ||
172 | Progress::Begin | ||
173 | } else if n_done < n_total { | ||
174 | Progress::Report | ||
175 | } else { | ||
176 | assert_eq!(n_done, n_total); | ||
177 | self.status = Status::Ready; | ||
178 | became_ready = true; | ||
179 | Progress::End | ||
180 | }; | ||
181 | self.report_progress( | ||
182 | "roots scanned", | ||
183 | state, | ||
184 | Some(format!("{}/{}", n_done, n_total)), | ||
185 | Some(Progress::percentage(n_done, n_total)), | ||
186 | ) | ||
187 | } | ||
188 | }, | ||
189 | Event::Flycheck(task) => match task { | 250 | Event::Flycheck(task) => match task { |
190 | flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { | 251 | flycheck::Message::AddDiagnostic { workspace_root, diagnostic } => { |
191 | let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( | 252 | let diagnostics = crate::diagnostics::to_proto::map_rust_diagnostic_to_lsp( |
@@ -194,7 +255,7 @@ impl GlobalState { | |||
194 | &workspace_root, | 255 | &workspace_root, |
195 | ); | 256 | ); |
196 | for diag in diagnostics { | 257 | for diag in diagnostics { |
197 | match url_to_file_id(&self.vfs.read().0, &diag.location.uri) { | 258 | match url_to_file_id(&self.vfs.read().0, &diag.url) { |
198 | Ok(file_id) => self.diagnostics.add_check_diagnostic( | 259 | Ok(file_id) => self.diagnostics.add_check_diagnostic( |
199 | file_id, | 260 | file_id, |
200 | diag.diagnostic, | 261 | diag.diagnostic, |
@@ -231,16 +292,16 @@ impl GlobalState { | |||
231 | } | 292 | } |
232 | 293 | ||
233 | let state_changed = self.process_changes(); | 294 | let state_changed = self.process_changes(); |
234 | if became_ready { | 295 | if prev_status == Status::Loading && self.status == Status::Ready { |
235 | if let Some(flycheck) = &self.flycheck { | 296 | if let Some(flycheck) = &self.flycheck { |
236 | flycheck.handle.update(); | 297 | flycheck.update(); |
237 | } | 298 | } |
238 | } | 299 | } |
239 | 300 | ||
240 | if self.status == Status::Ready && (state_changed || became_ready) { | 301 | if self.status == Status::Ready && (state_changed || prev_status == Status::Loading) { |
241 | let subscriptions = self | 302 | let subscriptions = self |
242 | .mem_docs | 303 | .mem_docs |
243 | .iter() | 304 | .keys() |
244 | .map(|path| self.vfs.read().0.file_id(&path).unwrap()) | 305 | .map(|path| self.vfs.read().0.file_id(&path).unwrap()) |
245 | .collect::<Vec<_>>(); | 306 | .collect::<Vec<_>>(); |
246 | 307 | ||
@@ -251,8 +312,12 @@ impl GlobalState { | |||
251 | for file_id in diagnostic_changes { | 312 | for file_id in diagnostic_changes { |
252 | let url = file_id_to_url(&self.vfs.read().0, file_id); | 313 | let url = file_id_to_url(&self.vfs.read().0, file_id); |
253 | let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect(); | 314 | let diagnostics = self.diagnostics.diagnostics_for(file_id).cloned().collect(); |
315 | let version = from_proto::vfs_path(&url) | ||
316 | .map(|path| self.mem_docs.get(&path)?.version) | ||
317 | .unwrap_or_default(); | ||
318 | |||
254 | self.send_notification::<lsp_types::notification::PublishDiagnostics>( | 319 | self.send_notification::<lsp_types::notification::PublishDiagnostics>( |
255 | lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version: None }, | 320 | lsp_types::PublishDiagnosticsParams { uri: url, diagnostics, version }, |
256 | ); | 321 | ); |
257 | } | 322 | } |
258 | } | 323 | } |
@@ -274,7 +339,7 @@ impl GlobalState { | |||
274 | self.register_request(&req, request_received); | 339 | self.register_request(&req, request_received); |
275 | 340 | ||
276 | RequestDispatcher { req: Some(req), global_state: self } | 341 | RequestDispatcher { req: Some(req), global_state: self } |
277 | .on_sync::<lsp_ext::CollectGarbage>(|s, ()| Ok(s.analysis_host.collect_garbage()))? | 342 | .on_sync::<lsp_ext::ReloadWorkspace>(|s, ()| Ok(s.fetch_workspaces()))? |
278 | .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? | 343 | .on_sync::<lsp_ext::JoinLines>(|s, p| handlers::handle_join_lines(s.snapshot(), p))? |
279 | .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? | 344 | .on_sync::<lsp_ext::OnEnter>(|s, p| handlers::handle_on_enter(s.snapshot(), p))? |
280 | .on_sync::<lsp_types::request::Shutdown>(|_, ()| Ok(()))? | 345 | .on_sync::<lsp_types::request::Shutdown>(|_, ()| Ok(()))? |
@@ -284,6 +349,7 @@ impl GlobalState { | |||
284 | .on_sync::<lsp_ext::MatchingBrace>(|s, p| { | 349 | .on_sync::<lsp_ext::MatchingBrace>(|s, p| { |
285 | handlers::handle_matching_brace(s.snapshot(), p) | 350 | handlers::handle_matching_brace(s.snapshot(), p) |
286 | })? | 351 | })? |
352 | .on_sync::<lsp_ext::MemoryUsage>(|s, p| handlers::handle_memory_usage(s, p))? | ||
287 | .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)? | 353 | .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)? |
288 | .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)? | 354 | .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)? |
289 | .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)? | 355 | .on::<lsp_ext::ExpandMacro>(handlers::handle_expand_macro)? |
@@ -340,7 +406,11 @@ impl GlobalState { | |||
340 | })? | 406 | })? |
341 | .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| { | 407 | .on::<lsp_types::notification::DidOpenTextDocument>(|this, params| { |
342 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { | 408 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
343 | if !this.mem_docs.insert(path.clone()) { | 409 | if this |
410 | .mem_docs | ||
411 | .insert(path.clone(), DocumentData::new(params.text_document.version)) | ||
412 | .is_some() | ||
413 | { | ||
344 | log::error!("duplicate DidOpenTextDocument: {}", path) | 414 | log::error!("duplicate DidOpenTextDocument: {}", path) |
345 | } | 415 | } |
346 | this.vfs | 416 | this.vfs |
@@ -352,36 +422,56 @@ impl GlobalState { | |||
352 | })? | 422 | })? |
353 | .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| { | 423 | .on::<lsp_types::notification::DidChangeTextDocument>(|this, params| { |
354 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { | 424 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
355 | assert!(this.mem_docs.contains(&path)); | 425 | let DidChangeTextDocumentParams { text_document, content_changes } = params; |
356 | let vfs = &mut this.vfs.write().0; | 426 | let vfs = &mut this.vfs.write().0; |
427 | let world = this.snapshot(); | ||
357 | let file_id = vfs.file_id(&path).unwrap(); | 428 | let file_id = vfs.file_id(&path).unwrap(); |
429 | |||
430 | // let file_id = vfs.file_id(&path).unwrap(); | ||
358 | let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); | 431 | let mut text = String::from_utf8(vfs.file_contents(file_id).to_vec()).unwrap(); |
359 | apply_document_changes(&mut text, params.content_changes); | 432 | let line_index = world.analysis.file_line_index(file_id)?; |
360 | vfs.set_file_contents(path, Some(text.into_bytes())) | 433 | apply_document_changes(&mut text, content_changes, Cow::Borrowed(&line_index)); |
434 | |||
435 | // The version passed in DidChangeTextDocument is the version after all edits are applied | ||
436 | // so we should apply it before the vfs is notified. | ||
437 | let doc = this.mem_docs.get_mut(&path).unwrap(); | ||
438 | doc.version = text_document.version; | ||
439 | |||
440 | vfs.set_file_contents(path.clone(), Some(text.into_bytes())); | ||
361 | } | 441 | } |
362 | Ok(()) | 442 | Ok(()) |
363 | })? | 443 | })? |
364 | .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| { | 444 | .on::<lsp_types::notification::DidCloseTextDocument>(|this, params| { |
445 | let mut version = None; | ||
365 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { | 446 | if let Ok(path) = from_proto::vfs_path(¶ms.text_document.uri) { |
366 | if !this.mem_docs.remove(&path) { | 447 | match this.mem_docs.remove(&path) { |
367 | log::error!("orphan DidCloseTextDocument: {}", path) | 448 | Some(doc) => version = doc.version, |
449 | None => log::error!("orphan DidCloseTextDocument: {}", path), | ||
368 | } | 450 | } |
451 | |||
369 | if let Some(path) = path.as_path() { | 452 | if let Some(path) = path.as_path() { |
370 | this.loader.handle.invalidate(path.to_path_buf()); | 453 | this.loader.handle.invalidate(path.to_path_buf()); |
371 | } | 454 | } |
372 | } | 455 | } |
456 | |||
457 | // Clear the diagnostics for the previously known version of the file. | ||
458 | // This prevents stale "cargo check" diagnostics if the file is | ||
459 | // closed, "cargo check" is run and then the file is reopened. | ||
373 | this.send_notification::<lsp_types::notification::PublishDiagnostics>( | 460 | this.send_notification::<lsp_types::notification::PublishDiagnostics>( |
374 | lsp_types::PublishDiagnosticsParams { | 461 | lsp_types::PublishDiagnosticsParams { |
375 | uri: params.text_document.uri, | 462 | uri: params.text_document.uri, |
376 | diagnostics: Vec::new(), | 463 | diagnostics: Vec::new(), |
377 | version: None, | 464 | version, |
378 | }, | 465 | }, |
379 | ); | 466 | ); |
380 | Ok(()) | 467 | Ok(()) |
381 | })? | 468 | })? |
382 | .on::<lsp_types::notification::DidSaveTextDocument>(|this, _params| { | 469 | .on::<lsp_types::notification::DidSaveTextDocument>(|this, params| { |
383 | if let Some(flycheck) = &this.flycheck { | 470 | if let Some(flycheck) = &this.flycheck { |
384 | flycheck.handle.update(); | 471 | flycheck.update(); |
472 | } | ||
473 | if let Ok(abs_path) = from_proto::abs_path(¶ms.text_document.uri) { | ||
474 | this.maybe_refresh(&[(abs_path, ChangeKind::Modify)]); | ||
385 | } | 475 | } |
386 | Ok(()) | 476 | Ok(()) |
387 | })? | 477 | })? |
@@ -403,10 +493,12 @@ impl GlobalState { | |||
403 | (Some(err), _) => { | 493 | (Some(err), _) => { |
404 | log::error!("failed to fetch the server settings: {:?}", err) | 494 | log::error!("failed to fetch the server settings: {:?}", err) |
405 | } | 495 | } |
406 | (None, Some(configs)) => { | 496 | (None, Some(mut configs)) => { |
407 | if let Some(new_config) = configs.get(0) { | 497 | if let Some(json) = configs.get_mut(0) { |
498 | // Note that json can be null according to the spec if the client can't | ||
499 | // provide a configuration. This is handled in Config::update below. | ||
408 | let mut config = this.config.clone(); | 500 | let mut config = this.config.clone(); |
409 | config.update(&new_config); | 501 | config.update(json.take()); |
410 | this.update_configuration(config); | 502 | this.update_configuration(config); |
411 | } | 503 | } |
412 | } | 504 | } |