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
21 .windows
22 .get(&self.active_window)
23 .map(|w| &w.buffers)
24 .expect("active window present")
25 .get(&buffer_id)
26 else {
27 return;
28 };
29 let language = state.language.clone();
30 let file_path = self
31 .active_window()
32 .buffer_metadata
33 .get(&buffer_id)
34 .and_then(|meta| meta.file_path().cloned());
35
36 let configs: Vec<_> = self
38 .lsp()
39 .as_ref()
40 .and_then(|lsp| lsp.get_configs(&language))
41 .map(|c| c.to_vec())
42 .unwrap_or_default();
43
44 if configs.is_empty() {
45 self.set_status_message(t!("lsp.no_server_configured").to_string());
46 return;
47 }
48
49 if configs.len() == 1 {
51 let __active_id = self.active_window;
52 let Some(lsp) = self
53 .windows
54 .get_mut(&__active_id)
55 .and_then(|w| w.lsp.as_mut())
56 else {
57 self.set_status_message(t!("lsp.no_manager").to_string());
58 return;
59 };
60
61 let (success, message) = lsp.manual_restart(&language, file_path.as_deref());
62 self.active_window_mut().status_message = Some(message);
63
64 if success {
65 self.reopen_buffers_for_language(&language);
66 }
67 return;
68 }
69
70 let mut suggestions: Vec<Suggestion> = Vec::new();
72
73 let enabled_names: Vec<_> = configs
75 .iter()
76 .filter(|c| c.enabled && !c.command.is_empty())
77 .map(|c| c.display_name())
78 .collect();
79 let all_description = if enabled_names.is_empty() {
80 Some("No enabled servers".to_string())
81 } else {
82 Some(enabled_names.join(", "))
83 };
84 suggestions.push(Suggestion {
85 text: format!("{} (all enabled)", language),
86 description: all_description,
87 value: Some(language.clone()),
88 disabled: enabled_names.is_empty(),
89 keybinding: None,
90 source: None,
91 });
92
93 for config in &configs {
95 if config.command.is_empty() {
96 continue;
97 }
98 let name = config.display_name();
99 let status = if config.enabled { "" } else { " [disabled]" };
100 suggestions.push(Suggestion {
101 text: format!("{}/{}{}", language, name, status),
102 description: Some(format!("Command: {}", config.command)),
103 value: Some(format!("{}/{}", language, name)),
104 disabled: false,
105 keybinding: None,
106 source: None,
107 });
108 }
109
110 self.active_window_mut().prompt = Some(Prompt::with_suggestions(
112 "Restart LSP server: ".to_string(),
113 PromptType::RestartLspServer,
114 suggestions.clone(),
115 ));
116
117 if let Some(prompt) = self.active_window_mut().prompt.as_mut() {
119 prompt.selected_suggestion = Some(0);
120 }
121 }
122
123 pub(crate) fn reopen_buffers_for_language(&mut self, language: &str) {
129 let buffers_for_language: Vec<_> = self
132 .buffers()
133 .iter()
134 .filter_map(|(buf_id, state)| {
135 if state.language == language {
136 self.active_window()
137 .buffer_metadata
138 .get(buf_id)
139 .and_then(|meta| meta.file_path().map(|p| (*buf_id, p.clone())))
140 } else {
141 None
142 }
143 })
144 .collect();
145
146 let enable_inlay_hints = self.config.editor.enable_inlay_hints;
147
148 for (buffer_id, buf_path) in buffers_for_language {
149 let Some(state) = self
150 .windows
151 .get(&self.active_window)
152 .map(|w| &w.buffers)
153 .expect("active window present")
154 .get(&buffer_id)
155 else {
156 continue;
157 };
158
159 let Some(content) = state.buffer.to_string() else {
160 continue; };
162
163 let Some(uri) = super::types::file_path_to_lsp_uri_with_translation(
164 &buf_path,
165 self.authority.path_translation.as_ref(),
166 ) else {
167 continue;
168 };
169
170 let lang_id = state.language.clone();
171 let line_count = state.buffer.line_count().unwrap_or(1000);
172 let buffer_version = state.buffer.version();
173
174 let __active_id = self.active_window;
175
176 if let Some(__win) = self.windows.get_mut(&__active_id) {
177 let Some(lsp) = __win.lsp.as_mut() else {
178 continue;
179 };
180 use crate::services::lsp::manager::LspSpawnResult;
182 if lsp.try_spawn(&lang_id, Some(&buf_path)) != LspSpawnResult::Spawned {
183 continue;
184 }
185
186 let opened_with = __win
189 .buffer_metadata
190 .get(&buffer_id)
191 .map(|m| m.lsp_opened_with.clone())
192 .unwrap_or_default();
193
194 let handles_needing_open: Vec<(String, u64)> = lsp
195 .get_handles(&lang_id)
196 .into_iter()
197 .filter(|sh| !opened_with.contains(&sh.handle.id()))
198 .map(|sh| (sh.name.clone(), sh.handle.id()))
199 .collect();
200
201 for (name, handle_id) in handles_needing_open {
203 let sh = lsp
204 .get_handles_mut(&lang_id)
205 .into_iter()
206 .find(|s| s.handle.id() == handle_id);
207
208 if let Some(sh) = sh {
209 if let Err(e) =
210 sh.handle
211 .did_open(uri.clone(), content.clone(), lang_id.clone())
212 {
213 tracing::warn!("LSP did_open to '{}' failed: {}", name, e);
214 } else if let Some(metadata) = __win.buffer_metadata.get_mut(&buffer_id) {
215 metadata.lsp_opened_with.insert(handle_id);
216 }
217 }
218 }
219 }
220
221 if enable_inlay_hints {
227 let __active_id = self.active_window;
228 if let Some(__win) = self.windows.get_mut(&__active_id) {
229 let __next_id = &mut __win.next_lsp_request_id;
230 let __pending = &mut __win.pending_inlay_hints_requests;
231 if let Some(lsp) = __win.lsp.as_mut() {
232 if let Some(sh) = lsp
233 .handle_for_feature_mut(&lang_id, crate::types::LspFeature::InlayHints)
234 {
235 let request_id = *__next_id;
236 *__next_id += 1;
237 let last_line = line_count.saturating_sub(1) as u32;
238 if let Err(e) = sh.handle.inlay_hints(
239 request_id,
240 uri.clone(),
241 0,
242 0,
243 last_line,
244 10000,
245 ) {
246 tracing::debug!(
247 "Failed to request inlay hints for {}: {}",
248 uri.as_str(),
249 e
250 );
251 } else {
252 __pending.insert(
253 request_id,
254 super::InlayHintsRequest {
255 buffer_id,
256 version: buffer_version,
257 },
258 );
259 }
260 }
261 }
262 }
263 }
264 }
265 }
266
267 pub fn handle_lsp_stop(&mut self) {
272 let running_languages: Vec<String> = self
273 .lsp()
274 .as_ref()
275 .map(|lsp| lsp.running_servers())
276 .unwrap_or_default();
277
278 if running_languages.is_empty() {
279 self.set_status_message(t!("lsp.no_servers_running").to_string());
280 return;
281 }
282
283 let mut suggestions: Vec<Suggestion> = Vec::new();
285 for lang in &running_languages {
286 let server_names: Vec<String> = self
287 .lsp()
288 .as_ref()
289 .map(|lsp| lsp.server_names_for_language(lang))
290 .unwrap_or_default();
291
292 if server_names.len() > 1 {
293 for name in &server_names {
295 let description = Some(format!("Server: {}", name));
296 suggestions.push(Suggestion {
297 text: format!("{}/{}", lang, name),
298 description,
299 value: Some(format!("{}/{}", lang, name)),
302 disabled: false,
303 keybinding: None,
304 source: None,
305 });
306 }
307 } else {
308 let description = self
310 .lsp()
311 .as_ref()
312 .and_then(|lsp| lsp.get_config(lang))
313 .filter(|c| !c.command.is_empty())
314 .map(|c| format!("Command: {}", c.command));
315
316 suggestions.push(Suggestion {
317 text: lang.clone(),
318 description,
319 value: Some(lang.clone()),
320 disabled: false,
321 keybinding: None,
322 source: None,
323 });
324 }
325 }
326
327 self.active_window_mut().prompt = Some(Prompt::with_suggestions(
329 "Stop LSP server: ".to_string(),
330 PromptType::StopLspServer,
331 suggestions.clone(),
332 ));
333
334 if let Some(prompt) = self.active_window_mut().prompt.as_mut() {
336 if suggestions.len() == 1 {
337 prompt.input = suggestions[0].text.clone();
339 prompt.cursor_pos = prompt.input.len();
340 prompt.selected_suggestion = Some(0);
341 } else if !prompt.suggestions.is_empty() {
342 prompt.selected_suggestion = Some(0);
344 }
345 }
346 }
347
348 pub fn handle_lsp_toggle_for_buffer(&mut self) {
353 let buffer_id = self.active_buffer();
354
355 let language = {
357 let Some(state) = self
358 .windows
359 .get(&self.active_window)
360 .map(|w| &w.buffers)
361 .expect("active window present")
362 .get(&buffer_id)
363 else {
364 return;
365 };
366 state.language.clone()
367 };
368
369 let lsp_configured = self
371 .lsp()
372 .as_ref()
373 .and_then(|lsp| lsp.get_config(&language))
374 .is_some();
375
376 if !lsp_configured {
377 self.set_status_message(t!("lsp.no_server_configured").to_string());
378 return;
379 }
380
381 let (was_enabled, file_path) = {
383 let Some(metadata) = self.active_window().buffer_metadata.get(&buffer_id) else {
384 return;
385 };
386 (metadata.lsp_enabled, metadata.file_path().cloned())
387 };
388
389 if was_enabled {
390 self.disable_lsp_for_buffer(buffer_id);
391 } else {
392 self.enable_lsp_for_buffer(buffer_id, &language, file_path);
393 }
394 }
395
396 pub fn handle_lsp_status_action(&mut self, action_key: &str) {
411 if let Some(rest) = action_key.strip_prefix("plugin:") {
420 let (plugin_id, item_id) = rest.split_once('|').unwrap_or((rest, ""));
421 self.plugin_manager.read().unwrap().run_hook(
422 "action_popup_result",
423 crate::services::plugins::hooks::HookArgs::ActionPopupResult {
424 popup_id: "lsp_status".to_string(),
425 action_id: format!("{}|{}", plugin_id, item_id),
426 },
427 );
428 return;
429 }
430 if action_key == "cancel_popup" {
431 return;
435 }
436 if let Some(target) = action_key.strip_prefix("autostart:") {
437 if let Some((language, server_name)) = target.split_once('/') {
443 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(language) {
444 for c in lsp_configs.as_mut_slice() {
445 if c.display_name() == server_name {
446 c.auto_start = true;
447 }
448 }
449 if let Err(e) = self.save_config() {
450 tracing::warn!(
451 "Failed to save config after enabling LSP auto-start: {}",
452 e
453 );
454 } else {
455 let config_path = self.dir_context.config_path();
456 self.emit_event(
457 "config_changed",
458 serde_json::json!({
459 "path": config_path.to_string_lossy(),
460 }),
461 );
462 }
463 }
464
465 let file_path = self
468 .active_window()
469 .buffer_metadata
470 .get(&self.active_buffer())
471 .and_then(|meta| meta.file_path().cloned());
472 let __active_id = self.active_window;
473 if let Some(lsp) = self
474 .windows
475 .get_mut(&__active_id)
476 .and_then(|w| w.lsp.as_mut())
477 {
478 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
479 self.active_window_mut().status_message = Some(message);
480 }
481 self.reopen_buffers_for_language(language);
482 }
483 } else if let Some(language) = action_key.strip_prefix("start:") {
484 let file_path = self
486 .active_window()
487 .buffer_metadata
488 .get(&self.active_buffer())
489 .and_then(|meta| meta.file_path().cloned());
490
491 let __active_id = self.active_window;
492
493 if let Some(lsp) = self
494 .windows
495 .get_mut(&__active_id)
496 .and_then(|w| w.lsp.as_mut())
497 {
498 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
499 self.active_window_mut().status_message = Some(message);
500 } else {
501 self.active_window_mut().status_message =
502 Some("No LSP manager available".to_string());
503 }
504 self.reopen_buffers_for_language(language);
505 } else if let Some(target) = action_key.strip_prefix("restart:") {
506 if let Some((language, server_name)) = target.split_once('/') {
508 let file_path = self
509 .active_window()
510 .buffer_metadata
511 .get(&self.active_buffer())
512 .and_then(|meta| meta.file_path().cloned());
513
514 let __active_id = self.active_window;
515
516 if let Some(lsp) = self
517 .windows
518 .get_mut(&__active_id)
519 .and_then(|w| w.lsp.as_mut())
520 {
521 lsp.shutdown_server_by_name(language, server_name);
523 }
524 self.active_window_mut()
526 .lsp_server_statuses
527 .remove(&(language.to_string(), server_name.to_string()));
528 let __active_id = self.active_window;
529 if let Some(lsp) = self
530 .windows
531 .get_mut(&__active_id)
532 .and_then(|w| w.lsp.as_mut())
533 {
534 let _ = lsp.manual_restart(language, file_path.as_deref());
535 }
536 self.reopen_buffers_for_language(language);
537 self.active_window_mut().status_message = Some(format!(
538 "Restarting LSP server: {}/{}",
539 language, server_name
540 ));
541 }
542 } else if let Some(target) = action_key.strip_prefix("stop:") {
543 if let Some((language, server_name)) = target.split_once('/') {
544 self.send_did_close_to_server(language, server_name);
551 let stopped = self.stop_lsp_server_and_cleanup(language, Some(server_name));
552 if stopped {
553 self.active_window_mut().status_message =
554 Some(format!("Stopped LSP server: {}/{}", language, server_name));
555 } else {
556 self.active_window_mut().status_message = Some(format!(
557 "LSP server not running: {}/{}",
558 language, server_name
559 ));
560 }
561 }
562 } else if let Some(language) = action_key.strip_prefix("log:") {
563 let log_path = crate::services::log_dirs::lsp_log_path(language);
564 if log_path.exists() {
565 match self.active_window_mut().open_local_file(&log_path) {
566 Ok(buffer_id) => {
567 self.active_window_mut()
568 .mark_buffer_read_only(buffer_id, true);
569 }
570 Err(e) => {
571 self.active_window_mut().status_message =
572 Some(format!("Failed to open LSP log: {}", e));
573 }
574 }
575 } else {
576 self.active_window_mut().status_message =
577 Some(format!("No log file found for {}", language));
578 }
579 } else if let Some(language) = action_key.strip_prefix("dismiss:") {
580 let lang = language.to_string();
591 self.active_window_mut().dismiss_lsp_language(&lang);
592 let mut changed = false;
593 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
594 for c in lsp_configs.as_mut_slice() {
595 if c.enabled {
596 c.enabled = false;
597 changed = true;
598 }
599 }
600 }
601 if changed {
602 if let Err(e) = self.save_config() {
603 tracing::warn!("Failed to save config after disabling LSP: {}", e);
604 } else {
605 let config_path = self.dir_context.config_path();
606 self.emit_event(
607 "config_changed",
608 serde_json::json!({
609 "path": config_path.to_string_lossy(),
610 }),
611 );
612 }
613 }
614
615 let running_server_names: Vec<String> = self
625 .active_window()
626 .lsp_server_statuses
627 .iter()
628 .filter_map(|((l, name), status)| {
629 if l == &lang
630 && !matches!(
631 status,
632 crate::services::async_bridge::LspServerStatus::Shutdown,
633 )
634 {
635 Some(name.clone())
636 } else {
637 None
638 }
639 })
640 .collect();
641 for name in &running_server_names {
642 self.send_did_close_to_server(&lang, name);
643 self.stop_lsp_server_and_cleanup(&lang, Some(name));
644 }
645
646 self.active_window_mut().status_message = Some(format!("LSP disabled for {}.", lang));
647 } else if let Some(language) = action_key.strip_prefix("enable:") {
648 let lang = language.to_string();
654 self.active_window_mut().undismiss_lsp_language(&lang);
655 let mut changed = false;
656 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
657 for c in lsp_configs.as_mut_slice() {
658 if !c.enabled {
659 c.enabled = true;
660 changed = true;
661 }
662 }
663 }
664 if changed {
665 if let Err(e) = self.save_config() {
666 tracing::warn!("Failed to save config after enabling LSP: {}", e);
667 } else {
668 let config_path = self.dir_context.config_path();
669 self.emit_event(
670 "config_changed",
671 serde_json::json!({
672 "path": config_path.to_string_lossy(),
673 }),
674 );
675 }
676 }
677 self.active_window_mut().status_message = Some(format!("LSP enabled for {}.", lang));
678 }
679 }
680
681 }
685
686impl crate::app::window::Window {
687 pub fn toggle_fold_at_cursor(&mut self) {
689 let buffer_id = self.active_buffer();
690 let pos = self.active_cursors().primary().position;
691 self.toggle_fold_at_byte(buffer_id, pos);
692 }
693
694 pub fn toggle_fold_at_line(&mut self, buffer_id: crate::model::event::BufferId, line: usize) {
700 let byte_pos = {
701 let Some(state) = self.buffers.get(&buffer_id) else {
702 return;
703 };
704 state.buffer.line_start_offset(line).unwrap_or_else(|| {
705 use crate::view::folding::indent_folding;
706 let approx = line * state.buffer.estimated_line_length();
707 indent_folding::find_line_start_byte(&state.buffer, approx)
708 })
709 };
710 self.toggle_fold_at_byte(buffer_id, byte_pos);
711 }
712
713 pub fn toggle_fold_at_byte(
715 &mut self,
716 buffer_id: crate::model::event::BufferId,
717 byte_pos: usize,
718 ) {
719 let Some(split_id) = self.buffers.split_manager().map(|m| m.active_split()) else {
720 return;
721 };
722
723 self.buffers
724 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
725 let buf_state = view_state.ensure_buffer_state(buffer_id);
726
727 let header_byte = {
729 use crate::view::folding::indent_folding;
730 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
731 };
732 if buf_state.folds.remove_by_header_byte(
733 &state.buffer,
734 &mut state.marker_list,
735 header_byte,
736 ) {
737 return;
738 }
739
740 if buf_state
742 .folds
743 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
744 {
745 return;
746 }
747
748 if !state.folding_ranges.is_empty() {
750 let resolved = state
752 .folding_ranges
753 .resolved(&state.buffer, &state.marker_list);
754 let line = state.buffer.get_line_number(byte_pos);
755 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
756 let mut exact_span = usize::MAX;
757 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
758 let mut containing_span = usize::MAX;
759
760 for range in &resolved {
761 let start_line = range.start_line as usize;
762 let range_end = range.end_line as usize;
763 if range_end <= start_line {
764 continue;
765 }
766 let span = range_end.saturating_sub(start_line);
767
768 if start_line == line && span < exact_span {
769 exact_span = span;
770 exact_range = Some(range);
771 }
772 if start_line <= line && line <= range_end && span < containing_span {
773 containing_span = span;
774 containing_range = Some(range);
775 }
776 }
777
778 let chosen = exact_range.or(containing_range);
779 let Some(range) = chosen else {
780 return;
781 };
782 let placeholder = range
783 .collapsed_text
784 .as_ref()
785 .filter(|text| !text.trim().is_empty())
786 .cloned();
787 let header_line = range.start_line as usize;
788 let end_line = range.end_line as usize;
789 let first_hidden = header_line.saturating_add(1);
790 if first_hidden > end_line {
791 return;
792 }
793 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
794 return;
795 };
796 let eb = state
797 .buffer
798 .line_start_offset(end_line.saturating_add(1))
799 .unwrap_or_else(|| state.buffer.len());
800 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
801 create_fold(state, buf_state, sb, eb, hb, placeholder);
802 } else {
803 use crate::view::folding::indent_folding;
805 let tab_size = state.buffer_settings.tab_size;
806 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
807 let est_ll = state.buffer.estimated_line_length();
808 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
809
810 let upward_bytes = max_upward * est_ll;
811 let load_start = byte_pos.saturating_sub(upward_bytes);
812 let load_end = byte_pos
813 .saturating_add(max_scan_bytes)
814 .min(state.buffer.len());
815 drop(
816 state
817 .buffer
818 .get_text_range_mut(load_start, load_end - load_start),
819 );
820
821 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
822 &state.buffer,
823 byte_pos,
824 tab_size,
825 max_scan_bytes,
826 max_upward,
827 ) {
828 create_fold(state, buf_state, sb, eb, hb, None);
829 }
830 }
831 });
832 }
833}
834
835fn create_fold(
841 state: &mut crate::state::EditorState,
842 buf_state: &mut crate::view::split::BufferViewState,
843 start_byte: usize,
844 end_byte: usize,
845 header_byte: usize,
846 placeholder: Option<String>,
847) {
848 if end_byte <= start_byte {
849 return;
850 }
851
852 buf_state.cursors.map(|cursor| {
854 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
855 let anchor_in_hidden = cursor
856 .anchor
857 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
858 if in_hidden_range || anchor_in_hidden {
859 cursor.position = header_byte;
860 cursor.anchor = None;
861 cursor.sticky_column = 0;
862 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
863 cursor.block_anchor = None;
864 cursor.deselect_on_move = true;
865 }
866 });
867
868 buf_state
869 .folds
870 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
871
872 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
874 buf_state.viewport.top_byte = header_byte;
875 buf_state.viewport.top_view_line_offset = 0;
876 }
877}
878
879impl Editor {
880 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
885 let uris: Vec<_> = self
886 .buffers()
887 .iter()
888 .filter(|(_, s)| s.language == language)
889 .filter_map(|(id, _)| {
890 self.active_window()
891 .buffer_metadata
892 .get(id)
893 .and_then(|m| m.file_uri())
894 .cloned()
895 })
896 .collect();
897
898 let __active_id = self.active_window;
899
900 if let Some(lsp) = self
901 .windows
902 .get_mut(&__active_id)
903 .and_then(|w| w.lsp.as_mut())
904 {
905 for sh in lsp.get_handles_mut(language) {
906 if sh.name == server_name {
907 for uri in &uris {
908 tracing::info!(
909 "Sending didClose for {} to '{}' (language: {})",
910 uri.as_str(),
911 sh.name,
912 language
913 );
914 if let Err(e) = sh.handle.did_close(uri.as_uri().clone()) {
915 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
916 }
917 }
918 break;
919 }
920 }
921 }
922 }
923
924 pub(crate) fn stop_lsp_server_and_cleanup(
950 &mut self,
951 language: &str,
952 server_name: Option<&str>,
953 ) -> bool {
954 let stopping_names: Vec<String> = if let Some(name) = server_name {
958 vec![name.to_string()]
959 } else {
960 self.lsp()
961 .map(|lsp| lsp.server_names_for_language(language))
962 .unwrap_or_default()
963 };
964
965 let __active_id = self.active_window;
966
967 let stopped = if let Some(lsp) = self
968 .windows
969 .get_mut(&__active_id)
970 .and_then(|w| w.lsp.as_mut())
971 {
972 if let Some(name) = server_name {
973 lsp.shutdown_server_by_name(language, name)
974 } else {
975 lsp.shutdown_server(language)
976 }
977 } else {
978 false
979 };
980
981 if !stopped {
982 return false;
983 }
984
985 for name in &stopping_names {
986 self.active_window_mut()
987 .lsp_server_statuses
988 .remove(&(language.to_string(), name.clone()));
989 self.clear_diagnostics_for_server(name);
992 }
993
994 let any_handle_left = self
1009 .lsp()
1010 .as_ref()
1011 .is_some_and(|lsp| lsp.has_handles(language));
1012 if !any_handle_left {
1013 self.active_window_mut()
1014 .lsp_progress
1015 .retain(|_, info| info.language != language);
1016 }
1017
1018 true
1019 }
1020
1021 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
1023 if let Some(uri) = self
1029 .active_window()
1030 .buffer_metadata
1031 .get(&buffer_id)
1032 .and_then(|m| m.file_uri())
1033 .cloned()
1034 {
1035 let language = self
1036 .buffers()
1037 .get(&buffer_id)
1038 .map(|s| s.language.clone())
1039 .unwrap_or_default();
1040 let __active_id = self.active_window;
1041 if let Some(lsp) = self
1042 .windows
1043 .get_mut(&__active_id)
1044 .and_then(|w| w.lsp.as_mut())
1045 {
1046 if !lsp.has_handles(&language) {
1048 tracing::warn!(
1049 "disable_lsp_for_buffer: no handle for language '{}'",
1050 language
1051 );
1052 } else {
1053 for sh in lsp.get_handles_mut(&language) {
1054 tracing::info!(
1055 "Sending didClose for {} to '{}' (language: {})",
1056 uri.as_str(),
1057 sh.name,
1058 language
1059 );
1060 if let Err(e) = sh.handle.did_close(uri.as_uri().clone()) {
1061 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
1062 }
1063 }
1064 }
1065 } else {
1066 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
1067 }
1068 } else {
1069 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
1070 }
1071
1072 if let Some(metadata) = self.active_window_mut().buffer_metadata.get_mut(&buffer_id) {
1074 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
1075 metadata.lsp_opened_with.clear();
1077 }
1078 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
1079
1080 let uri = self
1082 .active_window()
1083 .buffer_metadata
1084 .get(&buffer_id)
1085 .and_then(|m| m.file_uri())
1086 .map(|u| u.as_str().to_string());
1087
1088 if let Some(uri_str) = uri {
1089 self.stored_diagnostics_mut().remove(&uri_str);
1090 self.active_window_mut()
1091 .stored_push_diagnostics
1092 .remove(&uri_str);
1093 self.active_window_mut()
1094 .stored_pull_diagnostics
1095 .remove(&uri_str);
1096 self.active_window_mut()
1097 .diagnostic_result_ids
1098 .remove(&uri_str);
1099 self.stored_folding_ranges_mut().remove(&uri_str);
1100 }
1101
1102 if let Some((scheduled_buf, _)) = &self.active_window().scheduled_diagnostic_pull {
1104 if *scheduled_buf == buffer_id {
1105 self.active_window_mut().scheduled_diagnostic_pull = None;
1106 }
1107 }
1108
1109 if let Some((scheduled_buf, _)) = &self.active_window().scheduled_inlay_hints_request {
1111 if *scheduled_buf == buffer_id {
1112 self.active_window_mut().scheduled_inlay_hints_request = None;
1113 }
1114 }
1115
1116 self.active_window_mut()
1117 .folding_ranges_in_flight
1118 .remove(&buffer_id);
1119 self.active_window_mut()
1120 .folding_ranges_debounce
1121 .remove(&buffer_id);
1122 self.active_window_mut()
1123 .pending_folding_range_requests
1124 .retain(|_, req| req.buffer_id != buffer_id);
1125 self.active_window_mut()
1128 .pending_inlay_hints_requests
1129 .retain(|_, req| req.buffer_id != buffer_id);
1130
1131 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
1133 self.active_window_mut()
1134 .clear_lsp_overlays_for_buffer(buffer_id, &diagnostic_ns);
1135 }
1136
1137 fn enable_lsp_for_buffer(
1139 &mut self,
1140 buffer_id: crate::model::event::BufferId,
1141 language: &str,
1142 file_path: Option<std::path::PathBuf>,
1143 ) {
1144 if let Some(metadata) = self.active_window_mut().buffer_metadata.get_mut(&buffer_id) {
1146 metadata.lsp_enabled = true;
1147 metadata.lsp_disabled_reason = None;
1148 }
1149 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
1150
1151 if let Some(_path) = file_path {
1153 self.send_lsp_did_open_for_buffer(buffer_id, language);
1154 }
1155 }
1156
1157 fn send_lsp_did_open_for_buffer(
1159 &mut self,
1160 buffer_id: crate::model::event::BufferId,
1161 language: &str,
1162 ) {
1163 let (uri, text) = {
1165 let metadata = self.active_window().buffer_metadata.get(&buffer_id);
1166 let uri = metadata.and_then(|m| m.file_uri()).cloned();
1167 let text = self
1168 .buffers()
1169 .get(&buffer_id)
1170 .and_then(|state| state.buffer.to_string());
1171 (uri, text)
1172 };
1173
1174 let Some(uri) = uri else { return };
1175 let Some(text) = text else { return };
1176
1177 use crate::services::lsp::manager::LspSpawnResult;
1179 let file_path = self
1180 .active_window()
1181 .buffer_metadata
1182 .get(&buffer_id)
1183 .and_then(|m| m.file_path())
1184 .cloned();
1185 let inlay_buffer_info: Option<(u32, u32, u64)> = self
1189 .windows
1190 .get(&self.active_window)
1191 .and_then(|w| w.buffers.get(&buffer_id))
1192 .map(|state| {
1193 let line_count = state.buffer.line_count().unwrap_or(1000);
1194 (
1195 line_count.saturating_sub(1) as u32,
1196 10000u32,
1197 state.buffer.version(),
1198 )
1199 });
1200 let __active_id = self.active_window;
1201 let enable_inlay_hints = self.config.editor.enable_inlay_hints;
1202
1203 let Some(__win) = self.windows.get_mut(&__active_id) else {
1204 return;
1205 };
1206 let diagnostic_result_ids = &__win.diagnostic_result_ids;
1207 let __next_id = &mut __win.next_lsp_request_id;
1208 let buffer_metadata = &mut __win.buffer_metadata;
1209 let Some(lsp) = __win.lsp.as_mut() else {
1210 return;
1211 };
1212
1213 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
1214 return;
1215 }
1216
1217 let Some(handle) = lsp.get_handle_mut(language) else {
1218 return;
1219 };
1220
1221 let handle_id = handle.id();
1222 if let Err(e) = handle.did_open(uri.as_uri().clone(), text, language.to_string()) {
1223 tracing::warn!("Failed to send didOpen to LSP: {}", e);
1224 return;
1225 }
1226
1227 if let Some(metadata) = buffer_metadata.get_mut(&buffer_id) {
1229 metadata.lsp_opened_with.insert(handle_id);
1230 }
1231
1232 let request_id = {
1234 let id = *__next_id;
1235 *__next_id += 1;
1236 id
1237 };
1238 let previous_result_id = diagnostic_result_ids.get(uri.as_str()).cloned();
1239 if let Err(e) =
1240 handle.document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
1241 {
1242 tracing::warn!("LSP document_diagnostic request failed: {}", e);
1243 }
1244
1245 if enable_inlay_hints {
1247 let (last_line, last_char, buffer_version) =
1248 inlay_buffer_info.unwrap_or((999, 10000, 0));
1249
1250 let request_id = {
1251 let id = *__next_id;
1252 *__next_id += 1;
1253 id
1254 };
1255 if let Err(e) =
1256 handle.inlay_hints(request_id, uri.as_uri().clone(), 0, 0, last_line, last_char)
1257 {
1258 tracing::warn!("LSP inlay_hints request failed: {}", e);
1259 } else {
1260 __win.pending_inlay_hints_requests.insert(
1261 request_id,
1262 super::InlayHintsRequest {
1263 buffer_id,
1264 version: buffer_version,
1265 },
1266 );
1267 }
1268 }
1269
1270 let _ = __next_id;
1272 let _ = lsp;
1273 let _ = handle;
1274 self.active_window_mut()
1275 .schedule_folding_ranges_refresh(buffer_id);
1276 }
1277
1278 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
1284 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
1285
1286 #[cfg(feature = "embed-plugins")]
1288 let fresh_dts_path = {
1289 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
1290 else {
1291 tracing::warn!(
1292 "Cannot set up plugin dev LSP: embedded plugins directory not available"
1293 );
1294 return;
1295 };
1296 let path = embedded_dir.join("lib").join("fresh.d.ts");
1297 if !path.exists() {
1298 tracing::warn!(
1299 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1300 path
1301 );
1302 return;
1303 }
1304 path
1305 };
1306
1307 #[cfg(not(feature = "embed-plugins"))]
1308 let fresh_dts_path = {
1309 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1311 .join("plugins")
1312 .join("lib")
1313 .join("fresh.d.ts");
1314 if !path.exists() {
1315 tracing::warn!(
1316 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1317 path
1318 );
1319 return;
1320 }
1321 path
1322 };
1323
1324 let buffer_id_num: usize = buffer_id.0;
1326 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
1327 Ok(workspace) => {
1328 let plugin_file = workspace.plugin_file.clone();
1329
1330 let plugin_file_uri = super::types::LspUri::from_host_path(
1332 &plugin_file,
1333 self.authority.path_translation.as_ref(),
1334 );
1335 if let Some(uri) = plugin_file_uri {
1336 if let Some(metadata) =
1337 self.active_window_mut().buffer_metadata.get_mut(&buffer_id)
1338 {
1339 metadata.kind = super::types::BufferKind::File {
1340 path: plugin_file.clone(),
1341 uri: Some(uri),
1342 };
1343 metadata.lsp_enabled = true;
1344 metadata.lsp_disabled_reason = None;
1345 metadata.lsp_opened_with.clear();
1347
1348 tracing::info!(
1349 "Plugin dev LSP enabled for buffer {} via {:?}",
1350 buffer_id_num,
1351 plugin_file
1352 );
1353 }
1354 }
1355
1356 if let Some(state) = self
1358 .windows
1359 .get_mut(&self.active_window)
1360 .map(|w| &mut w.buffers)
1361 .expect("active window present")
1362 .get_mut(&buffer_id)
1363 {
1364 let first_line = state.buffer.first_line_lossy();
1365 let detected =
1366 crate::primitives::detected_language::DetectedLanguage::from_path(
1367 &plugin_file,
1368 first_line.as_deref(),
1369 &self.grammar_registry,
1370 &self.config.languages,
1371 );
1372 state.apply_language(detected);
1373 }
1374
1375 let __active_id = self.active_window;
1377 if let Some(lsp) = self
1378 .windows
1379 .get_mut(&__active_id)
1380 .and_then(|w| w.lsp.as_mut())
1381 {
1382 lsp.allow_language("typescript");
1383 }
1384
1385 let workspace_dir = workspace.dir().to_path_buf();
1387 self.active_window_mut()
1388 .plugin_dev_workspaces
1389 .insert(buffer_id, workspace);
1390
1391 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
1393
1394 if let Some(lsp) = self.lsp() {
1396 if let Some(handle) = lsp.get_handle("typescript") {
1397 if let Some(uri) = super::types::file_path_to_lsp_uri_with_translation(
1398 &workspace_dir,
1399 self.authority.path_translation.as_ref(),
1400 ) {
1401 let name = workspace_dir
1402 .file_name()
1403 .unwrap_or_default()
1404 .to_string_lossy()
1405 .into_owned();
1406 if let Err(e) = handle.add_workspace_folder(uri, name) {
1407 tracing::warn!("Failed to add plugin workspace folder: {}", e);
1408 } else {
1409 tracing::info!(
1410 "Added plugin workspace folder: {:?}",
1411 workspace_dir
1412 );
1413 }
1414 }
1415 }
1416 }
1417 }
1418 Err(e) => {
1419 tracing::warn!("Failed to create plugin dev workspace: {}", e);
1420 }
1421 }
1422 }
1423}