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.windows.get_mut(&__active_id).map(|w| &mut w.lsp) else {
53 self.set_status_message(t!("lsp.no_manager").to_string());
54 return;
55 };
56
57 let (success, message) = lsp.manual_restart(&language, file_path.as_deref());
58 self.active_window_mut().status_message = Some(message);
59
60 if success {
61 self.reopen_buffers_for_language(&language);
62 }
63 return;
64 }
65
66 let mut suggestions: Vec<Suggestion> = Vec::new();
68
69 let enabled_names: Vec<_> = configs
71 .iter()
72 .filter(|c| c.enabled && !c.command.is_empty())
73 .map(|c| c.display_name())
74 .collect();
75 let all_description = if enabled_names.is_empty() {
76 Some("No enabled servers".to_string())
77 } else {
78 Some(enabled_names.join(", "))
79 };
80 suggestions.push(Suggestion {
81 description_spans: None,
82 text: format!("{} (all enabled)", language),
83 description: all_description,
84 value: Some(language.clone()),
85 disabled: enabled_names.is_empty(),
86 keybinding: None,
87 source: None,
88 });
89
90 for config in &configs {
92 if config.command.is_empty() {
93 continue;
94 }
95 let name = config.display_name();
96 let status = if config.enabled { "" } else { " [disabled]" };
97 suggestions.push(Suggestion {
98 description_spans: None,
99 text: format!("{}/{}{}", language, name, status),
100 description: Some(format!("Command: {}", config.command)),
101 value: Some(format!("{}/{}", language, name)),
102 disabled: false,
103 keybinding: None,
104 source: None,
105 });
106 }
107
108 self.active_window_mut().prompt = Some(Prompt::with_suggestions(
110 "Restart LSP server: ".to_string(),
111 PromptType::RestartLspServer,
112 suggestions.clone(),
113 ));
114
115 if let Some(prompt) = self.active_window_mut().prompt.as_mut() {
117 prompt.selected_suggestion = Some(0);
118 }
119 }
120
121 pub(crate) fn reopen_buffers_for_language(&mut self, language: &str) {
127 let buffers_for_language: Vec<_> = self
130 .buffers()
131 .iter()
132 .filter_map(|(buf_id, state)| {
133 if state.language == language {
134 self.active_window()
135 .buffer_metadata
136 .get(buf_id)
137 .and_then(|meta| meta.file_path().map(|p| (*buf_id, p.clone())))
138 } else {
139 None
140 }
141 })
142 .collect();
143
144 let enable_inlay_hints = self.config.editor.enable_inlay_hints;
145
146 for (buffer_id, buf_path) in buffers_for_language {
147 let Some(state) = self
148 .windows
149 .get(&self.active_window)
150 .map(|w| &w.buffers)
151 .expect("active window present")
152 .get(&buffer_id)
153 else {
154 continue;
155 };
156
157 let Some(content) = state.buffer.to_string() else {
158 continue; };
160
161 let Some(uri) = super::types::file_path_to_lsp_uri_with_translation(
162 &buf_path,
163 self.authority().path_translation.as_ref(),
164 ) else {
165 continue;
166 };
167
168 let lang_id = state.language.clone();
169 let line_count = state.buffer.line_count().unwrap_or(1000);
170 let buffer_version = state.buffer.version();
171
172 let __active_id = self.active_window;
173
174 if let Some(__win) = self.windows.get_mut(&__active_id) {
175 let lsp = &mut __win.lsp;
176 use crate::services::lsp::manager::LspSpawnResult;
178 if lsp.try_spawn(&lang_id, Some(&buf_path)) != LspSpawnResult::Spawned {
179 continue;
180 }
181
182 let opened_with = __win
185 .buffer_metadata
186 .get(&buffer_id)
187 .map(|m| m.lsp_opened_with.clone())
188 .unwrap_or_default();
189
190 let handles_needing_open: Vec<(String, u64)> = lsp
191 .get_handles(&lang_id)
192 .into_iter()
193 .filter(|sh| !opened_with.contains(&sh.handle.id()))
194 .map(|sh| (sh.name.clone(), sh.handle.id()))
195 .collect();
196
197 for (name, handle_id) in handles_needing_open {
199 let sh = lsp
200 .get_handles_mut(&lang_id)
201 .into_iter()
202 .find(|s| s.handle.id() == handle_id);
203
204 if let Some(sh) = sh {
205 if let Err(e) =
206 sh.handle
207 .did_open(uri.clone(), content.clone(), lang_id.clone())
208 {
209 tracing::warn!("LSP did_open to '{}' failed: {}", name, e);
210 } else if let Some(metadata) = __win.buffer_metadata.get_mut(&buffer_id) {
211 metadata.lsp_opened_with.insert(handle_id);
212 }
213 }
214 }
215 }
216
217 if enable_inlay_hints {
223 let __active_id = self.active_window;
224 if let Some(__win) = self.windows.get_mut(&__active_id) {
225 let __next_id = &mut __win.next_lsp_request_id;
226 let __pending = &mut __win.pending_inlay_hints_requests;
227 {
228 let lsp = &mut __win.lsp;
229 if let Some(sh) = lsp
230 .handle_for_feature_mut(&lang_id, crate::types::LspFeature::InlayHints)
231 {
232 let request_id = *__next_id;
233 *__next_id += 1;
234 let last_line = line_count.saturating_sub(1) as u32;
235 if let Err(e) = sh.handle.inlay_hints(
236 request_id,
237 uri.clone(),
238 0,
239 0,
240 last_line,
241 10000,
242 ) {
243 tracing::debug!(
244 "Failed to request inlay hints for {}: {}",
245 uri.as_str(),
246 e
247 );
248 } else {
249 __pending.insert(
250 request_id,
251 super::InlayHintsRequest {
252 buffer_id,
253 version: buffer_version,
254 },
255 );
256 }
257 }
258 }
259 }
260 }
261 }
262 }
263
264 pub fn handle_lsp_stop(&mut self) {
269 let running_languages: Vec<String> = self
270 .lsp()
271 .as_ref()
272 .map(|lsp| lsp.running_servers())
273 .unwrap_or_default();
274
275 if running_languages.is_empty() {
276 self.set_status_message(t!("lsp.no_servers_running").to_string());
277 return;
278 }
279
280 let mut suggestions: Vec<Suggestion> = Vec::new();
282 for lang in &running_languages {
283 let server_names: Vec<String> = self
284 .lsp()
285 .as_ref()
286 .map(|lsp| lsp.server_names_for_language(lang))
287 .unwrap_or_default();
288
289 if server_names.len() > 1 {
290 for name in &server_names {
292 let description = Some(format!("Server: {}", name));
293 suggestions.push(Suggestion {
294 description_spans: None,
295 text: format!("{}/{}", lang, name),
296 description,
297 value: Some(format!("{}/{}", lang, name)),
300 disabled: false,
301 keybinding: None,
302 source: None,
303 });
304 }
305 } else {
306 let description = self
308 .lsp()
309 .as_ref()
310 .and_then(|lsp| lsp.get_config(lang))
311 .filter(|c| !c.command.is_empty())
312 .map(|c| format!("Command: {}", c.command));
313
314 suggestions.push(Suggestion {
315 description_spans: None,
316 text: lang.clone(),
317 description,
318 value: Some(lang.clone()),
319 disabled: false,
320 keybinding: None,
321 source: None,
322 });
323 }
324 }
325
326 self.active_window_mut().prompt = Some(Prompt::with_suggestions(
328 "Stop LSP server: ".to_string(),
329 PromptType::StopLspServer,
330 suggestions.clone(),
331 ));
332
333 if let Some(prompt) = self.active_window_mut().prompt.as_mut() {
335 if suggestions.len() == 1 {
336 prompt.input = suggestions[0].text.clone();
338 prompt.cursor_pos = prompt.input.len();
339 prompt.selected_suggestion = Some(0);
340 } else if !prompt.suggestions.is_empty() {
341 prompt.selected_suggestion = Some(0);
343 }
344 }
345 }
346
347 pub fn handle_lsp_toggle_for_buffer(&mut self) {
352 let buffer_id = self.active_buffer();
353
354 let language = {
356 let Some(state) = self
357 .windows
358 .get(&self.active_window)
359 .map(|w| &w.buffers)
360 .expect("active window present")
361 .get(&buffer_id)
362 else {
363 return;
364 };
365 state.language.clone()
366 };
367
368 let lsp_configured = self
370 .lsp()
371 .as_ref()
372 .and_then(|lsp| lsp.get_config(&language))
373 .is_some();
374
375 if !lsp_configured {
376 self.set_status_message(t!("lsp.no_server_configured").to_string());
377 return;
378 }
379
380 let (was_enabled, file_path) = {
382 let Some(metadata) = self.active_window().buffer_metadata.get(&buffer_id) else {
383 return;
384 };
385 (metadata.lsp_enabled, metadata.file_path().cloned())
386 };
387
388 if was_enabled {
389 self.disable_lsp_for_buffer(buffer_id);
390 } else {
391 self.enable_lsp_for_buffer(buffer_id, &language, file_path);
392 }
393 }
394
395 pub fn handle_lsp_status_action(&mut self, action_key: &str) {
410 if let Some(rest) = action_key.strip_prefix("plugin:") {
419 let (plugin_id, item_id) = rest.split_once('|').unwrap_or((rest, ""));
420 self.plugin_manager.read().unwrap().run_hook(
421 "action_popup_result",
422 crate::services::plugins::hooks::HookArgs::ActionPopupResult {
423 popup_id: "lsp_status".to_string(),
424 action_id: format!("{}|{}", plugin_id, item_id),
425 },
426 );
427 return;
428 }
429 if action_key == "cancel_popup" {
430 return;
434 }
435 if let Some(target) = action_key.strip_prefix("autostart:") {
436 if let Some((language, server_name)) = target.split_once('/') {
442 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(language) {
443 for c in lsp_configs.as_mut_slice() {
444 if c.display_name() == server_name {
445 c.auto_start = true;
446 }
447 }
448 if let Err(e) = self.save_config() {
449 tracing::warn!(
450 "Failed to save config after enabling LSP auto-start: {}",
451 e
452 );
453 } else {
454 let config_path = self.dir_context.config_path();
455 self.emit_event(
456 "config_changed",
457 serde_json::json!({
458 "path": config_path.to_string_lossy(),
459 }),
460 );
461 }
462 }
463
464 let file_path = self
467 .active_window()
468 .buffer_metadata
469 .get(&self.active_buffer())
470 .and_then(|meta| meta.file_path().cloned());
471 let __active_id = self.active_window;
472 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
473 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
474 self.active_window_mut().status_message = Some(message);
475 }
476 self.reopen_buffers_for_language(language);
477 }
478 } else if let Some(language) = action_key.strip_prefix("start:") {
479 let file_path = self
481 .active_window()
482 .buffer_metadata
483 .get(&self.active_buffer())
484 .and_then(|meta| meta.file_path().cloned());
485
486 let __active_id = self.active_window;
487
488 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
489 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
490 self.active_window_mut().status_message = Some(message);
491 } else {
492 self.active_window_mut().status_message =
493 Some("No LSP manager available".to_string());
494 }
495 self.reopen_buffers_for_language(language);
496 } else if let Some(target) = action_key.strip_prefix("restart:") {
497 if let Some((language, server_name)) = target.split_once('/') {
499 let file_path = self
500 .active_window()
501 .buffer_metadata
502 .get(&self.active_buffer())
503 .and_then(|meta| meta.file_path().cloned());
504
505 let __active_id = self.active_window;
506
507 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
508 lsp.shutdown_server_by_name(language, server_name);
510 }
511 self.active_window_mut()
513 .lsp_server_statuses
514 .remove(&(language.to_string(), server_name.to_string()));
515 let __active_id = self.active_window;
516 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
517 let _ = lsp.manual_restart(language, file_path.as_deref());
518 }
519 self.reopen_buffers_for_language(language);
520 self.active_window_mut().status_message = Some(format!(
521 "Restarting LSP server: {}/{}",
522 language, server_name
523 ));
524 }
525 } else if let Some(target) = action_key.strip_prefix("stop:") {
526 if let Some((language, server_name)) = target.split_once('/') {
527 self.send_did_close_to_server(language, server_name);
534 let stopped = self.stop_lsp_server_and_cleanup(language, Some(server_name));
535 if stopped {
536 self.active_window_mut().status_message =
537 Some(format!("Stopped LSP server: {}/{}", language, server_name));
538 } else {
539 self.active_window_mut().status_message = Some(format!(
540 "LSP server not running: {}/{}",
541 language, server_name
542 ));
543 }
544 }
545 } else if let Some(language) = action_key.strip_prefix("log:") {
546 let log_path = crate::services::log_dirs::lsp_log_path(language);
547 if log_path.exists() {
548 match self.active_window_mut().open_local_file(&log_path) {
549 Ok(buffer_id) => {
550 self.active_window_mut()
551 .mark_buffer_read_only(buffer_id, true);
552 }
553 Err(e) => {
554 self.active_window_mut().status_message =
555 Some(format!("Failed to open LSP log: {}", e));
556 }
557 }
558 } else {
559 self.active_window_mut().status_message =
560 Some(format!("No log file found for {}", language));
561 }
562 } else if let Some(language) = action_key.strip_prefix("dismiss:") {
563 let lang = language.to_string();
574 self.active_window_mut().dismiss_lsp_language(&lang);
575 let mut changed = false;
576 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
577 for c in lsp_configs.as_mut_slice() {
578 if c.enabled {
579 c.enabled = false;
580 changed = true;
581 }
582 }
583 }
584 if changed {
585 if let Err(e) = self.save_config() {
586 tracing::warn!("Failed to save config after disabling LSP: {}", e);
587 } else {
588 let config_path = self.dir_context.config_path();
589 self.emit_event(
590 "config_changed",
591 serde_json::json!({
592 "path": config_path.to_string_lossy(),
593 }),
594 );
595 }
596 }
597
598 let running_server_names: Vec<String> = self
608 .active_window()
609 .lsp_server_statuses
610 .iter()
611 .filter_map(|((l, name), status)| {
612 if l == &lang
613 && !matches!(
614 status,
615 crate::services::async_bridge::LspServerStatus::Shutdown,
616 )
617 {
618 Some(name.clone())
619 } else {
620 None
621 }
622 })
623 .collect();
624 for name in &running_server_names {
625 self.send_did_close_to_server(&lang, name);
626 self.stop_lsp_server_and_cleanup(&lang, Some(name));
627 }
628
629 self.active_window_mut().status_message = Some(format!("LSP disabled for {}.", lang));
630 } else if let Some(language) = action_key.strip_prefix("enable:") {
631 let lang = language.to_string();
637 self.active_window_mut().undismiss_lsp_language(&lang);
638 let mut changed = false;
639 if let Some(lsp_configs) = self.config_mut().lsp.get_mut(&lang) {
640 for c in lsp_configs.as_mut_slice() {
641 if !c.enabled {
642 c.enabled = true;
643 changed = true;
644 }
645 }
646 }
647 if changed {
648 if let Err(e) = self.save_config() {
649 tracing::warn!("Failed to save config after enabling LSP: {}", e);
650 } else {
651 let config_path = self.dir_context.config_path();
652 self.emit_event(
653 "config_changed",
654 serde_json::json!({
655 "path": config_path.to_string_lossy(),
656 }),
657 );
658 }
659 }
660 self.active_window_mut().status_message = Some(format!("LSP enabled for {}.", lang));
661 }
662 }
663
664 }
668
669impl crate::app::window::Window {
670 pub fn toggle_fold_at_cursor(&mut self) {
672 let buffer_id = self.active_buffer();
673 let pos = self.active_cursors().primary().position;
674 self.toggle_fold_at_byte(buffer_id, pos);
675 }
676
677 pub fn toggle_fold_at_line(&mut self, buffer_id: crate::model::event::BufferId, line: usize) {
683 let byte_pos = {
684 let Some(state) = self.buffers.get(&buffer_id) else {
685 return;
686 };
687 state.buffer.line_start_offset(line).unwrap_or_else(|| {
688 use crate::view::folding::indent_folding;
689 let approx = line * state.buffer.estimated_line_length();
690 indent_folding::find_line_start_byte(&state.buffer, approx)
691 })
692 };
693 self.toggle_fold_at_byte(buffer_id, byte_pos);
694 }
695
696 pub fn toggle_fold_at_byte(
698 &mut self,
699 buffer_id: crate::model::event::BufferId,
700 byte_pos: usize,
701 ) {
702 let Some(split_id) = self.buffers.split_manager().map(|m| m.active_split()) else {
703 return;
704 };
705
706 self.buffers
707 .with_buffer_and_split(buffer_id, split_id, |state, view_state| {
708 let buf_state = view_state.ensure_buffer_state(buffer_id);
709
710 let header_byte = {
712 use crate::view::folding::indent_folding;
713 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
714 };
715 if buf_state.folds.remove_by_header_byte(
716 &state.buffer,
717 &mut state.marker_list,
718 header_byte,
719 ) {
720 return;
721 }
722
723 if buf_state
725 .folds
726 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
727 {
728 return;
729 }
730
731 if !state.folding_ranges.is_empty() {
733 let resolved = state
735 .folding_ranges
736 .resolved(&state.buffer, &state.marker_list);
737 let line = state.buffer.get_line_number(byte_pos);
738 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
739 let mut exact_span = usize::MAX;
740 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
741 let mut containing_span = usize::MAX;
742
743 for range in &resolved {
744 let start_line = range.start_line as usize;
745 let range_end = range.end_line as usize;
746 if range_end <= start_line {
747 continue;
748 }
749 let span = range_end.saturating_sub(start_line);
750
751 if start_line == line && span < exact_span {
752 exact_span = span;
753 exact_range = Some(range);
754 }
755 if start_line <= line && line <= range_end && span < containing_span {
756 containing_span = span;
757 containing_range = Some(range);
758 }
759 }
760
761 let chosen = exact_range.or(containing_range);
762 let Some(range) = chosen else {
763 return;
764 };
765 let placeholder = range
766 .collapsed_text
767 .as_ref()
768 .filter(|text| !text.trim().is_empty())
769 .cloned();
770 let header_line = range.start_line as usize;
771 let end_line = range.end_line as usize;
772 let first_hidden = header_line.saturating_add(1);
773 if first_hidden > end_line {
774 return;
775 }
776 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
777 return;
778 };
779 let eb = state
780 .buffer
781 .line_start_offset(end_line.saturating_add(1))
782 .unwrap_or_else(|| state.buffer.len());
783 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
784 create_fold(state, buf_state, sb, eb, hb, placeholder);
785 } else {
786 use crate::view::folding::indent_folding;
788 let tab_size = state.buffer_settings.tab_size;
789 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
790 let est_ll = state.buffer.estimated_line_length();
791 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
792
793 let upward_bytes = max_upward * est_ll;
794 let load_start = byte_pos.saturating_sub(upward_bytes);
795 let load_end = byte_pos
796 .saturating_add(max_scan_bytes)
797 .min(state.buffer.len());
798 drop(
799 state
800 .buffer
801 .get_text_range_mut(load_start, load_end - load_start),
802 );
803
804 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
805 &state.buffer,
806 byte_pos,
807 tab_size,
808 max_scan_bytes,
809 max_upward,
810 ) {
811 create_fold(state, buf_state, sb, eb, hb, None);
812 }
813 }
814 });
815 }
816}
817
818fn create_fold(
824 state: &mut crate::state::EditorState,
825 buf_state: &mut crate::view::split::BufferViewState,
826 start_byte: usize,
827 end_byte: usize,
828 header_byte: usize,
829 placeholder: Option<String>,
830) {
831 if end_byte <= start_byte {
832 return;
833 }
834
835 buf_state.cursors.map(|cursor| {
837 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
838 let anchor_in_hidden = cursor
839 .anchor
840 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
841 if in_hidden_range || anchor_in_hidden {
842 cursor.position = header_byte;
843 cursor.anchor = None;
844 cursor.sticky_column = 0;
845 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
846 cursor.block_anchor = None;
847 cursor.deselect_on_move = true;
848 }
849 });
850
851 buf_state
852 .folds
853 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
854
855 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
857 buf_state.viewport.top_byte = header_byte;
858 buf_state.viewport.top_view_line_offset = 0;
859 }
860}
861
862impl Editor {
863 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
868 let uris: Vec<_> = self
869 .buffers()
870 .iter()
871 .filter(|(_, s)| s.language == language)
872 .filter_map(|(id, _)| {
873 self.active_window()
874 .buffer_metadata
875 .get(id)
876 .and_then(|m| m.file_uri())
877 .cloned()
878 })
879 .collect();
880
881 let __active_id = self.active_window;
882
883 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
884 for sh in lsp.get_handles_mut(language) {
885 if sh.name == server_name {
886 for uri in &uris {
887 tracing::info!(
888 "Sending didClose for {} to '{}' (language: {})",
889 uri.as_str(),
890 sh.name,
891 language
892 );
893 if let Err(e) = sh.handle.did_close(uri.as_uri().clone()) {
894 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
895 }
896 }
897 break;
898 }
899 }
900 }
901 }
902
903 pub(crate) fn stop_lsp_server_and_cleanup(
929 &mut self,
930 language: &str,
931 server_name: Option<&str>,
932 ) -> bool {
933 let stopping_names: Vec<String> = if let Some(name) = server_name {
937 vec![name.to_string()]
938 } else {
939 self.lsp()
940 .map(|lsp| lsp.server_names_for_language(language))
941 .unwrap_or_default()
942 };
943
944 let __active_id = self.active_window;
945
946 let stopped = if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
947 if let Some(name) = server_name {
948 lsp.shutdown_server_by_name(language, name)
949 } else {
950 lsp.shutdown_server(language)
951 }
952 } else {
953 false
954 };
955
956 if !stopped {
957 return false;
958 }
959
960 for name in &stopping_names {
961 self.active_window_mut()
962 .lsp_server_statuses
963 .remove(&(language.to_string(), name.clone()));
964 self.clear_diagnostics_for_server(name);
967 }
968
969 let any_handle_left = self
984 .lsp()
985 .as_ref()
986 .is_some_and(|lsp| lsp.has_handles(language));
987 if !any_handle_left {
988 self.active_window_mut()
989 .lsp_progress
990 .retain(|_, info| info.language != language);
991 }
992
993 true
994 }
995
996 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
998 if let Some(uri) = self
1004 .active_window()
1005 .buffer_metadata
1006 .get(&buffer_id)
1007 .and_then(|m| m.file_uri())
1008 .cloned()
1009 {
1010 let language = self
1011 .buffers()
1012 .get(&buffer_id)
1013 .map(|s| s.language.clone())
1014 .unwrap_or_default();
1015 let __active_id = self.active_window;
1016 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
1017 if !lsp.has_handles(&language) {
1019 tracing::warn!(
1020 "disable_lsp_for_buffer: no handle for language '{}'",
1021 language
1022 );
1023 } else {
1024 for sh in lsp.get_handles_mut(&language) {
1025 tracing::info!(
1026 "Sending didClose for {} to '{}' (language: {})",
1027 uri.as_str(),
1028 sh.name,
1029 language
1030 );
1031 if let Err(e) = sh.handle.did_close(uri.as_uri().clone()) {
1032 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
1033 }
1034 }
1035 }
1036 } else {
1037 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
1038 }
1039 } else {
1040 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
1041 }
1042
1043 if let Some(metadata) = self.active_window_mut().buffer_metadata.get_mut(&buffer_id) {
1045 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
1046 metadata.lsp_opened_with.clear();
1048 }
1049 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
1050
1051 let uri = self
1053 .active_window()
1054 .buffer_metadata
1055 .get(&buffer_id)
1056 .and_then(|m| m.file_uri())
1057 .map(|u| u.as_str().to_string());
1058
1059 if let Some(uri_str) = uri {
1060 self.stored_diagnostics_mut().remove(&uri_str);
1061 self.active_window_mut()
1062 .stored_push_diagnostics
1063 .remove(&uri_str);
1064 self.active_window_mut()
1065 .stored_pull_diagnostics
1066 .remove(&uri_str);
1067 self.active_window_mut()
1068 .diagnostic_result_ids
1069 .remove(&uri_str);
1070 self.stored_folding_ranges_mut().remove(&uri_str);
1071 }
1072
1073 if let Some((scheduled_buf, _)) = &self.active_window().scheduled_diagnostic_pull {
1075 if *scheduled_buf == buffer_id {
1076 self.active_window_mut().scheduled_diagnostic_pull = None;
1077 }
1078 }
1079
1080 if let Some((scheduled_buf, _)) = &self.active_window().scheduled_inlay_hints_request {
1082 if *scheduled_buf == buffer_id {
1083 self.active_window_mut().scheduled_inlay_hints_request = None;
1084 }
1085 }
1086
1087 self.active_window_mut()
1088 .folding_ranges_in_flight
1089 .remove(&buffer_id);
1090 self.active_window_mut()
1091 .folding_ranges_debounce
1092 .remove(&buffer_id);
1093 self.active_window_mut()
1094 .pending_folding_range_requests
1095 .retain(|_, req| req.buffer_id != buffer_id);
1096 self.active_window_mut()
1099 .pending_inlay_hints_requests
1100 .retain(|_, req| req.buffer_id != buffer_id);
1101
1102 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
1104 self.active_window_mut()
1105 .clear_lsp_overlays_for_buffer(buffer_id, &diagnostic_ns);
1106 }
1107
1108 fn enable_lsp_for_buffer(
1110 &mut self,
1111 buffer_id: crate::model::event::BufferId,
1112 language: &str,
1113 file_path: Option<std::path::PathBuf>,
1114 ) {
1115 if let Some(metadata) = self.active_window_mut().buffer_metadata.get_mut(&buffer_id) {
1117 metadata.lsp_enabled = true;
1118 metadata.lsp_disabled_reason = None;
1119 }
1120 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
1121
1122 if let Some(_path) = file_path {
1124 self.send_lsp_did_open_for_buffer(buffer_id, language);
1125 }
1126 }
1127
1128 fn send_lsp_did_open_for_buffer(
1130 &mut self,
1131 buffer_id: crate::model::event::BufferId,
1132 language: &str,
1133 ) {
1134 let (uri, text) = {
1136 let metadata = self.active_window().buffer_metadata.get(&buffer_id);
1137 let uri = metadata.and_then(|m| m.file_uri()).cloned();
1138 let text = self
1139 .buffers()
1140 .get(&buffer_id)
1141 .and_then(|state| state.buffer.to_string());
1142 (uri, text)
1143 };
1144
1145 let Some(uri) = uri else { return };
1146 let Some(text) = text else { return };
1147
1148 use crate::services::lsp::manager::LspSpawnResult;
1150 let file_path = self
1151 .active_window()
1152 .buffer_metadata
1153 .get(&buffer_id)
1154 .and_then(|m| m.file_path())
1155 .cloned();
1156 let inlay_buffer_info: Option<(u32, u32, u64)> = self
1160 .windows
1161 .get(&self.active_window)
1162 .and_then(|w| w.buffers.get(&buffer_id))
1163 .map(|state| {
1164 let line_count = state.buffer.line_count().unwrap_or(1000);
1165 (
1166 line_count.saturating_sub(1) as u32,
1167 10000u32,
1168 state.buffer.version(),
1169 )
1170 });
1171 let __active_id = self.active_window;
1172 let enable_inlay_hints = self.config.editor.enable_inlay_hints;
1173
1174 let Some(__win) = self.windows.get_mut(&__active_id) else {
1175 return;
1176 };
1177 let diagnostic_result_ids = &__win.diagnostic_result_ids;
1178 let __next_id = &mut __win.next_lsp_request_id;
1179 let buffer_metadata = &mut __win.buffer_metadata;
1180 let lsp = &mut __win.lsp;
1181
1182 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
1183 return;
1184 }
1185
1186 let Some(handle) = lsp.get_handle_mut(language) else {
1187 return;
1188 };
1189
1190 let handle_id = handle.id();
1191 if let Err(e) = handle.did_open(uri.as_uri().clone(), text, language.to_string()) {
1192 tracing::warn!("Failed to send didOpen to LSP: {}", e);
1193 return;
1194 }
1195
1196 if let Some(metadata) = buffer_metadata.get_mut(&buffer_id) {
1198 metadata.lsp_opened_with.insert(handle_id);
1199 }
1200
1201 let request_id = {
1203 let id = *__next_id;
1204 *__next_id += 1;
1205 id
1206 };
1207 let previous_result_id = diagnostic_result_ids.get(uri.as_str()).cloned();
1208 if let Err(e) =
1209 handle.document_diagnostic(request_id, uri.as_uri().clone(), previous_result_id)
1210 {
1211 tracing::warn!("LSP document_diagnostic request failed: {}", e);
1212 }
1213
1214 if enable_inlay_hints {
1216 let (last_line, last_char, buffer_version) =
1217 inlay_buffer_info.unwrap_or((999, 10000, 0));
1218
1219 let request_id = {
1220 let id = *__next_id;
1221 *__next_id += 1;
1222 id
1223 };
1224 if let Err(e) =
1225 handle.inlay_hints(request_id, uri.as_uri().clone(), 0, 0, last_line, last_char)
1226 {
1227 tracing::warn!("LSP inlay_hints request failed: {}", e);
1228 } else {
1229 __win.pending_inlay_hints_requests.insert(
1230 request_id,
1231 super::InlayHintsRequest {
1232 buffer_id,
1233 version: buffer_version,
1234 },
1235 );
1236 }
1237 }
1238
1239 let _ = __next_id;
1241 let _ = lsp;
1242 let _ = handle;
1243 self.active_window_mut()
1244 .schedule_folding_ranges_refresh(buffer_id);
1245 }
1246
1247 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
1253 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
1254
1255 #[cfg(feature = "embed-plugins")]
1257 let fresh_dts_path = {
1258 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
1259 else {
1260 tracing::warn!(
1261 "Cannot set up plugin dev LSP: embedded plugins directory not available"
1262 );
1263 return;
1264 };
1265 let path = embedded_dir.join("lib").join("fresh.d.ts");
1266 if !path.exists() {
1267 tracing::warn!(
1268 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1269 path
1270 );
1271 return;
1272 }
1273 path
1274 };
1275
1276 #[cfg(not(feature = "embed-plugins"))]
1277 let fresh_dts_path = {
1278 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
1280 .join("plugins")
1281 .join("lib")
1282 .join("fresh.d.ts");
1283 if !path.exists() {
1284 tracing::warn!(
1285 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
1286 path
1287 );
1288 return;
1289 }
1290 path
1291 };
1292
1293 let buffer_id_num: usize = buffer_id.0;
1295 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
1296 Ok(workspace) => {
1297 let plugin_file = workspace.plugin_file.clone();
1298
1299 let plugin_file_uri = super::types::LspUri::from_host_path(
1301 &plugin_file,
1302 self.authority().path_translation.as_ref(),
1303 );
1304 if let Some(uri) = plugin_file_uri {
1305 if let Some(metadata) =
1306 self.active_window_mut().buffer_metadata.get_mut(&buffer_id)
1307 {
1308 metadata.kind = super::types::BufferKind::File {
1309 path: plugin_file.clone(),
1310 uri: Some(uri),
1311 };
1312 metadata.lsp_enabled = true;
1313 metadata.lsp_disabled_reason = None;
1314 metadata.lsp_opened_with.clear();
1316
1317 tracing::info!(
1318 "Plugin dev LSP enabled for buffer {} via {:?}",
1319 buffer_id_num,
1320 plugin_file
1321 );
1322 }
1323 }
1324
1325 if let Some(state) = self
1327 .windows
1328 .get_mut(&self.active_window)
1329 .map(|w| &mut w.buffers)
1330 .expect("active window present")
1331 .get_mut(&buffer_id)
1332 {
1333 let first_line = state.buffer.first_line_lossy();
1334 let detected =
1335 crate::primitives::detected_language::DetectedLanguage::from_path(
1336 &plugin_file,
1337 first_line.as_deref(),
1338 &self.grammar_registry,
1339 &self.config.languages,
1340 );
1341 state.apply_language(detected);
1342 }
1343
1344 let __active_id = self.active_window;
1346 if let Some(lsp) = self.windows.get_mut(&__active_id).map(|w| &mut w.lsp) {
1347 lsp.allow_language("typescript");
1348 }
1349
1350 let workspace_dir = workspace.dir().to_path_buf();
1352 self.active_window_mut()
1353 .plugin_dev_workspaces
1354 .insert(buffer_id, workspace);
1355
1356 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
1358
1359 if let Some(lsp) = self.lsp() {
1361 if let Some(handle) = lsp.get_handle("typescript") {
1362 if let Some(uri) = super::types::file_path_to_lsp_uri_with_translation(
1363 &workspace_dir,
1364 self.authority().path_translation.as_ref(),
1365 ) {
1366 let name = workspace_dir
1367 .file_name()
1368 .unwrap_or_default()
1369 .to_string_lossy()
1370 .into_owned();
1371 if let Err(e) = handle.add_workspace_folder(uri, name) {
1372 tracing::warn!("Failed to add plugin workspace folder: {}", e);
1373 } else {
1374 tracing::info!(
1375 "Added plugin workspace folder: {:?}",
1376 workspace_dir
1377 );
1378 }
1379 }
1380 }
1381 }
1382 }
1383 Err(e) => {
1384 tracing::warn!("Failed to create plugin dev workspace: {}", e);
1385 }
1386 }
1387 }
1388}