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_with_translation(
145 &buf_path,
146 self.authority.path_translation.as_ref(),
147 ) else {
148 continue;
149 };
150
151 let lang_id = state.language.clone();
152 let line_count = state.buffer.line_count().unwrap_or(1000);
153 let buffer_version = state.buffer.version();
154
155 if let Some(lsp) = self.lsp.as_mut() {
156 use crate::services::lsp::manager::LspSpawnResult;
158 if lsp.try_spawn(&lang_id, Some(&buf_path)) != LspSpawnResult::Spawned {
159 continue;
160 }
161
162 let opened_with = self
165 .buffer_metadata
166 .get(&buffer_id)
167 .map(|m| m.lsp_opened_with.clone())
168 .unwrap_or_default();
169
170 let handles_needing_open: Vec<(String, u64)> = lsp
171 .get_handles(&lang_id)
172 .into_iter()
173 .filter(|sh| !opened_with.contains(&sh.handle.id()))
174 .map(|sh| (sh.name.clone(), sh.handle.id()))
175 .collect();
176
177 for (name, handle_id) in handles_needing_open {
179 let sh = lsp
180 .get_handles_mut(&lang_id)
181 .into_iter()
182 .find(|s| s.handle.id() == handle_id);
183
184 if let Some(sh) = sh {
185 if let Err(e) =
186 sh.handle
187 .did_open(uri.clone(), content.clone(), lang_id.clone())
188 {
189 tracing::warn!("LSP did_open to '{}' failed: {}", name, e);
190 } else if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
191 metadata.lsp_opened_with.insert(handle_id);
192 }
193 }
194 }
195 }
196
197 if enable_inlay_hints {
203 if let Some(lsp) = self.lsp.as_mut() {
204 if let Some(sh) =
205 lsp.handle_for_feature_mut(&lang_id, crate::types::LspFeature::InlayHints)
206 {
207 let request_id = self.next_lsp_request_id;
208 self.next_lsp_request_id += 1;
209 let last_line = line_count.saturating_sub(1) as u32;
210 if let Err(e) =
211 sh.handle
212 .inlay_hints(request_id, uri.clone(), 0, 0, last_line, 10000)
213 {
214 tracing::debug!(
215 "Failed to request inlay hints for {}: {}",
216 uri.as_str(),
217 e
218 );
219 } else {
220 self.pending_inlay_hints_requests.insert(
221 request_id,
222 super::InlayHintsRequest {
223 buffer_id,
224 version: buffer_version,
225 },
226 );
227 }
228 }
229 }
230 }
231 }
232 }
233
234 pub fn handle_lsp_stop(&mut self) {
239 let running_languages: Vec<String> = self
240 .lsp
241 .as_ref()
242 .map(|lsp| lsp.running_servers())
243 .unwrap_or_default();
244
245 if running_languages.is_empty() {
246 self.set_status_message(t!("lsp.no_servers_running").to_string());
247 return;
248 }
249
250 let mut suggestions: Vec<Suggestion> = Vec::new();
252 for lang in &running_languages {
253 let server_names: Vec<String> = self
254 .lsp
255 .as_ref()
256 .map(|lsp| lsp.server_names_for_language(lang))
257 .unwrap_or_default();
258
259 if server_names.len() > 1 {
260 for name in &server_names {
262 let description = Some(format!("Server: {}", name));
263 suggestions.push(Suggestion {
264 text: format!("{}/{}", lang, name),
265 description,
266 value: Some(format!("{}/{}", lang, name)),
269 disabled: false,
270 keybinding: None,
271 source: None,
272 });
273 }
274 } else {
275 let description = self
277 .lsp
278 .as_ref()
279 .and_then(|lsp| lsp.get_config(lang))
280 .filter(|c| !c.command.is_empty())
281 .map(|c| format!("Command: {}", c.command));
282
283 suggestions.push(Suggestion {
284 text: lang.clone(),
285 description,
286 value: Some(lang.clone()),
287 disabled: false,
288 keybinding: None,
289 source: None,
290 });
291 }
292 }
293
294 self.prompt = Some(Prompt::with_suggestions(
296 "Stop LSP server: ".to_string(),
297 PromptType::StopLspServer,
298 suggestions.clone(),
299 ));
300
301 if let Some(prompt) = self.prompt.as_mut() {
303 if suggestions.len() == 1 {
304 prompt.input = suggestions[0].text.clone();
306 prompt.cursor_pos = prompt.input.len();
307 prompt.selected_suggestion = Some(0);
308 } else if !prompt.suggestions.is_empty() {
309 prompt.selected_suggestion = Some(0);
311 }
312 }
313 }
314
315 pub fn handle_lsp_toggle_for_buffer(&mut self) {
320 let buffer_id = self.active_buffer();
321
322 let language = {
324 let Some(state) = self.buffers.get(&buffer_id) else {
325 return;
326 };
327 state.language.clone()
328 };
329
330 let lsp_configured = self
332 .lsp
333 .as_ref()
334 .and_then(|lsp| lsp.get_config(&language))
335 .is_some();
336
337 if !lsp_configured {
338 self.set_status_message(t!("lsp.no_server_configured").to_string());
339 return;
340 }
341
342 let (was_enabled, file_path) = {
344 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
345 return;
346 };
347 (metadata.lsp_enabled, metadata.file_path().cloned())
348 };
349
350 if was_enabled {
351 self.disable_lsp_for_buffer(buffer_id);
352 } else {
353 self.enable_lsp_for_buffer(buffer_id, &language, file_path);
354 }
355 }
356
357 pub fn is_lsp_language_user_dismissed(&self, language: &str) -> bool {
359 self.user_dismissed_lsp_languages.contains(language)
360 }
361
362 pub fn dismiss_lsp_language(&mut self, language: &str) {
366 self.user_dismissed_lsp_languages
367 .insert(language.to_string());
368 }
369
370 pub fn undismiss_lsp_language(&mut self, language: &str) {
373 self.user_dismissed_lsp_languages.remove(language);
374 }
375
376 pub fn handle_lsp_status_action(&mut self, action_key: &str) {
391 if action_key == "cancel_popup" {
392 return;
396 }
397 if let Some(target) = action_key.strip_prefix("autostart:") {
398 if let Some((language, server_name)) = target.split_once('/') {
404 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(language) {
405 for c in lsp_configs.as_mut_slice() {
406 if c.display_name() == server_name {
407 c.auto_start = true;
408 }
409 }
410 if let Err(e) = self.save_config() {
411 tracing::warn!(
412 "Failed to save config after enabling LSP auto-start: {}",
413 e
414 );
415 } else {
416 let config_path = self.dir_context.config_path();
417 self.emit_event(
418 "config_changed",
419 serde_json::json!({
420 "path": config_path.to_string_lossy(),
421 }),
422 );
423 }
424 }
425
426 let file_path = self
429 .buffer_metadata
430 .get(&self.active_buffer())
431 .and_then(|meta| meta.file_path().cloned());
432 if let Some(lsp) = self.lsp.as_mut() {
433 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
434 self.status_message = Some(message);
435 }
436 self.reopen_buffers_for_language(language);
437 }
438 } else if let Some(language) = action_key.strip_prefix("start:") {
439 let file_path = self
441 .buffer_metadata
442 .get(&self.active_buffer())
443 .and_then(|meta| meta.file_path().cloned());
444
445 if let Some(lsp) = self.lsp.as_mut() {
446 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
447 self.status_message = Some(message);
448 } else {
449 self.status_message = Some("No LSP manager available".to_string());
450 }
451 self.reopen_buffers_for_language(language);
452 } else if let Some(target) = action_key.strip_prefix("restart:") {
453 if let Some((language, server_name)) = target.split_once('/') {
455 let file_path = self
456 .buffer_metadata
457 .get(&self.active_buffer())
458 .and_then(|meta| meta.file_path().cloned());
459
460 if let Some(lsp) = self.lsp.as_mut() {
461 lsp.shutdown_server_by_name(language, server_name);
463 }
464 self.lsp_server_statuses
466 .remove(&(language.to_string(), server_name.to_string()));
467 if let Some(lsp) = self.lsp.as_mut() {
468 let _ = lsp.manual_restart(language, file_path.as_deref());
469 }
470 self.reopen_buffers_for_language(language);
471 self.status_message = Some(format!(
472 "Restarting LSP server: {}/{}",
473 language, server_name
474 ));
475 }
476 } else if let Some(target) = action_key.strip_prefix("stop:") {
477 if let Some((language, server_name)) = target.split_once('/') {
478 self.send_did_close_to_server(language, server_name);
485 let stopped = self.stop_lsp_server_and_cleanup(language, Some(server_name));
486 if stopped {
487 self.status_message =
488 Some(format!("Stopped LSP server: {}/{}", language, server_name));
489 } else {
490 self.status_message = Some(format!(
491 "LSP server not running: {}/{}",
492 language, server_name
493 ));
494 }
495 }
496 } else if let Some(language) = action_key.strip_prefix("log:") {
497 let log_path = crate::services::log_dirs::lsp_log_path(language);
498 if log_path.exists() {
499 match self.open_local_file(&log_path) {
500 Ok(buffer_id) => {
501 self.mark_buffer_read_only(buffer_id, true);
502 }
503 Err(e) => {
504 self.status_message = Some(format!("Failed to open LSP log: {}", e));
505 }
506 }
507 } else {
508 self.status_message = Some(format!("No log file found for {}", language));
509 }
510 } else if let Some(language) = action_key.strip_prefix("dismiss:") {
511 let lang = language.to_string();
522 self.dismiss_lsp_language(&lang);
523 let mut changed = false;
524 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
525 for c in lsp_configs.as_mut_slice() {
526 if c.enabled {
527 c.enabled = false;
528 changed = true;
529 }
530 }
531 }
532 if changed {
533 if let Err(e) = self.save_config() {
534 tracing::warn!("Failed to save config after disabling LSP: {}", e);
535 } else {
536 let config_path = self.dir_context.config_path();
537 self.emit_event(
538 "config_changed",
539 serde_json::json!({
540 "path": config_path.to_string_lossy(),
541 }),
542 );
543 }
544 }
545 self.status_message = Some(format!("LSP disabled for {}.", lang));
546 } else if let Some(language) = action_key.strip_prefix("enable:") {
547 let lang = language.to_string();
553 self.undismiss_lsp_language(&lang);
554 let mut changed = false;
555 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
556 for c in lsp_configs.as_mut_slice() {
557 if !c.enabled {
558 c.enabled = true;
559 changed = true;
560 }
561 }
562 }
563 if changed {
564 if let Err(e) = self.save_config() {
565 tracing::warn!("Failed to save config after enabling LSP: {}", e);
566 } else {
567 let config_path = self.dir_context.config_path();
568 self.emit_event(
569 "config_changed",
570 serde_json::json!({
571 "path": config_path.to_string_lossy(),
572 }),
573 );
574 }
575 }
576 self.status_message = Some(format!("LSP enabled for {}.", lang));
577 }
578 }
579
580 pub fn toggle_fold_at_cursor(&mut self) {
582 let buffer_id = self.active_buffer();
583 let pos = self.active_cursors().primary().position;
584 self.toggle_fold_at_byte(buffer_id, pos);
585 }
586
587 pub fn toggle_fold_at_line(&mut self, buffer_id: BufferId, line: usize) {
593 let byte_pos = {
594 let Some(state) = self.buffers.get(&buffer_id) else {
595 return;
596 };
597 state.buffer.line_start_offset(line).unwrap_or_else(|| {
598 use crate::view::folding::indent_folding;
599 let approx = line * state.buffer.estimated_line_length();
600 indent_folding::find_line_start_byte(&state.buffer, approx)
601 })
602 };
603 self.toggle_fold_at_byte(buffer_id, byte_pos);
604 }
605
606 pub fn toggle_fold_at_byte(&mut self, buffer_id: BufferId, byte_pos: usize) {
608 let split_id = self.split_manager.active_split();
609 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
610
611 let Some(state) = buffers.get_mut(&buffer_id) else {
612 return;
613 };
614
615 let Some(view_state) = split_view_states.get_mut(&split_id) else {
616 return;
617 };
618 let buf_state = view_state.ensure_buffer_state(buffer_id);
619
620 let header_byte = {
622 use crate::view::folding::indent_folding;
623 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
624 };
625 if buf_state
626 .folds
627 .remove_by_header_byte(&state.buffer, &mut state.marker_list, header_byte)
628 {
629 return;
630 }
631
632 if buf_state
634 .folds
635 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
636 {
637 return;
638 }
639
640 if !state.folding_ranges.is_empty() {
642 let resolved = state
646 .folding_ranges
647 .resolved(&state.buffer, &state.marker_list);
648 let line = state.buffer.get_line_number(byte_pos);
649 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
650 let mut exact_span = usize::MAX;
651 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
652 let mut containing_span = usize::MAX;
653
654 for range in &resolved {
655 let start_line = range.start_line as usize;
656 let range_end = range.end_line as usize;
657 if range_end <= start_line {
658 continue;
659 }
660 let span = range_end.saturating_sub(start_line);
661
662 if start_line == line && span < exact_span {
663 exact_span = span;
664 exact_range = Some(range);
665 }
666 if start_line <= line && line <= range_end && span < containing_span {
667 containing_span = span;
668 containing_range = Some(range);
669 }
670 }
671
672 let chosen = exact_range.or(containing_range);
673 let Some(range) = chosen else {
674 return;
675 };
676 let placeholder = range
677 .collapsed_text
678 .as_ref()
679 .filter(|text| !text.trim().is_empty())
680 .cloned();
681 let header_line = range.start_line as usize;
682 let end_line = range.end_line as usize;
683 let first_hidden = header_line.saturating_add(1);
684 if first_hidden > end_line {
685 return;
686 }
687 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
688 return;
689 };
690 let eb = state
691 .buffer
692 .line_start_offset(end_line.saturating_add(1))
693 .unwrap_or_else(|| state.buffer.len());
694 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
695 Self::create_fold(state, buf_state, sb, eb, hb, placeholder);
696 } else {
697 use crate::view::folding::indent_folding;
699 let tab_size = state.buffer_settings.tab_size;
700 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
701 let est_ll = state.buffer.estimated_line_length();
702 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
703
704 let upward_bytes = max_upward * est_ll;
707 let load_start = byte_pos.saturating_sub(upward_bytes);
708 let load_end = byte_pos
709 .saturating_add(max_scan_bytes)
710 .min(state.buffer.len());
711 drop(
714 state
715 .buffer
716 .get_text_range_mut(load_start, load_end - load_start),
717 );
718
719 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
720 &state.buffer,
721 byte_pos,
722 tab_size,
723 max_scan_bytes,
724 max_upward,
725 ) {
726 Self::create_fold(state, buf_state, sb, eb, hb, None);
727 }
728 }
729 }
730
731 fn create_fold(
732 state: &mut crate::state::EditorState,
733 buf_state: &mut crate::view::split::BufferViewState,
734 start_byte: usize,
735 end_byte: usize,
736 header_byte: usize,
737 placeholder: Option<String>,
738 ) {
739 if end_byte <= start_byte {
740 return;
741 }
742
743 buf_state.cursors.map(|cursor| {
745 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
746 let anchor_in_hidden = cursor
747 .anchor
748 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
749 if in_hidden_range || anchor_in_hidden {
750 cursor.position = header_byte;
751 cursor.anchor = None;
752 cursor.sticky_column = 0;
753 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
754 cursor.block_anchor = None;
755 cursor.deselect_on_move = true;
756 }
757 });
758
759 buf_state
760 .folds
761 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
762
763 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
765 buf_state.viewport.top_byte = header_byte;
766 buf_state.viewport.top_view_line_offset = 0;
767 }
768 }
769
770 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
775 let uris: Vec<_> = self
776 .buffers
777 .iter()
778 .filter(|(_, s)| s.language == language)
779 .filter_map(|(id, _)| {
780 self.buffer_metadata
781 .get(id)
782 .and_then(|m| m.file_uri())
783 .cloned()
784 })
785 .collect();
786
787 if let Some(lsp) = self.lsp.as_mut() {
788 for sh in lsp.get_handles_mut(language) {
789 if sh.name == server_name {
790 for uri in &uris {
791 tracing::info!(
792 "Sending didClose for {} to '{}' (language: {})",
793 uri.as_str(),
794 sh.name,
795 language
796 );
797 if let Err(e) = sh.handle.did_close(uri.as_uri().clone()) {
798 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
799 }
800 }
801 break;
802 }
803 }
804 }
805 }
806
807 pub(crate) fn stop_lsp_server_and_cleanup(
833 &mut self,
834 language: &str,
835 server_name: Option<&str>,
836 ) -> bool {
837 let stopping_names: Vec<String> = if let Some(name) = server_name {
841 vec![name.to_string()]
842 } else {
843 self.lsp
844 .as_ref()
845 .map(|lsp| lsp.server_names_for_language(language))
846 .unwrap_or_default()
847 };
848
849 let stopped = if let Some(lsp) = self.lsp.as_mut() {
850 if let Some(name) = server_name {
851 lsp.shutdown_server_by_name(language, name)
852 } else {
853 lsp.shutdown_server(language)
854 }
855 } else {
856 false
857 };
858
859 if !stopped {
860 return false;
861 }
862
863 for name in &stopping_names {
864 self.lsp_server_statuses
865 .remove(&(language.to_string(), name.clone()));
866 self.clear_diagnostics_for_server(name);
869 }
870
871 let any_handle_left = self
886 .lsp
887 .as_ref()
888 .is_some_and(|lsp| lsp.has_handles(language));
889 if !any_handle_left {
890 self.lsp_progress
891 .retain(|_, info| info.language != language);
892 }
893
894 true
895 }
896
897 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
899 if let Some(uri) = self
905 .buffer_metadata
906 .get(&buffer_id)
907 .and_then(|m| m.file_uri())
908 .cloned()
909 {
910 let language = self
911 .buffers
912 .get(&buffer_id)
913 .map(|s| s.language.clone())
914 .unwrap_or_default();
915 if let Some(lsp) = self.lsp.as_mut() {
916 if !lsp.has_handles(&language) {
918 tracing::warn!(
919 "disable_lsp_for_buffer: no handle for language '{}'",
920 language
921 );
922 } else {
923 for sh in lsp.get_handles_mut(&language) {
924 tracing::info!(
925 "Sending didClose for {} to '{}' (language: {})",
926 uri.as_str(),
927 sh.name,
928 language
929 );
930 if let Err(e) = sh.handle.did_close(uri.as_uri().clone()) {
931 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
932 }
933 }
934 }
935 } else {
936 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
937 }
938 } else {
939 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
940 }
941
942 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
944 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
945 metadata.lsp_opened_with.clear();
947 }
948 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
949
950 let uri = self
952 .buffer_metadata
953 .get(&buffer_id)
954 .and_then(|m| m.file_uri())
955 .map(|u| u.as_str().to_string());
956
957 if let Some(uri_str) = uri {
958 self.stored_diagnostics_mut().remove(&uri_str);
959 self.stored_push_diagnostics.remove(&uri_str);
960 self.stored_pull_diagnostics.remove(&uri_str);
961 self.diagnostic_result_ids.remove(&uri_str);
962 self.stored_folding_ranges_mut().remove(&uri_str);
963 }
964
965 if let Some((scheduled_buf, _)) = &self.scheduled_diagnostic_pull {
967 if *scheduled_buf == buffer_id {
968 self.scheduled_diagnostic_pull = None;
969 }
970 }
971
972 if let Some((scheduled_buf, _)) = &self.scheduled_inlay_hints_request {
974 if *scheduled_buf == buffer_id {
975 self.scheduled_inlay_hints_request = None;
976 }
977 }
978
979 self.folding_ranges_in_flight.remove(&buffer_id);
980 self.folding_ranges_debounce.remove(&buffer_id);
981 self.pending_folding_range_requests
982 .retain(|_, req| req.buffer_id != buffer_id);
983 self.pending_inlay_hints_requests
986 .retain(|_, req| req.buffer_id != buffer_id);
987
988 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
990 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
991 if let Some(state) = buffers.get_mut(&buffer_id) {
992 state
993 .overlays
994 .clear_namespace(&diagnostic_ns, &mut state.marker_list);
995 state.virtual_texts.clear(&mut state.marker_list);
996 state.folding_ranges.clear(&mut state.marker_list);
997 for view_state in split_view_states.values_mut() {
998 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
999 buf_state.folds.clear(&mut state.marker_list);
1000 }
1001 }
1002 }
1003 }
1004
1005 fn enable_lsp_for_buffer(
1007 &mut self,
1008 buffer_id: crate::model::event::BufferId,
1009 language: &str,
1010 file_path: Option<std::path::PathBuf>,
1011 ) {
1012 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1014 metadata.lsp_enabled = true;
1015 metadata.lsp_disabled_reason = None;
1016 }
1017 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
1018
1019 if let Some(_path) = file_path {
1021 self.send_lsp_did_open_for_buffer(buffer_id, language);
1022 }
1023 }
1024
1025 fn send_lsp_did_open_for_buffer(
1027 &mut self,
1028 buffer_id: crate::model::event::BufferId,
1029 language: &str,
1030 ) {
1031 let (uri, text) = {
1033 let metadata = self.buffer_metadata.get(&buffer_id);
1034 let uri = metadata.and_then(|m| m.file_uri()).cloned();
1035 let text = self
1036 .buffers
1037 .get(&buffer_id)
1038 .and_then(|state| state.buffer.to_string());
1039 (uri, text)
1040 };
1041
1042 let Some(uri) = uri else { return };
1043 let Some(text) = text else { return };
1044
1045 use crate::services::lsp::manager::LspSpawnResult;
1047 let file_path = self
1048 .buffer_metadata
1049 .get(&buffer_id)
1050 .and_then(|m| m.file_path())
1051 .cloned();
1052 let Some(lsp) = self.lsp.as_mut() else {
1053 return;
1054 };
1055
1056 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
1057 return;
1058 }
1059
1060 let Some(handle) = lsp.get_handle_mut(language) else {
1061 return;
1062 };
1063
1064 let handle_id = handle.id();
1065 if let Err(e) = handle.did_open(uri.as_uri().clone(), text, language.to_string()) {
1066 tracing::warn!("Failed to send didOpen to LSP: {}", e);
1067 return;
1068 }
1069
1070 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1072 metadata.lsp_opened_with.insert(handle_id);
1073 }
1074
1075 let request_id = self.next_lsp_request_id;
1077 self.next_lsp_request_id += 1;
1078 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
1079 if let Err(e) =
1080 handle.document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
1081 {
1082 tracing::warn!("LSP document_diagnostic request failed: {}", e);
1083 }
1084
1085 if self.config.editor.enable_inlay_hints {
1087 let (last_line, last_char, buffer_version) = self
1088 .buffers
1089 .get(&buffer_id)
1090 .map(|state| {
1091 let line_count = state.buffer.line_count().unwrap_or(1000);
1092 (
1093 line_count.saturating_sub(1) as u32,
1094 10000u32,
1095 state.buffer.version(),
1096 )
1097 })
1098 .unwrap_or((999, 10000, 0));
1099
1100 let request_id = self.next_lsp_request_id;
1101 self.next_lsp_request_id += 1;
1102 if let Err(e) =
1103 handle.inlay_hints(request_id, uri.as_uri().clone(), 0, 0, last_line, last_char)
1104 {
1105 tracing::warn!("LSP inlay_hints request failed: {}", e);
1106 } else {
1107 self.pending_inlay_hints_requests.insert(
1108 request_id,
1109 super::InlayHintsRequest {
1110 buffer_id,
1111 version: buffer_version,
1112 },
1113 );
1114 }
1115 }
1116
1117 self.schedule_folding_ranges_refresh(buffer_id);
1119 }
1120
1121 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
1127 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
1128
1129 #[cfg(feature = "embed-plugins")]
1131 let fresh_dts_path = {
1132 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
1133 else {
1134 tracing::warn!(
1135 "Cannot set up plugin dev LSP: embedded plugins directory not available"
1136 );
1137 return;
1138 };
1139 let path = embedded_dir.join("lib").join("fresh.d.ts");
1140 if !path.exists() {
1141 tracing::warn!(
1142 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1143 path
1144 );
1145 return;
1146 }
1147 path
1148 };
1149
1150 #[cfg(not(feature = "embed-plugins"))]
1151 let fresh_dts_path = {
1152 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1154 .join("plugins")
1155 .join("lib")
1156 .join("fresh.d.ts");
1157 if !path.exists() {
1158 tracing::warn!(
1159 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1160 path
1161 );
1162 return;
1163 }
1164 path
1165 };
1166
1167 let buffer_id_num: usize = buffer_id.0;
1169 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
1170 Ok(workspace) => {
1171 let plugin_file = workspace.plugin_file.clone();
1172
1173 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1175 if let Some(uri) = super::types::LspUri::from_host_path(
1176 &plugin_file,
1177 self.authority.path_translation.as_ref(),
1178 ) {
1179 metadata.kind = super::types::BufferKind::File {
1180 path: plugin_file.clone(),
1181 uri: Some(uri),
1182 };
1183 metadata.lsp_enabled = true;
1184 metadata.lsp_disabled_reason = None;
1185 metadata.lsp_opened_with.clear();
1187
1188 tracing::info!(
1189 "Plugin dev LSP enabled for buffer {} via {:?}",
1190 buffer_id_num,
1191 plugin_file
1192 );
1193 }
1194 }
1195
1196 if let Some(state) = self.buffers.get_mut(&buffer_id) {
1198 let first_line = state.buffer.first_line_lossy();
1199 let detected =
1200 crate::primitives::detected_language::DetectedLanguage::from_path(
1201 &plugin_file,
1202 first_line.as_deref(),
1203 &self.grammar_registry,
1204 &self.config.languages,
1205 );
1206 state.apply_language(detected);
1207 }
1208
1209 if let Some(lsp) = &mut self.lsp {
1211 lsp.allow_language("typescript");
1212 }
1213
1214 let workspace_dir = workspace.dir().to_path_buf();
1216 self.plugin_dev_workspaces.insert(buffer_id, workspace);
1217
1218 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
1220
1221 if let Some(lsp) = &self.lsp {
1223 if let Some(handle) = lsp.get_handle("typescript") {
1224 if let Some(uri) = super::types::file_path_to_lsp_uri_with_translation(
1225 &workspace_dir,
1226 self.authority.path_translation.as_ref(),
1227 ) {
1228 let name = workspace_dir
1229 .file_name()
1230 .unwrap_or_default()
1231 .to_string_lossy()
1232 .into_owned();
1233 if let Err(e) = handle.add_workspace_folder(uri, name) {
1234 tracing::warn!("Failed to add plugin workspace folder: {}", e);
1235 } else {
1236 tracing::info!(
1237 "Added plugin workspace folder: {:?}",
1238 workspace_dir
1239 );
1240 }
1241 }
1242 }
1243 }
1244 }
1245 Err(e) => {
1246 tracing::warn!("Failed to create plugin dev workspace: {}", e);
1247 }
1248 }
1249 }
1250}