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) {
383 if let Some(language) = action_key.strip_prefix("start:") {
384 let file_path = self
386 .buffer_metadata
387 .get(&self.active_buffer())
388 .and_then(|meta| meta.file_path().cloned());
389
390 if let Some(lsp) = self.lsp.as_mut() {
391 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
392 self.status_message = Some(message);
393 } else {
394 self.status_message = Some("No LSP manager available".to_string());
395 }
396 self.reopen_buffers_for_language(language);
397 } else if let Some(target) = action_key.strip_prefix("restart:") {
398 if let Some((language, server_name)) = target.split_once('/') {
400 let file_path = self
401 .buffer_metadata
402 .get(&self.active_buffer())
403 .and_then(|meta| meta.file_path().cloned());
404
405 if let Some(lsp) = self.lsp.as_mut() {
406 lsp.shutdown_server_by_name(language, server_name);
408 }
409 self.lsp_server_statuses
411 .remove(&(language.to_string(), server_name.to_string()));
412 if let Some(lsp) = self.lsp.as_mut() {
413 let _ = lsp.manual_restart(language, file_path.as_deref());
414 }
415 self.reopen_buffers_for_language(language);
416 self.status_message = Some(format!(
417 "Restarting LSP server: {}/{}",
418 language, server_name
419 ));
420 }
421 } else if let Some(target) = action_key.strip_prefix("stop:") {
422 if let Some((language, server_name)) = target.split_once('/') {
423 self.send_did_close_to_server(language, server_name);
430 let stopped = self.stop_lsp_server_and_cleanup(language, Some(server_name));
431 if stopped {
432 self.status_message =
433 Some(format!("Stopped LSP server: {}/{}", language, server_name));
434 } else {
435 self.status_message = Some(format!(
436 "LSP server not running: {}/{}",
437 language, server_name
438 ));
439 }
440 }
441 } else if let Some(language) = action_key.strip_prefix("log:") {
442 let log_path = crate::services::log_dirs::lsp_log_path(language);
443 if log_path.exists() {
444 match self.open_local_file(&log_path) {
445 Ok(buffer_id) => {
446 self.mark_buffer_read_only(buffer_id, true);
447 }
448 Err(e) => {
449 self.status_message = Some(format!("Failed to open LSP log: {}", e));
450 }
451 }
452 } else {
453 self.status_message = Some(format!("No log file found for {}", language));
454 }
455 } else if let Some(language) = action_key.strip_prefix("dismiss:") {
456 self.dismiss_lsp_language(language);
457 self.status_message = Some(format!(
458 "LSP pill dimmed for {}. Click it to re-enable.",
459 language
460 ));
461 } else if let Some(language) = action_key.strip_prefix("enable:") {
462 self.undismiss_lsp_language(language);
463 self.status_message = Some(format!("LSP pill restored for {}.", language));
464 }
465 }
466
467 pub fn toggle_fold_at_cursor(&mut self) {
469 let buffer_id = self.active_buffer();
470 let pos = self.active_cursors().primary().position;
471 self.toggle_fold_at_byte(buffer_id, pos);
472 }
473
474 pub fn toggle_fold_at_line(&mut self, buffer_id: BufferId, line: usize) {
480 let byte_pos = {
481 let Some(state) = self.buffers.get(&buffer_id) else {
482 return;
483 };
484 state.buffer.line_start_offset(line).unwrap_or_else(|| {
485 use crate::view::folding::indent_folding;
486 let approx = line * state.buffer.estimated_line_length();
487 indent_folding::find_line_start_byte(&state.buffer, approx)
488 })
489 };
490 self.toggle_fold_at_byte(buffer_id, byte_pos);
491 }
492
493 pub fn toggle_fold_at_byte(&mut self, buffer_id: BufferId, byte_pos: usize) {
495 let split_id = self.split_manager.active_split();
496 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
497
498 let Some(state) = buffers.get_mut(&buffer_id) else {
499 return;
500 };
501
502 let Some(view_state) = split_view_states.get_mut(&split_id) else {
503 return;
504 };
505 let buf_state = view_state.ensure_buffer_state(buffer_id);
506
507 let header_byte = {
509 use crate::view::folding::indent_folding;
510 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
511 };
512 if buf_state
513 .folds
514 .remove_by_header_byte(&state.buffer, &mut state.marker_list, header_byte)
515 {
516 return;
517 }
518
519 if buf_state
521 .folds
522 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
523 {
524 return;
525 }
526
527 if !state.folding_ranges.is_empty() {
529 let resolved = state
533 .folding_ranges
534 .resolved(&state.buffer, &state.marker_list);
535 let line = state.buffer.get_line_number(byte_pos);
536 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
537 let mut exact_span = usize::MAX;
538 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
539 let mut containing_span = usize::MAX;
540
541 for range in &resolved {
542 let start_line = range.start_line as usize;
543 let range_end = range.end_line as usize;
544 if range_end <= start_line {
545 continue;
546 }
547 let span = range_end.saturating_sub(start_line);
548
549 if start_line == line && span < exact_span {
550 exact_span = span;
551 exact_range = Some(range);
552 }
553 if start_line <= line && line <= range_end && span < containing_span {
554 containing_span = span;
555 containing_range = Some(range);
556 }
557 }
558
559 let chosen = exact_range.or(containing_range);
560 let Some(range) = chosen else {
561 return;
562 };
563 let placeholder = range
564 .collapsed_text
565 .as_ref()
566 .filter(|text| !text.trim().is_empty())
567 .cloned();
568 let header_line = range.start_line as usize;
569 let end_line = range.end_line as usize;
570 let first_hidden = header_line.saturating_add(1);
571 if first_hidden > end_line {
572 return;
573 }
574 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
575 return;
576 };
577 let eb = state
578 .buffer
579 .line_start_offset(end_line.saturating_add(1))
580 .unwrap_or_else(|| state.buffer.len());
581 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
582 Self::create_fold(state, buf_state, sb, eb, hb, placeholder);
583 } else {
584 use crate::view::folding::indent_folding;
586 let tab_size = state.buffer_settings.tab_size;
587 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
588 let est_ll = state.buffer.estimated_line_length();
589 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
590
591 let upward_bytes = max_upward * est_ll;
594 let load_start = byte_pos.saturating_sub(upward_bytes);
595 let load_end = byte_pos
596 .saturating_add(max_scan_bytes)
597 .min(state.buffer.len());
598 drop(
601 state
602 .buffer
603 .get_text_range_mut(load_start, load_end - load_start),
604 );
605
606 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
607 &state.buffer,
608 byte_pos,
609 tab_size,
610 max_scan_bytes,
611 max_upward,
612 ) {
613 Self::create_fold(state, buf_state, sb, eb, hb, None);
614 }
615 }
616 }
617
618 fn create_fold(
619 state: &mut crate::state::EditorState,
620 buf_state: &mut crate::view::split::BufferViewState,
621 start_byte: usize,
622 end_byte: usize,
623 header_byte: usize,
624 placeholder: Option<String>,
625 ) {
626 if end_byte <= start_byte {
627 return;
628 }
629
630 buf_state.cursors.map(|cursor| {
632 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
633 let anchor_in_hidden = cursor
634 .anchor
635 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
636 if in_hidden_range || anchor_in_hidden {
637 cursor.position = header_byte;
638 cursor.anchor = None;
639 cursor.sticky_column = 0;
640 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
641 cursor.block_anchor = None;
642 cursor.deselect_on_move = true;
643 }
644 });
645
646 buf_state
647 .folds
648 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
649
650 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
652 buf_state.viewport.top_byte = header_byte;
653 buf_state.viewport.top_view_line_offset = 0;
654 }
655 }
656
657 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
662 let uris: Vec<_> = self
663 .buffers
664 .iter()
665 .filter(|(_, s)| s.language == language)
666 .filter_map(|(id, _)| {
667 self.buffer_metadata
668 .get(id)
669 .and_then(|m| m.file_uri())
670 .cloned()
671 })
672 .collect();
673
674 if let Some(lsp) = self.lsp.as_mut() {
675 for sh in lsp.get_handles_mut(language) {
676 if sh.name == server_name {
677 for uri in &uris {
678 tracing::info!(
679 "Sending didClose for {} to '{}' (language: {})",
680 uri.as_str(),
681 sh.name,
682 language
683 );
684 if let Err(e) = sh.handle.did_close(uri.clone()) {
685 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
686 }
687 }
688 break;
689 }
690 }
691 }
692 }
693
694 pub(crate) fn stop_lsp_server_and_cleanup(
720 &mut self,
721 language: &str,
722 server_name: Option<&str>,
723 ) -> bool {
724 let stopping_names: Vec<String> = if let Some(name) = server_name {
728 vec![name.to_string()]
729 } else {
730 self.lsp
731 .as_ref()
732 .map(|lsp| lsp.server_names_for_language(language))
733 .unwrap_or_default()
734 };
735
736 let stopped = if let Some(lsp) = self.lsp.as_mut() {
737 if let Some(name) = server_name {
738 lsp.shutdown_server_by_name(language, name)
739 } else {
740 lsp.shutdown_server(language)
741 }
742 } else {
743 false
744 };
745
746 if !stopped {
747 return false;
748 }
749
750 for name in &stopping_names {
751 self.lsp_server_statuses
752 .remove(&(language.to_string(), name.clone()));
753 self.clear_diagnostics_for_server(name);
756 }
757
758 true
759 }
760
761 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
763 if let Some(uri) = self
769 .buffer_metadata
770 .get(&buffer_id)
771 .and_then(|m| m.file_uri())
772 .cloned()
773 {
774 let language = self
775 .buffers
776 .get(&buffer_id)
777 .map(|s| s.language.clone())
778 .unwrap_or_default();
779 if let Some(lsp) = self.lsp.as_mut() {
780 if !lsp.has_handles(&language) {
782 tracing::warn!(
783 "disable_lsp_for_buffer: no handle for language '{}'",
784 language
785 );
786 } else {
787 for sh in lsp.get_handles_mut(&language) {
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 }
799 } else {
800 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
801 }
802 } else {
803 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
804 }
805
806 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
808 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
809 metadata.lsp_opened_with.clear();
811 }
812 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
813
814 let uri = self
816 .buffer_metadata
817 .get(&buffer_id)
818 .and_then(|m| m.file_uri())
819 .map(|u| u.as_str().to_string());
820
821 if let Some(uri_str) = uri {
822 self.stored_diagnostics_mut().remove(&uri_str);
823 self.stored_push_diagnostics.remove(&uri_str);
824 self.stored_pull_diagnostics.remove(&uri_str);
825 self.diagnostic_result_ids.remove(&uri_str);
826 self.stored_folding_ranges_mut().remove(&uri_str);
827 }
828
829 if let Some((scheduled_buf, _)) = &self.scheduled_diagnostic_pull {
831 if *scheduled_buf == buffer_id {
832 self.scheduled_diagnostic_pull = None;
833 }
834 }
835
836 if let Some((scheduled_buf, _)) = &self.scheduled_inlay_hints_request {
838 if *scheduled_buf == buffer_id {
839 self.scheduled_inlay_hints_request = None;
840 }
841 }
842
843 self.folding_ranges_in_flight.remove(&buffer_id);
844 self.folding_ranges_debounce.remove(&buffer_id);
845 self.pending_folding_range_requests
846 .retain(|_, req| req.buffer_id != buffer_id);
847 self.pending_inlay_hints_requests
850 .retain(|_, req| req.buffer_id != buffer_id);
851
852 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
854 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
855 if let Some(state) = buffers.get_mut(&buffer_id) {
856 state
857 .overlays
858 .clear_namespace(&diagnostic_ns, &mut state.marker_list);
859 state.virtual_texts.clear(&mut state.marker_list);
860 state.folding_ranges.clear(&mut state.marker_list);
861 for view_state in split_view_states.values_mut() {
862 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
863 buf_state.folds.clear(&mut state.marker_list);
864 }
865 }
866 }
867 }
868
869 fn enable_lsp_for_buffer(
871 &mut self,
872 buffer_id: crate::model::event::BufferId,
873 language: &str,
874 file_path: Option<std::path::PathBuf>,
875 ) {
876 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
878 metadata.lsp_enabled = true;
879 metadata.lsp_disabled_reason = None;
880 }
881 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
882
883 if let Some(_path) = file_path {
885 self.send_lsp_did_open_for_buffer(buffer_id, language);
886 }
887 }
888
889 fn send_lsp_did_open_for_buffer(
891 &mut self,
892 buffer_id: crate::model::event::BufferId,
893 language: &str,
894 ) {
895 let (uri, text) = {
897 let metadata = self.buffer_metadata.get(&buffer_id);
898 let uri = metadata.and_then(|m| m.file_uri()).cloned();
899 let text = self
900 .buffers
901 .get(&buffer_id)
902 .and_then(|state| state.buffer.to_string());
903 (uri, text)
904 };
905
906 let Some(uri) = uri else { return };
907 let Some(text) = text else { return };
908
909 use crate::services::lsp::manager::LspSpawnResult;
911 let file_path = self
912 .buffer_metadata
913 .get(&buffer_id)
914 .and_then(|m| m.file_path())
915 .cloned();
916 let Some(lsp) = self.lsp.as_mut() else {
917 return;
918 };
919
920 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
921 return;
922 }
923
924 let Some(handle) = lsp.get_handle_mut(language) else {
925 return;
926 };
927
928 let handle_id = handle.id();
929 if let Err(e) = handle.did_open(uri.clone(), text, language.to_string()) {
930 tracing::warn!("Failed to send didOpen to LSP: {}", e);
931 return;
932 }
933
934 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
936 metadata.lsp_opened_with.insert(handle_id);
937 }
938
939 let request_id = self.next_lsp_request_id;
941 self.next_lsp_request_id += 1;
942 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
943 if let Err(e) = handle.document_diagnostic(request_id, uri.clone(), previous_result_id) {
944 tracing::warn!("LSP document_diagnostic request failed: {}", e);
945 }
946
947 if self.config.editor.enable_inlay_hints {
949 let (last_line, last_char, buffer_version) = self
950 .buffers
951 .get(&buffer_id)
952 .map(|state| {
953 let line_count = state.buffer.line_count().unwrap_or(1000);
954 (
955 line_count.saturating_sub(1) as u32,
956 10000u32,
957 state.buffer.version(),
958 )
959 })
960 .unwrap_or((999, 10000, 0));
961
962 let request_id = self.next_lsp_request_id;
963 self.next_lsp_request_id += 1;
964 if let Err(e) = handle.inlay_hints(request_id, uri, 0, 0, last_line, last_char) {
965 tracing::warn!("LSP inlay_hints request failed: {}", e);
966 } else {
967 self.pending_inlay_hints_requests.insert(
968 request_id,
969 super::InlayHintsRequest {
970 buffer_id,
971 version: buffer_version,
972 },
973 );
974 }
975 }
976
977 self.schedule_folding_ranges_refresh(buffer_id);
979 }
980
981 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
987 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
988
989 #[cfg(feature = "embed-plugins")]
991 let fresh_dts_path = {
992 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
993 else {
994 tracing::warn!(
995 "Cannot set up plugin dev LSP: embedded plugins directory not available"
996 );
997 return;
998 };
999 let path = embedded_dir.join("lib").join("fresh.d.ts");
1000 if !path.exists() {
1001 tracing::warn!(
1002 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1003 path
1004 );
1005 return;
1006 }
1007 path
1008 };
1009
1010 #[cfg(not(feature = "embed-plugins"))]
1011 let fresh_dts_path = {
1012 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1014 .join("plugins")
1015 .join("lib")
1016 .join("fresh.d.ts");
1017 if !path.exists() {
1018 tracing::warn!(
1019 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1020 path
1021 );
1022 return;
1023 }
1024 path
1025 };
1026
1027 let buffer_id_num: usize = buffer_id.0;
1029 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
1030 Ok(workspace) => {
1031 let plugin_file = workspace.plugin_file.clone();
1032
1033 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
1035 if let Some(uri) = super::types::file_path_to_lsp_uri(&plugin_file) {
1036 metadata.kind = super::types::BufferKind::File {
1037 path: plugin_file.clone(),
1038 uri: Some(uri),
1039 };
1040 metadata.lsp_enabled = true;
1041 metadata.lsp_disabled_reason = None;
1042 metadata.lsp_opened_with.clear();
1044
1045 tracing::info!(
1046 "Plugin dev LSP enabled for buffer {} via {:?}",
1047 buffer_id_num,
1048 plugin_file
1049 );
1050 }
1051 }
1052
1053 if let Some(state) = self.buffers.get_mut(&buffer_id) {
1055 let first_line = state.buffer.first_line_lossy();
1056 let detected =
1057 crate::primitives::detected_language::DetectedLanguage::from_path(
1058 &plugin_file,
1059 first_line.as_deref(),
1060 &self.grammar_registry,
1061 &self.config.languages,
1062 );
1063 state.apply_language(detected);
1064 }
1065
1066 if let Some(lsp) = &mut self.lsp {
1068 lsp.allow_language("typescript");
1069 }
1070
1071 let workspace_dir = workspace.dir().to_path_buf();
1073 self.plugin_dev_workspaces.insert(buffer_id, workspace);
1074
1075 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
1077
1078 if let Some(lsp) = &self.lsp {
1080 if let Some(handle) = lsp.get_handle("typescript") {
1081 if let Some(uri) = super::types::file_path_to_lsp_uri(&workspace_dir) {
1082 let name = workspace_dir
1083 .file_name()
1084 .unwrap_or_default()
1085 .to_string_lossy()
1086 .into_owned();
1087 if let Err(e) = handle.add_workspace_folder(uri, name) {
1088 tracing::warn!("Failed to add plugin workspace folder: {}", e);
1089 } else {
1090 tracing::info!(
1091 "Added plugin workspace folder: {:?}",
1092 workspace_dir
1093 );
1094 }
1095 }
1096 }
1097 }
1098 }
1099 Err(e) => {
1100 tracing::warn!("Failed to create plugin dev workspace: {}", e);
1101 }
1102 }
1103 }
1104}