1use anyhow::Result as AnyhowResult;
14use rust_i18n::t;
15use std::io;
16use std::time::{Duration, Instant};
17
18use lsp_types::TextDocumentContentChangeEvent;
19
20use crate::model::event::{BufferId, Event};
21use crate::primitives::word_navigation::{find_word_end, find_word_start};
22use crate::view::prompt::{Prompt, PromptType};
23
24use super::{uri_to_path, Editor, SemanticTokenRangeRequest};
25
26fn space_doc_paragraphs(text: &str) -> String {
33 text.replace("\n\n", "\x00")
34 .replace('\n', "\n\n")
35 .replace('\x00', "\n\n")
36}
37
38const SEMANTIC_TOKENS_FULL_DEBOUNCE_MS: u64 = 500;
39const SEMANTIC_TOKENS_RANGE_DEBOUNCE_MS: u64 = 50;
40const SEMANTIC_TOKENS_RANGE_PADDING_LINES: usize = 10;
41const FOLDING_RANGES_DEBOUNCE_MS: u64 = 300;
42
43impl Editor {
44 pub(crate) fn handle_completion_response(
46 &mut self,
47 request_id: u64,
48 items: Vec<lsp_types::CompletionItem>,
49 ) -> AnyhowResult<()> {
50 if self.pending_completion_request != Some(request_id) {
52 tracing::debug!(
53 "Ignoring completion response for outdated request {}",
54 request_id
55 );
56 return Ok(());
57 }
58
59 self.pending_completion_request = None;
60 self.update_lsp_status_from_server_statuses();
61
62 if items.is_empty() {
63 tracing::debug!("No completion items received");
64 return Ok(());
65 }
66
67 use crate::primitives::word_navigation::find_completion_word_start;
69 let cursor_pos = self.active_cursors().primary().position;
70 let (word_start, cursor_pos) = {
71 let state = self.active_state();
72 let word_start = find_completion_word_start(&state.buffer, cursor_pos);
73 (word_start, cursor_pos)
74 };
75 let prefix = if word_start < cursor_pos {
76 self.active_state_mut()
77 .get_text_range(word_start, cursor_pos)
78 .to_lowercase()
79 } else {
80 String::new()
81 };
82
83 let filtered_items: Vec<&lsp_types::CompletionItem> = if prefix.is_empty() {
85 items.iter().collect()
87 } else {
88 items
90 .iter()
91 .filter(|item| {
92 item.label.to_lowercase().starts_with(&prefix)
93 || item
94 .filter_text
95 .as_ref()
96 .map(|ft| ft.to_lowercase().starts_with(&prefix))
97 .unwrap_or(false)
98 })
99 .collect()
100 };
101
102 if filtered_items.is_empty() {
103 tracing::debug!("No completion items match prefix '{}'", prefix);
104 return Ok(());
105 }
106
107 let popup_data = crate::app::popup_actions::build_completion_popup(&filtered_items, 0);
108
109 self.completion_items = Some(items);
111
112 {
113 let buffer_id = self.active_buffer();
114 let split_id = self.split_manager.active_split();
115 let state = self.buffers.get_mut(&buffer_id).unwrap();
116 let cursors = &mut self.split_view_states.get_mut(&split_id).unwrap().cursors;
117 state.apply(
118 cursors,
119 &crate::model::event::Event::ShowPopup { popup: popup_data },
120 );
121 }
122
123 tracing::info!(
124 "Showing completion popup with {} items",
125 self.completion_items.as_ref().map_or(0, |i| i.len())
126 );
127
128 Ok(())
129 }
130
131 pub(crate) fn handle_goto_definition_response(
133 &mut self,
134 request_id: u64,
135 locations: Vec<lsp_types::Location>,
136 ) -> AnyhowResult<()> {
137 if self.pending_goto_definition_request != Some(request_id) {
139 tracing::debug!(
140 "Ignoring go-to-definition response for outdated request {}",
141 request_id
142 );
143 return Ok(());
144 }
145
146 self.pending_goto_definition_request = None;
147
148 if locations.is_empty() {
149 self.status_message = Some(t!("lsp.no_definition").to_string());
150 return Ok(());
151 }
152
153 let location = &locations[0];
155
156 if let Ok(path) = uri_to_path(&location.uri) {
158 let buffer_id = match self.open_file(&path) {
160 Ok(id) => id,
161 Err(e) => {
162 if let Some(confirmation) =
164 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
165 {
166 self.start_large_file_encoding_confirmation(confirmation);
167 } else {
168 self.set_status_message(
169 t!("file.error_opening", error = e.to_string()).to_string(),
170 );
171 }
172 return Ok(());
173 }
174 };
175
176 let line = location.range.start.line as usize;
178 let character = location.range.start.character as usize;
179
180 let position = self
182 .buffers
183 .get(&buffer_id)
184 .map(|state| state.buffer.line_col_to_position(line, character));
185
186 if let Some(position) = position {
187 let (cursor_id, old_position, old_anchor, old_sticky_column) = {
189 let cursors = self.active_cursors();
190 let primary = cursors.primary();
191 (
192 cursors.primary_id(),
193 primary.position,
194 primary.anchor,
195 primary.sticky_column,
196 )
197 };
198 let event = crate::model::event::Event::MoveCursor {
199 cursor_id,
200 old_position,
201 new_position: position,
202 old_anchor,
203 new_anchor: None,
204 old_sticky_column,
205 new_sticky_column: 0, };
207
208 let split_id = self.split_manager.active_split();
209 if let Some(state) = self.buffers.get_mut(&buffer_id) {
210 let cursors = &mut self.split_view_states.get_mut(&split_id).unwrap().cursors;
211 state.apply(cursors, &event);
212 }
213 }
214
215 self.status_message = Some(
216 t!(
217 "lsp.jumped_to_definition",
218 path = path.display().to_string(),
219 line = line + 1
220 )
221 .to_string(),
222 );
223 } else {
224 self.status_message = Some(t!("lsp.cannot_open_definition").to_string());
225 }
226
227 Ok(())
228 }
229
230 pub fn has_pending_lsp_requests(&self) -> bool {
232 self.pending_completion_request.is_some() || self.pending_goto_definition_request.is_some()
233 }
234
235 pub(crate) fn cancel_pending_lsp_requests(&mut self) {
239 self.scheduled_completion_trigger = None;
241 if let Some(request_id) = self.pending_completion_request.take() {
242 tracing::debug!("Canceling pending LSP completion request {}", request_id);
243 self.send_lsp_cancel_request(request_id);
245 self.update_lsp_status_from_server_statuses();
246 }
247 if let Some(request_id) = self.pending_goto_definition_request.take() {
248 tracing::debug!(
249 "Canceling pending LSP goto-definition request {}",
250 request_id
251 );
252 self.send_lsp_cancel_request(request_id);
254 self.update_lsp_status_from_server_statuses();
255 }
256 }
257
258 fn send_lsp_cancel_request(&mut self, request_id: u64) {
260 let buffer_id = self.active_buffer();
262 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
263 return;
264 };
265
266 if let Some(lsp) = self.lsp.as_mut() {
267 if let Some(handle) = lsp.get_handle_mut(&language) {
269 if let Err(e) = handle.cancel_request(request_id) {
270 tracing::warn!("Failed to send LSP cancel request: {}", e);
271 } else {
272 tracing::debug!("Sent $/cancelRequest for request_id={}", request_id);
273 }
274 }
275 }
276 }
277
278 pub(crate) fn with_lsp_for_buffer<F, R>(&mut self, buffer_id: BufferId, f: F) -> Option<R>
290 where
291 F: FnOnce(&crate::services::lsp::async_handler::LspHandle, &lsp_types::Uri, &str) -> R,
292 {
293 use crate::services::lsp::manager::LspSpawnResult;
294
295 let (uri, language) = {
297 let metadata = self.buffer_metadata.get(&buffer_id)?;
298 if !metadata.lsp_enabled {
299 return None;
300 }
301 let uri = metadata.file_uri()?.clone();
302 let language = self.buffers.get(&buffer_id)?.language.clone();
303 (uri, language)
304 };
305
306 let lsp = self.lsp.as_mut()?;
309 if lsp.try_spawn(&language) != LspSpawnResult::Spawned {
310 return None;
311 }
312
313 let handle_id = lsp.get_handle_mut(&language)?.id();
315
316 let needs_open = {
318 let metadata = self.buffer_metadata.get(&buffer_id)?;
319 !metadata.lsp_opened_with.contains(&handle_id)
320 };
321
322 if needs_open {
323 let text = self.buffers.get(&buffer_id)?.buffer.to_string()?;
325
326 let lsp = self.lsp.as_mut()?;
328 let handle = lsp.get_handle_mut(&language)?;
329 if let Err(e) = handle.did_open(uri.clone(), text, language.clone()) {
330 tracing::warn!("Failed to send didOpen: {}", e);
331 return None;
332 }
333
334 let metadata = self.buffer_metadata.get_mut(&buffer_id)?;
336 metadata.lsp_opened_with.insert(handle_id);
337
338 tracing::debug!(
339 "Sent didOpen for {} to LSP handle {} (language: {})",
340 uri.as_str(),
341 handle_id,
342 language
343 );
344 }
345
346 let lsp = self.lsp.as_mut()?;
348 let handle = lsp.get_handle_mut(&language)?;
349 Some(f(handle, &uri, &language))
350 }
351
352 pub(crate) fn request_completion(&mut self) {
354 let cursor_pos = self.active_cursors().primary().position;
356 let state = self.active_state();
357
358 let (line, character) = state.buffer.position_to_lsp_position(cursor_pos);
360 let buffer_id = self.active_buffer();
361 let request_id = self.next_lsp_request_id;
362
363 let sent = self
365 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
366 let result =
367 handle.completion(request_id, uri.clone(), line as u32, character as u32);
368 if result.is_ok() {
369 tracing::info!(
370 "Requested completion at {}:{}:{}",
371 uri.as_str(),
372 line,
373 character
374 );
375 }
376 result.is_ok()
377 })
378 .unwrap_or(false);
379
380 if sent {
381 self.next_lsp_request_id += 1;
382 self.pending_completion_request = Some(request_id);
383 self.lsp_status = "LSP: completion...".to_string();
384 }
385 }
386
387 pub(crate) fn maybe_trigger_completion(&mut self, c: char) {
397 let language = self.active_state().language.clone();
399
400 let is_lsp_trigger = self
402 .lsp
403 .as_ref()
404 .map(|lsp| lsp.is_completion_trigger_char(c, &language))
405 .unwrap_or(false);
406
407 let quick_suggestions_enabled = self.config.editor.quick_suggestions;
409 let suggest_on_trigger_chars = self.config.editor.suggest_on_trigger_characters;
410 let is_word_char = c.is_alphanumeric() || c == '_';
411
412 if is_lsp_trigger && suggest_on_trigger_chars {
414 tracing::debug!(
415 "Trigger character '{}' immediately triggers completion for language {}",
416 c,
417 language
418 );
419 self.scheduled_completion_trigger = None;
421 self.request_completion();
422 return;
423 }
424
425 if quick_suggestions_enabled && is_word_char {
427 let delay_ms = self.config.editor.quick_suggestions_delay_ms;
428 let trigger_time = Instant::now() + Duration::from_millis(delay_ms);
429
430 tracing::debug!(
431 "Scheduling completion trigger in {}ms for language {} (char '{}')",
432 delay_ms,
433 language,
434 c
435 );
436
437 self.scheduled_completion_trigger = Some(trigger_time);
440 } else {
441 self.scheduled_completion_trigger = None;
445 }
446 }
447
448 pub(crate) fn request_goto_definition(&mut self) -> AnyhowResult<()> {
450 let cursor_pos = self.active_cursors().primary().position;
452 let state = self.active_state();
453
454 let (line, character) = state.buffer.position_to_lsp_position(cursor_pos);
456 let buffer_id = self.active_buffer();
457 let request_id = self.next_lsp_request_id;
458
459 let sent = self
461 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
462 let result =
463 handle.goto_definition(request_id, uri.clone(), line as u32, character as u32);
464 if result.is_ok() {
465 tracing::info!(
466 "Requested go-to-definition at {}:{}:{}",
467 uri.as_str(),
468 line,
469 character
470 );
471 }
472 result.is_ok()
473 })
474 .unwrap_or(false);
475
476 if sent {
477 self.next_lsp_request_id += 1;
478 self.pending_goto_definition_request = Some(request_id);
479 }
480
481 Ok(())
482 }
483
484 pub(crate) fn request_hover(&mut self) -> AnyhowResult<()> {
486 let cursor_pos = self.active_cursors().primary().position;
488 let state = self.active_state();
489
490 let (line, character) = state.buffer.position_to_lsp_position(cursor_pos);
492
493 if let Some(pos) = state.buffer.offset_to_position(cursor_pos) {
495 tracing::debug!(
496 "Hover request: cursor_byte={}, line={}, byte_col={}, utf16_col={}",
497 cursor_pos,
498 pos.line,
499 pos.column,
500 character
501 );
502 }
503
504 let buffer_id = self.active_buffer();
505 let request_id = self.next_lsp_request_id;
506
507 let sent = self
509 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
510 let result = handle.hover(request_id, uri.clone(), line as u32, character as u32);
511 if result.is_ok() {
512 tracing::info!(
513 "Requested hover at {}:{}:{} (byte_pos={})",
514 uri.as_str(),
515 line,
516 character,
517 cursor_pos
518 );
519 }
520 result.is_ok()
521 })
522 .unwrap_or(false);
523
524 if sent {
525 self.next_lsp_request_id += 1;
526 self.pending_hover_request = Some(request_id);
527 self.lsp_status = "LSP: hover...".to_string();
528 }
529
530 Ok(())
531 }
532
533 pub(crate) fn request_hover_at_position(&mut self, byte_pos: usize) -> AnyhowResult<()> {
536 let state = self.active_state();
538
539 let (line, character) = state.buffer.position_to_lsp_position(byte_pos);
541
542 if let Some(pos) = state.buffer.offset_to_position(byte_pos) {
544 tracing::trace!(
545 "Mouse hover request: byte_pos={}, line={}, byte_col={}, utf16_col={}",
546 byte_pos,
547 pos.line,
548 pos.column,
549 character
550 );
551 }
552
553 let buffer_id = self.active_buffer();
554 let request_id = self.next_lsp_request_id;
555
556 let sent = self
558 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
559 let result = handle.hover(request_id, uri.clone(), line as u32, character as u32);
560 if result.is_ok() {
561 tracing::trace!(
562 "Mouse hover requested at {}:{}:{} (byte_pos={})",
563 uri.as_str(),
564 line,
565 character,
566 byte_pos
567 );
568 }
569 result.is_ok()
570 })
571 .unwrap_or(false);
572
573 if sent {
574 self.next_lsp_request_id += 1;
575 self.pending_hover_request = Some(request_id);
576 self.lsp_status = "LSP: hover...".to_string();
577 }
578
579 Ok(())
580 }
581
582 pub(crate) fn handle_hover_response(
584 &mut self,
585 request_id: u64,
586 contents: String,
587 is_markdown: bool,
588 range: Option<((u32, u32), (u32, u32))>,
589 ) {
590 if self.pending_hover_request != Some(request_id) {
592 tracing::debug!("Ignoring stale hover response: {}", request_id);
593 return;
594 }
595
596 self.pending_hover_request = None;
597 self.update_lsp_status_from_server_statuses();
598
599 if contents.is_empty() {
600 self.set_status_message(t!("lsp.no_hover").to_string());
601 self.hover_symbol_range = None;
602 return;
603 }
604
605 tracing::debug!(
607 "LSP hover content (markdown={}):\n{}",
608 is_markdown,
609 contents
610 );
611
612 if let Some(((start_line, start_char), (end_line, end_char))) = range {
614 let state = self.active_state();
615 let start_byte = state
616 .buffer
617 .lsp_position_to_byte(start_line as usize, start_char as usize);
618 let end_byte = state
619 .buffer
620 .lsp_position_to_byte(end_line as usize, end_char as usize);
621 self.hover_symbol_range = Some((start_byte, end_byte));
622 tracing::debug!(
623 "Hover symbol range: {}..{} (LSP {}:{}..{}:{})",
624 start_byte,
625 end_byte,
626 start_line,
627 start_char,
628 end_line,
629 end_char
630 );
631
632 if let Some(old_handle) = self.hover_symbol_overlay.take() {
634 let remove_event = crate::model::event::Event::RemoveOverlay { handle: old_handle };
635 self.apply_event_to_active_buffer(&remove_event);
636 }
637
638 let event = crate::model::event::Event::AddOverlay {
640 namespace: None,
641 range: start_byte..end_byte,
642 face: crate::model::event::OverlayFace::Background {
643 color: (80, 80, 120), },
645 priority: 90, message: None,
647 extend_to_line_end: false,
648 url: None,
649 };
650 self.apply_event_to_active_buffer(&event);
651 if let Some(state) = self.buffers.get(&self.active_buffer()) {
653 self.hover_symbol_overlay = state.overlays.all().last().map(|o| o.handle.clone());
654 }
655 } else {
656 if let Some((hover_byte_pos, _, _, _)) = self.mouse_state.lsp_hover_state {
659 let state = self.active_state();
660 let start_byte = find_word_start(&state.buffer, hover_byte_pos);
661 let end_byte = find_word_end(&state.buffer, hover_byte_pos);
662 if start_byte < end_byte {
663 self.hover_symbol_range = Some((start_byte, end_byte));
664 tracing::debug!(
665 "Hover symbol range (computed from word boundaries): {}..{}",
666 start_byte,
667 end_byte
668 );
669 } else {
670 self.hover_symbol_range = None;
671 }
672 } else {
673 self.hover_symbol_range = None;
674 }
675 }
676
677 use crate::view::popup::{Popup, PopupPosition};
679 use ratatui::style::Style;
680
681 let mut popup = if is_markdown {
683 Popup::markdown(&contents, &self.theme, Some(&self.grammar_registry))
684 } else {
685 let lines: Vec<String> = contents.lines().map(|s| s.to_string()).collect();
687 Popup::text(lines, &self.theme)
688 };
689
690 popup.title = Some(t!("lsp.popup_hover").to_string());
692 popup.transient = true;
693 popup.position = if let Some((x, y)) = self.mouse_hover_screen_position.take() {
695 PopupPosition::Fixed { x, y: y + 1 }
697 } else {
698 PopupPosition::BelowCursor
699 };
700 popup.width = 80;
701 let dynamic_height = (self.terminal_height * 60 / 100).clamp(15, 40);
704 popup.max_height = dynamic_height;
705 popup.border_style = Style::default().fg(self.theme.popup_border_fg);
706 popup.background_style = Style::default().bg(self.theme.popup_bg);
707
708 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
710 state.popups.show(popup);
711 tracing::info!("Showing hover popup (markdown={})", is_markdown);
712 }
713
714 self.mouse_state.lsp_hover_request_sent = true;
717 }
718
719 pub(crate) fn apply_inlay_hints_to_state(
721 state: &mut crate::state::EditorState,
722 hints: &[lsp_types::InlayHint],
723 ) {
724 use crate::view::virtual_text::VirtualTextPosition;
725 use ratatui::style::{Color, Style};
726
727 state.virtual_texts.clear(&mut state.marker_list);
729
730 if hints.is_empty() {
731 return;
732 }
733
734 let hint_style = Style::default().fg(Color::Rgb(128, 128, 128));
736
737 for hint in hints {
738 let byte_offset = state.buffer.lsp_position_to_byte(
740 hint.position.line as usize,
741 hint.position.character as usize,
742 );
743
744 let text = match &hint.label {
746 lsp_types::InlayHintLabel::String(s) => s.clone(),
747 lsp_types::InlayHintLabel::LabelParts(parts) => {
748 parts.iter().map(|p| p.value.as_str()).collect::<String>()
749 }
750 };
751
752 if state.buffer.is_empty() {
758 continue;
759 }
760
761 let (byte_offset, position) = if byte_offset >= state.buffer.len() {
762 (
764 state.buffer.len().saturating_sub(1),
765 VirtualTextPosition::AfterChar,
766 )
767 } else {
768 (byte_offset, VirtualTextPosition::BeforeChar)
769 };
770
771 let display_text = text;
773
774 state.virtual_texts.add(
775 &mut state.marker_list,
776 byte_offset,
777 display_text,
778 hint_style,
779 position,
780 0, );
782 }
783
784 tracing::debug!("Applied {} inlay hints as virtual text", hints.len());
785 }
786
787 pub(crate) fn request_references(&mut self) -> AnyhowResult<()> {
789 let cursor_pos = self.active_cursors().primary().position;
791 let state = self.active_state();
792
793 let symbol = {
795 let text = match state.buffer.to_string() {
796 Some(t) => t,
797 None => {
798 self.set_status_message(t!("error.buffer_not_loaded").to_string());
799 return Ok(());
800 }
801 };
802 let bytes = text.as_bytes();
803 let buf_len = bytes.len();
804
805 if cursor_pos <= buf_len {
806 let is_word_char = |c: char| c.is_alphanumeric() || c == '_';
808
809 let mut start = cursor_pos;
811 while start > 0 {
812 start -= 1;
814 while start > 0 && (bytes[start] & 0xC0) == 0x80 {
816 start -= 1;
817 }
818 if let Some(ch) = text[start..].chars().next() {
820 if !is_word_char(ch) {
821 start += ch.len_utf8();
822 break;
823 }
824 } else {
825 break;
826 }
827 }
828
829 let mut end = cursor_pos;
831 while end < buf_len {
832 if let Some(ch) = text[end..].chars().next() {
833 if is_word_char(ch) {
834 end += ch.len_utf8();
835 } else {
836 break;
837 }
838 } else {
839 break;
840 }
841 }
842
843 if start < end {
844 text[start..end].to_string()
845 } else {
846 String::new()
847 }
848 } else {
849 String::new()
850 }
851 };
852
853 let (line, character) = state.buffer.position_to_lsp_position(cursor_pos);
855 let buffer_id = self.active_buffer();
856 let request_id = self.next_lsp_request_id;
857
858 let sent = self
860 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
861 let result =
862 handle.references(request_id, uri.clone(), line as u32, character as u32);
863 if result.is_ok() {
864 tracing::info!(
865 "Requested find references at {}:{}:{} (byte_pos={})",
866 uri.as_str(),
867 line,
868 character,
869 cursor_pos
870 );
871 }
872 result.is_ok()
873 })
874 .unwrap_or(false);
875
876 if sent {
877 self.next_lsp_request_id += 1;
878 self.pending_references_request = Some(request_id);
879 self.pending_references_symbol = symbol;
880 self.lsp_status = "LSP: finding references...".to_string();
881 }
882
883 Ok(())
884 }
885
886 pub(crate) fn request_signature_help(&mut self) {
888 let cursor_pos = self.active_cursors().primary().position;
890 let state = self.active_state();
891
892 let (line, character) = state.buffer.position_to_lsp_position(cursor_pos);
894 let buffer_id = self.active_buffer();
895 let request_id = self.next_lsp_request_id;
896
897 let sent = self
899 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
900 let result =
901 handle.signature_help(request_id, uri.clone(), line as u32, character as u32);
902 if result.is_ok() {
903 tracing::info!(
904 "Requested signature help at {}:{}:{} (byte_pos={})",
905 uri.as_str(),
906 line,
907 character,
908 cursor_pos
909 );
910 }
911 result.is_ok()
912 })
913 .unwrap_or(false);
914
915 if sent {
916 self.next_lsp_request_id += 1;
917 self.pending_signature_help_request = Some(request_id);
918 self.lsp_status = "LSP: signature help...".to_string();
919 }
920 }
921
922 pub(crate) fn handle_signature_help_response(
924 &mut self,
925 request_id: u64,
926 signature_help: Option<lsp_types::SignatureHelp>,
927 ) {
928 if self.pending_signature_help_request != Some(request_id) {
930 tracing::debug!("Ignoring stale signature help response: {}", request_id);
931 return;
932 }
933
934 self.pending_signature_help_request = None;
935 self.update_lsp_status_from_server_statuses();
936
937 let signature_help = match signature_help {
938 Some(help) if !help.signatures.is_empty() => help,
939 _ => {
940 tracing::debug!("No signature help available");
941 return;
942 }
943 };
944
945 let active_signature_idx = signature_help.active_signature.unwrap_or(0) as usize;
947 let signature = match signature_help.signatures.get(active_signature_idx) {
948 Some(sig) => sig,
949 None => return,
950 };
951
952 let mut content = String::new();
954
955 content.push_str(&signature.label);
957 content.push('\n');
958
959 let active_param = signature_help
961 .active_parameter
962 .or(signature.active_parameter)
963 .unwrap_or(0) as usize;
964
965 if let Some(params) = &signature.parameters {
967 if let Some(param) = params.get(active_param) {
968 let param_label = match ¶m.label {
970 lsp_types::ParameterLabel::Simple(s) => s.clone(),
971 lsp_types::ParameterLabel::LabelOffsets(offsets) => {
972 let start = offsets[0] as usize;
974 let end = offsets[1] as usize;
975 if end <= signature.label.len() {
976 signature.label[start..end].to_string()
977 } else {
978 String::new()
979 }
980 }
981 };
982
983 if !param_label.is_empty() {
984 content.push_str(&format!("\n> {}\n", param_label));
985 }
986
987 if let Some(doc) = ¶m.documentation {
989 let doc_text = match doc {
990 lsp_types::Documentation::String(s) => s.clone(),
991 lsp_types::Documentation::MarkupContent(m) => m.value.clone(),
992 };
993 if !doc_text.is_empty() {
994 content.push('\n');
995 content.push_str(&doc_text);
996 content.push('\n');
997 }
998 }
999 }
1000 }
1001
1002 if let Some(doc) = &signature.documentation {
1004 let doc_text = match doc {
1005 lsp_types::Documentation::String(s) => s.clone(),
1006 lsp_types::Documentation::MarkupContent(m) => m.value.clone(),
1007 };
1008 if !doc_text.is_empty() {
1009 content.push_str("\n---\n\n");
1010 content.push_str(&space_doc_paragraphs(&doc_text));
1011 }
1012 }
1013
1014 use crate::view::popup::{Popup, PopupPosition};
1016 use ratatui::style::Style;
1017
1018 let mut popup = Popup::markdown(&content, &self.theme, Some(&self.grammar_registry));
1019 popup.title = Some(t!("lsp.popup_signature").to_string());
1020 popup.transient = true;
1021 popup.position = PopupPosition::BelowCursor;
1022 popup.width = 60;
1023 popup.max_height = 20;
1024 popup.border_style = Style::default().fg(self.theme.popup_border_fg);
1025 popup.background_style = Style::default().bg(self.theme.popup_bg);
1026
1027 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
1029 state.popups.show(popup);
1030 tracing::info!(
1031 "Showing signature help popup for {} signatures",
1032 signature_help.signatures.len()
1033 );
1034 }
1035 }
1036
1037 pub(crate) fn request_code_actions(&mut self) -> AnyhowResult<()> {
1039 let cursor_pos = self.active_cursors().primary().position;
1041 let selection_range = self.active_cursors().primary().selection_range();
1042 let state = self.active_state();
1043
1044 let (line, character) = state.buffer.position_to_lsp_position(cursor_pos);
1046
1047 let (start_line, start_char, end_line, end_char) = if let Some(range) = selection_range {
1049 let (s_line, s_char) = state.buffer.position_to_lsp_position(range.start);
1050 let (e_line, e_char) = state.buffer.position_to_lsp_position(range.end);
1051 (s_line as u32, s_char as u32, e_line as u32, e_char as u32)
1052 } else {
1053 (line as u32, character as u32, line as u32, character as u32)
1054 };
1055
1056 let diagnostics: Vec<lsp_types::Diagnostic> = Vec::new();
1059 let buffer_id = self.active_buffer();
1060 let request_id = self.next_lsp_request_id;
1061
1062 let sent = self
1064 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
1065 let result = handle.code_actions(
1066 request_id,
1067 uri.clone(),
1068 start_line,
1069 start_char,
1070 end_line,
1071 end_char,
1072 diagnostics,
1073 );
1074 if result.is_ok() {
1075 tracing::info!(
1076 "Requested code actions at {}:{}:{}-{}:{} (byte_pos={})",
1077 uri.as_str(),
1078 start_line,
1079 start_char,
1080 end_line,
1081 end_char,
1082 cursor_pos
1083 );
1084 }
1085 result.is_ok()
1086 })
1087 .unwrap_or(false);
1088
1089 if sent {
1090 self.next_lsp_request_id += 1;
1091 self.pending_code_actions_request = Some(request_id);
1092 self.lsp_status = "LSP: code actions...".to_string();
1093 }
1094
1095 Ok(())
1096 }
1097
1098 pub(crate) fn handle_code_actions_response(
1100 &mut self,
1101 request_id: u64,
1102 actions: Vec<lsp_types::CodeActionOrCommand>,
1103 ) {
1104 if self.pending_code_actions_request != Some(request_id) {
1106 tracing::debug!("Ignoring stale code actions response: {}", request_id);
1107 return;
1108 }
1109
1110 self.pending_code_actions_request = None;
1111 self.update_lsp_status_from_server_statuses();
1112
1113 if actions.is_empty() {
1114 self.set_status_message(t!("lsp.no_code_actions").to_string());
1115 return;
1116 }
1117
1118 let mut lines: Vec<String> = Vec::new();
1120 lines.push(format!("Code Actions ({}):", actions.len()));
1121 lines.push(String::new());
1122
1123 for (i, action) in actions.iter().enumerate() {
1124 let title = match action {
1125 lsp_types::CodeActionOrCommand::Command(cmd) => &cmd.title,
1126 lsp_types::CodeActionOrCommand::CodeAction(ca) => &ca.title,
1127 };
1128 lines.push(format!(" {}. {}", i + 1, title));
1129 }
1130
1131 lines.push(String::new());
1132 lines.push(t!("lsp.code_action_hint").to_string());
1133
1134 use crate::view::popup::{Popup, PopupPosition};
1136 use ratatui::style::Style;
1137
1138 let mut popup = Popup::text(lines, &self.theme);
1139 popup.title = Some(t!("lsp.popup_code_actions").to_string());
1140 popup.position = PopupPosition::BelowCursor;
1141 popup.width = 60;
1142 popup.max_height = 15;
1143 popup.border_style = Style::default().fg(self.theme.popup_border_fg);
1144 popup.background_style = Style::default().bg(self.theme.popup_bg);
1145
1146 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
1148 state.popups.show(popup);
1149 tracing::info!("Showing code actions popup with {} actions", actions.len());
1150 }
1151
1152 self.set_status_message(
1155 t!("lsp.code_actions_not_implemented", count = actions.len()).to_string(),
1156 );
1157 }
1158
1159 pub(crate) fn handle_references_response(
1161 &mut self,
1162 request_id: u64,
1163 locations: Vec<lsp_types::Location>,
1164 ) -> AnyhowResult<()> {
1165 tracing::info!(
1166 "handle_references_response: received {} locations for request_id={}",
1167 locations.len(),
1168 request_id
1169 );
1170
1171 if self.pending_references_request != Some(request_id) {
1173 tracing::debug!("Ignoring stale references response: {}", request_id);
1174 return Ok(());
1175 }
1176
1177 self.pending_references_request = None;
1178 self.update_lsp_status_from_server_statuses();
1179
1180 if locations.is_empty() {
1181 self.set_status_message(t!("lsp.no_references").to_string());
1182 return Ok(());
1183 }
1184
1185 let lsp_locations: Vec<crate::services::plugins::hooks::LspLocation> = locations
1187 .iter()
1188 .map(|loc| {
1189 let file = if loc.uri.scheme().map(|s| s.as_str()) == Some("file") {
1191 loc.uri.path().as_str().to_string()
1193 } else {
1194 loc.uri.as_str().to_string()
1195 };
1196
1197 crate::services::plugins::hooks::LspLocation {
1198 file,
1199 line: loc.range.start.line + 1, column: loc.range.start.character + 1, }
1202 })
1203 .collect();
1204
1205 let count = lsp_locations.len();
1206 let symbol = std::mem::take(&mut self.pending_references_symbol);
1207 self.set_status_message(
1208 t!("lsp.found_references", count = count, symbol = &symbol).to_string(),
1209 );
1210
1211 self.plugin_manager.run_hook(
1213 "lsp_references",
1214 crate::services::plugins::hooks::HookArgs::LspReferences {
1215 symbol: symbol.clone(),
1216 locations: lsp_locations,
1217 },
1218 );
1219
1220 tracing::info!(
1221 "Fired lsp_references hook with {} locations for symbol '{}'",
1222 count,
1223 symbol
1224 );
1225
1226 Ok(())
1227 }
1228
1229 pub(crate) fn apply_lsp_text_edits(
1232 &mut self,
1233 buffer_id: BufferId,
1234 mut edits: Vec<lsp_types::TextEdit>,
1235 ) -> AnyhowResult<usize> {
1236 if edits.is_empty() {
1237 return Ok(0);
1238 }
1239
1240 edits.sort_by(|a, b| {
1242 b.range
1243 .start
1244 .line
1245 .cmp(&a.range.start.line)
1246 .then(b.range.start.character.cmp(&a.range.start.character))
1247 });
1248
1249 let mut batch_events = Vec::new();
1251 let mut changes = 0;
1252
1253 let cursor_id = {
1255 let split_id = self
1256 .split_manager
1257 .splits_for_buffer(buffer_id)
1258 .into_iter()
1259 .next()
1260 .unwrap_or_else(|| self.split_manager.active_split());
1261 self.split_view_states
1262 .get(&split_id)
1263 .map(|vs| vs.cursors.primary_id())
1264 .unwrap_or_else(|| self.active_cursors().primary_id())
1265 };
1266
1267 for edit in edits {
1269 let state = self
1270 .buffers
1271 .get_mut(&buffer_id)
1272 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Buffer not found"))?;
1273
1274 let start_line = edit.range.start.line as usize;
1276 let start_char = edit.range.start.character as usize;
1277 let end_line = edit.range.end.line as usize;
1278 let end_char = edit.range.end.character as usize;
1279
1280 let start_pos = state.buffer.lsp_position_to_byte(start_line, start_char);
1281 let end_pos = state.buffer.lsp_position_to_byte(end_line, end_char);
1282 let buffer_len = state.buffer.len();
1283
1284 let old_text = if start_pos < end_pos && end_pos <= buffer_len {
1286 state.get_text_range(start_pos, end_pos)
1287 } else {
1288 format!(
1289 "<invalid range: start={}, end={}, buffer_len={}>",
1290 start_pos, end_pos, buffer_len
1291 )
1292 };
1293 tracing::debug!(
1294 " Converting LSP range line {}:{}-{}:{} to bytes {}..{} (replacing {:?} with {:?})",
1295 start_line, start_char, end_line, end_char,
1296 start_pos, end_pos, old_text, edit.new_text
1297 );
1298
1299 if start_pos < end_pos {
1301 let deleted_text = state.get_text_range(start_pos, end_pos);
1302 let delete_event = Event::Delete {
1303 range: start_pos..end_pos,
1304 deleted_text,
1305 cursor_id,
1306 };
1307 batch_events.push(delete_event);
1308 }
1309
1310 if !edit.new_text.is_empty() {
1312 let insert_event = Event::Insert {
1313 position: start_pos,
1314 text: edit.new_text.clone(),
1315 cursor_id,
1316 };
1317 batch_events.push(insert_event);
1318 }
1319
1320 changes += 1;
1321 }
1322
1323 if !batch_events.is_empty() {
1325 self.apply_events_to_buffer_as_bulk_edit(
1326 buffer_id,
1327 batch_events,
1328 "LSP Rename".to_string(),
1329 )?;
1330 }
1331
1332 Ok(changes)
1333 }
1334
1335 pub fn handle_rename_response(
1337 &mut self,
1338 _request_id: u64,
1339 result: Result<lsp_types::WorkspaceEdit, String>,
1340 ) -> AnyhowResult<()> {
1341 self.update_lsp_status_from_server_statuses();
1342
1343 match result {
1344 Ok(workspace_edit) => {
1345 tracing::debug!(
1347 "Received WorkspaceEdit: changes={:?}, document_changes={:?}",
1348 workspace_edit.changes.as_ref().map(|c| c.len()),
1349 workspace_edit.document_changes.as_ref().map(|dc| match dc {
1350 lsp_types::DocumentChanges::Edits(e) => format!("{} edits", e.len()),
1351 lsp_types::DocumentChanges::Operations(o) =>
1352 format!("{} operations", o.len()),
1353 })
1354 );
1355
1356 let mut total_changes = 0;
1358
1359 if let Some(changes) = workspace_edit.changes {
1361 for (uri, edits) in changes {
1362 if let Ok(path) = uri_to_path(&uri) {
1363 let buffer_id = match self.open_file(&path) {
1364 Ok(id) => id,
1365 Err(e) => {
1366 if let Some(confirmation) = e.downcast_ref::<
1368 crate::model::buffer::LargeFileEncodingConfirmation,
1369 >() {
1370 self.start_large_file_encoding_confirmation(confirmation);
1371 } else {
1372 self.set_status_message(
1373 t!("file.error_opening", error = e.to_string())
1374 .to_string(),
1375 );
1376 }
1377 return Ok(());
1378 }
1379 };
1380 total_changes += self.apply_lsp_text_edits(buffer_id, edits)?;
1381 }
1382 }
1383 }
1384
1385 if let Some(document_changes) = workspace_edit.document_changes {
1388 use lsp_types::DocumentChanges;
1389
1390 let text_edits = match document_changes {
1391 DocumentChanges::Edits(edits) => edits,
1392 DocumentChanges::Operations(ops) => {
1393 ops.into_iter()
1395 .filter_map(|op| {
1396 if let lsp_types::DocumentChangeOperation::Edit(edit) = op {
1397 Some(edit)
1398 } else {
1399 None
1400 }
1401 })
1402 .collect()
1403 }
1404 };
1405
1406 for text_doc_edit in text_edits {
1407 let uri = text_doc_edit.text_document.uri;
1408
1409 if let Ok(path) = uri_to_path(&uri) {
1410 let buffer_id = match self.open_file(&path) {
1411 Ok(id) => id,
1412 Err(e) => {
1413 if let Some(confirmation) = e.downcast_ref::<
1415 crate::model::buffer::LargeFileEncodingConfirmation,
1416 >() {
1417 self.start_large_file_encoding_confirmation(confirmation);
1418 } else {
1419 self.set_status_message(
1420 t!("file.error_opening", error = e.to_string())
1421 .to_string(),
1422 );
1423 }
1424 return Ok(());
1425 }
1426 };
1427
1428 let edits: Vec<lsp_types::TextEdit> = text_doc_edit
1430 .edits
1431 .into_iter()
1432 .map(|one_of| match one_of {
1433 lsp_types::OneOf::Left(text_edit) => text_edit,
1434 lsp_types::OneOf::Right(annotated) => annotated.text_edit,
1435 })
1436 .collect();
1437
1438 tracing::info!(
1440 "Applying {} edits from rust-analyzer for {:?}:",
1441 edits.len(),
1442 path
1443 );
1444 for (i, edit) in edits.iter().enumerate() {
1445 tracing::info!(
1446 " Edit {}: line {}:{}-{}:{} -> {:?}",
1447 i,
1448 edit.range.start.line,
1449 edit.range.start.character,
1450 edit.range.end.line,
1451 edit.range.end.character,
1452 edit.new_text
1453 );
1454 }
1455
1456 total_changes += self.apply_lsp_text_edits(buffer_id, edits)?;
1457 }
1458 }
1459 }
1460
1461 self.status_message = Some(t!("lsp.renamed", count = total_changes).to_string());
1462 }
1463 Err(error) => {
1464 if error.contains("content modified") || error.contains("-32801") {
1468 tracing::debug!(
1469 "LSP rename: ContentModified error (expected, ignoring): {}",
1470 error
1471 );
1472 self.status_message = Some(t!("lsp.rename_cancelled").to_string());
1473 } else {
1474 self.status_message = Some(t!("lsp.rename_failed", error = &error).to_string());
1476 }
1477 }
1478 }
1479
1480 Ok(())
1481 }
1482
1483 pub(crate) fn apply_events_to_buffer_as_bulk_edit(
1488 &mut self,
1489 buffer_id: BufferId,
1490 events: Vec<Event>,
1491 description: String,
1492 ) -> AnyhowResult<()> {
1493 use crate::model::event::CursorId;
1494
1495 if events.is_empty() {
1496 return Ok(());
1497 }
1498
1499 let batch_for_lsp = Event::Batch {
1501 events: events.clone(),
1502 description: description.clone(),
1503 };
1504
1505 let original_active = self.active_buffer();
1508 self.split_manager.set_active_buffer_id(buffer_id);
1509 let lsp_changes = self.collect_lsp_changes(&batch_for_lsp);
1510 self.split_manager.set_active_buffer_id(original_active);
1511
1512 let split_id_for_cursors = self
1515 .split_manager
1516 .splits_for_buffer(buffer_id)
1517 .into_iter()
1518 .next()
1519 .unwrap_or_else(|| self.split_manager.active_split());
1520 let old_cursors: Vec<(CursorId, usize, Option<usize>)> = self
1521 .split_view_states
1522 .get(&split_id_for_cursors)
1523 .and_then(|vs| vs.keyed_states.get(&buffer_id))
1524 .map(|bvs| {
1525 bvs.cursors
1526 .iter()
1527 .map(|(id, c)| (id, c.position, c.anchor))
1528 .collect()
1529 })
1530 .unwrap_or_default();
1531
1532 let state = self
1533 .buffers
1534 .get_mut(&buffer_id)
1535 .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "Buffer not found"))?;
1536
1537 let old_snapshot = state.buffer.snapshot_buffer_state();
1539
1540 let mut edits: Vec<(usize, usize, String)> = Vec::new();
1542 for event in &events {
1543 match event {
1544 Event::Insert { position, text, .. } => {
1545 edits.push((*position, 0, text.clone()));
1546 }
1547 Event::Delete { range, .. } => {
1548 edits.push((range.start, range.len(), String::new()));
1549 }
1550 _ => {}
1551 }
1552 }
1553
1554 edits.sort_by(|a, b| b.0.cmp(&a.0));
1556
1557 let edit_refs: Vec<(usize, usize, &str)> = edits
1559 .iter()
1560 .map(|(pos, del, text)| (*pos, *del, text.as_str()))
1561 .collect();
1562
1563 let _delta = state.buffer.apply_bulk_edits(&edit_refs);
1565
1566 let mut position_deltas: Vec<(usize, isize)> = Vec::new();
1568 for (pos, del_len, text) in &edits {
1569 let delta = text.len() as isize - *del_len as isize;
1570 position_deltas.push((*pos, delta));
1571 }
1572 position_deltas.sort_by_key(|(pos, _)| *pos);
1573
1574 let calc_shift = |original_pos: usize| -> isize {
1575 let mut shift: isize = 0;
1576 for (edit_pos, delta) in &position_deltas {
1577 if *edit_pos < original_pos {
1578 shift += delta;
1579 }
1580 }
1581 shift
1582 };
1583
1584 let buffer_len = state.buffer.len();
1586 let new_cursors: Vec<(CursorId, usize, Option<usize>)> = old_cursors
1587 .iter()
1588 .map(|(id, pos, anchor)| {
1589 let shift = calc_shift(*pos);
1590 let new_pos = ((*pos as isize + shift).max(0) as usize).min(buffer_len);
1591 let new_anchor = anchor.map(|a| {
1592 let anchor_shift = calc_shift(a);
1593 ((a as isize + anchor_shift).max(0) as usize).min(buffer_len)
1594 });
1595 (*id, new_pos, new_anchor)
1596 })
1597 .collect();
1598
1599 let new_snapshot = state.buffer.snapshot_buffer_state();
1601
1602 state.highlighter.invalidate_all();
1604
1605 if let Some(vs) = self.split_view_states.get_mut(&split_id_for_cursors) {
1607 if let Some(bvs) = vs.keyed_states.get_mut(&buffer_id) {
1608 for (cursor_id, new_pos, new_anchor) in &new_cursors {
1609 if let Some(cursor) = bvs.cursors.get_mut(*cursor_id) {
1610 cursor.position = *new_pos;
1611 cursor.anchor = *new_anchor;
1612 }
1613 }
1614 }
1615 }
1616
1617 let bulk_edit = Event::BulkEdit {
1619 old_snapshot: Some(old_snapshot),
1620 new_snapshot: Some(new_snapshot),
1621 old_cursors,
1622 new_cursors,
1623 description,
1624 };
1625
1626 if let Some(event_log) = self.event_logs.get_mut(&buffer_id) {
1628 event_log.append(bulk_edit);
1629 }
1630
1631 self.send_lsp_changes_for_buffer(buffer_id, lsp_changes);
1633
1634 Ok(())
1635 }
1636
1637 pub(crate) fn send_lsp_changes_for_buffer(
1639 &mut self,
1640 buffer_id: BufferId,
1641 changes: Vec<TextDocumentContentChangeEvent>,
1642 ) {
1643 if changes.is_empty() {
1644 return;
1645 }
1646
1647 let metadata = match self.buffer_metadata.get(&buffer_id) {
1649 Some(m) => m,
1650 None => {
1651 tracing::debug!(
1652 "send_lsp_changes_for_buffer: no metadata for buffer {:?}",
1653 buffer_id
1654 );
1655 return;
1656 }
1657 };
1658
1659 if !metadata.lsp_enabled {
1660 tracing::debug!("send_lsp_changes_for_buffer: LSP disabled for this buffer");
1661 return;
1662 }
1663
1664 let uri = match metadata.file_uri() {
1666 Some(u) => u.clone(),
1667 None => {
1668 tracing::debug!(
1669 "send_lsp_changes_for_buffer: no URI for buffer (not a file or URI creation failed)"
1670 );
1671 return;
1672 }
1673 };
1674
1675 let language = match self.buffers.get(&buffer_id).map(|s| s.language.clone()) {
1677 Some(l) => l,
1678 None => {
1679 tracing::debug!(
1680 "send_lsp_changes_for_buffer: no buffer state for {:?}",
1681 buffer_id
1682 );
1683 return;
1684 }
1685 };
1686
1687 tracing::trace!(
1688 "send_lsp_changes_for_buffer: sending {} changes to {} in single didChange notification",
1689 changes.len(),
1690 uri.as_str()
1691 );
1692
1693 use crate::services::lsp::manager::LspSpawnResult;
1695 let Some(lsp) = self.lsp.as_mut() else {
1696 tracing::debug!("send_lsp_changes_for_buffer: no LSP manager available");
1697 return;
1698 };
1699
1700 if lsp.try_spawn(&language) != LspSpawnResult::Spawned {
1701 tracing::debug!(
1702 "send_lsp_changes_for_buffer: LSP not running for {} (auto_start disabled)",
1703 language
1704 );
1705 return;
1706 }
1707
1708 let Some(handle) = lsp.get_handle_mut(&language) else {
1710 return;
1711 };
1712 let handle_id = handle.id();
1713
1714 let needs_open = {
1716 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
1717 return;
1718 };
1719 !metadata.lsp_opened_with.contains(&handle_id)
1720 };
1721
1722 if needs_open {
1723 let text = match self
1725 .buffers
1726 .get(&buffer_id)
1727 .and_then(|s| s.buffer.to_string())
1728 {
1729 Some(t) => t,
1730 None => {
1731 tracing::debug!(
1732 "send_lsp_changes_for_buffer: buffer text not available for didOpen"
1733 );
1734 return;
1735 }
1736 };
1737
1738 let Some(lsp) = self.lsp.as_mut() else { return };
1740 let Some(handle) = lsp.get_handle_mut(&language) else {
1741 return;
1742 };
1743 if let Err(e) = handle.did_open(uri.clone(), text, language.clone()) {
1744 tracing::warn!("Failed to send didOpen before didChange: {}", e);
1745 return;
1746 }
1747 tracing::debug!(
1748 "Sent didOpen for {} to LSP handle {} before didChange",
1749 uri.as_str(),
1750 handle_id
1751 );
1752
1753 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1755 metadata.lsp_opened_with.insert(handle_id);
1756 }
1757
1758 return;
1762 }
1763
1764 let Some(lsp) = self.lsp.as_mut() else { return };
1766 let Some(client) = lsp.get_handle_mut(&language) else {
1767 return;
1768 };
1769 if let Err(e) = client.did_change(uri, changes) {
1770 tracing::warn!("Failed to send didChange to LSP: {}", e);
1771 } else {
1772 tracing::trace!("Successfully sent batched didChange to LSP");
1773
1774 if let Some(state) = self.buffers.get(&buffer_id) {
1777 if let Some(path) = state.buffer.file_path() {
1778 crate::services::lsp::diagnostics::invalidate_cache_for_file(
1779 &path.to_string_lossy(),
1780 );
1781 }
1782 }
1783
1784 self.scheduled_diagnostic_pull = Some((
1786 buffer_id,
1787 std::time::Instant::now() + std::time::Duration::from_millis(1000),
1788 ));
1789 }
1790 }
1791
1792 pub(crate) fn start_rename(&mut self) -> AnyhowResult<()> {
1794 use crate::primitives::word_navigation::{find_word_end, find_word_start};
1795
1796 let cursor_pos = self.active_cursors().primary().position;
1798 let (word_start, word_end) = {
1799 let state = self.active_state();
1800
1801 let word_start = find_word_start(&state.buffer, cursor_pos);
1803 let word_end = find_word_end(&state.buffer, cursor_pos);
1804
1805 if word_start >= word_end {
1807 self.status_message = Some(t!("lsp.no_symbol_at_cursor").to_string());
1808 return Ok(());
1809 }
1810
1811 (word_start, word_end)
1812 };
1813
1814 let word_text = self.active_state_mut().get_text_range(word_start, word_end);
1816
1817 let overlay_handle = self.add_overlay(
1819 None,
1820 word_start..word_end,
1821 crate::model::event::OverlayFace::Background {
1822 color: (50, 100, 200), },
1824 100,
1825 Some(t!("lsp.popup_renaming").to_string()),
1826 );
1827
1828 let mut prompt = Prompt::new(
1831 "Rename to: ".to_string(),
1832 PromptType::LspRename {
1833 original_text: word_text.clone(),
1834 start_pos: word_start,
1835 end_pos: word_end,
1836 overlay_handle,
1837 },
1838 );
1839 prompt.set_input(word_text);
1841
1842 self.prompt = Some(prompt);
1843 Ok(())
1844 }
1845
1846 pub(crate) fn cancel_rename_overlay(&mut self, handle: &crate::view::overlay::OverlayHandle) {
1848 self.remove_overlay(handle.clone());
1849 }
1850
1851 pub(crate) fn perform_lsp_rename(
1853 &mut self,
1854 new_name: String,
1855 original_text: String,
1856 start_pos: usize,
1857 overlay_handle: crate::view::overlay::OverlayHandle,
1858 ) {
1859 self.cancel_rename_overlay(&overlay_handle);
1861
1862 if new_name == original_text {
1864 self.status_message = Some(t!("lsp.name_unchanged").to_string());
1865 return;
1866 }
1867
1868 let rename_pos = start_pos;
1871
1872 let state = self.active_state();
1875 let (line, character) = state.buffer.position_to_lsp_position(rename_pos);
1876 let buffer_id = self.active_buffer();
1877 let request_id = self.next_lsp_request_id;
1878
1879 let sent = self
1881 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
1882 let result = handle.rename(
1883 request_id,
1884 uri.clone(),
1885 line as u32,
1886 character as u32,
1887 new_name.clone(),
1888 );
1889 if result.is_ok() {
1890 tracing::info!(
1891 "Requested rename at {}:{}:{} to '{}'",
1892 uri.as_str(),
1893 line,
1894 character,
1895 new_name
1896 );
1897 }
1898 result.is_ok()
1899 })
1900 .unwrap_or(false);
1901
1902 if sent {
1903 self.next_lsp_request_id += 1;
1904 self.lsp_status = "LSP: rename...".to_string();
1905 } else if self
1906 .buffer_metadata
1907 .get(&buffer_id)
1908 .and_then(|m| m.file_path())
1909 .is_none()
1910 {
1911 self.status_message = Some(t!("lsp.cannot_rename_unsaved").to_string());
1912 }
1913 }
1914
1915 pub(crate) fn request_inlay_hints_for_active_buffer(&mut self) {
1917 if !self.config.editor.enable_inlay_hints {
1918 return;
1919 }
1920
1921 let buffer_id = self.active_buffer();
1922
1923 let line_count = if let Some(state) = self.buffers.get(&buffer_id) {
1925 state.buffer.line_count().unwrap_or(1000)
1926 } else {
1927 return;
1928 };
1929 let last_line = line_count.saturating_sub(1) as u32;
1930 let request_id = self.next_lsp_request_id;
1931
1932 let sent = self
1934 .with_lsp_for_buffer(buffer_id, |handle, uri, _language| {
1935 let result = handle.inlay_hints(request_id, uri.clone(), 0, 0, last_line, 10000);
1936 if result.is_ok() {
1937 tracing::info!(
1938 "Requested inlay hints for {} (request_id={})",
1939 uri.as_str(),
1940 request_id
1941 );
1942 } else if let Err(e) = &result {
1943 tracing::debug!("Failed to request inlay hints: {}", e);
1944 }
1945 result.is_ok()
1946 })
1947 .unwrap_or(false);
1948
1949 if sent {
1950 self.next_lsp_request_id += 1;
1951 self.pending_inlay_hints_request = Some(request_id);
1952 }
1953 }
1954
1955 pub(crate) fn schedule_folding_ranges_refresh(&mut self, buffer_id: BufferId) {
1957 let next_time = Instant::now() + Duration::from_millis(FOLDING_RANGES_DEBOUNCE_MS);
1958 self.folding_ranges_debounce.insert(buffer_id, next_time);
1959 }
1960
1961 pub(crate) fn maybe_request_folding_ranges_debounced(&mut self, buffer_id: BufferId) {
1963 let Some(ready_at) = self.folding_ranges_debounce.get(&buffer_id).copied() else {
1964 return;
1965 };
1966 if Instant::now() < ready_at {
1967 return;
1968 }
1969
1970 self.folding_ranges_debounce.remove(&buffer_id);
1971 self.request_folding_ranges_for_buffer(buffer_id);
1972 }
1973
1974 pub(crate) fn request_folding_ranges_for_buffer(&mut self, buffer_id: BufferId) {
1976 if self.folding_ranges_in_flight.contains_key(&buffer_id) {
1977 return;
1978 }
1979
1980 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
1981 return;
1982 };
1983 if !metadata.lsp_enabled {
1984 return;
1985 }
1986 let Some(uri) = metadata.file_uri().cloned() else {
1987 return;
1988 };
1989
1990 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
1991 return;
1992 };
1993
1994 let Some(lsp) = self.lsp.as_mut() else {
1995 return;
1996 };
1997
1998 if !lsp.folding_ranges_supported(&language) {
1999 return;
2000 }
2001
2002 use crate::services::lsp::manager::LspSpawnResult;
2004 if lsp.try_spawn(&language) != LspSpawnResult::Spawned {
2005 return;
2006 }
2007
2008 let Some(handle) = lsp.get_handle_mut(&language) else {
2009 return;
2010 };
2011
2012 let request_id = self.next_lsp_request_id;
2013 self.next_lsp_request_id += 1;
2014 let buffer_version = self
2015 .buffers
2016 .get(&buffer_id)
2017 .map(|s| s.buffer.version())
2018 .unwrap_or(0);
2019
2020 match handle.folding_ranges(request_id, uri) {
2021 Ok(()) => {
2022 self.pending_folding_range_requests.insert(
2023 request_id,
2024 super::FoldingRangeRequest {
2025 buffer_id,
2026 version: buffer_version,
2027 },
2028 );
2029 self.folding_ranges_in_flight
2030 .insert(buffer_id, (request_id, buffer_version));
2031 }
2032 Err(e) => {
2033 tracing::debug!("Failed to request folding ranges: {}", e);
2034 }
2035 }
2036 }
2037
2038 pub(crate) fn maybe_request_semantic_tokens(&mut self, buffer_id: BufferId) {
2040 if !self.config.editor.enable_semantic_tokens_full {
2041 return;
2042 }
2043
2044 if self.semantic_tokens_in_flight.contains_key(&buffer_id) {
2046 return;
2047 }
2048
2049 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2050 return;
2051 };
2052 if !metadata.lsp_enabled {
2053 return;
2054 }
2055 let Some(uri) = metadata.file_uri().cloned() else {
2056 return;
2057 };
2058 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
2060 return;
2061 };
2062
2063 let Some(lsp) = self.lsp.as_mut() else {
2064 return;
2065 };
2066
2067 if !lsp.semantic_tokens_full_supported(&language) {
2068 return;
2069 }
2070 if lsp.semantic_tokens_legend(&language).is_none() {
2071 return;
2072 }
2073
2074 use crate::services::lsp::manager::LspSpawnResult;
2076 if lsp.try_spawn(&language) != LspSpawnResult::Spawned {
2077 return;
2078 }
2079
2080 let Some(state) = self.buffers.get(&buffer_id) else {
2081 return;
2082 };
2083 let buffer_version = state.buffer.version();
2084 if let Some(store) = state.semantic_tokens.as_ref() {
2085 if store.version == buffer_version {
2086 return; }
2088 }
2089
2090 let previous_result_id = state
2091 .semantic_tokens
2092 .as_ref()
2093 .and_then(|store| store.result_id.clone());
2094 let supports_delta = lsp.semantic_tokens_full_delta_supported(&language);
2095 let use_delta = previous_result_id.is_some() && supports_delta;
2096
2097 let Some(handle) = lsp.get_handle_mut(&language) else {
2098 return;
2099 };
2100
2101 let request_id = self.next_lsp_request_id;
2102 self.next_lsp_request_id += 1;
2103
2104 let request_kind = if use_delta {
2105 super::SemanticTokensFullRequestKind::FullDelta
2106 } else {
2107 super::SemanticTokensFullRequestKind::Full
2108 };
2109
2110 let request_result = if use_delta {
2111 handle.semantic_tokens_full_delta(request_id, uri, previous_result_id.unwrap())
2112 } else {
2113 handle.semantic_tokens_full(request_id, uri)
2114 };
2115
2116 match request_result {
2117 Ok(_) => {
2118 self.pending_semantic_token_requests.insert(
2119 request_id,
2120 super::SemanticTokenFullRequest {
2121 buffer_id,
2122 version: buffer_version,
2123 kind: request_kind,
2124 },
2125 );
2126 self.semantic_tokens_in_flight
2127 .insert(buffer_id, (request_id, buffer_version, request_kind));
2128 }
2129 Err(e) => {
2130 tracing::debug!("Failed to request semantic tokens: {}", e);
2131 }
2132 }
2133 }
2134
2135 pub(crate) fn schedule_semantic_tokens_full_refresh(&mut self, buffer_id: BufferId) {
2137 if !self.config.editor.enable_semantic_tokens_full {
2138 return;
2139 }
2140
2141 let next_time = Instant::now() + Duration::from_millis(SEMANTIC_TOKENS_FULL_DEBOUNCE_MS);
2142 self.semantic_tokens_full_debounce
2143 .insert(buffer_id, next_time);
2144 }
2145
2146 pub(crate) fn maybe_request_semantic_tokens_full_debounced(&mut self, buffer_id: BufferId) {
2148 if !self.config.editor.enable_semantic_tokens_full {
2149 self.semantic_tokens_full_debounce.remove(&buffer_id);
2150 return;
2151 }
2152
2153 let Some(ready_at) = self.semantic_tokens_full_debounce.get(&buffer_id).copied() else {
2154 return;
2155 };
2156 if Instant::now() < ready_at {
2157 return;
2158 }
2159
2160 self.semantic_tokens_full_debounce.remove(&buffer_id);
2161 self.maybe_request_semantic_tokens(buffer_id);
2162 }
2163
2164 pub(crate) fn maybe_request_semantic_tokens_range(
2166 &mut self,
2167 buffer_id: BufferId,
2168 start_line: usize,
2169 end_line: usize,
2170 ) {
2171 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
2172 return;
2173 };
2174 if !metadata.lsp_enabled {
2175 return;
2176 }
2177 let Some(uri) = metadata.file_uri().cloned() else {
2178 return;
2179 };
2180 let Some(language) = self.buffers.get(&buffer_id).map(|s| s.language.clone()) else {
2182 return;
2183 };
2184
2185 let Some(lsp) = self.lsp.as_mut() else {
2186 return;
2187 };
2188
2189 if !lsp.semantic_tokens_range_supported(&language) {
2190 self.maybe_request_semantic_tokens(buffer_id);
2192 return;
2193 }
2194 if lsp.semantic_tokens_legend(&language).is_none() {
2195 return;
2196 }
2197
2198 use crate::services::lsp::manager::LspSpawnResult;
2200 if lsp.try_spawn(&language) != LspSpawnResult::Spawned {
2201 return;
2202 }
2203
2204 let Some(handle) = lsp.get_handle_mut(&language) else {
2205 return;
2206 };
2207 let Some(state) = self.buffers.get(&buffer_id) else {
2208 return;
2209 };
2210
2211 let buffer_version = state.buffer.version();
2212 let mut padded_start = start_line.saturating_sub(SEMANTIC_TOKENS_RANGE_PADDING_LINES);
2213 let mut padded_end = end_line.saturating_add(SEMANTIC_TOKENS_RANGE_PADDING_LINES);
2214
2215 if let Some(line_count) = state.buffer.line_count() {
2216 if line_count == 0 {
2217 return;
2218 }
2219 let max_line = line_count.saturating_sub(1);
2220 padded_start = padded_start.min(max_line);
2221 padded_end = padded_end.min(max_line);
2222 }
2223
2224 let start_byte = state.buffer.line_start_offset(padded_start).unwrap_or(0);
2225 let end_char = state
2226 .buffer
2227 .get_line(padded_end)
2228 .map(|line| String::from_utf8_lossy(&line).encode_utf16().count())
2229 .unwrap_or(0);
2230 let end_byte = if state.buffer.line_start_offset(padded_end).is_some() {
2231 state.buffer.lsp_position_to_byte(padded_end, end_char)
2232 } else {
2233 state.buffer.len()
2234 };
2235
2236 if start_byte >= end_byte {
2237 return;
2238 }
2239
2240 let range = start_byte..end_byte;
2241 if let Some((in_flight_id, in_flight_start, in_flight_end, in_flight_version)) =
2242 self.semantic_tokens_range_in_flight.get(&buffer_id)
2243 {
2244 if *in_flight_start == padded_start
2245 && *in_flight_end == padded_end
2246 && *in_flight_version == buffer_version
2247 {
2248 return;
2249 }
2250 if let Err(e) = handle.cancel_request(*in_flight_id) {
2251 tracing::debug!("Failed to cancel semantic token range request: {}", e);
2252 }
2253 self.pending_semantic_token_range_requests
2254 .remove(in_flight_id);
2255 self.semantic_tokens_range_in_flight.remove(&buffer_id);
2256 }
2257
2258 if let Some((applied_start, applied_end, applied_version)) =
2259 self.semantic_tokens_range_applied.get(&buffer_id)
2260 {
2261 if *applied_start == padded_start
2262 && *applied_end == padded_end
2263 && *applied_version == buffer_version
2264 {
2265 return;
2266 }
2267 }
2268
2269 let now = Instant::now();
2270 if let Some((last_start, last_end, last_version, last_time)) =
2271 self.semantic_tokens_range_last_request.get(&buffer_id)
2272 {
2273 if *last_start == padded_start
2274 && *last_end == padded_end
2275 && *last_version == buffer_version
2276 && now.duration_since(*last_time)
2277 < Duration::from_millis(SEMANTIC_TOKENS_RANGE_DEBOUNCE_MS)
2278 {
2279 return;
2280 }
2281 }
2282
2283 let lsp_range = lsp_types::Range {
2284 start: lsp_types::Position {
2285 line: padded_start as u32,
2286 character: 0,
2287 },
2288 end: lsp_types::Position {
2289 line: padded_end as u32,
2290 character: end_char as u32,
2291 },
2292 };
2293
2294 let request_id = self.next_lsp_request_id;
2295 self.next_lsp_request_id += 1;
2296
2297 match handle.semantic_tokens_range(request_id, uri, lsp_range) {
2298 Ok(_) => {
2299 self.pending_semantic_token_range_requests.insert(
2300 request_id,
2301 SemanticTokenRangeRequest {
2302 buffer_id,
2303 version: buffer_version,
2304 range: range.clone(),
2305 start_line: padded_start,
2306 end_line: padded_end,
2307 },
2308 );
2309 self.semantic_tokens_range_in_flight.insert(
2310 buffer_id,
2311 (request_id, padded_start, padded_end, buffer_version),
2312 );
2313 self.semantic_tokens_range_last_request
2314 .insert(buffer_id, (padded_start, padded_end, buffer_version, now));
2315 }
2316 Err(e) => {
2317 tracing::debug!("Failed to request semantic token range: {}", e);
2318 }
2319 }
2320 }
2321}
2322
2323#[cfg(test)]
2324mod tests {
2325 use crate::model::filesystem::StdFileSystem;
2326 use std::sync::Arc;
2327
2328 fn test_fs() -> Arc<dyn crate::model::filesystem::FileSystem + Send + Sync> {
2329 Arc::new(StdFileSystem)
2330 }
2331 use super::Editor;
2332 use crate::model::buffer::Buffer;
2333 use crate::state::EditorState;
2334 use crate::view::virtual_text::VirtualTextPosition;
2335 use lsp_types::{InlayHint, InlayHintKind, InlayHintLabel, Position};
2336
2337 fn make_hint(line: u32, character: u32, label: &str, kind: Option<InlayHintKind>) -> InlayHint {
2338 InlayHint {
2339 position: Position { line, character },
2340 label: InlayHintLabel::String(label.to_string()),
2341 kind,
2342 text_edits: None,
2343 tooltip: None,
2344 padding_left: None,
2345 padding_right: None,
2346 data: None,
2347 }
2348 }
2349
2350 #[test]
2351 fn test_inlay_hint_inserts_before_character() {
2352 let mut state = EditorState::new(
2353 80,
2354 24,
2355 crate::config::LARGE_FILE_THRESHOLD_BYTES as usize,
2356 test_fs(),
2357 );
2358 state.buffer = Buffer::from_str_test("ab");
2359
2360 if !state.buffer.is_empty() {
2361 state.marker_list.adjust_for_insert(0, state.buffer.len());
2362 }
2363
2364 let hints = vec![make_hint(0, 1, ": i32", Some(InlayHintKind::TYPE))];
2365 Editor::apply_inlay_hints_to_state(&mut state, &hints);
2366
2367 let lookup = state
2368 .virtual_texts
2369 .build_lookup(&state.marker_list, 0, state.buffer.len());
2370 let vtexts = lookup.get(&1).expect("expected hint at byte offset 1");
2371 assert_eq!(vtexts.len(), 1);
2372 assert_eq!(vtexts[0].text, ": i32");
2373 assert_eq!(vtexts[0].position, VirtualTextPosition::BeforeChar);
2374 }
2375
2376 #[test]
2377 fn test_inlay_hint_at_eof_renders_after_last_char() {
2378 let mut state = EditorState::new(
2379 80,
2380 24,
2381 crate::config::LARGE_FILE_THRESHOLD_BYTES as usize,
2382 test_fs(),
2383 );
2384 state.buffer = Buffer::from_str_test("ab");
2385
2386 if !state.buffer.is_empty() {
2387 state.marker_list.adjust_for_insert(0, state.buffer.len());
2388 }
2389
2390 let hints = vec![make_hint(0, 2, ": i32", Some(InlayHintKind::TYPE))];
2391 Editor::apply_inlay_hints_to_state(&mut state, &hints);
2392
2393 let lookup = state
2394 .virtual_texts
2395 .build_lookup(&state.marker_list, 0, state.buffer.len());
2396 let vtexts = lookup.get(&1).expect("expected hint anchored to last byte");
2397 assert_eq!(vtexts.len(), 1);
2398 assert_eq!(vtexts[0].text, ": i32");
2399 assert_eq!(vtexts[0].position, VirtualTextPosition::AfterChar);
2400 }
2401
2402 #[test]
2403 fn test_inlay_hint_empty_buffer_is_ignored() {
2404 let mut state = EditorState::new(
2405 80,
2406 24,
2407 crate::config::LARGE_FILE_THRESHOLD_BYTES as usize,
2408 test_fs(),
2409 );
2410 state.buffer = Buffer::from_str_test("");
2411
2412 let hints = vec![make_hint(0, 0, ": i32", Some(InlayHintKind::TYPE))];
2413 Editor::apply_inlay_hints_to_state(&mut state, &hints);
2414
2415 assert!(state.virtual_texts.is_empty());
2416 }
2417
2418 #[test]
2419 fn test_space_doc_paragraphs_inserts_blank_lines() {
2420 use super::space_doc_paragraphs;
2421
2422 let input = "sep\n description.\nend\n another.";
2424 let result = space_doc_paragraphs(input);
2425 assert_eq!(result, "sep\n\n description.\n\nend\n\n another.");
2426 }
2427
2428 #[test]
2429 fn test_space_doc_paragraphs_preserves_existing_blank_lines() {
2430 use super::space_doc_paragraphs;
2431
2432 let input = "First paragraph.\n\nSecond paragraph.";
2434 let result = space_doc_paragraphs(input);
2435 assert_eq!(result, "First paragraph.\n\nSecond paragraph.");
2436 }
2437
2438 #[test]
2439 fn test_space_doc_paragraphs_plain_text() {
2440 use super::space_doc_paragraphs;
2441
2442 let input = "Just a single line of docs.";
2443 let result = space_doc_paragraphs(input);
2444 assert_eq!(result, "Just a single line of docs.");
2445 }
2446}