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 lsp = &mut __win.lsp;
849
850 let Some(sh) = lsp.handle_for_feature_mut(&language, crate::types::LspFeature::InlayHints)
852 else {
853 return;
854 };
855 let client = &mut sh.handle;
856
857 let __next_id = &mut __win.next_lsp_request_id;
858 let __pending = &mut __win.pending_inlay_hints_requests;
859
860 for (buffer_id, uri, line_count, version) in buffer_infos {
866 let request_id = *__next_id;
867 *__next_id += 1;
868
869 let last_line = line_count.saturating_sub(1) as u32;
870 if let Err(e) =
871 client.inlay_hints(request_id, uri.as_uri().clone(), 0, 0, last_line, 10000)
872 {
873 tracing::debug!(
874 "Failed to re-request inlay hints for {}: {}",
875 uri.as_str(),
876 e
877 );
878 } else {
879 __pending.insert(request_id, super::InlayHintsRequest { buffer_id, version });
880 tracing::info!(
881 "Re-requested inlay hints for {} (request_id={})",
882 uri.as_str(),
883 request_id
884 );
885 }
886 }
887
888 self.request_folding_ranges_for_language(&language);
890 }
891
892 pub(super) fn handle_lsp_diagnostic_refresh(&mut self, language: String) {
895 tracing::info!(
896 "LSP ({}) diagnostic refresh requested, re-pulling diagnostics",
897 language
898 );
899 self.pull_diagnostics_for_language(&language);
900 }
901
902 pub(super) fn handle_lsp_inlay_hint_refresh(&mut self, language: String) {
903 tracing::info!(
904 "LSP ({}) inlay-hint refresh requested, re-pulling inlay hints",
905 language
906 );
907 self.request_inlay_hints_for_language(&language);
908 }
909
910 pub(super) fn handle_lsp_semantic_tokens_refresh(&mut self, language: String) {
911 tracing::info!(
912 "LSP ({}) semantic-tokens refresh requested, re-pulling semantic tokens",
913 language
914 );
915 self.request_semantic_tokens_for_language(&language);
916 }
917
918 pub(super) fn handle_lsp_dynamic_capabilities(
923 &mut self,
924 language: String,
925 server_name: String,
926 register: bool,
927 registrations: Vec<(String, Option<serde_json::Value>)>,
928 ) {
929 tracing::info!(
930 "LSP ({}) server '{}' {} {} capability registration(s)",
931 language,
932 server_name,
933 if register {
934 "registered"
935 } else {
936 "unregistered"
937 },
938 registrations.len()
939 );
940
941 let __active_id = self.active_window;
942 let changed = self
943 .windows
944 .get_mut(&__active_id)
945 .map(|w| &mut w.lsp)
946 .is_some_and(|lsp| {
947 lsp.apply_dynamic_capabilities(&server_name, register, ®istrations)
948 });
949
950 if changed && register {
953 self.request_semantic_tokens_for_language(&language);
954 self.request_folding_ranges_for_language(&language);
955 self.request_inlay_hints_for_language(&language);
956 self.pull_diagnostics_for_language(&language);
957 }
958 }
959
960 pub(super) fn pull_diagnostics_for_language(&mut self, language: &str) {
962 let uris: Vec<_> = self
965 .buffers_for_language(language)
966 .into_iter()
967 .map(|(_, uri)| uri)
968 .collect();
969
970 if uris.is_empty() {
971 return;
972 }
973
974 let __active_id = self.active_window;
975 let Some(__win) = self.windows.get_mut(&__active_id) else {
976 return;
977 };
978 let diagnostic_result_ids = &__win.diagnostic_result_ids;
979 let lsp = &mut __win.lsp;
980 let Some(sh) = lsp.handle_for_feature_mut(language, crate::types::LspFeature::Diagnostics)
981 else {
982 return;
983 };
984 let client = &mut sh.handle;
985 let __next_id = &mut __win.next_lsp_request_id;
986
987 for uri in uris {
988 let request_id = *__next_id;
989 *__next_id += 1;
990 let previous_result_id = diagnostic_result_ids.get(uri.as_str()).cloned();
991 if let Err(e) =
992 client.document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
993 {
994 tracing::debug!("Failed to re-pull diagnostics for {}: {}", uri.as_str(), e);
995 } else {
996 tracing::info!(
997 "Re-pulling diagnostics for {} (request_id={})",
998 uri.as_str(),
999 request_id
1000 );
1001 }
1002 }
1003 }
1004
1005 pub(super) fn handle_lsp_progress(
1007 &mut self,
1008 language: String,
1009 token: String,
1010 value: LspProgressValue,
1011 ) {
1012 match value {
1013 LspProgressValue::Begin {
1014 title,
1015 message,
1016 percentage,
1017 } => {
1018 self.active_window_mut().lsp_progress.insert(
1019 token.clone(),
1020 LspProgressInfo {
1021 language,
1022 title,
1023 message,
1024 percentage,
1025 },
1026 );
1027 }
1028 LspProgressValue::Report {
1029 message,
1030 percentage,
1031 } => {
1032 if let Some(info) = self.active_window_mut().lsp_progress.get_mut(&token) {
1033 info.message = message;
1034 info.percentage = percentage;
1035 }
1036 }
1037 LspProgressValue::End { .. } => {
1038 self.active_window_mut().lsp_progress.remove(&token);
1039 }
1040 }
1041 self.refresh_lsp_status_popup_if_open();
1046 }
1047
1048 pub(super) fn handle_lsp_window_message(
1050 &mut self,
1051 language: String,
1052 message_type: LspMessageType,
1053 message: String,
1054 ) {
1055 self.active_window_mut()
1057 .lsp_window_messages
1058 .push(LspMessageEntry {
1059 language: language.clone(),
1060 message_type,
1061 message: message.clone(),
1062 timestamp: Instant::now(),
1063 });
1064
1065 if self.active_window_mut().lsp_window_messages.len() > 100 {
1067 self.active_window_mut().lsp_window_messages.remove(0);
1068 }
1069
1070 match message_type {
1072 LspMessageType::Error | LspMessageType::Warning => {
1073 self.active_window_mut().status_message =
1074 Some(format!("LSP ({}): {}", language, message));
1075 }
1076 _ => {
1077 }
1079 }
1080 }
1081
1082 pub(super) fn handle_lsp_log_message(
1084 &mut self,
1085 language: String,
1086 message_type: LspMessageType,
1087 message: String,
1088 ) {
1089 self.active_window_mut()
1090 .lsp_log_messages
1091 .push(LspMessageEntry {
1092 language,
1093 message_type,
1094 message,
1095 timestamp: Instant::now(),
1096 });
1097
1098 if self.active_window_mut().lsp_log_messages.len() > 500 {
1100 self.active_window_mut().lsp_log_messages.remove(0);
1101 }
1102 }
1103
1104 pub(super) fn handle_lsp_status_update(
1106 &mut self,
1107 language: String,
1108 server_name: String,
1109 status: LspServerStatus,
1110 ) {
1111 use crate::services::async_bridge::LspServerStatus;
1112
1113 let server_name_ref = server_name.clone();
1114 let key = (language.clone(), server_name);
1115
1116 let old_status = self
1118 .active_window_mut()
1119 .lsp_server_statuses
1120 .get(&key)
1121 .cloned();
1122
1123 self.active_window_mut()
1125 .lsp_server_statuses
1126 .insert(key, status);
1127
1128 self.active_window_mut().update_lsp_warning_domain();
1130
1131 if status == LspServerStatus::Running {
1135 let was_already_running = old_status
1136 .as_ref()
1137 .is_some_and(|s| matches!(s, LspServerStatus::Running));
1138 if !was_already_running {
1139 let scope = self
1140 .lsp()
1141 .as_ref()
1142 .and_then(|lsp| lsp.server_scope(&server_name_ref).cloned());
1143 match scope {
1144 Some(scope) if scope.is_universal() => {
1145 let languages: Vec<String> =
1146 self.buffers().languages().into_iter().collect();
1147 for lang in languages {
1148 self.reopen_buffers_for_language(&lang);
1149 }
1150 }
1151 Some(scope) => {
1152 for lang in scope.languages() {
1153 self.reopen_buffers_for_language(lang);
1154 }
1155 }
1156 None => {
1157 self.reopen_buffers_for_language(&language);
1159 }
1160 }
1161 }
1162 }
1163
1164 if status == LspServerStatus::Error {
1166 let was_running = old_status
1167 .as_ref()
1168 .map(|s| matches!(s, LspServerStatus::Running | LspServerStatus::Initializing))
1169 .unwrap_or(false);
1170
1171 if was_running {
1172 self.clear_diagnostics_for_server(&server_name_ref);
1175
1176 let __active_id = self.active_window;
1177
1178 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
1179 let message = lsp.handle_server_crash(&language, &server_name_ref);
1180 self.active_window_mut().status_message = Some(message);
1181 }
1182 }
1183 }
1184
1185 if matches!(status, LspServerStatus::Error | LspServerStatus::Shutdown) {
1194 let any_running_for_lang =
1195 self.active_window()
1196 .lsp_server_statuses
1197 .iter()
1198 .any(|((lang, _), s)| {
1199 lang == &language
1200 && !matches!(s, LspServerStatus::Error | LspServerStatus::Shutdown,)
1201 });
1202 if !any_running_for_lang {
1203 let lang_owned = language.clone();
1204 self.active_window_mut()
1205 .lsp_progress
1206 .retain(|_, info| info.language != lang_owned);
1207 }
1208 self.refresh_lsp_status_popup_if_open();
1211 }
1212
1213 let status_str = match status {
1215 LspServerStatus::Starting => "starting",
1216 LspServerStatus::Initializing => "initializing",
1217 LspServerStatus::Running => "running",
1218 LspServerStatus::Error => "error",
1219 LspServerStatus::Shutdown => "shutdown",
1220 };
1221 let old_status_str = old_status
1222 .map(|s| match s {
1223 LspServerStatus::Starting => "starting",
1224 LspServerStatus::Initializing => "initializing",
1225 LspServerStatus::Running => "running",
1226 LspServerStatus::Error => "error",
1227 LspServerStatus::Shutdown => "shutdown",
1228 })
1229 .unwrap_or("none");
1230
1231 self.emit_event(
1232 crate::model::control_event::events::LSP_STATUS_CHANGED.name,
1233 serde_json::json!({
1234 "language": language,
1235 "old_status": old_status_str,
1236 "status": status_str
1237 }),
1238 );
1239 }
1240
1241 #[allow(dead_code)] pub(super) fn handle_custom_notification(
1244 &mut self,
1245 language: String,
1246 method: String,
1247 params: Option<Value>,
1248 ) {
1249 tracing::debug!("Custom LSP notification {} from {}", method, language);
1250 let payload = serde_json::json!({
1251 "language": language,
1252 "method": method,
1253 "params": params,
1254 });
1255 self.emit_event("lsp/custom_notification", payload);
1256 }
1257
1258 pub(super) fn handle_lsp_server_request(
1262 &mut self,
1263 language: String,
1264 server_command: String,
1265 method: String,
1266 params: Option<Value>,
1267 ) {
1268 tracing::debug!(
1269 "LSP server request {} from {} ({})",
1270 method,
1271 language,
1272 server_command
1273 );
1274
1275 let params_str = params.map(|p| p.to_string());
1277
1278 self.plugin_manager.read().unwrap().run_hook(
1280 "lsp_server_request",
1281 crate::services::plugins::hooks::HookArgs::LspServerRequest {
1282 language,
1283 method,
1284 server_command,
1285 params: params_str,
1286 },
1287 );
1288 }
1289
1290 pub(super) fn handle_plugin_lsp_response(
1292 &mut self,
1293 request_id: u64,
1294 result: Result<Value, String>,
1295 ) {
1296 use fresh_core::api::JsCallbackId;
1297 tracing::debug!("Received plugin LSP response (request_id={})", request_id);
1298 let callback_id = JsCallbackId::from(request_id);
1299 match result {
1300 Ok(value) => {
1301 self.plugin_manager
1302 .read()
1303 .unwrap()
1304 .resolve_callback(callback_id, value.to_string());
1305 }
1306 Err(err) => {
1307 self.plugin_manager
1308 .read()
1309 .unwrap()
1310 .reject_callback(callback_id, err);
1311 }
1312 }
1313 }
1314
1315 pub(super) fn handle_plugin_response(&mut self, response: fresh_core::api::PluginResponse) {
1317 tracing::debug!("Received plugin response: {:?}", response);
1318 self.send_plugin_response(response);
1319 }
1320}
1321
1322impl Editor {
1327 pub(super) fn handle_async_file_changed(&mut self, path: String) -> bool {
1332 const DEBOUNCE_WINDOW: Duration = Duration::from_secs(10);
1333 const RAPID_REVERT_THRESHOLD: u32 = 10; if !self.active_window().auto_revert_enabled {
1337 return false;
1338 }
1339
1340 let path_buf = PathBuf::from(&path);
1341
1342 let is_file_open = self
1344 .buffers()
1345 .iter()
1346 .any(|(_, state)| state.buffer.file_path() == Some(&path_buf));
1347
1348 if !is_file_open {
1349 tracing::trace!("Ignoring file change event for non-open file: {}", path);
1350 return false;
1351 }
1352
1353 let mut should_disable = false;
1355 let now = self.time_source.now();
1356 let elapsed_window_ok = if let Some((window_start, _)) =
1357 self.active_window().file_rapid_change_counts.get(&path_buf)
1358 {
1359 self.time_source.elapsed_since(*window_start) < DEBOUNCE_WINDOW
1360 } else {
1361 false
1362 };
1363 if let Some((window_start, count)) = self
1364 .active_window_mut()
1365 .file_rapid_change_counts
1366 .get_mut(&path_buf)
1367 {
1368 if elapsed_window_ok {
1369 *count += 1;
1370
1371 if *count >= RAPID_REVERT_THRESHOLD {
1372 should_disable = true;
1373 tracing::info!(
1374 "Auto-revert disabled for {:?} ({} reverts in {:?})",
1375 path_buf,
1376 count,
1377 DEBOUNCE_WINDOW
1378 );
1379 }
1380 } else {
1381 *count = 1;
1383 *window_start = now;
1384 }
1385 } else {
1386 let now = self.time_source.now();
1388 self.active_window_mut()
1389 .file_rapid_change_counts
1390 .insert(path_buf.clone(), (now, 1));
1391 }
1392 if should_disable {
1393 self.active_window_mut().auto_revert_enabled = false;
1394 self.active_window_mut().status_message = Some(format!(
1395 "Auto-revert disabled: {} is updating too frequently (use Ctrl+Shift+R to re-enable)",
1396 path_buf.file_name().unwrap_or_default().to_string_lossy()
1397 ));
1398 return false;
1399 }
1400
1401 tracing::info!("File changed externally: {}", path);
1402 self.handle_file_changed(&path);
1403 true
1404 }
1405}
1406
1407impl Editor {
1412 pub(super) fn handle_file_explorer_initialized(
1414 &mut self,
1415 window: fresh_core::WindowId,
1416 view: FileTreeView,
1417 ) {
1418 tracing::info!("File explorer initialized for window {window}");
1419 let defaults = crate::app::file_explorer::FileExplorerViewDefaults {
1420 show_hidden: self.config.file_explorer.show_hidden,
1421 show_gitignored: self.config.file_explorer.show_gitignored,
1422 compact_directories: self.config.file_explorer.compact_directories,
1423 };
1424 let is_active = window == self.active_window_id();
1425 let Some(win) = self.windows.get_mut(&window) else {
1430 return;
1431 };
1432 win.install_initialized_file_explorer(view, defaults);
1433 if is_active {
1434 self.set_status_message(t!("status.file_explorer_ready").to_string());
1435 }
1436 }
1437
1438 pub(super) fn handle_file_explorer_toggle_node(&mut self, node_id: NodeId) {
1440 tracing::debug!("File explorer toggle completed for node {:?}", node_id);
1441 }
1442
1443 pub(super) fn handle_file_explorer_refresh_node(&mut self, node_id: NodeId) {
1445 tracing::debug!("File explorer refresh completed for node {:?}", node_id);
1446 self.set_status_message(t!("explorer.refreshed_default").to_string());
1447 }
1448
1449 pub(super) fn handle_file_explorer_expanded_to_path(
1451 &mut self,
1452 window: fresh_core::WindowId,
1453 view: FileTreeView,
1454 ) {
1455 tracing::trace!(
1456 "handle_file_explorer_expanded_to_path: restoring file_explorer for window {window}"
1457 );
1458 if let Some(win) = self.windows.get_mut(&window) {
1460 win.install_expanded_file_explorer(view);
1461 }
1462 }
1463}
1464
1465impl Editor {
1470 pub(super) fn handle_plugin_process_output(
1472 &mut self,
1473 callback_id: fresh_core::api::JsCallbackId,
1474 stdout: String,
1475 stderr: String,
1476 exit_code: i32,
1477 ) {
1478 tracing::debug!(
1479 "Process {} completed: exit_code={}, stdout_len={}, stderr_len={}",
1480 callback_id,
1481 exit_code,
1482 stdout.len(),
1483 stderr.len()
1484 );
1485 let result = fresh_core::api::SpawnResult {
1488 stdout,
1489 stderr,
1490 exit_code,
1491 };
1492 self.plugin_manager
1493 .read()
1494 .unwrap()
1495 .resolve_callback(callback_id, serde_json::to_string(&result).unwrap());
1496 }
1497
1498 #[cfg(feature = "plugins")]
1503 pub(super) fn process_plugin_commands(&mut self) -> bool {
1504 let commands = self.plugin_manager.write().unwrap().process_commands();
1505 if commands.is_empty() {
1506 return false;
1507 }
1508
1509 use fresh_core::api::PluginCommand as Pc;
1526 let has_visual_commands = commands.iter().any(|c| match c {
1527 Pc::HookCompleted { .. }
1528 | Pc::Delay { .. }
1529 | Pc::SpawnProcess { .. }
1530 | Pc::SpawnBackgroundProcess { .. }
1531 | Pc::KillBackgroundProcess { .. }
1532 | Pc::SpawnProcessWait { .. }
1533 | Pc::HttpFetch { .. }
1534 | Pc::WatchPath { .. }
1535 | Pc::UnwatchPath { .. }
1536 | Pc::SetGlobalState { .. }
1537 | Pc::SetWindowState { .. }
1538 | Pc::SetViewState { .. } => false,
1539 Pc::SetStatusBarValue {
1540 buffer_id,
1541 key,
1542 value,
1543 } => {
1544 self.current_status_bar_value(fresh_core::BufferId(*buffer_id as usize), key)
1545 != Some(value.as_str())
1546 }
1547 _ => true,
1548 });
1549
1550 let cmd_names: Vec<String> = commands.iter().map(|c| c.debug_variant_name()).collect();
1551 tracing::trace!(
1552 count = commands.len(),
1553 cmds = ?cmd_names,
1554 "process_plugin_commands"
1555 );
1556
1557 for command in &commands {
1558 match command {
1559 fresh_core::api::PluginCommand::RegisterGrammar {
1560 language,
1561 grammar_path,
1562 extensions,
1563 } => {
1564 tracing::info!(
1565 "[SYNTAX DEBUG] processing RegisterGrammar: lang='{}', path='{}', ext={:?}",
1566 language,
1567 grammar_path,
1568 extensions
1569 );
1570 }
1571 fresh_core::api::PluginCommand::ReloadGrammars { .. } => {
1572 tracing::info!("[SYNTAX DEBUG] processing ReloadGrammars command");
1573 }
1574 _ => {}
1575 }
1576 }
1577
1578 for command in commands {
1579 tracing::trace!(
1580 "process_plugin_commands: handling command {:?}",
1581 std::mem::discriminant(&command)
1582 );
1583 if let Err(e) = self.handle_plugin_command(command) {
1584 tracing::error!("Error handling TypeScript plugin command: {}", e);
1585 }
1586 }
1587
1588 self.flush_pending_grammars();
1590
1591 has_visual_commands
1592 }
1593
1594 #[cfg(feature = "plugins")]
1596 pub(super) fn process_pending_plugin_actions(&mut self) {
1597 self.pending_plugin_actions
1598 .retain(|(action_name, receiver)| {
1599 match receiver.try_recv() {
1600 Ok(result) => {
1601 match result {
1602 Ok(()) => {
1603 tracing::info!(
1604 "Plugin action '{}' executed successfully",
1605 action_name
1606 );
1607 }
1608 Err(e) => {
1609 tracing::error!("Plugin action '{}' error: {}", action_name, e);
1610 }
1611 }
1612 false }
1614 Err(std::sync::mpsc::TryRecvError::Empty) => {
1615 true }
1617 Err(std::sync::mpsc::TryRecvError::Disconnected) => {
1618 tracing::error!(
1619 "Plugin thread disconnected during action '{}'",
1620 action_name
1621 );
1622 false }
1624 }
1625 });
1626 }
1627
1628 #[cfg(feature = "plugins")]
1638 #[doc(hidden)]
1639 pub fn pending_plugin_actions_is_empty(&self) -> bool {
1640 self.pending_plugin_actions.is_empty()
1641 }
1642
1643 #[cfg(not(feature = "plugins"))]
1646 #[doc(hidden)]
1647 pub fn pending_plugin_actions_is_empty(&self) -> bool {
1648 true
1649 }
1650
1651 pub(super) fn process_pending_lsp_restarts(&mut self) {
1653 let __active_id = self.active_window;
1654 let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) else {
1655 return;
1656 };
1657
1658 let restart_results = lsp.process_pending_restarts();
1659
1660 for (language, success, message) in restart_results {
1661 self.active_window_mut().status_message = Some(message.clone());
1662
1663 if success {
1664 self.resend_did_open_for_language(&language);
1665 }
1666 }
1667 }
1668
1669 pub(super) fn resend_did_open_for_language(&mut self, language: &str) {
1671 let buffers_for_language: Vec<_> = self
1673 .buffers()
1674 .iter()
1675 .filter_map(|(buf_id, state)| {
1676 if state.language == language {
1677 self.active_window()
1678 .buffer_metadata
1679 .get(buf_id)
1680 .and_then(|meta| meta.file_path().map(|p| (*buf_id, p.clone())))
1681 } else {
1682 None
1683 }
1684 })
1685 .collect();
1686
1687 for (buffer_id, path) in buffers_for_language {
1689 if let Some(state) = self
1690 .windows
1691 .get(&self.active_window)
1692 .map(|w| &w.buffers)
1693 .expect("active window present")
1694 .get(&buffer_id)
1695 {
1696 let content = match state.buffer.to_string() {
1697 Some(c) => c,
1698 None => continue, };
1700 let uri: Option<lsp_types::Uri> =
1701 super::types::file_path_to_lsp_uri_with_translation(
1702 &path,
1703 self.authority().path_translation.as_ref(),
1704 );
1705
1706 if let Some(uri) = uri {
1707 let lang_id = state.language.clone();
1708 let __active_id = self.active_window;
1709 if let Some(__win) = self.windows.get_mut(&__active_id) {
1710 {
1711 let lsp = &mut __win.lsp;
1712 for sh in lsp.get_handles_mut(&lang_id) {
1716 let handle_id = sh.handle.id();
1717 if let Err(e) = sh.handle.did_open(
1718 uri.clone(),
1719 content.clone(),
1720 lang_id.clone(),
1721 ) {
1722 tracing::warn!(
1723 "LSP did_open failed for '{}' after restart: {}",
1724 sh.name,
1725 e
1726 );
1727 } else if let Some(metadata) =
1728 __win.buffer_metadata.get_mut(&buffer_id)
1729 {
1730 metadata.lsp_opened_with.insert(handle_id);
1734 }
1735 }
1736 }
1737 }
1738 }
1739 }
1740 }
1741 }
1742
1743 pub(super) fn request_semantic_tokens_for_language(&mut self, language: &str) {
1745 let buffer_ids: Vec<_> = self
1746 .buffers_for_language(language)
1747 .into_iter()
1748 .map(|(id, _)| id)
1749 .collect();
1750 for buffer_id in buffer_ids {
1751 self.active_window_mut()
1752 .schedule_semantic_tokens_full_refresh(buffer_id);
1753 }
1754 }
1755
1756 pub(super) fn request_folding_ranges_for_language(&mut self, language: &str) {
1758 let buffer_ids: Vec<_> = self
1759 .buffers_for_language(language)
1760 .into_iter()
1761 .map(|(id, _)| id)
1762 .collect();
1763 for buffer_id in buffer_ids {
1764 self.active_window_mut()
1765 .schedule_folding_ranges_refresh(buffer_id);
1766 }
1767 }
1768
1769 pub(super) fn request_inlay_hints_for_language(&mut self, language: &str) {
1777 let buffer_ids: Vec<_> = self
1778 .buffers_for_language(language)
1779 .into_iter()
1780 .map(|(id, _)| id)
1781 .collect();
1782 for buffer_id in buffer_ids {
1783 self.request_inlay_hints_for_buffer(buffer_id);
1784 }
1785 }
1786}
1787
1788fn semantic_tokens_to_raw(tokens: &[SemanticToken]) -> Vec<u32> {
1789 let mut raw = Vec::with_capacity(tokens.len().saturating_mul(5));
1790 for token in tokens {
1791 raw.push(token.delta_line);
1792 raw.push(token.delta_start);
1793 raw.push(token.length);
1794 raw.push(token.token_type);
1795 raw.push(token.token_modifiers_bitset);
1796 }
1797 raw
1798}
1799
1800fn decode_semantic_token_raw_data(
1801 buffer: &Buffer,
1802 legend: &SemanticTokensLegend,
1803 data: &[u32],
1804 base_line: usize,
1805) -> Vec<SemanticTokenSpan> {
1806 if !data.len().is_multiple_of(5) {
1807 tracing::warn!(
1808 "Semantic token data length {} is not divisible by 5",
1809 data.len()
1810 );
1811 return Vec::new();
1812 }
1813
1814 let mut result = Vec::with_capacity(data.len() / 5);
1815 let mut current_line = base_line as u32;
1816 let mut current_start = 0u32;
1817
1818 for chunk in data.chunks_exact(5) {
1819 let delta_line = chunk[0];
1820 let delta_start = chunk[1];
1821 let length = chunk[2];
1822 let token_type = chunk[3];
1823 let token_modifiers_bitset = chunk[4];
1824
1825 current_line += delta_line;
1826 if delta_line == 0 {
1827 current_start += delta_start;
1828 } else {
1829 current_start = delta_start;
1830 }
1831
1832 let start_utf16 = current_start as usize;
1833 let end_utf16 = start_utf16 + length as usize;
1834 let start_byte = buffer.lsp_position_to_byte(current_line as usize, start_utf16);
1835 let end_byte = buffer.lsp_position_to_byte(current_line as usize, end_utf16);
1836
1837 let token_type_name = legend
1838 .token_types
1839 .get(token_type as usize)
1840 .map(|ty| ty.as_str().to_string())
1841 .unwrap_or_else(|| "unknown".to_string());
1842
1843 let mut modifiers = Vec::new();
1844 for (idx, modifier) in legend.token_modifiers.iter().enumerate() {
1845 if (token_modifiers_bitset >> idx) & 1 == 1 {
1846 modifiers.push(modifier.as_str().to_string());
1847 }
1848 }
1849
1850 result.push(SemanticTokenSpan {
1851 range: start_byte..end_byte,
1852 token_type: token_type_name,
1853 modifiers,
1854 });
1855 }
1856
1857 result
1858}
1859
1860struct SemanticTokenDecode {
1861 raw: Vec<u32>,
1862 spans: Vec<SemanticTokenSpan>,
1863}
1864
1865struct SemanticTokensFullDecode {
1866 result_id: Option<String>,
1867 raw_data: Vec<u32>,
1868 spans: Vec<SemanticTokenSpan>,
1869}
1870
1871struct SemanticTokensDeltaDecode {
1872 result_id: Option<String>,
1873 raw_data: Vec<u32>,
1874}
1875
1876fn decode_semantic_token_data(
1877 buffer: &Buffer,
1878 legend: &SemanticTokensLegend,
1879 data: &[SemanticToken],
1880 base_line: usize,
1881) -> SemanticTokenDecode {
1882 let raw = semantic_tokens_to_raw(data);
1883 let spans = decode_semantic_token_raw_data(buffer, legend, &raw, base_line);
1884 SemanticTokenDecode { raw, spans }
1885}
1886
1887fn apply_semantic_token_edits(
1888 mut data: Vec<u32>,
1889 edits: &[SemanticTokensEdit],
1890) -> Option<Vec<u32>> {
1891 if edits.is_empty() {
1892 return Some(data);
1893 }
1894
1895 for edit in edits.iter().rev() {
1896 let start = edit.start as usize;
1897 let delete_count = edit.delete_count as usize;
1898 if start > data.len() || start.saturating_add(delete_count) > data.len() {
1899 return None;
1900 }
1901
1902 let insert = edit
1903 .data
1904 .as_ref()
1905 .map(|tokens| semantic_tokens_to_raw(tokens))
1906 .unwrap_or_default();
1907
1908 data.splice(start..start + delete_count, insert);
1909 }
1910
1911 Some(data)
1912}
1913
1914#[cfg(test)]
1915mod tests {
1916 use super::*;
1917
1918 #[test]
1919 fn semantic_token_delta_edits_apply() {
1920 let base = vec![0, 0, 2, 0, 0, 0, 3, 4, 1, 0];
1921 let edit = SemanticTokensEdit {
1922 start: 5,
1923 delete_count: 5,
1924 data: Some(vec![SemanticToken {
1925 delta_line: 0,
1926 delta_start: 5,
1927 length: 1,
1928 token_type: 2,
1929 token_modifiers_bitset: 0,
1930 }]),
1931 };
1932
1933 let updated = apply_semantic_token_edits(base, &[edit]).expect("edit should apply");
1934 assert_eq!(updated.len(), 10);
1935 assert_eq!(&updated[5..10], &[0, 5, 1, 2, 0]);
1936 }
1937}