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
25 let lsp_configured = self
27 .lsp
28 .as_ref()
29 .and_then(|lsp| lsp.get_config(&language))
30 .is_some();
31
32 if !lsp_configured {
33 self.set_status_message(t!("lsp.no_server_configured").to_string());
34 return;
35 }
36
37 let Some(lsp) = self.lsp.as_mut() else {
39 self.set_status_message(t!("lsp.no_manager").to_string());
40 return;
41 };
42
43 let (success, message) = lsp.manual_restart(&language);
44 self.status_message = Some(message);
45
46 if !success {
47 return;
48 }
49
50 self.reopen_buffers_for_language(&language);
52 }
53
54 pub(crate) fn reopen_buffers_for_language(&mut self, language: &str) {
58 let buffers_for_language: Vec<_> = self
61 .buffers
62 .iter()
63 .filter_map(|(buf_id, state)| {
64 if state.language == language {
65 self.buffer_metadata
66 .get(buf_id)
67 .and_then(|meta| meta.file_path().map(|p| (*buf_id, p.clone())))
68 } else {
69 None
70 }
71 })
72 .collect();
73
74 for (buffer_id, buf_path) in buffers_for_language {
75 let Some(state) = self.buffers.get(&buffer_id) else {
76 continue;
77 };
78
79 let Some(content) = state.buffer.to_string() else {
80 continue; };
82
83 let Some(uri) = super::types::file_path_to_lsp_uri(&buf_path) else {
84 continue;
85 };
86
87 let lang_id = state.language.clone();
88
89 if let Some(lsp) = self.lsp.as_mut() {
90 use crate::services::lsp::manager::LspSpawnResult;
92 if lsp.try_spawn(&lang_id) == LspSpawnResult::Spawned {
93 if let Some(handle) = lsp.get_handle_mut(&lang_id) {
94 let handle_id = handle.id();
95 if let Err(e) = handle.did_open(uri, content, lang_id) {
96 tracing::warn!("LSP did_open failed: {}", e);
97 } else {
98 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
101 metadata.lsp_opened_with.insert(handle_id);
102 }
103 }
104 }
105 }
106 }
107 }
108 }
109
110 pub fn handle_lsp_stop(&mut self) {
115 let running_servers: Vec<String> = self
116 .lsp
117 .as_ref()
118 .map(|lsp| lsp.running_servers())
119 .unwrap_or_default();
120
121 if running_servers.is_empty() {
122 self.set_status_message(t!("lsp.no_servers_running").to_string());
123 return;
124 }
125
126 let suggestions: Vec<Suggestion> = running_servers
128 .iter()
129 .map(|lang| {
130 let description = self
131 .lsp
132 .as_ref()
133 .and_then(|lsp| lsp.get_config(lang))
134 .filter(|c| !c.command.is_empty())
135 .map(|c| format!("Command: {}", c.command));
136
137 Suggestion {
138 text: lang.clone(),
139 description,
140 value: Some(lang.clone()),
141 disabled: false,
142 keybinding: None,
143 source: None,
144 }
145 })
146 .collect();
147
148 self.prompt = Some(Prompt::with_suggestions(
150 "Stop LSP server: ".to_string(),
151 PromptType::StopLspServer,
152 suggestions,
153 ));
154
155 if let Some(prompt) = self.prompt.as_mut() {
157 if running_servers.len() == 1 {
158 prompt.input = running_servers[0].clone();
160 prompt.cursor_pos = prompt.input.len();
161 prompt.selected_suggestion = Some(0);
162 } else if !prompt.suggestions.is_empty() {
163 prompt.selected_suggestion = Some(0);
165 }
166 }
167 }
168
169 pub fn handle_lsp_toggle_for_buffer(&mut self) {
174 let buffer_id = self.active_buffer();
175
176 let language = {
178 let Some(state) = self.buffers.get(&buffer_id) else {
179 return;
180 };
181 state.language.clone()
182 };
183
184 let lsp_configured = self
186 .lsp
187 .as_ref()
188 .and_then(|lsp| lsp.get_config(&language))
189 .is_some();
190
191 if !lsp_configured {
192 self.set_status_message(t!("lsp.no_server_configured").to_string());
193 return;
194 }
195
196 let (was_enabled, file_path) = {
198 let Some(metadata) = self.buffer_metadata.get(&buffer_id) else {
199 return;
200 };
201 (metadata.lsp_enabled, metadata.file_path().cloned())
202 };
203
204 if was_enabled {
205 self.disable_lsp_for_buffer(buffer_id);
206 } else {
207 self.enable_lsp_for_buffer(buffer_id, &language, file_path);
208 }
209 }
210
211 pub fn toggle_fold_at_cursor(&mut self) {
213 let buffer_id = self.active_buffer();
214 let pos = self.active_cursors().primary().position;
215 self.toggle_fold_at_byte(buffer_id, pos);
216 }
217
218 pub fn toggle_fold_at_line(&mut self, buffer_id: BufferId, line: usize) {
224 let byte_pos = {
225 let Some(state) = self.buffers.get(&buffer_id) else {
226 return;
227 };
228 state.buffer.line_start_offset(line).unwrap_or_else(|| {
229 use crate::view::folding::indent_folding;
230 let approx = line * state.buffer.estimated_line_length();
231 indent_folding::find_line_start_byte(&state.buffer, approx)
232 })
233 };
234 self.toggle_fold_at_byte(buffer_id, byte_pos);
235 }
236
237 pub fn toggle_fold_at_byte(&mut self, buffer_id: BufferId, byte_pos: usize) {
239 let split_id = self.split_manager.active_split();
240 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
241
242 let Some(state) = buffers.get_mut(&buffer_id) else {
243 return;
244 };
245
246 let Some(view_state) = split_view_states.get_mut(&split_id) else {
247 return;
248 };
249 let buf_state = view_state.ensure_buffer_state(buffer_id);
250
251 let header_byte = {
253 use crate::view::folding::indent_folding;
254 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
255 };
256 if buf_state
257 .folds
258 .remove_by_header_byte(&state.buffer, &mut state.marker_list, header_byte)
259 {
260 return;
261 }
262
263 if buf_state
265 .folds
266 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
267 {
268 return;
269 }
270
271 if !state.folding_ranges.is_empty() {
273 let line = state.buffer.get_line_number(byte_pos);
276 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
277 let mut exact_span = usize::MAX;
278 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
279 let mut containing_span = usize::MAX;
280
281 for range in &state.folding_ranges {
282 let start_line = range.start_line as usize;
283 let range_end = range.end_line as usize;
284 if range_end <= start_line {
285 continue;
286 }
287 let span = range_end.saturating_sub(start_line);
288
289 if start_line == line && span < exact_span {
290 exact_span = span;
291 exact_range = Some(range);
292 }
293 if start_line <= line && line <= range_end && span < containing_span {
294 containing_span = span;
295 containing_range = Some(range);
296 }
297 }
298
299 let chosen = exact_range.or(containing_range);
300 let Some(range) = chosen else {
301 return;
302 };
303 let placeholder = range
304 .collapsed_text
305 .as_ref()
306 .filter(|text| !text.trim().is_empty())
307 .cloned();
308 let header_line = range.start_line as usize;
309 let end_line = range.end_line as usize;
310 let first_hidden = header_line.saturating_add(1);
311 if first_hidden > end_line {
312 return;
313 }
314 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
315 return;
316 };
317 let eb = state
318 .buffer
319 .line_start_offset(end_line.saturating_add(1))
320 .unwrap_or_else(|| state.buffer.len());
321 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
322 Self::create_fold(state, buf_state, sb, eb, hb, placeholder);
323 } else {
324 use crate::view::folding::indent_folding;
326 let tab_size = state.buffer_settings.tab_size;
327 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
328 let est_ll = state.buffer.estimated_line_length();
329 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
330
331 let upward_bytes = max_upward * est_ll;
334 let load_start = byte_pos.saturating_sub(upward_bytes);
335 let load_end = byte_pos
336 .saturating_add(max_scan_bytes)
337 .min(state.buffer.len());
338 drop(
341 state
342 .buffer
343 .get_text_range_mut(load_start, load_end - load_start),
344 );
345
346 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
347 &state.buffer,
348 byte_pos,
349 tab_size,
350 max_scan_bytes,
351 max_upward,
352 ) {
353 Self::create_fold(state, buf_state, sb, eb, hb, None);
354 }
355 }
356 }
357
358 fn create_fold(
359 state: &mut crate::state::EditorState,
360 buf_state: &mut crate::view::split::BufferViewState,
361 start_byte: usize,
362 end_byte: usize,
363 header_byte: usize,
364 placeholder: Option<String>,
365 ) {
366 if end_byte <= start_byte {
367 return;
368 }
369
370 buf_state.cursors.map(|cursor| {
372 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
373 let anchor_in_hidden = cursor
374 .anchor
375 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
376 if in_hidden_range || anchor_in_hidden {
377 cursor.position = header_byte;
378 cursor.anchor = None;
379 cursor.sticky_column = 0;
380 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
381 cursor.block_anchor = None;
382 cursor.deselect_on_move = true;
383 }
384 });
385
386 buf_state
387 .folds
388 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
389
390 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
392 buf_state.viewport.top_byte = header_byte;
393 buf_state.viewport.top_view_line_offset = 0;
394 }
395 }
396
397 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
399 if let Some(uri) = self
405 .buffer_metadata
406 .get(&buffer_id)
407 .and_then(|m| m.file_uri())
408 .cloned()
409 {
410 let language = self
411 .buffers
412 .get(&buffer_id)
413 .map(|s| s.language.clone())
414 .unwrap_or_default();
415 if let Some(lsp) = self.lsp.as_mut() {
416 if let Some(handle) = lsp.get_handle_mut(&language) {
417 tracing::info!(
418 "Sending didClose for {} (language: {})",
419 uri.as_str(),
420 language
421 );
422 if let Err(e) = handle.did_close(uri) {
423 tracing::warn!("Failed to send didClose to LSP: {}", e);
424 }
425 } else {
426 tracing::warn!(
427 "disable_lsp_for_buffer: no handle for language '{}'",
428 language
429 );
430 }
431 } else {
432 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
433 }
434 } else {
435 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
436 }
437
438 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
440 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
441 metadata.lsp_opened_with.clear();
443 }
444 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
445
446 let uri = self
448 .buffer_metadata
449 .get(&buffer_id)
450 .and_then(|m| m.file_uri())
451 .map(|u| u.as_str().to_string());
452
453 if let Some(uri_str) = uri {
454 self.stored_diagnostics.remove(&uri_str);
455 self.stored_push_diagnostics.remove(&uri_str);
456 self.stored_pull_diagnostics.remove(&uri_str);
457 self.diagnostic_result_ids.remove(&uri_str);
458 self.stored_folding_ranges.remove(&uri_str);
459 }
460
461 if let Some((scheduled_buf, _)) = &self.scheduled_diagnostic_pull {
463 if *scheduled_buf == buffer_id {
464 self.scheduled_diagnostic_pull = None;
465 }
466 }
467
468 self.folding_ranges_in_flight.remove(&buffer_id);
469 self.folding_ranges_debounce.remove(&buffer_id);
470 self.pending_folding_range_requests
471 .retain(|_, req| req.buffer_id != buffer_id);
472
473 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
475 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
476 if let Some(state) = buffers.get_mut(&buffer_id) {
477 state
478 .overlays
479 .clear_namespace(&diagnostic_ns, &mut state.marker_list);
480 state.virtual_texts.clear(&mut state.marker_list);
481 state.folding_ranges.clear();
482 for view_state in split_view_states.values_mut() {
483 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
484 buf_state.folds.clear(&mut state.marker_list);
485 }
486 }
487 }
488 }
489
490 fn enable_lsp_for_buffer(
492 &mut self,
493 buffer_id: crate::model::event::BufferId,
494 language: &str,
495 file_path: Option<std::path::PathBuf>,
496 ) {
497 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
499 metadata.lsp_enabled = true;
500 metadata.lsp_disabled_reason = None;
501 }
502 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
503
504 if let Some(_path) = file_path {
506 self.send_lsp_did_open_for_buffer(buffer_id, language);
507 }
508 }
509
510 fn send_lsp_did_open_for_buffer(
512 &mut self,
513 buffer_id: crate::model::event::BufferId,
514 language: &str,
515 ) {
516 let (uri, text) = {
518 let metadata = self.buffer_metadata.get(&buffer_id);
519 let uri = metadata.and_then(|m| m.file_uri()).cloned();
520 let text = self
521 .buffers
522 .get(&buffer_id)
523 .and_then(|state| state.buffer.to_string());
524 (uri, text)
525 };
526
527 let Some(uri) = uri else { return };
528 let Some(text) = text else { return };
529
530 use crate::services::lsp::manager::LspSpawnResult;
532 let Some(lsp) = self.lsp.as_mut() else {
533 return;
534 };
535
536 if lsp.try_spawn(language) != LspSpawnResult::Spawned {
537 return;
538 }
539
540 let Some(handle) = lsp.get_handle_mut(language) else {
541 return;
542 };
543
544 let handle_id = handle.id();
545 if let Err(e) = handle.did_open(uri.clone(), text, language.to_string()) {
546 tracing::warn!("Failed to send didOpen to LSP: {}", e);
547 return;
548 }
549
550 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
552 metadata.lsp_opened_with.insert(handle_id);
553 }
554
555 let request_id = self.next_lsp_request_id;
557 self.next_lsp_request_id += 1;
558 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
559 if let Err(e) = handle.document_diagnostic(request_id, uri.clone(), previous_result_id) {
560 tracing::warn!("LSP document_diagnostic request failed: {}", e);
561 }
562
563 if self.config.editor.enable_inlay_hints {
565 let (last_line, last_char) = self
566 .buffers
567 .get(&buffer_id)
568 .map(|state| {
569 let line_count = state.buffer.line_count().unwrap_or(1000);
570 (line_count.saturating_sub(1) as u32, 10000u32)
571 })
572 .unwrap_or((999, 10000));
573
574 let request_id = self.next_lsp_request_id;
575 self.next_lsp_request_id += 1;
576 if let Err(e) = handle.inlay_hints(request_id, uri, 0, 0, last_line, last_char) {
577 tracing::warn!("LSP inlay_hints request failed: {}", e);
578 }
579 }
580
581 self.schedule_folding_ranges_refresh(buffer_id);
583 }
584
585 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
591 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
592
593 #[cfg(feature = "embed-plugins")]
595 let fresh_dts_path = {
596 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
597 else {
598 tracing::warn!(
599 "Cannot set up plugin dev LSP: embedded plugins directory not available"
600 );
601 return;
602 };
603 let path = embedded_dir.join("lib").join("fresh.d.ts");
604 if !path.exists() {
605 tracing::warn!(
606 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
607 path
608 );
609 return;
610 }
611 path
612 };
613
614 #[cfg(not(feature = "embed-plugins"))]
615 let fresh_dts_path = {
616 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
618 .join("plugins")
619 .join("lib")
620 .join("fresh.d.ts");
621 if !path.exists() {
622 tracing::warn!(
623 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
624 path
625 );
626 return;
627 }
628 path
629 };
630
631 let buffer_id_num: usize = buffer_id.0;
633 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
634 Ok(workspace) => {
635 let plugin_file = workspace.plugin_file.clone();
636
637 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
639 if let Some(uri) = super::types::file_path_to_lsp_uri(&plugin_file) {
640 metadata.kind = super::types::BufferKind::File {
641 path: plugin_file.clone(),
642 uri: Some(uri),
643 };
644 metadata.lsp_enabled = true;
645 metadata.lsp_disabled_reason = None;
646 metadata.lsp_opened_with.clear();
648
649 tracing::info!(
650 "Plugin dev LSP enabled for buffer {} via {:?}",
651 buffer_id_num,
652 plugin_file
653 );
654 }
655 }
656
657 if let Some(state) = self.buffers.get_mut(&buffer_id) {
659 let detected =
660 crate::primitives::detected_language::DetectedLanguage::from_path(
661 &plugin_file,
662 &self.grammar_registry,
663 &self.config.languages,
664 );
665 state.apply_language(detected);
666 }
667
668 if let Some(lsp) = &mut self.lsp {
670 lsp.allow_language("typescript");
671 }
672
673 let workspace_dir = workspace.dir().to_path_buf();
675 self.plugin_dev_workspaces.insert(buffer_id, workspace);
676
677 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
679
680 if let Some(lsp) = &self.lsp {
682 if let Some(handle) = lsp.get_handle("typescript") {
683 if let Some(uri) = super::types::file_path_to_lsp_uri(&workspace_dir) {
684 let name = workspace_dir
685 .file_name()
686 .unwrap_or_default()
687 .to_string_lossy()
688 .into_owned();
689 if let Err(e) = handle.add_workspace_folder(uri, name) {
690 tracing::warn!("Failed to add plugin workspace folder: {}", e);
691 } else {
692 tracing::info!(
693 "Added plugin workspace folder: {:?}",
694 workspace_dir
695 );
696 }
697 }
698 }
699 }
700 }
701 Err(e) => {
702 tracing::warn!("Failed to create plugin dev workspace: {}", e);
703 }
704 }
705 }
706}