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 for (buffer_id, buf_path) in buffers_for_language {
134 let Some(state) = self.buffers.get(&buffer_id) else {
135 continue;
136 };
137
138 let Some(content) = state.buffer.to_string() else {
139 continue; };
141
142 let Some(uri) = super::types::file_path_to_lsp_uri(&buf_path) else {
143 continue;
144 };
145
146 let lang_id = state.language.clone();
147
148 if let Some(lsp) = self.lsp.as_mut() {
149 use crate::services::lsp::manager::LspSpawnResult;
151 if lsp.try_spawn(&lang_id, Some(&buf_path)) != LspSpawnResult::Spawned {
152 continue;
153 }
154
155 let opened_with = self
158 .buffer_metadata
159 .get(&buffer_id)
160 .map(|m| m.lsp_opened_with.clone())
161 .unwrap_or_default();
162
163 let handles_needing_open: Vec<(String, u64)> = lsp
164 .get_handles(&lang_id)
165 .iter()
166 .filter(|sh| !opened_with.contains(&sh.handle.id()))
167 .map(|sh| (sh.name.clone(), sh.handle.id()))
168 .collect();
169
170 for (name, handle_id) in handles_needing_open {
172 let sh = lsp
173 .get_handles_mut(&lang_id)
174 .iter_mut()
175 .find(|s| s.handle.id() == handle_id);
176
177 if let Some(sh) = sh {
178 if let Err(e) =
179 sh.handle
180 .did_open(uri.clone(), content.clone(), lang_id.clone())
181 {
182 tracing::warn!("LSP did_open to '{}' failed: {}", name, e);
183 } else if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
184 metadata.lsp_opened_with.insert(handle_id);
185 }
186 }
187 }
188 }
189 }
190 }
191
192 pub fn handle_lsp_stop(&mut self) {
197 let running_languages: Vec<String> = self
198 .lsp
199 .as_ref()
200 .map(|lsp| lsp.running_servers())
201 .unwrap_or_default();
202
203 if running_languages.is_empty() {
204 self.set_status_message(t!("lsp.no_servers_running").to_string());
205 return;
206 }
207
208 let mut suggestions: Vec<Suggestion> = Vec::new();
210 for lang in &running_languages {
211 let server_names: Vec<String> = self
212 .lsp
213 .as_ref()
214 .map(|lsp| lsp.server_names_for_language(lang))
215 .unwrap_or_default();
216
217 if server_names.len() > 1 {
218 for name in &server_names {
220 let description = Some(format!("Server: {}", name));
221 suggestions.push(Suggestion {
222 text: format!("{}/{}", lang, name),
223 description,
224 value: Some(format!("{}/{}", lang, name)),
227 disabled: false,
228 keybinding: None,
229 source: None,
230 });
231 }
232 } else {
233 let description = self
235 .lsp
236 .as_ref()
237 .and_then(|lsp| lsp.get_config(lang))
238 .filter(|c| !c.command.is_empty())
239 .map(|c| format!("Command: {}", c.command));
240
241 suggestions.push(Suggestion {
242 text: lang.clone(),
243 description,
244 value: Some(lang.clone()),
245 disabled: false,
246 keybinding: None,
247 source: None,
248 });
249 }
250 }
251
252 self.prompt = Some(Prompt::with_suggestions(
254 "Stop LSP server: ".to_string(),
255 PromptType::StopLspServer,
256 suggestions.clone(),
257 ));
258
259 if let Some(prompt) = self.prompt.as_mut() {
261 if suggestions.len() == 1 {
262 prompt.input = suggestions[0].text.clone();
264 prompt.cursor_pos = prompt.input.len();
265 prompt.selected_suggestion = Some(0);
266 } else if !prompt.suggestions.is_empty() {
267 prompt.selected_suggestion = Some(0);
269 }
270 }
271 }
272
273 pub fn handle_lsp_toggle_for_buffer(&mut self) {
278 let buffer_id = self.active_buffer();
279
280 let language = {
282 let Some(state) = self.buffers.get(&buffer_id) else {
283 return;
284 };
285 state.language.clone()
286 };
287
288 let lsp_configured = self
290 .lsp
291 .as_ref()
292 .and_then(|lsp| lsp.get_config(&language))
293 .is_some();
294
295 if !lsp_configured {
296 self.set_status_message(t!("lsp.no_server_configured").to_string());
297 return;
298 }
299
300 let (was_enabled, file_path) = {
302 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
303 return;
304 };
305 (metadata.lsp_enabled, metadata.file_path().cloned())
306 };
307
308 if was_enabled {
309 self.disable_lsp_for_buffer(buffer_id);
310 } else {
311 self.enable_lsp_for_buffer(buffer_id, &language, file_path);
312 }
313 }
314
315 pub fn toggle_fold_at_cursor(&mut self) {
317 let buffer_id = self.active_buffer();
318 let pos = self.active_cursors().primary().position;
319 self.toggle_fold_at_byte(buffer_id, pos);
320 }
321
322 pub fn toggle_fold_at_line(&mut self, buffer_id: BufferId, line: usize) {
328 let byte_pos = {
329 let Some(state) = self.buffers.get(&buffer_id) else {
330 return;
331 };
332 state.buffer.line_start_offset(line).unwrap_or_else(|| {
333 use crate::view::folding::indent_folding;
334 let approx = line * state.buffer.estimated_line_length();
335 indent_folding::find_line_start_byte(&state.buffer, approx)
336 })
337 };
338 self.toggle_fold_at_byte(buffer_id, byte_pos);
339 }
340
341 pub fn toggle_fold_at_byte(&mut self, buffer_id: BufferId, byte_pos: usize) {
343 let split_id = self.split_manager.active_split();
344 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
345
346 let Some(state) = buffers.get_mut(&buffer_id) else {
347 return;
348 };
349
350 let Some(view_state) = split_view_states.get_mut(&split_id) else {
351 return;
352 };
353 let buf_state = view_state.ensure_buffer_state(buffer_id);
354
355 let header_byte = {
357 use crate::view::folding::indent_folding;
358 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
359 };
360 if buf_state
361 .folds
362 .remove_by_header_byte(&state.buffer, &mut state.marker_list, header_byte)
363 {
364 return;
365 }
366
367 if buf_state
369 .folds
370 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
371 {
372 return;
373 }
374
375 if !state.folding_ranges.is_empty() {
377 let line = state.buffer.get_line_number(byte_pos);
380 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
381 let mut exact_span = usize::MAX;
382 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
383 let mut containing_span = usize::MAX;
384
385 for range in &state.folding_ranges {
386 let start_line = range.start_line as usize;
387 let range_end = range.end_line as usize;
388 if range_end <= start_line {
389 continue;
390 }
391 let span = range_end.saturating_sub(start_line);
392
393 if start_line == line && span < exact_span {
394 exact_span = span;
395 exact_range = Some(range);
396 }
397 if start_line <= line && line <= range_end && span < containing_span {
398 containing_span = span;
399 containing_range = Some(range);
400 }
401 }
402
403 let chosen = exact_range.or(containing_range);
404 let Some(range) = chosen else {
405 return;
406 };
407 let placeholder = range
408 .collapsed_text
409 .as_ref()
410 .filter(|text| !text.trim().is_empty())
411 .cloned();
412 let header_line = range.start_line as usize;
413 let end_line = range.end_line as usize;
414 let first_hidden = header_line.saturating_add(1);
415 if first_hidden > end_line {
416 return;
417 }
418 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
419 return;
420 };
421 let eb = state
422 .buffer
423 .line_start_offset(end_line.saturating_add(1))
424 .unwrap_or_else(|| state.buffer.len());
425 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
426 Self::create_fold(state, buf_state, sb, eb, hb, placeholder);
427 } else {
428 use crate::view::folding::indent_folding;
430 let tab_size = state.buffer_settings.tab_size;
431 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
432 let est_ll = state.buffer.estimated_line_length();
433 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
434
435 let upward_bytes = max_upward * est_ll;
438 let load_start = byte_pos.saturating_sub(upward_bytes);
439 let load_end = byte_pos
440 .saturating_add(max_scan_bytes)
441 .min(state.buffer.len());
442 drop(
445 state
446 .buffer
447 .get_text_range_mut(load_start, load_end - load_start),
448 );
449
450 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
451 &state.buffer,
452 byte_pos,
453 tab_size,
454 max_scan_bytes,
455 max_upward,
456 ) {
457 Self::create_fold(state, buf_state, sb, eb, hb, None);
458 }
459 }
460 }
461
462 fn create_fold(
463 state: &mut crate::state::EditorState,
464 buf_state: &mut crate::view::split::BufferViewState,
465 start_byte: usize,
466 end_byte: usize,
467 header_byte: usize,
468 placeholder: Option<String>,
469 ) {
470 if end_byte <= start_byte {
471 return;
472 }
473
474 buf_state.cursors.map(|cursor| {
476 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
477 let anchor_in_hidden = cursor
478 .anchor
479 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
480 if in_hidden_range || anchor_in_hidden {
481 cursor.position = header_byte;
482 cursor.anchor = None;
483 cursor.sticky_column = 0;
484 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
485 cursor.block_anchor = None;
486 cursor.deselect_on_move = true;
487 }
488 });
489
490 buf_state
491 .folds
492 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
493
494 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
496 buf_state.viewport.top_byte = header_byte;
497 buf_state.viewport.top_view_line_offset = 0;
498 }
499 }
500
501 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
506 let uris: Vec<_> = self
507 .buffers
508 .iter()
509 .filter(|(_, s)| s.language == language)
510 .filter_map(|(id, _)| {
511 self.buffer_metadata
512 .get(id)
513 .and_then(|m| m.file_uri())
514 .cloned()
515 })
516 .collect();
517
518 if let Some(lsp) = self.lsp.as_mut() {
519 for sh in lsp.get_handles_mut(language) {
520 if sh.name == server_name {
521 for uri in &uris {
522 tracing::info!(
523 "Sending didClose for {} to '{}' (language: {})",
524 uri.as_str(),
525 sh.name,
526 language
527 );
528 if let Err(e) = sh.handle.did_close(uri.clone()) {
529 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
530 }
531 }
532 break;
533 }
534 }
535 }
536 }
537
538 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
540 if let Some(uri) = self
546 .buffer_metadata
547 .get(&buffer_id)
548 .and_then(|m| m.file_uri())
549 .cloned()
550 {
551 let language = self
552 .buffers
553 .get(&buffer_id)
554 .map(|s| s.language.clone())
555 .unwrap_or_default();
556 if let Some(lsp) = self.lsp.as_mut() {
557 let handles = lsp.get_handles_mut(&language);
559 if handles.is_empty() {
560 tracing::warn!(
561 "disable_lsp_for_buffer: no handle for language '{}'",
562 language
563 );
564 } else {
565 for sh in handles {
566 tracing::info!(
567 "Sending didClose for {} to '{}' (language: {})",
568 uri.as_str(),
569 sh.name,
570 language
571 );
572 if let Err(e) = sh.handle.did_close(uri.clone()) {
573 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
574 }
575 }
576 }
577 } else {
578 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
579 }
580 } else {
581 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
582 }
583
584 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
586 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
587 metadata.lsp_opened_with.clear();
589 }
590 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
591
592 let uri = self
594 .buffer_metadata
595 .get(&buffer_id)
596 .and_then(|m| m.file_uri())
597 .map(|u| u.as_str().to_string());
598
599 if let Some(uri_str) = uri {
600 self.stored_diagnostics.remove(&uri_str);
601 self.stored_push_diagnostics.remove(&uri_str);
602 self.stored_pull_diagnostics.remove(&uri_str);
603 self.diagnostic_result_ids.remove(&uri_str);
604 self.stored_folding_ranges.remove(&uri_str);
605 }
606
607 if let Some((scheduled_buf, _)) = &self.scheduled_diagnostic_pull {
609 if *scheduled_buf == buffer_id {
610 self.scheduled_diagnostic_pull = None;
611 }
612 }
613
614 self.folding_ranges_in_flight.remove(&buffer_id);
615 self.folding_ranges_debounce.remove(&buffer_id);
616 self.pending_folding_range_requests
617 .retain(|_, req| req.buffer_id != buffer_id);
618
619 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
621 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
622 if let Some(state) = buffers.get_mut(&buffer_id) {
623 state
624 .overlays
625 .clear_namespace(&diagnostic_ns, &mut state.marker_list);
626 state.virtual_texts.clear(&mut state.marker_list);
627 state.folding_ranges.clear();
628 for view_state in split_view_states.values_mut() {
629 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
630 buf_state.folds.clear(&mut state.marker_list);
631 }
632 }
633 }
634 }
635
636 fn enable_lsp_for_buffer(
638 &mut self,
639 buffer_id: crate::model::event::BufferId,
640 language: &str,
641 file_path: Option<std::path::PathBuf>,
642 ) {
643 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
645 metadata.lsp_enabled = true;
646 metadata.lsp_disabled_reason = None;
647 }
648 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
649
650 if let Some(_path) = file_path {
652 self.send_lsp_did_open_for_buffer(buffer_id, language);
653 }
654 }
655
656 fn send_lsp_did_open_for_buffer(
658 &mut self,
659 buffer_id: crate::model::event::BufferId,
660 language: &str,
661 ) {
662 let (uri, text) = {
664 let metadata = self.buffer_metadata.get(&buffer_id);
665 let uri = metadata.and_then(|m| m.file_uri()).cloned();
666 let text = self
667 .buffers
668 .get(&buffer_id)
669 .and_then(|state| state.buffer.to_string());
670 (uri, text)
671 };
672
673 let Some(uri) = uri else { return };
674 let Some(text) = text else { return };
675
676 use crate::services::lsp::manager::LspSpawnResult;
678 let file_path = self
679 .buffer_metadata
680 .get(&buffer_id)
681 .and_then(|m| m.file_path())
682 .cloned();
683 let Some(lsp) = self.lsp.as_mut() else {
684 return;
685 };
686
687 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
688 return;
689 }
690
691 let Some(handle) = lsp.get_handle_mut(language) else {
692 return;
693 };
694
695 let handle_id = handle.id();
696 if let Err(e) = handle.did_open(uri.clone(), text, language.to_string()) {
697 tracing::warn!("Failed to send didOpen to LSP: {}", e);
698 return;
699 }
700
701 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
703 metadata.lsp_opened_with.insert(handle_id);
704 }
705
706 let request_id = self.next_lsp_request_id;
708 self.next_lsp_request_id += 1;
709 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
710 if let Err(e) = handle.document_diagnostic(request_id, uri.clone(), previous_result_id) {
711 tracing::warn!("LSP document_diagnostic request failed: {}", e);
712 }
713
714 if self.config.editor.enable_inlay_hints {
716 let (last_line, last_char) = self
717 .buffers
718 .get(&buffer_id)
719 .map(|state| {
720 let line_count = state.buffer.line_count().unwrap_or(1000);
721 (line_count.saturating_sub(1) as u32, 10000u32)
722 })
723 .unwrap_or((999, 10000));
724
725 let request_id = self.next_lsp_request_id;
726 self.next_lsp_request_id += 1;
727 if let Err(e) = handle.inlay_hints(request_id, uri, 0, 0, last_line, last_char) {
728 tracing::warn!("LSP inlay_hints request failed: {}", e);
729 }
730 }
731
732 self.schedule_folding_ranges_refresh(buffer_id);
734 }
735
736 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
742 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
743
744 #[cfg(feature = "embed-plugins")]
746 let fresh_dts_path = {
747 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
748 else {
749 tracing::warn!(
750 "Cannot set up plugin dev LSP: embedded plugins directory not available"
751 );
752 return;
753 };
754 let path = embedded_dir.join("lib").join("fresh.d.ts");
755 if !path.exists() {
756 tracing::warn!(
757 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
758 path
759 );
760 return;
761 }
762 path
763 };
764
765 #[cfg(not(feature = "embed-plugins"))]
766 let fresh_dts_path = {
767 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
769 .join("plugins")
770 .join("lib")
771 .join("fresh.d.ts");
772 if !path.exists() {
773 tracing::warn!(
774 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
775 path
776 );
777 return;
778 }
779 path
780 };
781
782 let buffer_id_num: usize = buffer_id.0;
784 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
785 Ok(workspace) => {
786 let plugin_file = workspace.plugin_file.clone();
787
788 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
790 if let Some(uri) = super::types::file_path_to_lsp_uri(&plugin_file) {
791 metadata.kind = super::types::BufferKind::File {
792 path: plugin_file.clone(),
793 uri: Some(uri),
794 };
795 metadata.lsp_enabled = true;
796 metadata.lsp_disabled_reason = None;
797 metadata.lsp_opened_with.clear();
799
800 tracing::info!(
801 "Plugin dev LSP enabled for buffer {} via {:?}",
802 buffer_id_num,
803 plugin_file
804 );
805 }
806 }
807
808 if let Some(state) = self.buffers.get_mut(&buffer_id) {
810 let detected =
811 crate::primitives::detected_language::DetectedLanguage::from_path(
812 &plugin_file,
813 &self.grammar_registry,
814 &self.config.languages,
815 );
816 state.apply_language(detected);
817 }
818
819 if let Some(lsp) = &mut self.lsp {
821 lsp.allow_language("typescript");
822 }
823
824 let workspace_dir = workspace.dir().to_path_buf();
826 self.plugin_dev_workspaces.insert(buffer_id, workspace);
827
828 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
830
831 if let Some(lsp) = &self.lsp {
833 if let Some(handle) = lsp.get_handle("typescript") {
834 if let Some(uri) = super::types::file_path_to_lsp_uri(&workspace_dir) {
835 let name = workspace_dir
836 .file_name()
837 .unwrap_or_default()
838 .to_string_lossy()
839 .into_owned();
840 if let Err(e) = handle.add_workspace_folder(uri, name) {
841 tracing::warn!("Failed to add plugin workspace folder: {}", e);
842 } else {
843 tracing::info!(
844 "Added plugin workspace folder: {:?}",
845 workspace_dir
846 );
847 }
848 }
849 }
850 }
851 }
852 Err(e) => {
853 tracing::warn!("Failed to create plugin dev workspace: {}", e);
854 }
855 }
856 }
857}