1use std::ops::Range;
15
16use rust_i18n::t;
17
18use crate::model::event::Event;
19
20use super::Editor;
21
22impl Editor {
23 pub fn add_overlay(
27 &mut self,
28 namespace: Option<crate::view::overlay::OverlayNamespace>,
29 range: Range<usize>,
30 face: crate::model::event::OverlayFace,
31 priority: i32,
32 message: Option<String>,
33 ) -> crate::view::overlay::OverlayHandle {
34 let event = Event::AddOverlay {
35 namespace,
36 range,
37 face,
38 priority,
39 message,
40 extend_to_line_end: false,
41 url: None,
42 };
43 self.apply_event_to_active_buffer(&event);
44 let state = self.active_state();
46 state
47 .overlays
48 .all()
49 .last()
50 .map(|o| o.handle.clone())
51 .unwrap_or_default()
52 }
53
54 pub fn remove_overlay(&mut self, handle: crate::view::overlay::OverlayHandle) {
56 let event = Event::RemoveOverlay { handle };
57 self.apply_event_to_active_buffer(&event);
58 }
59
60 pub fn remove_overlays_in_range(&mut self, range: Range<usize>) {
62 let event = Event::RemoveOverlaysInRange { range };
63 self.active_event_log_mut().append(event.clone());
64 self.apply_event_to_active_buffer(&event);
65 }
66
67 pub fn clear_overlays(&mut self) {
69 let event = Event::ClearOverlays;
70 self.active_event_log_mut().append(event.clone());
71 self.apply_event_to_active_buffer(&event);
72 }
73
74 pub fn show_popup(&mut self, popup: crate::model::event::PopupData) {
78 let event = Event::ShowPopup { popup };
79 self.active_event_log_mut().append(event.clone());
80 self.apply_event_to_active_buffer(&event);
81 }
82
83 pub fn show_popup_with_resolver(
88 &mut self,
89 popup: crate::model::event::PopupData,
90 resolver: crate::view::popup::PopupResolver,
91 ) {
92 self.show_popup(popup);
93 if let Some(top) = self.active_state_mut().popups.top_mut() {
94 top.resolver = resolver;
95 }
96 }
97
98 pub fn hide_popup(&mut self) {
100 if self.global_popups.is_visible() {
104 self.global_popups.hide();
105
106 if let Some(handle) = self.hover.take_symbol_overlay() {
110 let remove_overlay_event = crate::model::event::Event::RemoveOverlay { handle };
111 self.apply_event_to_active_buffer(&remove_overlay_event);
112 }
113 self.hover.set_symbol_range(None);
114 return;
115 }
116
117 let event = Event::HidePopup;
118 self.active_event_log_mut().append(event.clone());
119 self.apply_event_to_active_buffer(&event);
120
121 let active = self.active_buffer();
123 if let Some((wait_id, true)) = self.wait_tracking.remove(&active) {
124 self.completed_waits.push(wait_id);
125 }
126
127 if let Some(handle) = self.hover.take_symbol_overlay() {
129 let remove_overlay_event = crate::model::event::Event::RemoveOverlay { handle };
130 self.apply_event_to_active_buffer(&remove_overlay_event);
131 }
132 self.hover.set_symbol_range(None);
133 }
134
135 pub(super) fn dismiss_transient_popups(&mut self) {
138 let is_transient_popup = self
141 .active_state()
142 .popups
143 .top()
144 .is_some_and(|p| p.transient);
145
146 if is_transient_popup {
147 self.hide_popup();
148 tracing::trace!("Dismissed transient popup");
149 }
150 }
151
152 pub(super) fn scroll_popup(&mut self, delta: i32) {
155 if let Some(popup) = self.global_popups.top_mut() {
156 popup.scroll_by(delta);
157 return;
158 }
159 if let Some(popup) = self.active_state_mut().popups.top_mut() {
160 popup.scroll_by(delta);
161 tracing::debug!(
162 "Scrolled popup by {}, new offset: {}",
163 delta,
164 popup.scroll_offset
165 );
166 }
167 }
168
169 pub(super) fn on_editor_focus_lost(&mut self) {
177 self.active_state_mut().on_focus_lost();
179
180 self.mouse_state.lsp_hover_state = None;
182 self.mouse_state.lsp_hover_request_sent = false;
183 self.hover.clear_pending();
184
185 if let Some(handle) = self.hover.take_symbol_overlay() {
187 let remove_overlay_event = crate::model::event::Event::RemoveOverlay { handle };
188 self.apply_event_to_active_buffer(&remove_overlay_event);
189 }
190 self.hover.set_symbol_range(None);
191
192 self.goto_line_preview = None;
196 }
197
198 pub fn clear_popups(&mut self) {
200 let event = Event::ClearPopups;
201 self.active_event_log_mut().append(event.clone());
202 self.apply_event_to_active_buffer(&event);
203 }
204
205 pub fn show_lsp_confirmation_popup(&mut self, language: &str) {
212 use crate::model::event::{
213 PopupContentData, PopupData, PopupKindHint, PopupListItemData, PopupPositionData,
214 };
215
216 let server_info = if let Some(lsp) = &self.lsp {
218 if let Some(config) = lsp.get_config(language) {
219 if !config.command.is_empty() {
220 format!("{} ({})", language, config.command)
221 } else {
222 language.to_string()
223 }
224 } else {
225 language.to_string()
226 }
227 } else {
228 language.to_string()
229 };
230
231 let popup = PopupData {
232 kind: PopupKindHint::List,
233 title: Some(format!("Start LSP Server: {}?", server_info)),
234 description: None,
235 transient: false,
236 content: PopupContentData::List {
237 items: vec![
238 PopupListItemData {
239 text: "Allow this time".to_string(),
240 detail: Some("Start the LSP server for this session".to_string()),
241 icon: None,
242 data: Some("allow_once".to_string()),
243 },
244 PopupListItemData {
245 text: "Always allow".to_string(),
246 detail: Some("Always start this LSP server automatically".to_string()),
247 icon: None,
248 data: Some("allow_always".to_string()),
249 },
250 PopupListItemData {
251 text: "Don't start".to_string(),
252 detail: Some("Cancel LSP server startup".to_string()),
253 icon: None,
254 data: Some("deny".to_string()),
255 },
256 ],
257 selected: 0,
258 },
259 position: PopupPositionData::Centered,
260 width: 50,
261 max_height: 8,
262 bordered: true,
263 };
264
265 self.show_popup_with_resolver(
269 popup,
270 crate::view::popup::PopupResolver::LspConfirm {
271 language: language.to_string(),
272 },
273 );
274 }
275
276 pub fn handle_lsp_confirmation_response(&mut self, language: &str, action: &str) -> bool {
286 let language = language.to_string();
287
288 let file_path = self
290 .buffer_metadata
291 .get(&self.active_buffer())
292 .and_then(|meta| meta.file_path().cloned());
293
294 match action {
295 "allow_once" => {
296 if let Some(lsp) = &mut self.lsp {
298 lsp.allow_language(&language);
300 if lsp.force_spawn(&language, file_path.as_deref()).is_some() {
302 tracing::info!("LSP server for {} started (allowed once)", language);
303 self.set_status_message(
304 t!("lsp.server_started", language = language).to_string(),
305 );
306 } else {
307 self.set_status_message(
308 t!("lsp.failed_to_start", language = language).to_string(),
309 );
310 }
311 }
312 self.notify_lsp_current_file_opened(&language);
314 }
315 "allow_always" => {
316 if let Some(lsp) = &mut self.lsp {
318 lsp.allow_language(&language);
319 if lsp.force_spawn(&language, file_path.as_deref()).is_some() {
321 tracing::info!("LSP server for {} started (always allowed)", language);
322 self.set_status_message(
323 t!("lsp.server_started_auto", language = language).to_string(),
324 );
325 } else {
326 self.set_status_message(
327 t!("lsp.failed_to_start", language = language).to_string(),
328 );
329 }
330 }
331 self.notify_lsp_current_file_opened(&language);
333 }
334 _ => {
335 tracing::info!("LSP server for {} startup declined by user", language);
337 self.set_status_message(
338 t!("lsp.startup_cancelled", language = language).to_string(),
339 );
340 }
341 }
342
343 true
344 }
345
346 fn notify_lsp_current_file_opened(&mut self, language: &str) {
351 let metadata = match self.buffer_metadata.get(&self.active_buffer()) {
353 Some(m) => m,
354 None => {
355 tracing::debug!(
356 "notify_lsp_current_file_opened: no metadata for buffer {:?}",
357 self.active_buffer()
358 );
359 return;
360 }
361 };
362
363 if !metadata.lsp_enabled {
364 tracing::debug!("notify_lsp_current_file_opened: LSP disabled for this buffer");
365 return;
366 }
367
368 let file_path = metadata.file_path().cloned();
370
371 let uri = match metadata.file_uri() {
373 Some(u) => u.clone(),
374 None => {
375 tracing::debug!(
376 "notify_lsp_current_file_opened: no URI for buffer (not a file or URI creation failed)"
377 );
378 return;
379 }
380 };
381
382 let active_buffer = self.active_buffer();
384
385 let file_language = match self.buffers.get(&active_buffer).map(|s| s.language.clone()) {
387 Some(l) => l,
388 None => {
389 tracing::debug!("notify_lsp_current_file_opened: no buffer state");
390 return;
391 }
392 };
393
394 if file_language != language {
396 tracing::debug!(
397 "notify_lsp_current_file_opened: file language {} doesn't match server {}",
398 file_language,
399 language
400 );
401 return;
402 }
403 let (text, line_count, buffer_version) =
404 if let Some(state) = self.buffers.get(&active_buffer) {
405 let text = match state.buffer.to_string() {
406 Some(t) => t,
407 None => {
408 tracing::debug!("notify_lsp_current_file_opened: buffer not fully loaded");
409 return;
410 }
411 };
412 let line_count = state.buffer.line_count().unwrap_or(1000);
413 (text, line_count, state.buffer.version())
414 } else {
415 tracing::debug!("notify_lsp_current_file_opened: no buffer state");
416 return;
417 };
418
419 if let Some(lsp) = &mut self.lsp {
421 if lsp.force_spawn(language, file_path.as_deref()).is_some() {
423 tracing::info!("Sending didOpen to LSP servers for: {}", uri.as_str());
424 let mut any_opened = false;
425 for sh in lsp.get_handles_mut(language) {
426 if let Err(e) =
427 sh.handle
428 .did_open(uri.clone(), text.clone(), file_language.clone())
429 {
430 tracing::warn!("Failed to send didOpen to '{}': {}", sh.name, e);
431 } else {
432 any_opened = true;
433 }
434 }
435
436 if any_opened {
437 tracing::info!("Successfully sent didOpen to LSP after confirmation");
438
439 if let Some(handle) = lsp.get_handle_mut(language) {
441 let previous_result_id =
442 self.diagnostic_result_ids.get(uri.as_str()).cloned();
443 let request_id = self.next_lsp_request_id;
444 self.next_lsp_request_id += 1;
445
446 if let Err(e) =
447 handle.document_diagnostic(request_id, uri.clone(), previous_result_id)
448 {
449 tracing::debug!(
450 "Failed to request pull diagnostics (server may not support): {}",
451 e
452 );
453 }
454
455 if self.config.editor.enable_inlay_hints {
457 let request_id = self.next_lsp_request_id;
458 self.next_lsp_request_id += 1;
459
460 let last_line = line_count.saturating_sub(1) as u32;
461 let last_char = 10000u32;
462
463 if let Err(e) = handle.inlay_hints(
464 request_id,
465 uri.clone(),
466 0,
467 0,
468 last_line,
469 last_char,
470 ) {
471 tracing::debug!(
472 "Failed to request inlay hints (server may not support): {}",
473 e
474 );
475 } else {
476 self.pending_inlay_hints_requests.insert(
477 request_id,
478 super::InlayHintsRequest {
479 buffer_id: active_buffer,
480 version: buffer_version,
481 },
482 );
483 }
484 }
485 }
486 }
487 }
488 }
489 }
490
491 pub fn has_pending_lsp_confirmation(&self) -> bool {
496 use crate::view::popup::PopupResolver;
497 let matches_lsp_confirm = |p: &crate::view::popup::Popup| -> bool {
498 matches!(p.resolver, PopupResolver::LspConfirm { .. })
499 };
500 self.global_popups.top().is_some_and(matches_lsp_confirm)
501 || self
502 .active_state()
503 .popups
504 .top()
505 .is_some_and(matches_lsp_confirm)
506 }
507
508 pub fn popup_select_next(&mut self) {
510 if let Some(popup) = self.global_popups.top_mut() {
511 popup.select_next();
512 return;
513 }
514 let event = Event::PopupSelectNext;
515 self.active_event_log_mut().append(event.clone());
516 self.apply_event_to_active_buffer(&event);
517 }
518
519 pub fn popup_select_prev(&mut self) {
521 if let Some(popup) = self.global_popups.top_mut() {
522 popup.select_prev();
523 return;
524 }
525 let event = Event::PopupSelectPrev;
526 self.active_event_log_mut().append(event.clone());
527 self.apply_event_to_active_buffer(&event);
528 }
529
530 pub fn popup_page_down(&mut self) {
532 if let Some(popup) = self.global_popups.top_mut() {
533 popup.page_down();
534 return;
535 }
536 let event = Event::PopupPageDown;
537 self.active_event_log_mut().append(event.clone());
538 self.apply_event_to_active_buffer(&event);
539 }
540
541 pub fn popup_page_up(&mut self) {
543 if let Some(popup) = self.global_popups.top_mut() {
544 popup.page_up();
545 return;
546 }
547 let event = Event::PopupPageUp;
548 self.active_event_log_mut().append(event.clone());
549 self.apply_event_to_active_buffer(&event);
550 }
551}