1use super::Editor;
7use crate::input::commands::Suggestion;
8use crate::model::event::BufferId;
9use crate::view::prompt::{Prompt, PromptType};
10use rust_i18n::t;
11
12impl Editor {
13 pub fn handle_lsp_restart(&mut self) {
18 let buffer_id = self.active_buffer();
20 let Some(state) = self.buffers.get(&buffer_id) else {
21 return;
22 };
23 let language = state.language.clone();
24 let file_path = self
25 .buffer_metadata
26 .get(&buffer_id)
27 .and_then(|meta| meta.file_path().cloned());
28
29 let configs: Vec<_> = self
31 .lsp
32 .as_ref()
33 .and_then(|lsp| lsp.get_configs(&language))
34 .map(|c| c.to_vec())
35 .unwrap_or_default();
36
37 if configs.is_empty() {
38 self.set_status_message(t!("lsp.no_server_configured").to_string());
39 return;
40 }
41
42 if configs.len() == 1 {
44 let Some(lsp) = self.lsp.as_mut() else {
45 self.set_status_message(t!("lsp.no_manager").to_string());
46 return;
47 };
48
49 let (success, message) = lsp.manual_restart(&language, file_path.as_deref());
50 self.status_message = Some(message);
51
52 if success {
53 self.reopen_buffers_for_language(&language);
54 }
55 return;
56 }
57
58 let mut suggestions: Vec<Suggestion> = Vec::new();
60
61 let enabled_names: Vec<_> = configs
63 .iter()
64 .filter(|c| c.enabled && !c.command.is_empty())
65 .map(|c| c.display_name())
66 .collect();
67 let all_description = if enabled_names.is_empty() {
68 Some("No enabled servers".to_string())
69 } else {
70 Some(enabled_names.join(", "))
71 };
72 suggestions.push(Suggestion {
73 text: format!("{} (all enabled)", language),
74 description: all_description,
75 value: Some(language.clone()),
76 disabled: enabled_names.is_empty(),
77 keybinding: None,
78 source: None,
79 });
80
81 for config in &configs {
83 if config.command.is_empty() {
84 continue;
85 }
86 let name = config.display_name();
87 let status = if config.enabled { "" } else { " [disabled]" };
88 suggestions.push(Suggestion {
89 text: format!("{}/{}{}", language, name, status),
90 description: Some(format!("Command: {}", config.command)),
91 value: Some(format!("{}/{}", language, name)),
92 disabled: false,
93 keybinding: None,
94 source: None,
95 });
96 }
97
98 self.prompt = Some(Prompt::with_suggestions(
100 "Restart LSP server: ".to_string(),
101 PromptType::RestartLspServer,
102 suggestions.clone(),
103 ));
104
105 if let Some(prompt) = self.prompt.as_mut() {
107 prompt.selected_suggestion = Some(0);
108 }
109 }
110
111 pub(crate) fn reopen_buffers_for_language(&mut self, language: &str) {
117 let buffers_for_language: Vec<_> = self
120 .buffers
121 .iter()
122 .filter_map(|(buf_id, state)| {
123 if state.language == language {
124 self.buffer_metadata
125 .get(buf_id)
126 .and_then(|meta| meta.file_path().map(|p| (*buf_id, p.clone())))
127 } else {
128 None
129 }
130 })
131 .collect();
132
133 let enable_inlay_hints = self.config.editor.enable_inlay_hints;
134
135 for (buffer_id, buf_path) in buffers_for_language {
136 let Some(state) = self.buffers.get(&buffer_id) else {
137 continue;
138 };
139
140 let Some(content) = state.buffer.to_string() else {
141 continue; };
143
144 let Some(uri) = super::types::file_path_to_lsp_uri(&buf_path) else {
145 continue;
146 };
147
148 let lang_id = state.language.clone();
149 let line_count = state.buffer.line_count().unwrap_or(1000);
150 let buffer_version = state.buffer.version();
151
152 if let Some(lsp) = self.lsp.as_mut() {
153 use crate::services::lsp::manager::LspSpawnResult;
155 if lsp.try_spawn(&lang_id, Some(&buf_path)) != LspSpawnResult::Spawned {
156 continue;
157 }
158
159 let opened_with = self
162 .buffer_metadata
163 .get(&buffer_id)
164 .map(|m| m.lsp_opened_with.clone())
165 .unwrap_or_default();
166
167 let handles_needing_open: Vec<(String, u64)> = lsp
168 .get_handles(&lang_id)
169 .into_iter()
170 .filter(|sh| !opened_with.contains(&sh.handle.id()))
171 .map(|sh| (sh.name.clone(), sh.handle.id()))
172 .collect();
173
174 for (name, handle_id) in handles_needing_open {
176 let sh = lsp
177 .get_handles_mut(&lang_id)
178 .into_iter()
179 .find(|s| s.handle.id() == handle_id);
180
181 if let Some(sh) = sh {
182 if let Err(e) =
183 sh.handle
184 .did_open(uri.clone(), content.clone(), lang_id.clone())
185 {
186 tracing::warn!("LSP did_open to '{}' failed: {}", name, e);
187 } else if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
188 metadata.lsp_opened_with.insert(handle_id);
189 }
190 }
191 }
192 }
193
194 if enable_inlay_hints {
200 if let Some(lsp) = self.lsp.as_mut() {
201 if let Some(sh) =
202 lsp.handle_for_feature_mut(&lang_id, crate::types::LspFeature::InlayHints)
203 {
204 let request_id = self.next_lsp_request_id;
205 self.next_lsp_request_id += 1;
206 let last_line = line_count.saturating_sub(1) as u32;
207 if let Err(e) =
208 sh.handle
209 .inlay_hints(request_id, uri.clone(), 0, 0, last_line, 10000)
210 {
211 tracing::debug!(
212 "Failed to request inlay hints for {}: {}",
213 uri.as_str(),
214 e
215 );
216 } else {
217 self.pending_inlay_hints_requests.insert(
218 request_id,
219 super::InlayHintsRequest {
220 buffer_id,
221 version: buffer_version,
222 },
223 );
224 }
225 }
226 }
227 }
228 }
229 }
230
231 pub fn handle_lsp_stop(&mut self) {
236 let running_languages: Vec<String> = self
237 .lsp
238 .as_ref()
239 .map(|lsp| lsp.running_servers())
240 .unwrap_or_default();
241
242 if running_languages.is_empty() {
243 self.set_status_message(t!("lsp.no_servers_running").to_string());
244 return;
245 }
246
247 let mut suggestions: Vec<Suggestion> = Vec::new();
249 for lang in &running_languages {
250 let server_names: Vec<String> = self
251 .lsp
252 .as_ref()
253 .map(|lsp| lsp.server_names_for_language(lang))
254 .unwrap_or_default();
255
256 if server_names.len() > 1 {
257 for name in &server_names {
259 let description = Some(format!("Server: {}", name));
260 suggestions.push(Suggestion {
261 text: format!("{}/{}", lang, name),
262 description,
263 value: Some(format!("{}/{}", lang, name)),
266 disabled: false,
267 keybinding: None,
268 source: None,
269 });
270 }
271 } else {
272 let description = self
274 .lsp
275 .as_ref()
276 .and_then(|lsp| lsp.get_config(lang))
277 .filter(|c| !c.command.is_empty())
278 .map(|c| format!("Command: {}", c.command));
279
280 suggestions.push(Suggestion {
281 text: lang.clone(),
282 description,
283 value: Some(lang.clone()),
284 disabled: false,
285 keybinding: None,
286 source: None,
287 });
288 }
289 }
290
291 self.prompt = Some(Prompt::with_suggestions(
293 "Stop LSP server: ".to_string(),
294 PromptType::StopLspServer,
295 suggestions.clone(),
296 ));
297
298 if let Some(prompt) = self.prompt.as_mut() {
300 if suggestions.len() == 1 {
301 prompt.input = suggestions[0].text.clone();
303 prompt.cursor_pos = prompt.input.len();
304 prompt.selected_suggestion = Some(0);
305 } else if !prompt.suggestions.is_empty() {
306 prompt.selected_suggestion = Some(0);
308 }
309 }
310 }
311
312 pub fn handle_lsp_toggle_for_buffer(&mut self) {
317 let buffer_id = self.active_buffer();
318
319 let language = {
321 let Some(state) = self.buffers.get(&buffer_id) else {
322 return;
323 };
324 state.language.clone()
325 };
326
327 let lsp_configured = self
329 .lsp
330 .as_ref()
331 .and_then(|lsp| lsp.get_config(&language))
332 .is_some();
333
334 if !lsp_configured {
335 self.set_status_message(t!("lsp.no_server_configured").to_string());
336 return;
337 }
338
339 let (was_enabled, file_path) = {
341 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
342 return;
343 };
344 (metadata.lsp_enabled, metadata.file_path().cloned())
345 };
346
347 if was_enabled {
348 self.disable_lsp_for_buffer(buffer_id);
349 } else {
350 self.enable_lsp_for_buffer(buffer_id, &language, file_path);
351 }
352 }
353
354 pub fn is_lsp_language_user_dismissed(&self, language: &str) -> bool {
356 self.user_dismissed_lsp_languages.contains(language)
357 }
358
359 pub fn dismiss_lsp_language(&mut self, language: &str) {
363 self.user_dismissed_lsp_languages
364 .insert(language.to_string());
365 }
366
367 pub fn undismiss_lsp_language(&mut self, language: &str) {
370 self.user_dismissed_lsp_languages.remove(language);
371 }
372
373 pub fn handle_lsp_status_action(&mut self, action_key: &str) {
388 if action_key == "cancel_popup" {
389 return;
393 }
394 if let Some(target) = action_key.strip_prefix("autostart:") {
395 if let Some((language, server_name)) = target.split_once('/') {
401 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(language) {
402 for c in lsp_configs.as_mut_slice() {
403 if c.display_name() == server_name {
404 c.auto_start = true;
405 }
406 }
407 if let Err(e) = self.save_config() {
408 tracing::warn!(
409 "Failed to save config after enabling LSP auto-start: {}",
410 e
411 );
412 } else {
413 let config_path = self.dir_context.config_path();
414 self.emit_event(
415 "config_changed",
416 serde_json::json!({
417 "path": config_path.to_string_lossy(),
418 }),
419 );
420 }
421 }
422
423 let file_path = self
426 .buffer_metadata
427 .get(&self.active_buffer())
428 .and_then(|meta| meta.file_path().cloned());
429 if let Some(lsp) = self.lsp.as_mut() {
430 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
431 self.status_message = Some(message);
432 }
433 self.reopen_buffers_for_language(language);
434 }
435 } else if let Some(language) = action_key.strip_prefix("start:") {
436 let file_path = self
438 .buffer_metadata
439 .get(&self.active_buffer())
440 .and_then(|meta| meta.file_path().cloned());
441
442 if let Some(lsp) = self.lsp.as_mut() {
443 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
444 self.status_message = Some(message);
445 } else {
446 self.status_message = Some("No LSP manager available".to_string());
447 }
448 self.reopen_buffers_for_language(language);
449 } else if let Some(target) = action_key.strip_prefix("restart:") {
450 if let Some((language, server_name)) = target.split_once('/') {
452 let file_path = self
453 .buffer_metadata
454 .get(&self.active_buffer())
455 .and_then(|meta| meta.file_path().cloned());
456
457 if let Some(lsp) = self.lsp.as_mut() {
458 lsp.shutdown_server_by_name(language, server_name);
460 }
461 self.lsp_server_statuses
463 .remove(&(language.to_string(), server_name.to_string()));
464 if let Some(lsp) = self.lsp.as_mut() {
465 let _ = lsp.manual_restart(language, file_path.as_deref());
466 }
467 self.reopen_buffers_for_language(language);
468 self.status_message = Some(format!(
469 "Restarting LSP server: {}/{}",
470 language, server_name
471 ));
472 }
473 } else if let Some(target) = action_key.strip_prefix("stop:") {
474 if let Some((language, server_name)) = target.split_once('/') {
475 self.send_did_close_to_server(language, server_name);
482 let stopped = self.stop_lsp_server_and_cleanup(language, Some(server_name));
483 if stopped {
484 self.status_message =
485 Some(format!("Stopped LSP server: {}/{}", language, server_name));
486 } else {
487 self.status_message = Some(format!(
488 "LSP server not running: {}/{}",
489 language, server_name
490 ));
491 }
492 }
493 } else if let Some(language) = action_key.strip_prefix("log:") {
494 let log_path = crate::services::log_dirs::lsp_log_path(language);
495 if log_path.exists() {
496 match self.open_local_file(&log_path) {
497 Ok(buffer_id) => {
498 self.mark_buffer_read_only(buffer_id, true);
499 }
500 Err(e) => {
501 self.status_message = Some(format!("Failed to open LSP log: {}", e));
502 }
503 }
504 } else {
505 self.status_message = Some(format!("No log file found for {}", language));
506 }
507 } else if let Some(language) = action_key.strip_prefix("dismiss:") {
508 let lang = language.to_string();
519 self.dismiss_lsp_language(&lang);
520 let mut changed = false;
521 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
522 for c in lsp_configs.as_mut_slice() {
523 if c.enabled {
524 c.enabled = false;
525 changed = true;
526 }
527 }
528 }
529 if changed {
530 if let Err(e) = self.save_config() {
531 tracing::warn!("Failed to save config after disabling LSP: {}", e);
532 } else {
533 let config_path = self.dir_context.config_path();
534 self.emit_event(
535 "config_changed",
536 serde_json::json!({
537 "path": config_path.to_string_lossy(),
538 }),
539 );
540 }
541 }
542 self.status_message = Some(format!("LSP disabled for {}.", lang));
543 } else if let Some(language) = action_key.strip_prefix("enable:") {
544 let lang = language.to_string();
550 self.undismiss_lsp_language(&lang);
551 let mut changed = false;
552 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
553 for c in lsp_configs.as_mut_slice() {
554 if !c.enabled {
555 c.enabled = true;
556 changed = true;
557 }
558 }
559 }
560 if changed {
561 if let Err(e) = self.save_config() {
562 tracing::warn!("Failed to save config after enabling LSP: {}", e);
563 } else {
564 let config_path = self.dir_context.config_path();
565 self.emit_event(
566 "config_changed",
567 serde_json::json!({
568 "path": config_path.to_string_lossy(),
569 }),
570 );
571 }
572 }
573 self.status_message = Some(format!("LSP enabled for {}.", lang));
574 }
575 }
576
577 pub fn toggle_fold_at_cursor(&mut self) {
579 let buffer_id = self.active_buffer();
580 let pos = self.active_cursors().primary().position;
581 self.toggle_fold_at_byte(buffer_id, pos);
582 }
583
584 pub fn toggle_fold_at_line(&mut self, buffer_id: BufferId, line: usize) {
590 let byte_pos = {
591 let Some(state) = self.buffers.get(&buffer_id) else {
592 return;
593 };
594 state.buffer.line_start_offset(line).unwrap_or_else(|| {
595 use crate::view::folding::indent_folding;
596 let approx = line * state.buffer.estimated_line_length();
597 indent_folding::find_line_start_byte(&state.buffer, approx)
598 })
599 };
600 self.toggle_fold_at_byte(buffer_id, byte_pos);
601 }
602
603 pub fn toggle_fold_at_byte(&mut self, buffer_id: BufferId, byte_pos: usize) {
605 let split_id = self.split_manager.active_split();
606 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
607
608 let Some(state) = buffers.get_mut(&buffer_id) else {
609 return;
610 };
611
612 let Some(view_state) = split_view_states.get_mut(&split_id) else {
613 return;
614 };
615 let buf_state = view_state.ensure_buffer_state(buffer_id);
616
617 let header_byte = {
619 use crate::view::folding::indent_folding;
620 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
621 };
622 if buf_state
623 .folds
624 .remove_by_header_byte(&state.buffer, &mut state.marker_list, header_byte)
625 {
626 return;
627 }
628
629 if buf_state
631 .folds
632 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
633 {
634 return;
635 }
636
637 if !state.folding_ranges.is_empty() {
639 let resolved = state
643 .folding_ranges
644 .resolved(&state.buffer, &state.marker_list);
645 let line = state.buffer.get_line_number(byte_pos);
646 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
647 let mut exact_span = usize::MAX;
648 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
649 let mut containing_span = usize::MAX;
650
651 for range in &resolved {
652 let start_line = range.start_line as usize;
653 let range_end = range.end_line as usize;
654 if range_end <= start_line {
655 continue;
656 }
657 let span = range_end.saturating_sub(start_line);
658
659 if start_line == line && span < exact_span {
660 exact_span = span;
661 exact_range = Some(range);
662 }
663 if start_line <= line && line <= range_end && span < containing_span {
664 containing_span = span;
665 containing_range = Some(range);
666 }
667 }
668
669 let chosen = exact_range.or(containing_range);
670 let Some(range) = chosen else {
671 return;
672 };
673 let placeholder = range
674 .collapsed_text
675 .as_ref()
676 .filter(|text| !text.trim().is_empty())
677 .cloned();
678 let header_line = range.start_line as usize;
679 let end_line = range.end_line as usize;
680 let first_hidden = header_line.saturating_add(1);
681 if first_hidden > end_line {
682 return;
683 }
684 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
685 return;
686 };
687 let eb = state
688 .buffer
689 .line_start_offset(end_line.saturating_add(1))
690 .unwrap_or_else(|| state.buffer.len());
691 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
692 Self::create_fold(state, buf_state, sb, eb, hb, placeholder);
693 } else {
694 use crate::view::folding::indent_folding;
696 let tab_size = state.buffer_settings.tab_size;
697 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
698 let est_ll = state.buffer.estimated_line_length();
699 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
700
701 let upward_bytes = max_upward * est_ll;
704 let load_start = byte_pos.saturating_sub(upward_bytes);
705 let load_end = byte_pos
706 .saturating_add(max_scan_bytes)
707 .min(state.buffer.len());
708 drop(
711 state
712 .buffer
713 .get_text_range_mut(load_start, load_end - load_start),
714 );
715
716 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
717 &state.buffer,
718 byte_pos,
719 tab_size,
720 max_scan_bytes,
721 max_upward,
722 ) {
723 Self::create_fold(state, buf_state, sb, eb, hb, None);
724 }
725 }
726 }
727
728 fn create_fold(
729 state: &mut crate::state::EditorState,
730 buf_state: &mut crate::view::split::BufferViewState,
731 start_byte: usize,
732 end_byte: usize,
733 header_byte: usize,
734 placeholder: Option<String>,
735 ) {
736 if end_byte <= start_byte {
737 return;
738 }
739
740 buf_state.cursors.map(|cursor| {
742 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
743 let anchor_in_hidden = cursor
744 .anchor
745 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
746 if in_hidden_range || anchor_in_hidden {
747 cursor.position = header_byte;
748 cursor.anchor = None;
749 cursor.sticky_column = 0;
750 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
751 cursor.block_anchor = None;
752 cursor.deselect_on_move = true;
753 }
754 });
755
756 buf_state
757 .folds
758 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
759
760 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
762 buf_state.viewport.top_byte = header_byte;
763 buf_state.viewport.top_view_line_offset = 0;
764 }
765 }
766
767 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
772 let uris: Vec<_> = self
773 .buffers
774 .iter()
775 .filter(|(_, s)| s.language == language)
776 .filter_map(|(id, _)| {
777 self.buffer_metadata
778 .get(id)
779 .and_then(|m| m.file_uri())
780 .cloned()
781 })
782 .collect();
783
784 if let Some(lsp) = self.lsp.as_mut() {
785 for sh in lsp.get_handles_mut(language) {
786 if sh.name == server_name {
787 for uri in &uris {
788 tracing::info!(
789 "Sending didClose for {} to '{}' (language: {})",
790 uri.as_str(),
791 sh.name,
792 language
793 );
794 if let Err(e) = sh.handle.did_close(uri.clone()) {
795 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
796 }
797 }
798 break;
799 }
800 }
801 }
802 }
803
804 pub(crate) fn stop_lsp_server_and_cleanup(
830 &mut self,
831 language: &str,
832 server_name: Option<&str>,
833 ) -> bool {
834 let stopping_names: Vec<String> = if let Some(name) = server_name {
838 vec![name.to_string()]
839 } else {
840 self.lsp
841 .as_ref()
842 .map(|lsp| lsp.server_names_for_language(language))
843 .unwrap_or_default()
844 };
845
846 let stopped = if let Some(lsp) = self.lsp.as_mut() {
847 if let Some(name) = server_name {
848 lsp.shutdown_server_by_name(language, name)
849 } else {
850 lsp.shutdown_server(language)
851 }
852 } else {
853 false
854 };
855
856 if !stopped {
857 return false;
858 }
859
860 for name in &stopping_names {
861 self.lsp_server_statuses
862 .remove(&(language.to_string(), name.clone()));
863 self.clear_diagnostics_for_server(name);
866 }
867
868 let any_handle_left = self
883 .lsp
884 .as_ref()
885 .is_some_and(|lsp| lsp.has_handles(language));
886 if !any_handle_left {
887 self.lsp_progress
888 .retain(|_, info| info.language != language);
889 }
890
891 true
892 }
893
894 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
896 if let Some(uri) = self
902 .buffer_metadata
903 .get(&buffer_id)
904 .and_then(|m| m.file_uri())
905 .cloned()
906 {
907 let language = self
908 .buffers
909 .get(&buffer_id)
910 .map(|s| s.language.clone())
911 .unwrap_or_default();
912 if let Some(lsp) = self.lsp.as_mut() {
913 if !lsp.has_handles(&language) {
915 tracing::warn!(
916 "disable_lsp_for_buffer: no handle for language '{}'",
917 language
918 );
919 } else {
920 for sh in lsp.get_handles_mut(&language) {
921 tracing::info!(
922 "Sending didClose for {} to '{}' (language: {})",
923 uri.as_str(),
924 sh.name,
925 language
926 );
927 if let Err(e) = sh.handle.did_close(uri.clone()) {
928 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
929 }
930 }
931 }
932 } else {
933 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
934 }
935 } else {
936 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
937 }
938
939 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
941 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
942 metadata.lsp_opened_with.clear();
944 }
945 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
946
947 let uri = self
949 .buffer_metadata
950 .get(&buffer_id)
951 .and_then(|m| m.file_uri())
952 .map(|u| u.as_str().to_string());
953
954 if let Some(uri_str) = uri {
955 self.stored_diagnostics_mut().remove(&uri_str);
956 self.stored_push_diagnostics.remove(&uri_str);
957 self.stored_pull_diagnostics.remove(&uri_str);
958 self.diagnostic_result_ids.remove(&uri_str);
959 self.stored_folding_ranges_mut().remove(&uri_str);
960 }
961
962 if let Some((scheduled_buf, _)) = &self.scheduled_diagnostic_pull {
964 if *scheduled_buf == buffer_id {
965 self.scheduled_diagnostic_pull = None;
966 }
967 }
968
969 if let Some((scheduled_buf, _)) = &self.scheduled_inlay_hints_request {
971 if *scheduled_buf == buffer_id {
972 self.scheduled_inlay_hints_request = None;
973 }
974 }
975
976 self.folding_ranges_in_flight.remove(&buffer_id);
977 self.folding_ranges_debounce.remove(&buffer_id);
978 self.pending_folding_range_requests
979 .retain(|_, req| req.buffer_id != buffer_id);
980 self.pending_inlay_hints_requests
983 .retain(|_, req| req.buffer_id != buffer_id);
984
985 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
987 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
988 if let Some(state) = buffers.get_mut(&buffer_id) {
989 state
990 .overlays
991 .clear_namespace(&diagnostic_ns, &mut state.marker_list);
992 state.virtual_texts.clear(&mut state.marker_list);
993 state.folding_ranges.clear(&mut state.marker_list);
994 for view_state in split_view_states.values_mut() {
995 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
996 buf_state.folds.clear(&mut state.marker_list);
997 }
998 }
999 }
1000 }
1001
1002 fn enable_lsp_for_buffer(
1004 &mut self,
1005 buffer_id: crate::model::event::BufferId,
1006 language: &str,
1007 file_path: Option<std::path::PathBuf>,
1008 ) {
1009 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1011 metadata.lsp_enabled = true;
1012 metadata.lsp_disabled_reason = None;
1013 }
1014 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
1015
1016 if let Some(_path) = file_path {
1018 self.send_lsp_did_open_for_buffer(buffer_id, language);
1019 }
1020 }
1021
1022 fn send_lsp_did_open_for_buffer(
1024 &mut self,
1025 buffer_id: crate::model::event::BufferId,
1026 language: &str,
1027 ) {
1028 let (uri, text) = {
1030 let metadata = self.buffer_metadata.get(&buffer_id);
1031 let uri = metadata.and_then(|m| m.file_uri()).cloned();
1032 let text = self
1033 .buffers
1034 .get(&buffer_id)
1035 .and_then(|state| state.buffer.to_string());
1036 (uri, text)
1037 };
1038
1039 let Some(uri) = uri else { return };
1040 let Some(text) = text else { return };
1041
1042 use crate::services::lsp::manager::LspSpawnResult;
1044 let file_path = self
1045 .buffer_metadata
1046 .get(&buffer_id)
1047 .and_then(|m| m.file_path())
1048 .cloned();
1049 let Some(lsp) = self.lsp.as_mut() else {
1050 return;
1051 };
1052
1053 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
1054 return;
1055 }
1056
1057 let Some(handle) = lsp.get_handle_mut(language) else {
1058 return;
1059 };
1060
1061 let handle_id = handle.id();
1062 if let Err(e) = handle.did_open(uri.clone(), text, language.to_string()) {
1063 tracing::warn!("Failed to send didOpen to LSP: {}", e);
1064 return;
1065 }
1066
1067 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1069 metadata.lsp_opened_with.insert(handle_id);
1070 }
1071
1072 let request_id = self.next_lsp_request_id;
1074 self.next_lsp_request_id += 1;
1075 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
1076 if let Err(e) = handle.document_diagnostic(request_id, uri.clone(), previous_result_id) {
1077 tracing::warn!("LSP document_diagnostic request failed: {}", e);
1078 }
1079
1080 if self.config.editor.enable_inlay_hints {
1082 let (last_line, last_char, buffer_version) = self
1083 .buffers
1084 .get(&buffer_id)
1085 .map(|state| {
1086 let line_count = state.buffer.line_count().unwrap_or(1000);
1087 (
1088 line_count.saturating_sub(1) as u32,
1089 10000u32,
1090 state.buffer.version(),
1091 )
1092 })
1093 .unwrap_or((999, 10000, 0));
1094
1095 let request_id = self.next_lsp_request_id;
1096 self.next_lsp_request_id += 1;
1097 if let Err(e) = handle.inlay_hints(request_id, uri, 0, 0, last_line, last_char) {
1098 tracing::warn!("LSP inlay_hints request failed: {}", e);
1099 } else {
1100 self.pending_inlay_hints_requests.insert(
1101 request_id,
1102 super::InlayHintsRequest {
1103 buffer_id,
1104 version: buffer_version,
1105 },
1106 );
1107 }
1108 }
1109
1110 self.schedule_folding_ranges_refresh(buffer_id);
1112 }
1113
1114 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
1120 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
1121
1122 #[cfg(feature = "embed-plugins")]
1124 let fresh_dts_path = {
1125 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
1126 else {
1127 tracing::warn!(
1128 "Cannot set up plugin dev LSP: embedded plugins directory not available"
1129 );
1130 return;
1131 };
1132 let path = embedded_dir.join("lib").join("fresh.d.ts");
1133 if !path.exists() {
1134 tracing::warn!(
1135 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1136 path
1137 );
1138 return;
1139 }
1140 path
1141 };
1142
1143 #[cfg(not(feature = "embed-plugins"))]
1144 let fresh_dts_path = {
1145 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1147 .join("plugins")
1148 .join("lib")
1149 .join("fresh.d.ts");
1150 if !path.exists() {
1151 tracing::warn!(
1152 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1153 path
1154 );
1155 return;
1156 }
1157 path
1158 };
1159
1160 let buffer_id_num: usize = buffer_id.0;
1162 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
1163 Ok(workspace) => {
1164 let plugin_file = workspace.plugin_file.clone();
1165
1166 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1168 if let Some(uri) = super::types::file_path_to_lsp_uri(&plugin_file) {
1169 metadata.kind = super::types::BufferKind::File {
1170 path: plugin_file.clone(),
1171 uri: Some(uri),
1172 };
1173 metadata.lsp_enabled = true;
1174 metadata.lsp_disabled_reason = None;
1175 metadata.lsp_opened_with.clear();
1177
1178 tracing::info!(
1179 "Plugin dev LSP enabled for buffer {} via {:?}",
1180 buffer_id_num,
1181 plugin_file
1182 );
1183 }
1184 }
1185
1186 if let Some(state) = self.buffers.get_mut(&buffer_id) {
1188 let first_line = state.buffer.first_line_lossy();
1189 let detected =
1190 crate::primitives::detected_language::DetectedLanguage::from_path(
1191 &plugin_file,
1192 first_line.as_deref(),
1193 &self.grammar_registry,
1194 &self.config.languages,
1195 );
1196 state.apply_language(detected);
1197 }
1198
1199 if let Some(lsp) = &mut self.lsp {
1201 lsp.allow_language("typescript");
1202 }
1203
1204 let workspace_dir = workspace.dir().to_path_buf();
1206 self.plugin_dev_workspaces.insert(buffer_id, workspace);
1207
1208 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
1210
1211 if let Some(lsp) = &self.lsp {
1213 if let Some(handle) = lsp.get_handle("typescript") {
1214 if let Some(uri) = super::types::file_path_to_lsp_uri(&workspace_dir) {
1215 let name = workspace_dir
1216 .file_name()
1217 .unwrap_or_default()
1218 .to_string_lossy()
1219 .into_owned();
1220 if let Err(e) = handle.add_workspace_folder(uri, name) {
1221 tracing::warn!("Failed to add plugin workspace folder: {}", e);
1222 } else {
1223 tracing::info!(
1224 "Added plugin workspace folder: {:?}",
1225 workspace_dir
1226 );
1227 }
1228 }
1229 }
1230 }
1231 }
1232 Err(e) => {
1233 tracing::warn!("Failed to create plugin dev workspace: {}", e);
1234 }
1235 }
1236 }
1237}