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 hide_popup(&mut self) {
85 let event = Event::HidePopup;
86 self.active_event_log_mut().append(event.clone());
87 self.apply_event_to_active_buffer(&event);
88
89 let active = self.active_buffer();
91 if let Some((wait_id, true)) = self.wait_tracking.remove(&active) {
92 self.completed_waits.push(wait_id);
93 }
94
95 if let Some(handle) = self.hover.take_symbol_overlay() {
97 let remove_overlay_event = crate::model::event::Event::RemoveOverlay { handle };
98 self.apply_event_to_active_buffer(&remove_overlay_event);
99 }
100 self.hover.set_symbol_range(None);
101 }
102
103 pub(super) fn dismiss_transient_popups(&mut self) {
106 let is_transient_popup = self
107 .active_state()
108 .popups
109 .top()
110 .is_some_and(|p| p.transient);
111
112 if is_transient_popup {
113 self.hide_popup();
114 tracing::trace!("Dismissed transient popup");
115 }
116 }
117
118 pub(super) fn scroll_popup(&mut self, delta: i32) {
121 if let Some(popup) = self.active_state_mut().popups.top_mut() {
122 popup.scroll_by(delta);
123 tracing::debug!(
124 "Scrolled popup by {}, new offset: {}",
125 delta,
126 popup.scroll_offset
127 );
128 }
129 }
130
131 pub(super) fn on_editor_focus_lost(&mut self) {
139 self.active_state_mut().on_focus_lost();
141
142 self.mouse_state.lsp_hover_state = None;
144 self.mouse_state.lsp_hover_request_sent = false;
145 self.hover.clear_pending();
146
147 if let Some(handle) = self.hover.take_symbol_overlay() {
149 let remove_overlay_event = crate::model::event::Event::RemoveOverlay { handle };
150 self.apply_event_to_active_buffer(&remove_overlay_event);
151 }
152 self.hover.set_symbol_range(None);
153 }
154
155 pub fn clear_popups(&mut self) {
157 let event = Event::ClearPopups;
158 self.active_event_log_mut().append(event.clone());
159 self.apply_event_to_active_buffer(&event);
160 }
161
162 pub fn show_lsp_confirmation_popup(&mut self, language: &str) {
169 use crate::model::event::{
170 PopupContentData, PopupData, PopupKindHint, PopupListItemData, PopupPositionData,
171 };
172
173 self.pending_lsp_confirmation = Some(language.to_string());
175
176 let server_info = if let Some(lsp) = &self.lsp {
178 if let Some(config) = lsp.get_config(language) {
179 if !config.command.is_empty() {
180 format!("{} ({})", language, config.command)
181 } else {
182 language.to_string()
183 }
184 } else {
185 language.to_string()
186 }
187 } else {
188 language.to_string()
189 };
190
191 let popup = PopupData {
192 kind: PopupKindHint::List,
193 title: Some(format!("Start LSP Server: {}?", server_info)),
194 description: None,
195 transient: false,
196 content: PopupContentData::List {
197 items: vec![
198 PopupListItemData {
199 text: "Allow this time".to_string(),
200 detail: Some("Start the LSP server for this session".to_string()),
201 icon: None,
202 data: Some("allow_once".to_string()),
203 },
204 PopupListItemData {
205 text: "Always allow".to_string(),
206 detail: Some("Always start this LSP server automatically".to_string()),
207 icon: None,
208 data: Some("allow_always".to_string()),
209 },
210 PopupListItemData {
211 text: "Don't start".to_string(),
212 detail: Some("Cancel LSP server startup".to_string()),
213 icon: None,
214 data: Some("deny".to_string()),
215 },
216 ],
217 selected: 0,
218 },
219 position: PopupPositionData::Centered,
220 width: 50,
221 max_height: 8,
222 bordered: true,
223 };
224
225 self.show_popup(popup);
226 }
227
228 pub fn handle_lsp_confirmation_response(&mut self, action: &str) -> bool {
236 let Some(language) = self.pending_lsp_confirmation.take() else {
237 return false;
238 };
239
240 let file_path = self
242 .buffer_metadata
243 .get(&self.active_buffer())
244 .and_then(|meta| meta.file_path().cloned());
245
246 match action {
247 "allow_once" => {
248 if let Some(lsp) = &mut self.lsp {
250 lsp.allow_language(&language);
252 if lsp.force_spawn(&language, file_path.as_deref()).is_some() {
254 tracing::info!("LSP server for {} started (allowed once)", language);
255 self.set_status_message(
256 t!("lsp.server_started", language = language).to_string(),
257 );
258 } else {
259 self.set_status_message(
260 t!("lsp.failed_to_start", language = language).to_string(),
261 );
262 }
263 }
264 self.notify_lsp_current_file_opened(&language);
266 }
267 "allow_always" => {
268 if let Some(lsp) = &mut self.lsp {
270 lsp.allow_language(&language);
271 if lsp.force_spawn(&language, file_path.as_deref()).is_some() {
273 tracing::info!("LSP server for {} started (always allowed)", language);
274 self.set_status_message(
275 t!("lsp.server_started_auto", language = language).to_string(),
276 );
277 } else {
278 self.set_status_message(
279 t!("lsp.failed_to_start", language = language).to_string(),
280 );
281 }
282 }
283 self.notify_lsp_current_file_opened(&language);
285 }
286 _ => {
287 tracing::info!("LSP server for {} startup declined by user", language);
289 self.set_status_message(
290 t!("lsp.startup_cancelled", language = language).to_string(),
291 );
292 }
293 }
294
295 true
296 }
297
298 fn notify_lsp_current_file_opened(&mut self, language: &str) {
303 let metadata = match self.buffer_metadata.get(&self.active_buffer()) {
305 Some(m) => m,
306 None => {
307 tracing::debug!(
308 "notify_lsp_current_file_opened: no metadata for buffer {:?}",
309 self.active_buffer()
310 );
311 return;
312 }
313 };
314
315 if !metadata.lsp_enabled {
316 tracing::debug!("notify_lsp_current_file_opened: LSP disabled for this buffer");
317 return;
318 }
319
320 let file_path = metadata.file_path().cloned();
322
323 let uri = match metadata.file_uri() {
325 Some(u) => u.clone(),
326 None => {
327 tracing::debug!(
328 "notify_lsp_current_file_opened: no URI for buffer (not a file or URI creation failed)"
329 );
330 return;
331 }
332 };
333
334 let active_buffer = self.active_buffer();
336
337 let file_language = match self.buffers.get(&active_buffer).map(|s| s.language.clone()) {
339 Some(l) => l,
340 None => {
341 tracing::debug!("notify_lsp_current_file_opened: no buffer state");
342 return;
343 }
344 };
345
346 if file_language != language {
348 tracing::debug!(
349 "notify_lsp_current_file_opened: file language {} doesn't match server {}",
350 file_language,
351 language
352 );
353 return;
354 }
355 let (text, line_count, buffer_version) =
356 if let Some(state) = self.buffers.get(&active_buffer) {
357 let text = match state.buffer.to_string() {
358 Some(t) => t,
359 None => {
360 tracing::debug!("notify_lsp_current_file_opened: buffer not fully loaded");
361 return;
362 }
363 };
364 let line_count = state.buffer.line_count().unwrap_or(1000);
365 (text, line_count, state.buffer.version())
366 } else {
367 tracing::debug!("notify_lsp_current_file_opened: no buffer state");
368 return;
369 };
370
371 if let Some(lsp) = &mut self.lsp {
373 if lsp.force_spawn(language, file_path.as_deref()).is_some() {
375 tracing::info!("Sending didOpen to LSP servers for: {}", uri.as_str());
376 let mut any_opened = false;
377 for sh in lsp.get_handles_mut(language) {
378 if let Err(e) =
379 sh.handle
380 .did_open(uri.clone(), text.clone(), file_language.clone())
381 {
382 tracing::warn!("Failed to send didOpen to '{}': {}", sh.name, e);
383 } else {
384 any_opened = true;
385 }
386 }
387
388 if any_opened {
389 tracing::info!("Successfully sent didOpen to LSP after confirmation");
390
391 if let Some(handle) = lsp.get_handle_mut(language) {
393 let previous_result_id =
394 self.diagnostic_result_ids.get(uri.as_str()).cloned();
395 let request_id = self.next_lsp_request_id;
396 self.next_lsp_request_id += 1;
397
398 if let Err(e) =
399 handle.document_diagnostic(request_id, uri.clone(), previous_result_id)
400 {
401 tracing::debug!(
402 "Failed to request pull diagnostics (server may not support): {}",
403 e
404 );
405 }
406
407 if self.config.editor.enable_inlay_hints {
409 let request_id = self.next_lsp_request_id;
410 self.next_lsp_request_id += 1;
411
412 let last_line = line_count.saturating_sub(1) as u32;
413 let last_char = 10000u32;
414
415 if let Err(e) = handle.inlay_hints(
416 request_id,
417 uri.clone(),
418 0,
419 0,
420 last_line,
421 last_char,
422 ) {
423 tracing::debug!(
424 "Failed to request inlay hints (server may not support): {}",
425 e
426 );
427 } else {
428 self.pending_inlay_hints_requests.insert(
429 request_id,
430 super::InlayHintsRequest {
431 buffer_id: active_buffer,
432 version: buffer_version,
433 },
434 );
435 }
436 }
437 }
438 }
439 }
440 }
441 }
442
443 pub fn has_pending_lsp_confirmation(&self) -> bool {
445 self.pending_lsp_confirmation.is_some()
446 }
447
448 pub fn popup_select_next(&mut self) {
450 let event = Event::PopupSelectNext;
451 self.active_event_log_mut().append(event.clone());
452 self.apply_event_to_active_buffer(&event);
453 }
454
455 pub fn popup_select_prev(&mut self) {
457 let event = Event::PopupSelectPrev;
458 self.active_event_log_mut().append(event.clone());
459 self.apply_event_to_active_buffer(&event);
460 }
461
462 pub fn popup_page_down(&mut self) {
464 let event = Event::PopupPageDown;
465 self.active_event_log_mut().append(event.clone());
466 self.apply_event_to_active_buffer(&event);
467 }
468
469 pub fn popup_page_up(&mut self) {
471 let event = Event::PopupPageUp;
472 self.active_event_log_mut().append(event.clone());
473 self.apply_event_to_active_buffer(&event);
474 }
475}