1use crate::model::buffer::Buffer;
11use crate::model::event::BufferId;
12use crate::services::async_bridge::{
13 LspMessageType, LspProgressValue, LspSemanticTokensResponse, LspServerStatus,
14};
15use crate::state::{SemanticTokenSpan, SemanticTokenStore};
16use crate::view::file_tree::{FileTreeView, NodeId};
17use lsp_types::{
18 Diagnostic, FoldingRange, InlayHint, SemanticToken, SemanticTokensEdit,
19 SemanticTokensFullDeltaResult, SemanticTokensLegend, SemanticTokensRangeResult,
20 SemanticTokensResult,
21};
22use rust_i18n::t;
23use serde_json::Value;
24use std::path::PathBuf;
25use std::time::{Duration, Instant};
26
27use super::types::{LspMessageEntry, LspProgressInfo};
28use super::Editor;
29
30impl Editor {
35 pub(super) fn find_buffer_by_uri(&self, uri: &str) -> Option<BufferId> {
39 self.active_window()
46 .buffer_metadata
47 .iter()
48 .find(|(_, m)| m.file_uri().map(|u| u.as_str() == uri).unwrap_or(false))
49 .map(|(buffer_id, _)| *buffer_id)
50 }
51
52 pub(crate) fn buffers_for_language(
67 &self,
68 language: &str,
69 ) -> Vec<(BufferId, crate::app::types::LspUri)> {
70 self.windows
71 .get(&self.active_window)
72 .map(|w| &w.buffers)
73 .expect("active window present")
74 .iter()
75 .filter_map(|(buffer_id, state)| {
76 if state.language != language {
77 return None;
78 }
79 self.active_window()
80 .buffer_metadata
81 .get(buffer_id)
82 .and_then(|m| m.file_uri().cloned())
83 .map(|uri| (*buffer_id, uri))
84 })
85 .collect()
86 }
87
88 fn apply_diagnostics_to_buffer(
92 &mut self,
93 uri: &str,
94 diagnostics: &[Diagnostic],
95 ) -> Option<(BufferId, bool)> {
96 let buffer_id = self.find_buffer_by_uri(uri)?;
97 let state = self
98 .windows
99 .get_mut(&self.active_window)
100 .map(|w| &mut w.buffers)
101 .expect("active window present")
102 .get_mut(&buffer_id)?;
103 let updated = crate::services::lsp::diagnostics::apply_diagnostics_to_state_cached(
104 state,
105 diagnostics,
106 &*self.theme.read().unwrap(),
107 );
108 Some((buffer_id, updated))
109 }
110}
111
112impl Editor {
117 fn merge_and_apply_diagnostics(&mut self, uri: &str) {
119 let mut merged = Vec::new();
121 if let Some(server_map) = self.active_window_mut().stored_push_diagnostics.get(uri) {
122 for diagnostics in server_map.values() {
123 merged.extend(diagnostics.iter().cloned());
124 }
125 }
126 if let Some(pull) = self.active_window_mut().stored_pull_diagnostics.get(uri) {
127 merged.extend(pull.iter().cloned());
128 }
129
130 if merged.is_empty() {
132 self.stored_diagnostics_mut().remove(uri);
133 } else {
134 self.stored_diagnostics_mut()
135 .insert(uri.to_string(), merged.clone());
136 }
137
138 if let Some((buffer_id, updated)) = self.apply_diagnostics_to_buffer(uri, &merged) {
139 if updated {
140 tracing::info!(
141 "Applied {} diagnostics to buffer {:?} (overlays updated)",
142 merged.len(),
143 buffer_id
144 );
145 } else {
146 tracing::debug!(
147 "Diagnostics unchanged for buffer {:?} ({} diagnostics, cache hit)",
148 buffer_id,
149 merged.len()
150 );
151 }
152 } else {
153 tracing::debug!("No buffer found for diagnostic URI: {}", uri);
154 }
155
156 let count = merged.len();
158 self.plugin_manager.read().unwrap().run_hook(
159 "diagnostics_updated",
160 crate::services::plugins::hooks::HookArgs::DiagnosticsUpdated {
161 uri: uri.to_string(),
162 count,
163 },
164 );
165 }
166
167 pub(super) fn handle_lsp_diagnostics(
169 &mut self,
170 uri: String,
171 diagnostics: Vec<Diagnostic>,
172 server_name: String,
173 ) {
174 if let Some(lsp) = self.lsp() {
178 if !lsp.has_server_named(&server_name) {
179 tracing::debug!(
180 "Dropping diagnostics from stopped server '{}' for {}",
181 server_name,
182 uri
183 );
184 return;
185 }
186 }
187
188 tracing::debug!(
189 "Processing {} push diagnostics from '{}' for {}",
190 diagnostics.len(),
191 server_name,
192 uri
193 );
194
195 let server_map = self
196 .active_window_mut()
197 .stored_push_diagnostics
198 .entry(uri.clone())
199 .or_default();
200 if diagnostics.is_empty() {
201 server_map.remove(&server_name);
202 if server_map.is_empty() {
204 self.active_window_mut()
205 .stored_push_diagnostics
206 .remove(&uri);
207 }
208 } else {
209 server_map.insert(server_name, diagnostics);
210 }
211
212 self.merge_and_apply_diagnostics(&uri);
213 }
214
215 pub(super) fn handle_lsp_pulled_diagnostics(
217 &mut self,
218 uri: String,
219 result_id: Option<String>,
220 diagnostics: Vec<Diagnostic>,
221 unchanged: bool,
222 ) {
223 if unchanged {
224 tracing::debug!(
225 "Diagnostics unchanged for {} (result_id: {:?})",
226 uri,
227 result_id
228 );
229 return;
230 }
231
232 tracing::debug!(
233 "Processing {} pulled diagnostics for {} (result_id: {:?})",
234 diagnostics.len(),
235 uri,
236 result_id
237 );
238
239 if let Some(result_id) = result_id {
241 self.active_window_mut()
242 .diagnostic_result_ids
243 .insert(uri.clone(), result_id);
244 }
245
246 if diagnostics.is_empty() {
247 self.active_window_mut()
248 .stored_pull_diagnostics
249 .remove(&uri);
250 } else {
251 self.active_window_mut()
252 .stored_pull_diagnostics
253 .insert(uri.clone(), diagnostics);
254 }
255
256 self.merge_and_apply_diagnostics(&uri);
257 }
258
259 pub(crate) fn clear_diagnostics_for_server(&mut self, server_name: &str) {
265 let affected_uris: Vec<String> = self
267 .active_window()
268 .stored_push_diagnostics
269 .iter()
270 .filter_map(|(uri, server_map)| {
271 if server_map.contains_key(server_name) {
272 Some(uri.clone())
273 } else {
274 None
275 }
276 })
277 .collect();
278
279 if affected_uris.is_empty() {
280 return;
281 }
282
283 tracing::info!(
284 "Clearing diagnostics from server '{}' for {} URIs",
285 server_name,
286 affected_uris.len()
287 );
288
289 for uri in &affected_uris {
290 if let Some(server_map) = self
291 .active_window_mut()
292 .stored_push_diagnostics
293 .get_mut(uri)
294 {
295 server_map.remove(server_name);
296 if server_map.is_empty() {
297 self.active_window_mut().stored_push_diagnostics.remove(uri);
298 }
299 }
300
301 crate::services::lsp::diagnostics::invalidate_cache_for_file(uri);
305
306 self.merge_and_apply_diagnostics(uri);
307 }
308 }
309}
310
311impl Editor {
316 pub(super) fn handle_lsp_inlay_hints(
320 &mut self,
321 request_id: u64,
322 uri: String,
323 hints: Vec<InlayHint>,
324 ) {
325 self.active_window_mut()
326 .handle_lsp_inlay_hints(request_id, uri, hints);
327 }
328}
329
330impl crate::app::window::Window {
331 pub fn handle_lsp_inlay_hints(
336 &mut self,
337 request_id: u64,
338 uri: String,
339 hints: Vec<lsp_types::InlayHint>,
340 ) {
341 let Some(request) = self.pending_inlay_hints_requests.remove(&request_id) else {
342 tracing::debug!(
343 "Ignoring stale inlay hints response (request_id={})",
344 request_id
345 );
346 return;
347 };
348
349 let state_version = match self.buffers.get(&request.buffer_id) {
354 Some(s) => s.buffer.version(),
355 None => return, };
357 if state_version != request.version {
358 tracing::debug!(
359 "Ignoring stale inlay hints for {} (request_id={}, version={}, current={})",
360 uri,
361 request_id,
362 request.version,
363 state_version
364 );
365 return;
366 }
367
368 tracing::info!(
369 "Received {} inlay hints for {} (request_id={})",
370 hints.len(),
371 uri,
372 request_id
373 );
374
375 if let Some(state) = self.buffers.get_mut(&request.buffer_id) {
376 super::Editor::apply_inlay_hints_to_state(state, &hints);
377 tracing::info!(
378 "Applied {} inlay hints as virtual text to buffer {:?}",
379 hints.len(),
380 request.buffer_id
381 );
382 }
383 }
384}
385
386impl Editor {
387 pub(super) fn handle_lsp_folding_ranges(
393 &mut self,
394 request_id: u64,
395 uri: String,
396 ranges: Vec<FoldingRange>,
397 ) {
398 enum FoldingDispatch {
404 Stale { buffer_id: BufferId },
405 Apply { buffer_id: BufferId },
406 Skip,
407 }
408 let dispatch = {
409 let win = self.active_window_mut();
410 let Some(request) = win.pending_folding_range_requests.remove(&request_id) else {
411 tracing::debug!(
412 "Ignoring folding ranges response without pending request (request_id={})",
413 request_id
414 );
415 return;
416 };
417 win.folding_ranges_in_flight.remove(&request.buffer_id);
418 match win.buffers.get(&request.buffer_id) {
419 Some(state) if state.buffer.version() == request.version => {
420 FoldingDispatch::Apply {
421 buffer_id: request.buffer_id,
422 }
423 }
424 Some(state) => {
425 tracing::debug!(
426 "Ignoring stale folding ranges for {} (request_id={}, version={}, current={})",
427 uri,
428 request_id,
429 request.version,
430 state.buffer.version()
431 );
432 FoldingDispatch::Stale {
433 buffer_id: request.buffer_id,
434 }
435 }
436 None => FoldingDispatch::Skip,
437 }
438 };
439 let buffer_id = match dispatch {
440 FoldingDispatch::Apply { buffer_id } => buffer_id,
441 FoldingDispatch::Stale { buffer_id } => {
442 self.active_window_mut()
443 .schedule_folding_ranges_refresh(buffer_id);
444 return;
445 }
446 FoldingDispatch::Skip => return,
447 };
448
449 if ranges.is_empty() {
450 self.stored_folding_ranges_mut().remove(&uri);
451 } else {
452 self.stored_folding_ranges_mut().insert(uri.clone(), ranges);
453 }
454
455 let lsp_ranges = self
456 .active_window()
457 .stored_folding_ranges
458 .get(&uri)
459 .cloned()
460 .unwrap_or_default();
461 self.active_window_mut()
462 .apply_folding_ranges_response(buffer_id, lsp_ranges);
463 }
464
465 pub(super) fn handle_lsp_semantic_tokens(
467 &mut self,
468 request_id: u64,
469 uri: String,
470 response: LspSemanticTokensResponse,
471 ) {
472 let (
473 buffer_id,
474 target_version,
475 full_request_kind,
476 requested_range,
477 requested_start_line,
478 requested_end_line,
479 ) = if let Some(range_request) = self
480 .active_window_mut()
481 .take_pending_semantic_token_range_request(request_id)
482 {
483 (
484 range_request.buffer_id,
485 range_request.version,
486 None,
487 Some(range_request.range),
488 Some(range_request.start_line),
489 Some(range_request.end_line),
490 )
491 } else if let Some(full_request) = self
492 .active_window_mut()
493 .take_pending_semantic_token_request(request_id)
494 {
495 (
496 full_request.buffer_id,
497 full_request.version,
498 Some(full_request.kind),
499 None,
500 None,
501 None,
502 )
503 } else {
504 tracing::debug!(
505 "Semantic tokens response {} for {} without pending entry",
506 request_id,
507 uri
508 );
509 return;
510 };
511
512 let Some(language) = self
514 .windows
515 .get(&self.active_window)
516 .map(|w| &w.buffers)
517 .expect("active window present")
518 .get(&buffer_id)
519 .map(|s| s.language.clone())
520 else {
521 return;
522 };
523
524 let legend = match self
525 .lsp()
526 .as_ref()
527 .and_then(|manager| manager.semantic_tokens_legend(&language).cloned())
528 {
529 Some(legend) => legend,
530 None => {
531 tracing::debug!("Semantic tokens legend missing for language {}", language);
532 return;
533 }
534 };
535
536 let Some(state) = self
537 .windows
538 .get_mut(&self.active_window)
539 .map(|w| &mut w.buffers)
540 .expect("active window present")
541 .get_mut(&buffer_id)
542 else {
543 return;
544 };
545
546 let current_version = state.buffer.version();
547 if current_version != target_version {
548 return;
550 }
551
552 match (requested_range, full_request_kind) {
553 (Some(range), None) => {
554 let result = match response {
555 LspSemanticTokensResponse::Range(result) => result,
556 _ => {
557 tracing::warn!(
558 "Semantic tokens range response {} for {} had mismatched type",
559 request_id,
560 uri
561 );
562 return;
563 }
564 };
565
566 match result {
567 Err(_) => {
568 }
572 Ok(tokens_opt) => {
573 let spans = match tokens_opt {
574 Some(SemanticTokensRangeResult::Tokens(tokens)) => {
575 let decoded = decode_semantic_token_data(
579 &state.buffer,
580 &legend,
581 &tokens.data,
582 0,
583 );
584 decoded.spans
585 }
586 Some(SemanticTokensRangeResult::Partial(partial)) => {
587 let decoded = decode_semantic_token_data(
588 &state.buffer,
589 &legend,
590 &partial.data,
591 0,
592 );
593 decoded.spans
594 }
595 None => Vec::new(),
596 };
597
598 let applied = crate::services::lsp::semantic_tokens::apply_semantic_tokens_range_to_state(
599 state,
600 range.clone(),
601 &spans,
602 &*self.theme.read().unwrap(),
603 );
604 if applied {
605 self.active_window_mut()
606 .semantic_tokens_range_applied
607 .insert(
608 buffer_id,
609 (
610 requested_start_line.unwrap_or(0),
611 requested_end_line.unwrap_or(0),
612 current_version,
613 ),
614 );
615 }
616 }
617 }
618 }
619 (None, Some(super::SemanticTokensFullRequestKind::Full)) => {
620 let result = match response {
621 LspSemanticTokensResponse::Full(result) => result,
622 _ => {
623 tracing::warn!(
624 "Semantic tokens response {} for {} had mismatched type",
625 request_id,
626 uri
627 );
628 return;
629 }
630 };
631
632 match result {
633 Err(_) => {
634 }
636 Ok(tokens_opt) => {
637 let decoded = match tokens_opt {
638 Some(SemanticTokensResult::Tokens(tokens)) => {
639 let decoded = decode_semantic_token_data(
640 &state.buffer,
641 &legend,
642 &tokens.data,
643 0,
644 );
645 SemanticTokensFullDecode {
646 result_id: tokens.result_id.clone(),
647 raw_data: decoded.raw,
648 spans: decoded.spans,
649 }
650 }
651 Some(SemanticTokensResult::Partial(partial)) => {
652 let decoded = decode_semantic_token_data(
653 &state.buffer,
654 &legend,
655 &partial.data,
656 0,
657 );
658 SemanticTokensFullDecode {
659 result_id: None,
660 raw_data: decoded.raw,
661 spans: decoded.spans,
662 }
663 }
664 None => SemanticTokensFullDecode {
665 result_id: None,
666 raw_data: Vec::new(),
667 spans: Vec::new(),
668 },
669 };
670
671 crate::services::lsp::semantic_tokens::apply_semantic_tokens_to_state(
672 state,
673 &decoded.spans,
674 &*self.theme.read().unwrap(),
675 );
676
677 state.set_semantic_tokens(SemanticTokenStore {
678 version: current_version,
679 result_id: decoded.result_id,
680 data: decoded.raw_data,
681 tokens: decoded.spans,
682 });
683 }
684 }
685 }
686 (None, Some(super::SemanticTokensFullRequestKind::FullDelta)) => {
687 let result = match response {
688 LspSemanticTokensResponse::FullDelta(result) => result,
689 _ => {
690 tracing::warn!(
691 "Semantic tokens delta response {} for {} had mismatched type",
692 request_id,
693 uri
694 );
695 return;
696 }
697 };
698
699 match result {
700 Err(_) => {
701 }
703 Ok(tokens_opt) => {
704 let existing_store = state.semantic_tokens.as_ref();
705 let existing_result_id =
706 existing_store.and_then(|store| store.result_id.clone());
707 let existing_data = existing_store.map(|store| store.data.clone());
708
709 let decoded = match tokens_opt {
710 Some(SemanticTokensFullDeltaResult::Tokens(tokens)) => {
711 SemanticTokensDeltaDecode {
712 result_id: tokens.result_id.clone(),
713 raw_data: semantic_tokens_to_raw(&tokens.data),
714 }
715 }
716 Some(SemanticTokensFullDeltaResult::TokensDelta(delta)) => {
717 let Some(existing) = existing_data else {
718 tracing::warn!(
719 "Semantic tokens delta response {} for {} missing baseline",
720 request_id,
721 uri
722 );
723 return;
724 };
725 let updated = match apply_semantic_token_edits(
726 existing,
727 &delta.edits,
728 ) {
729 Some(data) => data,
730 None => {
731 tracing::warn!(
732 "Semantic tokens delta response {} for {} had invalid edits",
733 request_id,
734 uri
735 );
736 return;
737 }
738 };
739 SemanticTokensDeltaDecode {
740 result_id: delta.result_id.clone().or(existing_result_id),
741 raw_data: updated,
742 }
743 }
744 Some(SemanticTokensFullDeltaResult::PartialTokensDelta { edits }) => {
745 let Some(existing) = existing_data else {
746 tracing::warn!(
747 "Semantic tokens delta response {} for {} missing baseline",
748 request_id,
749 uri
750 );
751 return;
752 };
753 let updated = match apply_semantic_token_edits(existing, &edits) {
754 Some(data) => data,
755 None => {
756 tracing::warn!(
757 "Semantic tokens delta response {} for {} had invalid edits",
758 request_id,
759 uri
760 );
761 return;
762 }
763 };
764 SemanticTokensDeltaDecode {
765 result_id: existing_result_id,
766 raw_data: updated,
767 }
768 }
769 None => SemanticTokensDeltaDecode {
770 result_id: None,
771 raw_data: Vec::new(),
772 },
773 };
774
775 let spans = decode_semantic_token_raw_data(
776 &state.buffer,
777 &legend,
778 &decoded.raw_data,
779 0,
780 );
781
782 crate::services::lsp::semantic_tokens::apply_semantic_tokens_to_state(
783 state,
784 &spans,
785 &*self.theme.read().unwrap(),
786 );
787
788 state.set_semantic_tokens(SemanticTokenStore {
789 version: current_version,
790 result_id: decoded.result_id,
791 data: decoded.raw_data,
792 tokens: spans,
793 });
794 }
795 }
796 }
797 _ => {
798 tracing::warn!(
799 "Semantic tokens response {} for {} had mismatched pending state",
800 request_id,
801 uri
802 );
803 }
804 }
805 }
806
807 pub(super) fn handle_lsp_server_quiescent(&mut self, language: String) {
809 tracing::info!(
810 "LSP ({}) project fully loaded, re-requesting diagnostics and inlay hints",
811 language
812 );
813
814 self.pull_diagnostics_for_language(&language);
817
818 if !self.config.editor.enable_inlay_hints {
820 self.request_folding_ranges_for_language(&language);
822 return;
823 }
824
825 let buffer_infos: Vec<_> = self
831 .buffers_for_language(&language)
832 .into_iter()
833 .map(|(buffer_id, uri)| {
834 let (line_count, version) = self
835 .buffers()
836 .get(&buffer_id)
837 .map(|s| (s.buffer.line_count().unwrap_or(1000), s.buffer.version()))
838 .unwrap_or((1000, 0));
839 (buffer_id, uri, line_count, version)
840 })
841 .collect();
842
843 let __active_id = self.active_window;
844
845 let Some(__win) = self.windows.get_mut(&__active_id) else {
846 return;
847 };
848 let Some(lsp) = __win.lsp.as_mut() else {
849 return;
850 };
851
852 let Some(sh) = lsp.handle_for_feature_mut(&language, crate::types::LspFeature::InlayHints)
854 else {
855 return;
856 };
857 let client = &mut sh.handle;
858
859 let __next_id = &mut __win.next_lsp_request_id;
860 let __pending = &mut __win.pending_inlay_hints_requests;
861
862 for (buffer_id, uri, line_count, version) in buffer_infos {
868 let request_id = *__next_id;
869 *__next_id += 1;
870
871 let last_line = line_count.saturating_sub(1) as u32;
872 if let Err(e) =
873 client.inlay_hints(request_id, uri.as_uri().clone(), 0, 0, last_line, 10000)
874 {
875 tracing::debug!(
876 "Failed to re-request inlay hints for {}: {}",
877 uri.as_str(),
878 e
879 );
880 } else {
881 __pending.insert(request_id, super::InlayHintsRequest { buffer_id, version });
882 tracing::info!(
883 "Re-requested inlay hints for {} (request_id={})",
884 uri.as_str(),
885 request_id
886 );
887 }
888 }
889
890 self.request_folding_ranges_for_language(&language);
892 }
893
894 pub(super) fn handle_lsp_diagnostic_refresh(&mut self, language: String) {
897 tracing::info!(
898 "LSP ({}) diagnostic refresh requested, re-pulling diagnostics",
899 language
900 );
901 self.pull_diagnostics_for_language(&language);
902 }
903
904 pub(super) fn pull_diagnostics_for_language(&mut self, language: &str) {
906 let uris: Vec<_> = self
909 .buffers_for_language(language)
910 .into_iter()
911 .map(|(_, uri)| uri)
912 .collect();
913
914 if uris.is_empty() {
915 return;
916 }
917
918 let __active_id = self.active_window;
919 let Some(__win) = self.windows.get_mut(&__active_id) else {
920 return;
921 };
922 let diagnostic_result_ids = &__win.diagnostic_result_ids;
923 let Some(lsp) = __win.lsp.as_mut() else {
924 return;
925 };
926 let Some(sh) = lsp.handle_for_feature_mut(language, crate::types::LspFeature::Diagnostics)
927 else {
928 return;
929 };
930 let client = &mut sh.handle;
931 let __next_id = &mut __win.next_lsp_request_id;
932
933 for uri in uris {
934 let request_id = *__next_id;
935 *__next_id += 1;
936 let previous_result_id = diagnostic_result_ids.get(uri.as_str()).cloned();
937 if let Err(e) =
938 client.document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
939 {
940 tracing::debug!("Failed to re-pull diagnostics for {}: {}", uri.as_str(), e);
941 } else {
942 tracing::info!(
943 "Re-pulling diagnostics for {} (request_id={})",
944 uri.as_str(),
945 request_id
946 );
947 }
948 }
949 }
950
951 pub(super) fn handle_lsp_progress(
953 &mut self,
954 language: String,
955 token: String,
956 value: LspProgressValue,
957 ) {
958 match value {
959 LspProgressValue::Begin {
960 title,
961 message,
962 percentage,
963 } => {
964 self.active_window_mut().lsp_progress.insert(
965 token.clone(),
966 LspProgressInfo {
967 language,
968 title,
969 message,
970 percentage,
971 },
972 );
973 }
974 LspProgressValue::Report {
975 message,
976 percentage,
977 } => {
978 if let Some(info) = self.active_window_mut().lsp_progress.get_mut(&token) {
979 info.message = message;
980 info.percentage = percentage;
981 }
982 }
983 LspProgressValue::End { .. } => {
984 self.active_window_mut().lsp_progress.remove(&token);
985 }
986 }
987 self.refresh_lsp_status_popup_if_open();
992 }
993
994 pub(super) fn handle_lsp_window_message(
996 &mut self,
997 language: String,
998 message_type: LspMessageType,
999 message: String,
1000 ) {
1001 self.active_window_mut()
1003 .lsp_window_messages
1004 .push(LspMessageEntry {
1005 language: language.clone(),
1006 message_type,
1007 message: message.clone(),
1008 timestamp: Instant::now(),
1009 });
1010
1011 if self.active_window_mut().lsp_window_messages.len() > 100 {
1013 self.active_window_mut().lsp_window_messages.remove(0);
1014 }
1015
1016 match message_type {
1018 LspMessageType::Error | LspMessageType::Warning => {
1019 self.active_window_mut().status_message =
1020 Some(format!("LSP ({}): {}", language, message));
1021 }
1022 _ => {
1023 }
1025 }
1026 }
1027
1028 pub(super) fn handle_lsp_log_message(
1030 &mut self,
1031 language: String,
1032 message_type: LspMessageType,
1033 message: String,
1034 ) {
1035 self.active_window_mut()
1036 .lsp_log_messages
1037 .push(LspMessageEntry {
1038 language,
1039 message_type,
1040 message,
1041 timestamp: Instant::now(),
1042 });
1043
1044 if self.active_window_mut().lsp_log_messages.len() > 500 {
1046 self.active_window_mut().lsp_log_messages.remove(0);
1047 }
1048 }
1049
1050 pub(super) fn handle_lsp_status_update(
1052 &mut self,
1053 language: String,
1054 server_name: String,
1055 status: LspServerStatus,
1056 ) {
1057 use crate::services::async_bridge::LspServerStatus;
1058
1059 let server_name_ref = server_name.clone();
1060 let key = (language.clone(), server_name);
1061
1062 let old_status = self
1064 .active_window_mut()
1065 .lsp_server_statuses
1066 .get(&key)
1067 .cloned();
1068
1069 self.active_window_mut()
1071 .lsp_server_statuses
1072 .insert(key, status);
1073
1074 self.active_window_mut().update_lsp_warning_domain();
1076
1077 if status == LspServerStatus::Running {
1081 let was_already_running = old_status
1082 .as_ref()
1083 .is_some_and(|s| matches!(s, LspServerStatus::Running));
1084 if !was_already_running {
1085 let scope = self
1086 .lsp()
1087 .as_ref()
1088 .and_then(|lsp| lsp.server_scope(&server_name_ref).cloned());
1089 match scope {
1090 Some(scope) if scope.is_universal() => {
1091 let languages: Vec<String> =
1092 self.buffers().languages().into_iter().collect();
1093 for lang in languages {
1094 self.reopen_buffers_for_language(&lang);
1095 }
1096 }
1097 Some(scope) => {
1098 for lang in scope.languages() {
1099 self.reopen_buffers_for_language(lang);
1100 }
1101 }
1102 None => {
1103 self.reopen_buffers_for_language(&language);
1105 }
1106 }
1107 }
1108 }
1109
1110 if status == LspServerStatus::Error {
1112 let was_running = old_status
1113 .as_ref()
1114 .map(|s| matches!(s, LspServerStatus::Running | LspServerStatus::Initializing))
1115 .unwrap_or(false);
1116
1117 if was_running {
1118 self.clear_diagnostics_for_server(&server_name_ref);
1121
1122 let __active_id = self.active_window;
1123
1124 if let Some(lsp) = self
1125 .windows
1126 .get_mut(&__active_id)
1127 .and_then(|w| w.lsp.as_mut())
1128 {
1129 let message = lsp.handle_server_crash(&language, &server_name_ref);
1130 self.active_window_mut().status_message = Some(message);
1131 }
1132 }
1133 }
1134
1135 if matches!(status, LspServerStatus::Error | LspServerStatus::Shutdown) {
1144 let any_running_for_lang =
1145 self.active_window()
1146 .lsp_server_statuses
1147 .iter()
1148 .any(|((lang, _), s)| {
1149 lang == &language
1150 && !matches!(s, LspServerStatus::Error | LspServerStatus::Shutdown,)
1151 });
1152 if !any_running_for_lang {
1153 let lang_owned = language.clone();
1154 self.active_window_mut()
1155 .lsp_progress
1156 .retain(|_, info| info.language != lang_owned);
1157 }
1158 self.refresh_lsp_status_popup_if_open();
1161 }
1162
1163 let status_str = match status {
1165 LspServerStatus::Starting => "starting",
1166 LspServerStatus::Initializing => "initializing",
1167 LspServerStatus::Running => "running",
1168 LspServerStatus::Error => "error",
1169 LspServerStatus::Shutdown => "shutdown",
1170 };
1171 let old_status_str = old_status
1172 .map(|s| match s {
1173 LspServerStatus::Starting => "starting",
1174 LspServerStatus::Initializing => "initializing",
1175 LspServerStatus::Running => "running",
1176 LspServerStatus::Error => "error",
1177 LspServerStatus::Shutdown => "shutdown",
1178 })
1179 .unwrap_or("none");
1180
1181 self.emit_event(
1182 crate::model::control_event::events::LSP_STATUS_CHANGED.name,
1183 serde_json::json!({
1184 "language": language,
1185 "old_status": old_status_str,
1186 "status": status_str
1187 }),
1188 );
1189 }
1190
1191 #[allow(dead_code)] pub(super) fn handle_custom_notification(
1194 &mut self,
1195 language: String,
1196 method: String,
1197 params: Option<Value>,
1198 ) {
1199 tracing::debug!("Custom LSP notification {} from {}", method, language);
1200 let payload = serde_json::json!({
1201 "language": language,
1202 "method": method,
1203 "params": params,
1204 });
1205 self.emit_event("lsp/custom_notification", payload);
1206 }
1207
1208 pub(super) fn handle_lsp_server_request(
1212 &mut self,
1213 language: String,
1214 server_command: String,
1215 method: String,
1216 params: Option<Value>,
1217 ) {
1218 tracing::debug!(
1219 "LSP server request {} from {} ({})",
1220 method,
1221 language,
1222 server_command
1223 );
1224
1225 let params_str = params.map(|p| p.to_string());
1227
1228 self.plugin_manager.read().unwrap().run_hook(
1230 "lsp_server_request",
1231 crate::services::plugins::hooks::HookArgs::LspServerRequest {
1232 language,
1233 method,
1234 server_command,
1235 params: params_str,
1236 },
1237 );
1238 }
1239
1240 pub(super) fn handle_plugin_lsp_response(
1242 &mut self,
1243 request_id: u64,
1244 result: Result<Value, String>,
1245 ) {
1246 use fresh_core::api::JsCallbackId;
1247 tracing::debug!("Received plugin LSP response (request_id={})", request_id);
1248 let callback_id = JsCallbackId::from(request_id);
1249 match result {
1250 Ok(value) => {
1251 self.plugin_manager
1252 .read()
1253 .unwrap()
1254 .resolve_callback(callback_id, value.to_string());
1255 }
1256 Err(err) => {
1257 self.plugin_manager
1258 .read()
1259 .unwrap()
1260 .reject_callback(callback_id, err);
1261 }
1262 }
1263 }
1264
1265 pub(super) fn handle_plugin_response(&mut self, response: fresh_core::api::PluginResponse) {
1267 tracing::debug!("Received plugin response: {:?}", response);
1268 self.send_plugin_response(response);
1269 }
1270}
1271
1272impl Editor {
1277 pub(super) fn handle_async_file_changed(&mut self, path: String) -> bool {
1282 const DEBOUNCE_WINDOW: Duration = Duration::from_secs(10);
1283 const RAPID_REVERT_THRESHOLD: u32 = 10; if !self.active_window().auto_revert_enabled {
1287 return false;
1288 }
1289
1290 let path_buf = PathBuf::from(&path);
1291
1292 let is_file_open = self
1294 .buffers()
1295 .iter()
1296 .any(|(_, state)| state.buffer.file_path() == Some(&path_buf));
1297
1298 if !is_file_open {
1299 tracing::trace!("Ignoring file change event for non-open file: {}", path);
1300 return false;
1301 }
1302
1303 let mut should_disable = false;
1305 let now = self.time_source.now();
1306 let elapsed_window_ok = if let Some((window_start, _)) =
1307 self.active_window().file_rapid_change_counts.get(&path_buf)
1308 {
1309 self.time_source.elapsed_since(*window_start) < DEBOUNCE_WINDOW
1310 } else {
1311 false
1312 };
1313 if let Some((window_start, count)) = self
1314 .active_window_mut()
1315 .file_rapid_change_counts
1316 .get_mut(&path_buf)
1317 {
1318 if elapsed_window_ok {
1319 *count += 1;
1320
1321 if *count >= RAPID_REVERT_THRESHOLD {
1322 should_disable = true;
1323 tracing::info!(
1324 "Auto-revert disabled for {:?} ({} reverts in {:?})",
1325 path_buf,
1326 count,
1327 DEBOUNCE_WINDOW
1328 );
1329 }
1330 } else {
1331 *count = 1;
1333 *window_start = now;
1334 }
1335 } else {
1336 let now = self.time_source.now();
1338 self.active_window_mut()
1339 .file_rapid_change_counts
1340 .insert(path_buf.clone(), (now, 1));
1341 }
1342 if should_disable {
1343 self.active_window_mut().auto_revert_enabled = false;
1344 self.active_window_mut().status_message = Some(format!(
1345 "Auto-revert disabled: {} is updating too frequently (use Ctrl+Shift+R to re-enable)",
1346 path_buf.file_name().unwrap_or_default().to_string_lossy()
1347 ));
1348 return false;
1349 }
1350
1351 tracing::info!("File changed externally: {}", path);
1352 self.handle_file_changed(&path);
1353 true
1354 }
1355}
1356
1357impl Editor {
1362 pub(super) fn handle_file_explorer_initialized(&mut self, mut view: FileTreeView) {
1364 tracing::info!("File explorer initialized");
1365
1366 let root_id = view.tree().root_id();
1368 let root_path = view.tree().get_node(root_id).map(|n| n.entry.path.clone());
1369
1370 if let Some(root_path) = root_path {
1371 crate::app::file_operations::load_gitignore_via_fs(
1372 self.authority.filesystem.as_ref(),
1373 &mut view,
1374 &root_path,
1375 );
1376 tracing::debug!("Loaded root .gitignore from {:?}", root_path);
1377 }
1378
1379 let show_hidden = self
1383 .active_window_mut()
1384 .pending_file_explorer_show_hidden
1385 .take()
1386 .unwrap_or(self.config.file_explorer.show_hidden);
1387 view.ignore_patterns_mut().set_show_hidden(show_hidden);
1388 tracing::debug!("Applied show_hidden={} on init", show_hidden);
1389
1390 let show_gitignored = self
1391 .active_window_mut()
1392 .pending_file_explorer_show_gitignored
1393 .take()
1394 .unwrap_or(self.config.file_explorer.show_gitignored);
1395 view.ignore_patterns_mut()
1396 .set_show_gitignored(show_gitignored);
1397 tracing::debug!("Applied show_gitignored={} on init", show_gitignored);
1398
1399 view.set_compact_directories(self.config.file_explorer.compact_directories);
1400
1401 self.active_window_mut().file_explorer = Some(view);
1402 self.set_status_message(t!("status.file_explorer_ready").to_string());
1403
1404 if self.file_explorer_visible() {
1410 self.active_window_mut().sync_file_explorer_to_active_file();
1411 }
1412 }
1413
1414 pub(super) fn handle_file_explorer_toggle_node(&mut self, node_id: NodeId) {
1416 tracing::debug!("File explorer toggle completed for node {:?}", node_id);
1417 }
1418
1419 pub(super) fn handle_file_explorer_refresh_node(&mut self, node_id: NodeId) {
1421 tracing::debug!("File explorer refresh completed for node {:?}", node_id);
1422 self.set_status_message(t!("explorer.refreshed_default").to_string());
1423 }
1424
1425 pub(super) fn handle_file_explorer_expanded_to_path(&mut self, mut view: FileTreeView) {
1427 tracing::trace!(
1428 "handle_file_explorer_expanded_to_path: restoring file_explorer after async expand"
1429 );
1430 view.update_scroll_for_selection();
1431 self.active_window_mut().file_explorer = Some(view);
1432 self.active_window_mut().file_explorer_sync_in_progress = false;
1433 }
1434}
1435
1436impl Editor {
1441 pub(super) fn handle_plugin_process_output(
1443 &mut self,
1444 callback_id: fresh_core::api::JsCallbackId,
1445 stdout: String,
1446 stderr: String,
1447 exit_code: i32,
1448 ) {
1449 tracing::debug!(
1450 "Process {} completed: exit_code={}, stdout_len={}, stderr_len={}",
1451 callback_id,
1452 exit_code,
1453 stdout.len(),
1454 stderr.len()
1455 );
1456 let result = fresh_core::api::SpawnResult {
1459 stdout,
1460 stderr,
1461 exit_code,
1462 };
1463 self.plugin_manager
1464 .read()
1465 .unwrap()
1466 .resolve_callback(callback_id, serde_json::to_string(&result).unwrap());
1467 }
1468
1469 pub(super) fn process_plugin_commands(&mut self) -> bool {
1474 let commands = self.plugin_manager.write().unwrap().process_commands();
1475 if commands.is_empty() {
1476 return false;
1477 }
1478
1479 use fresh_core::api::PluginCommand as Pc;
1496 let has_visual_commands = commands.iter().any(|c| match c {
1497 Pc::HookCompleted { .. }
1498 | Pc::Delay { .. }
1499 | Pc::SpawnProcess { .. }
1500 | Pc::SpawnBackgroundProcess { .. }
1501 | Pc::KillBackgroundProcess { .. }
1502 | Pc::SpawnProcessWait { .. }
1503 | Pc::HttpFetch { .. }
1504 | Pc::WatchPath { .. }
1505 | Pc::UnwatchPath { .. }
1506 | Pc::SetGlobalState { .. }
1507 | Pc::SetWindowState { .. }
1508 | Pc::SetViewState { .. } => false,
1509 Pc::SetStatusBarValue {
1510 buffer_id,
1511 key,
1512 value,
1513 } => {
1514 self.current_status_bar_value(fresh_core::BufferId(*buffer_id as usize), key)
1515 != Some(value.as_str())
1516 }
1517 _ => true,
1518 });
1519
1520 let cmd_names: Vec<String> = commands.iter().map(|c| c.debug_variant_name()).collect();
1521 tracing::trace!(
1522 count = commands.len(),
1523 cmds = ?cmd_names,
1524 "process_plugin_commands"
1525 );
1526
1527 for command in &commands {
1528 match command {
1529 fresh_core::api::PluginCommand::RegisterGrammar {
1530 language,
1531 grammar_path,
1532 extensions,
1533 } => {
1534 tracing::info!(
1535 "[SYNTAX DEBUG] processing RegisterGrammar: lang='{}', path='{}', ext={:?}",
1536 language,
1537 grammar_path,
1538 extensions
1539 );
1540 }
1541 fresh_core::api::PluginCommand::ReloadGrammars { .. } => {
1542 tracing::info!("[SYNTAX DEBUG] processing ReloadGrammars command");
1543 }
1544 _ => {}
1545 }
1546 }
1547
1548 for command in commands {
1549 tracing::trace!(
1550 "process_plugin_commands: handling command {:?}",
1551 std::mem::discriminant(&command)
1552 );
1553 if let Err(e) = self.handle_plugin_command(command) {
1554 tracing::error!("Error handling TypeScript plugin command: {}", e);
1555 }
1556 }
1557
1558 self.flush_pending_grammars();
1560
1561 has_visual_commands
1562 }
1563
1564 #[cfg(feature = "plugins")]
1566 pub(super) fn process_pending_plugin_actions(&mut self) {
1567 self.pending_plugin_actions
1568 .retain(|(action_name, receiver)| {
1569 match receiver.try_recv() {
1570 Ok(result) => {
1571 match result {
1572 Ok(()) => {
1573 tracing::info!(
1574 "Plugin action '{}' executed successfully",
1575 action_name
1576 );
1577 }
1578 Err(e) => {
1579 tracing::error!("Plugin action '{}' error: {}", action_name, e);
1580 }
1581 }
1582 false }
1584 Err(std::sync::mpsc::TryRecvError::Empty) => {
1585 true }
1587 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
1588 tracing::error!(
1589 "Plugin thread disconnected during action '{}'",
1590 action_name
1591 );
1592 false }
1594 }
1595 });
1596 }
1597
1598 #[cfg(feature = "plugins")]
1608 #[doc(hidden)]
1609 pub fn pending_plugin_actions_is_empty(&self) -> bool {
1610 self.pending_plugin_actions.is_empty()
1611 }
1612
1613 #[cfg(not(feature = "plugins"))]
1616 #[doc(hidden)]
1617 pub fn pending_plugin_actions_is_empty(&self) -> bool {
1618 true
1619 }
1620
1621 pub(super) fn process_pending_lsp_restarts(&mut self) {
1623 let __active_id = self.active_window;
1624 let Some(lsp) = self
1625 .windows
1626 .get_mut(&__active_id)
1627 .and_then(|w| w.lsp.as_mut())
1628 else {
1629 return;
1630 };
1631
1632 let restart_results = lsp.process_pending_restarts();
1633
1634 for (language, success, message) in restart_results {
1635 self.active_window_mut().status_message = Some(message.clone());
1636
1637 if success {
1638 self.resend_did_open_for_language(&language);
1639 }
1640 }
1641 }
1642
1643 pub(super) fn resend_did_open_for_language(&mut self, language: &str) {
1645 let buffers_for_language: Vec<_> = self
1647 .buffers()
1648 .iter()
1649 .filter_map(|(buf_id, state)| {
1650 if state.language == language {
1651 self.active_window()
1652 .buffer_metadata
1653 .get(buf_id)
1654 .and_then(|meta| meta.file_path().map(|p| (*buf_id, p.clone())))
1655 } else {
1656 None
1657 }
1658 })
1659 .collect();
1660
1661 for (buffer_id, path) in buffers_for_language {
1663 if let Some(state) = self
1664 .windows
1665 .get(&self.active_window)
1666 .map(|w| &w.buffers)
1667 .expect("active window present")
1668 .get(&buffer_id)
1669 {
1670 let content = match state.buffer.to_string() {
1671 Some(c) => c,
1672 None => continue, };
1674 let uri: Option<lsp_types::Uri> =
1675 super::types::file_path_to_lsp_uri_with_translation(
1676 &path,
1677 self.authority.path_translation.as_ref(),
1678 );
1679
1680 if let Some(uri) = uri {
1681 let lang_id = state.language.clone();
1682 let __active_id = self.active_window;
1683 if let Some(__win) = self.windows.get_mut(&__active_id) {
1684 if let Some(lsp) = __win.lsp.as_mut() {
1685 for sh in lsp.get_handles_mut(&lang_id) {
1689 let handle_id = sh.handle.id();
1690 if let Err(e) = sh.handle.did_open(
1691 uri.clone(),
1692 content.clone(),
1693 lang_id.clone(),
1694 ) {
1695 tracing::warn!(
1696 "LSP did_open failed for '{}' after restart: {}",
1697 sh.name,
1698 e
1699 );
1700 } else if let Some(metadata) =
1701 __win.buffer_metadata.get_mut(&buffer_id)
1702 {
1703 metadata.lsp_opened_with.insert(handle_id);
1707 }
1708 }
1709 }
1710 }
1711 }
1712 }
1713 }
1714 }
1715
1716 pub(super) fn request_semantic_tokens_for_language(&mut self, language: &str) {
1718 let buffer_ids: Vec<_> = self
1719 .buffers_for_language(language)
1720 .into_iter()
1721 .map(|(id, _)| id)
1722 .collect();
1723 for buffer_id in buffer_ids {
1724 self.active_window_mut()
1725 .schedule_semantic_tokens_full_refresh(buffer_id);
1726 }
1727 }
1728
1729 pub(super) fn request_folding_ranges_for_language(&mut self, language: &str) {
1731 let buffer_ids: Vec<_> = self
1732 .buffers_for_language(language)
1733 .into_iter()
1734 .map(|(id, _)| id)
1735 .collect();
1736 for buffer_id in buffer_ids {
1737 self.active_window_mut()
1738 .schedule_folding_ranges_refresh(buffer_id);
1739 }
1740 }
1741
1742 pub(super) fn request_inlay_hints_for_language(&mut self, language: &str) {
1750 let buffer_ids: Vec<_> = self
1751 .buffers_for_language(language)
1752 .into_iter()
1753 .map(|(id, _)| id)
1754 .collect();
1755 for buffer_id in buffer_ids {
1756 self.request_inlay_hints_for_buffer(buffer_id);
1757 }
1758 }
1759}
1760
1761fn semantic_tokens_to_raw(tokens: &[SemanticToken]) -> Vec<u32> {
1762 let mut raw = Vec::with_capacity(tokens.len().saturating_mul(5));
1763 for token in tokens {
1764 raw.push(token.delta_line);
1765 raw.push(token.delta_start);
1766 raw.push(token.length);
1767 raw.push(token.token_type);
1768 raw.push(token.token_modifiers_bitset);
1769 }
1770 raw
1771}
1772
1773fn decode_semantic_token_raw_data(
1774 buffer: &Buffer,
1775 legend: &SemanticTokensLegend,
1776 data: &[u32],
1777 base_line: usize,
1778) -> Vec<SemanticTokenSpan> {
1779 if !data.len().is_multiple_of(5) {
1780 tracing::warn!(
1781 "Semantic token data length {} is not divisible by 5",
1782 data.len()
1783 );
1784 return Vec::new();
1785 }
1786
1787 let mut result = Vec::with_capacity(data.len() / 5);
1788 let mut current_line = base_line as u32;
1789 let mut current_start = 0u32;
1790
1791 for chunk in data.chunks_exact(5) {
1792 let delta_line = chunk[0];
1793 let delta_start = chunk[1];
1794 let length = chunk[2];
1795 let token_type = chunk[3];
1796 let token_modifiers_bitset = chunk[4];
1797
1798 current_line += delta_line;
1799 if delta_line == 0 {
1800 current_start += delta_start;
1801 } else {
1802 current_start = delta_start;
1803 }
1804
1805 let start_utf16 = current_start as usize;
1806 let end_utf16 = start_utf16 + length as usize;
1807 let start_byte = buffer.lsp_position_to_byte(current_line as usize, start_utf16);
1808 let end_byte = buffer.lsp_position_to_byte(current_line as usize, end_utf16);
1809
1810 let token_type_name = legend
1811 .token_types
1812 .get(token_type as usize)
1813 .map(|ty| ty.as_str().to_string())
1814 .unwrap_or_else(|| "unknown".to_string());
1815
1816 let mut modifiers = Vec::new();
1817 for (idx, modifier) in legend.token_modifiers.iter().enumerate() {
1818 if (token_modifiers_bitset >> idx) & 1 == 1 {
1819 modifiers.push(modifier.as_str().to_string());
1820 }
1821 }
1822
1823 result.push(SemanticTokenSpan {
1824 range: start_byte..end_byte,
1825 token_type: token_type_name,
1826 modifiers,
1827 });
1828 }
1829
1830 result
1831}
1832
1833struct SemanticTokenDecode {
1834 raw: Vec<u32>,
1835 spans: Vec<SemanticTokenSpan>,
1836}
1837
1838struct SemanticTokensFullDecode {
1839 result_id: Option<String>,
1840 raw_data: Vec<u32>,
1841 spans: Vec<SemanticTokenSpan>,
1842}
1843
1844struct SemanticTokensDeltaDecode {
1845 result_id: Option<String>,
1846 raw_data: Vec<u32>,
1847}
1848
1849fn decode_semantic_token_data(
1850 buffer: &Buffer,
1851 legend: &SemanticTokensLegend,
1852 data: &[SemanticToken],
1853 base_line: usize,
1854) -> SemanticTokenDecode {
1855 let raw = semantic_tokens_to_raw(data);
1856 let spans = decode_semantic_token_raw_data(buffer, legend, &raw, base_line);
1857 SemanticTokenDecode { raw, spans }
1858}
1859
1860fn apply_semantic_token_edits(
1861 mut data: Vec<u32>,
1862 edits: &[SemanticTokensEdit],
1863) -> Option<Vec<u32>> {
1864 if edits.is_empty() {
1865 return Some(data);
1866 }
1867
1868 for edit in edits.iter().rev() {
1869 let start = edit.start as usize;
1870 let delete_count = edit.delete_count as usize;
1871 if start > data.len() || start.saturating_add(delete_count) > data.len() {
1872 return None;
1873 }
1874
1875 let insert = edit
1876 .data
1877 .as_ref()
1878 .map(|tokens| semantic_tokens_to_raw(tokens))
1879 .unwrap_or_default();
1880
1881 data.splice(start..start + delete_count, insert);
1882 }
1883
1884 Some(data)
1885}
1886
1887#[cfg(test)]
1888mod tests {
1889 use super::*;
1890
1891 #[test]
1892 fn semantic_token_delta_edits_apply() {
1893 let base = vec![0, 0, 2, 0, 0, 0, 3, 4, 1, 0];
1894 let edit = SemanticTokensEdit {
1895 start: 5,
1896 delete_count: 5,
1897 data: Some(vec![SemanticToken {
1898 delta_line: 0,
1899 delta_start: 5,
1900 length: 1,
1901 token_type: 2,
1902 token_modifiers_bitset: 0,
1903 }]),
1904 };
1905
1906 let updated = apply_semantic_token_edits(base, &[edit]).expect("edit should apply");
1907 assert_eq!(updated.len(), 10);
1908 assert_eq!(&updated[5..10], &[0, 5, 1, 2, 0]);
1909 }
1910}