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 .into_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 .into_iter()
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 handle_lsp_status_action(&mut self, action_key: &str) {
323 if let Some(language) = action_key.strip_prefix("start:") {
324 let file_path = self
326 .buffer_metadata
327 .get(&self.active_buffer())
328 .and_then(|meta| meta.file_path().cloned());
329
330 if let Some(lsp) = self.lsp.as_mut() {
331 let (_, message) = lsp.manual_restart(language, file_path.as_deref());
332 self.status_message = Some(message);
333 } else {
334 self.status_message = Some("No LSP manager available".to_string());
335 }
336 self.reopen_buffers_for_language(language);
337 } else if let Some(target) = action_key.strip_prefix("restart:") {
338 if let Some((language, server_name)) = target.split_once('/') {
340 let file_path = self
341 .buffer_metadata
342 .get(&self.active_buffer())
343 .and_then(|meta| meta.file_path().cloned());
344
345 if let Some(lsp) = self.lsp.as_mut() {
346 lsp.shutdown_server_by_name(language, server_name);
348 }
349 self.lsp_server_statuses
351 .remove(&(language.to_string(), server_name.to_string()));
352 self.update_lsp_status_from_server_statuses();
353
354 if let Some(lsp) = self.lsp.as_mut() {
355 let _ = lsp.manual_restart(language, file_path.as_deref());
356 }
357 self.reopen_buffers_for_language(language);
358 self.status_message = Some(format!(
359 "Restarting LSP server: {}/{}",
360 language, server_name
361 ));
362 }
363 } else if let Some(target) = action_key.strip_prefix("stop:") {
364 if let Some((language, server_name)) = target.split_once('/') {
365 let stopped = if let Some(lsp) = self.lsp.as_mut() {
366 lsp.shutdown_server_by_name(language, server_name)
367 } else {
368 false
369 };
370 if stopped {
371 self.lsp_server_statuses
372 .remove(&(language.to_string(), server_name.to_string()));
373 self.update_lsp_status_from_server_statuses();
374 self.status_message =
375 Some(format!("Stopped LSP server: {}/{}", language, server_name));
376 } else {
377 self.status_message = Some(format!(
378 "LSP server not running: {}/{}",
379 language, server_name
380 ));
381 }
382 }
383 } else if let Some(language) = action_key.strip_prefix("log:") {
384 let log_path = crate::services::log_dirs::lsp_log_path(language);
385 if log_path.exists() {
386 match self.open_local_file(&log_path) {
387 Ok(buffer_id) => {
388 self.mark_buffer_read_only(buffer_id, true);
389 }
390 Err(e) => {
391 self.status_message = Some(format!("Failed to open LSP log: {}", e));
392 }
393 }
394 } else {
395 self.status_message = Some(format!("No log file found for {}", language));
396 }
397 }
398 }
399
400 pub fn toggle_fold_at_cursor(&mut self) {
402 let buffer_id = self.active_buffer();
403 let pos = self.active_cursors().primary().position;
404 self.toggle_fold_at_byte(buffer_id, pos);
405 }
406
407 pub fn toggle_fold_at_line(&mut self, buffer_id: BufferId, line: usize) {
413 let byte_pos = {
414 let Some(state) = self.buffers.get(&buffer_id) else {
415 return;
416 };
417 state.buffer.line_start_offset(line).unwrap_or_else(|| {
418 use crate::view::folding::indent_folding;
419 let approx = line * state.buffer.estimated_line_length();
420 indent_folding::find_line_start_byte(&state.buffer, approx)
421 })
422 };
423 self.toggle_fold_at_byte(buffer_id, byte_pos);
424 }
425
426 pub fn toggle_fold_at_byte(&mut self, buffer_id: BufferId, byte_pos: usize) {
428 let split_id = self.split_manager.active_split();
429 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
430
431 let Some(state) = buffers.get_mut(&buffer_id) else {
432 return;
433 };
434
435 let Some(view_state) = split_view_states.get_mut(&split_id) else {
436 return;
437 };
438 let buf_state = view_state.ensure_buffer_state(buffer_id);
439
440 let header_byte = {
442 use crate::view::folding::indent_folding;
443 indent_folding::find_line_start_byte(&state.buffer, byte_pos)
444 };
445 if buf_state
446 .folds
447 .remove_by_header_byte(&state.buffer, &mut state.marker_list, header_byte)
448 {
449 return;
450 }
451
452 if buf_state
454 .folds
455 .remove_if_contains_byte(&mut state.marker_list, byte_pos)
456 {
457 return;
458 }
459
460 if !state.folding_ranges.is_empty() {
462 let line = state.buffer.get_line_number(byte_pos);
465 let mut exact_range: Option<&lsp_types::FoldingRange> = None;
466 let mut exact_span = usize::MAX;
467 let mut containing_range: Option<&lsp_types::FoldingRange> = None;
468 let mut containing_span = usize::MAX;
469
470 for range in &state.folding_ranges {
471 let start_line = range.start_line as usize;
472 let range_end = range.end_line as usize;
473 if range_end <= start_line {
474 continue;
475 }
476 let span = range_end.saturating_sub(start_line);
477
478 if start_line == line && span < exact_span {
479 exact_span = span;
480 exact_range = Some(range);
481 }
482 if start_line <= line && line <= range_end && span < containing_span {
483 containing_span = span;
484 containing_range = Some(range);
485 }
486 }
487
488 let chosen = exact_range.or(containing_range);
489 let Some(range) = chosen else {
490 return;
491 };
492 let placeholder = range
493 .collapsed_text
494 .as_ref()
495 .filter(|text| !text.trim().is_empty())
496 .cloned();
497 let header_line = range.start_line as usize;
498 let end_line = range.end_line as usize;
499 let first_hidden = header_line.saturating_add(1);
500 if first_hidden > end_line {
501 return;
502 }
503 let Some(sb) = state.buffer.line_start_offset(first_hidden) else {
504 return;
505 };
506 let eb = state
507 .buffer
508 .line_start_offset(end_line.saturating_add(1))
509 .unwrap_or_else(|| state.buffer.len());
510 let hb = state.buffer.line_start_offset(header_line).unwrap_or(0);
511 Self::create_fold(state, buf_state, sb, eb, hb, placeholder);
512 } else {
513 use crate::view::folding::indent_folding;
515 let tab_size = state.buffer_settings.tab_size;
516 let max_upward = crate::config::INDENT_FOLD_MAX_UPWARD_SCAN;
517 let est_ll = state.buffer.estimated_line_length();
518 let max_scan_bytes = crate::config::INDENT_FOLD_MAX_SCAN_LINES * est_ll;
519
520 let upward_bytes = max_upward * est_ll;
523 let load_start = byte_pos.saturating_sub(upward_bytes);
524 let load_end = byte_pos
525 .saturating_add(max_scan_bytes)
526 .min(state.buffer.len());
527 drop(
530 state
531 .buffer
532 .get_text_range_mut(load_start, load_end - load_start),
533 );
534
535 if let Some((hb, sb, eb)) = indent_folding::find_fold_range_at_byte(
536 &state.buffer,
537 byte_pos,
538 tab_size,
539 max_scan_bytes,
540 max_upward,
541 ) {
542 Self::create_fold(state, buf_state, sb, eb, hb, None);
543 }
544 }
545 }
546
547 fn create_fold(
548 state: &mut crate::state::EditorState,
549 buf_state: &mut crate::view::split::BufferViewState,
550 start_byte: usize,
551 end_byte: usize,
552 header_byte: usize,
553 placeholder: Option<String>,
554 ) {
555 if end_byte <= start_byte {
556 return;
557 }
558
559 buf_state.cursors.map(|cursor| {
561 let in_hidden_range = cursor.position >= start_byte && cursor.position < end_byte;
562 let anchor_in_hidden = cursor
563 .anchor
564 .is_some_and(|anchor| anchor >= start_byte && anchor < end_byte);
565 if in_hidden_range || anchor_in_hidden {
566 cursor.position = header_byte;
567 cursor.anchor = None;
568 cursor.sticky_column = 0;
569 cursor.selection_mode = crate::model::cursor::SelectionMode::Normal;
570 cursor.block_anchor = None;
571 cursor.deselect_on_move = true;
572 }
573 });
574
575 buf_state
576 .folds
577 .add(&mut state.marker_list, start_byte, end_byte, placeholder);
578
579 if buf_state.viewport.top_byte >= start_byte && buf_state.viewport.top_byte < end_byte {
581 buf_state.viewport.top_byte = header_byte;
582 buf_state.viewport.top_view_line_offset = 0;
583 }
584 }
585
586 pub(crate) fn send_did_close_to_server(&mut self, language: &str, server_name: &str) {
591 let uris: Vec<_> = self
592 .buffers
593 .iter()
594 .filter(|(_, s)| s.language == language)
595 .filter_map(|(id, _)| {
596 self.buffer_metadata
597 .get(id)
598 .and_then(|m| m.file_uri())
599 .cloned()
600 })
601 .collect();
602
603 if let Some(lsp) = self.lsp.as_mut() {
604 for sh in lsp.get_handles_mut(language) {
605 if sh.name == server_name {
606 for uri in &uris {
607 tracing::info!(
608 "Sending didClose for {} to '{}' (language: {})",
609 uri.as_str(),
610 sh.name,
611 language
612 );
613 if let Err(e) = sh.handle.did_close(uri.clone()) {
614 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
615 }
616 }
617 break;
618 }
619 }
620 }
621 }
622
623 pub(crate) fn disable_lsp_for_buffer(&mut self, buffer_id: crate::model::event::BufferId) {
625 if let Some(uri) = self
631 .buffer_metadata
632 .get(&buffer_id)
633 .and_then(|m| m.file_uri())
634 .cloned()
635 {
636 let language = self
637 .buffers
638 .get(&buffer_id)
639 .map(|s| s.language.clone())
640 .unwrap_or_default();
641 if let Some(lsp) = self.lsp.as_mut() {
642 if !lsp.has_handles(&language) {
644 tracing::warn!(
645 "disable_lsp_for_buffer: no handle for language '{}'",
646 language
647 );
648 } else {
649 for sh in lsp.get_handles_mut(&language) {
650 tracing::info!(
651 "Sending didClose for {} to '{}' (language: {})",
652 uri.as_str(),
653 sh.name,
654 language
655 );
656 if let Err(e) = sh.handle.did_close(uri.clone()) {
657 tracing::warn!("Failed to send didClose to '{}': {}", sh.name, e);
658 }
659 }
660 }
661 } else {
662 tracing::warn!("disable_lsp_for_buffer: no LSP manager");
663 }
664 } else {
665 tracing::warn!("disable_lsp_for_buffer: no URI for buffer");
666 }
667
668 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
670 metadata.disable_lsp(t!("lsp.disabled.user").to_string());
671 metadata.lsp_opened_with.clear();
673 }
674 self.set_status_message(t!("lsp.disabled_for_buffer").to_string());
675
676 let uri = self
678 .buffer_metadata
679 .get(&buffer_id)
680 .and_then(|m| m.file_uri())
681 .map(|u| u.as_str().to_string());
682
683 if let Some(uri_str) = uri {
684 self.stored_diagnostics.remove(&uri_str);
685 self.stored_push_diagnostics.remove(&uri_str);
686 self.stored_pull_diagnostics.remove(&uri_str);
687 self.diagnostic_result_ids.remove(&uri_str);
688 self.stored_folding_ranges.remove(&uri_str);
689 }
690
691 if let Some((scheduled_buf, _)) = &self.scheduled_diagnostic_pull {
693 if *scheduled_buf == buffer_id {
694 self.scheduled_diagnostic_pull = None;
695 }
696 }
697
698 self.folding_ranges_in_flight.remove(&buffer_id);
699 self.folding_ranges_debounce.remove(&buffer_id);
700 self.pending_folding_range_requests
701 .retain(|_, req| req.buffer_id != buffer_id);
702
703 let diagnostic_ns = crate::services::lsp::diagnostics::lsp_diagnostic_namespace();
705 let (buffers, split_view_states) = (&mut self.buffers, &mut self.split_view_states);
706 if let Some(state) = buffers.get_mut(&buffer_id) {
707 state
708 .overlays
709 .clear_namespace(&diagnostic_ns, &mut state.marker_list);
710 state.virtual_texts.clear(&mut state.marker_list);
711 state.folding_ranges.clear();
712 for view_state in split_view_states.values_mut() {
713 if let Some(buf_state) = view_state.keyed_states.get_mut(&buffer_id) {
714 buf_state.folds.clear(&mut state.marker_list);
715 }
716 }
717 }
718 }
719
720 fn enable_lsp_for_buffer(
722 &mut self,
723 buffer_id: crate::model::event::BufferId,
724 language: &str,
725 file_path: Option<std::path::PathBuf>,
726 ) {
727 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
729 metadata.lsp_enabled = true;
730 metadata.lsp_disabled_reason = None;
731 }
732 self.set_status_message(t!("lsp.enabled_for_buffer").to_string());
733
734 if let Some(_path) = file_path {
736 self.send_lsp_did_open_for_buffer(buffer_id, language);
737 }
738 }
739
740 fn send_lsp_did_open_for_buffer(
742 &mut self,
743 buffer_id: crate::model::event::BufferId,
744 language: &str,
745 ) {
746 let (uri, text) = {
748 let metadata = self.buffer_metadata.get(&buffer_id);
749 let uri = metadata.and_then(|m| m.file_uri()).cloned();
750 let text = self
751 .buffers
752 .get(&buffer_id)
753 .and_then(|state| state.buffer.to_string());
754 (uri, text)
755 };
756
757 let Some(uri) = uri else { return };
758 let Some(text) = text else { return };
759
760 use crate::services::lsp::manager::LspSpawnResult;
762 let file_path = self
763 .buffer_metadata
764 .get(&buffer_id)
765 .and_then(|m| m.file_path())
766 .cloned();
767 let Some(lsp) = self.lsp.as_mut() else {
768 return;
769 };
770
771 if lsp.try_spawn(language, file_path.as_deref()) != LspSpawnResult::Spawned {
772 return;
773 }
774
775 let Some(handle) = lsp.get_handle_mut(language) else {
776 return;
777 };
778
779 let handle_id = handle.id();
780 if let Err(e) = handle.did_open(uri.clone(), text, language.to_string()) {
781 tracing::warn!("Failed to send didOpen to LSP: {}", e);
782 return;
783 }
784
785 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
787 metadata.lsp_opened_with.insert(handle_id);
788 }
789
790 let request_id = self.next_lsp_request_id;
792 self.next_lsp_request_id += 1;
793 let previous_result_id = self.diagnostic_result_ids.get(uri.as_str()).cloned();
794 if let Err(e) = handle.document_diagnostic(request_id, uri.clone(), previous_result_id) {
795 tracing::warn!("LSP document_diagnostic request failed: {}", e);
796 }
797
798 if self.config.editor.enable_inlay_hints {
800 let (last_line, last_char) = self
801 .buffers
802 .get(&buffer_id)
803 .map(|state| {
804 let line_count = state.buffer.line_count().unwrap_or(1000);
805 (line_count.saturating_sub(1) as u32, 10000u32)
806 })
807 .unwrap_or((999, 10000));
808
809 let request_id = self.next_lsp_request_id;
810 self.next_lsp_request_id += 1;
811 if let Err(e) = handle.inlay_hints(request_id, uri, 0, 0, last_line, last_char) {
812 tracing::warn!("LSP inlay_hints request failed: {}", e);
813 }
814 }
815
816 self.schedule_folding_ranges_refresh(buffer_id);
818 }
819
820 pub(crate) fn setup_plugin_dev_lsp(&mut self, buffer_id: BufferId, content: &str) {
826 use crate::services::plugins::plugin_dev_workspace::PluginDevWorkspace;
827
828 #[cfg(feature = "embed-plugins")]
830 let fresh_dts_path = {
831 let Some(embedded_dir) = crate::services::plugins::embedded::get_embedded_plugins_dir()
832 else {
833 tracing::warn!(
834 "Cannot set up plugin dev LSP: embedded plugins directory not available"
835 );
836 return;
837 };
838 let path = embedded_dir.join("lib").join("fresh.d.ts");
839 if !path.exists() {
840 tracing::warn!(
841 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
842 path
843 );
844 return;
845 }
846 path
847 };
848
849 #[cfg(not(feature = "embed-plugins"))]
850 let fresh_dts_path = {
851 let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
853 .join("plugins")
854 .join("lib")
855 .join("fresh.d.ts");
856 if !path.exists() {
857 tracing::warn!(
858 "Cannot set up plugin dev LSP: fresh.d.ts not found at {:?}",
859 path
860 );
861 return;
862 }
863 path
864 };
865
866 let buffer_id_num: usize = buffer_id.0;
868 match PluginDevWorkspace::create(buffer_id_num, content, &fresh_dts_path) {
869 Ok(workspace) => {
870 let plugin_file = workspace.plugin_file.clone();
871
872 if let Some(metadata) = self.buffer_metadata.get_mut(&buffer_id) {
874 if let Some(uri) = super::types::file_path_to_lsp_uri(&plugin_file) {
875 metadata.kind = super::types::BufferKind::File {
876 path: plugin_file.clone(),
877 uri: Some(uri),
878 };
879 metadata.lsp_enabled = true;
880 metadata.lsp_disabled_reason = None;
881 metadata.lsp_opened_with.clear();
883
884 tracing::info!(
885 "Plugin dev LSP enabled for buffer {} via {:?}",
886 buffer_id_num,
887 plugin_file
888 );
889 }
890 }
891
892 if let Some(state) = self.buffers.get_mut(&buffer_id) {
894 let detected =
895 crate::primitives::detected_language::DetectedLanguage::from_path(
896 &plugin_file,
897 &self.grammar_registry,
898 &self.config.languages,
899 );
900 state.apply_language(detected);
901 }
902
903 if let Some(lsp) = &mut self.lsp {
905 lsp.allow_language("typescript");
906 }
907
908 let workspace_dir = workspace.dir().to_path_buf();
910 self.plugin_dev_workspaces.insert(buffer_id, workspace);
911
912 self.send_lsp_did_open_for_buffer(buffer_id, "typescript");
914
915 if let Some(lsp) = &self.lsp {
917 if let Some(handle) = lsp.get_handle("typescript") {
918 if let Some(uri) = super::types::file_path_to_lsp_uri(&workspace_dir) {
919 let name = workspace_dir
920 .file_name()
921 .unwrap_or_default()
922 .to_string_lossy()
923 .into_owned();
924 if let Err(e) = handle.add_workspace_folder(uri, name) {
925 tracing::warn!("Failed to add plugin workspace folder: {}", e);
926 } else {
927 tracing::info!(
928 "Added plugin workspace folder: {:?}",
929 workspace_dir
930 );
931 }
932 }
933 }
934 }
935 }
936 Err(e) => {
937 tracing::warn!("Failed to create plugin dev workspace: {}", e);
938 }
939 }
940 }
941}