fresh/app/types.rs
1use crate::app::file_open::SortMode;
2use crate::input::keybindings::Action;
3use crate::model::event::{BufferId, SplitDirection, SplitId};
4use crate::services::async_bridge::LspMessageType;
5use ratatui::layout::Rect;
6use rust_i18n::t;
7use std::collections::{HashMap, HashSet};
8use std::ops::Range;
9use std::path::{Path, PathBuf};
10
11pub const DEFAULT_BACKGROUND_FILE: &str = "scripts/landscape-wide.txt";
12
13/// Pre-calculated line information for an event
14/// Calculated BEFORE buffer modification so line numbers are accurate
15#[derive(Debug, Clone, Default)]
16pub(super) struct EventLineInfo {
17 /// Start line (0-indexed) where the change begins
18 pub start_line: usize,
19 /// End line (0-indexed) where the change ends (in original buffer for deletes)
20 pub end_line: usize,
21 /// Number of lines added (for inserts) or removed (for deletes)
22 pub line_delta: i32,
23}
24
25/// Search state for find/replace functionality
26#[derive(Debug, Clone)]
27pub(super) struct SearchState {
28 /// The search query
29 pub query: String,
30 /// All match positions in the buffer (byte offsets)
31 pub matches: Vec<usize>,
32 /// Index of the currently selected match
33 pub current_match_index: Option<usize>,
34 /// Whether search wraps around at document boundaries
35 pub wrap_search: bool,
36 /// Optional search range (for search in selection)
37 pub search_range: Option<Range<usize>>,
38}
39
40/// A bookmark in the editor (position in a specific buffer)
41#[derive(Debug, Clone)]
42pub(super) struct Bookmark {
43 /// Buffer ID where the bookmark is set
44 pub buffer_id: BufferId,
45 /// Byte offset position in the buffer
46 pub position: usize,
47}
48
49/// State for interactive replace (query-replace)
50#[derive(Debug, Clone)]
51pub(super) struct InteractiveReplaceState {
52 /// The search pattern
53 pub search: String,
54 /// The replacement text
55 pub replacement: String,
56 /// Current match position (byte offset of the match we're at)
57 pub current_match_pos: usize,
58 /// Starting position (to detect when we've wrapped around full circle)
59 pub start_pos: usize,
60 /// Whether we've wrapped around to the beginning
61 pub has_wrapped: bool,
62 /// Number of replacements made so far
63 pub replacements_made: usize,
64}
65
66/// The kind of buffer (file-backed or virtual)
67#[derive(Debug, Clone, PartialEq)]
68pub enum BufferKind {
69 /// A buffer backed by a file on disk
70 File {
71 /// Path to the file
72 path: PathBuf,
73 /// LSP URI for the file
74 uri: Option<lsp_types::Uri>,
75 },
76 /// A virtual buffer (not backed by a file)
77 /// Used for special buffers like *Diagnostics*, *Grep*, etc.
78 Virtual {
79 /// The buffer's mode (e.g., "diagnostics-list", "grep-results")
80 mode: String,
81 },
82}
83
84/// Metadata associated with a buffer
85#[derive(Debug, Clone)]
86pub struct BufferMetadata {
87 /// The kind of buffer (file or virtual)
88 pub kind: BufferKind,
89
90 /// Display name for the buffer (project-relative path or filename or *BufferName*)
91 pub display_name: String,
92
93 /// Whether LSP is enabled for this buffer (always false for virtual buffers)
94 pub lsp_enabled: bool,
95
96 /// Reason LSP is disabled (if applicable)
97 pub lsp_disabled_reason: Option<String>,
98
99 /// Whether the buffer is read-only (typically true for virtual buffers)
100 pub read_only: bool,
101
102 /// Whether the buffer contains binary content
103 /// Binary buffers are automatically read-only and render unprintable chars as code points
104 pub binary: bool,
105
106 /// LSP server instance IDs that have received didOpen for this buffer.
107 /// Used to ensure didOpen is sent before any requests to a new/restarted server.
108 /// When a server restarts, it gets a new ID, so didOpen is automatically resent.
109 /// Old IDs are harmless - they just remain in the set but don't match any active server.
110 pub lsp_opened_with: HashSet<u64>,
111
112 /// Whether this buffer should be hidden from tabs (used for composite source buffers)
113 pub hidden_from_tabs: bool,
114
115 /// Stable recovery ID for unnamed buffers.
116 /// For file-backed buffers, recovery ID is computed from the path hash.
117 /// For unnamed buffers, this is generated once and reused across auto-saves.
118 pub recovery_id: Option<String>,
119}
120
121impl BufferMetadata {
122 /// Get the file path if this is a file-backed buffer
123 pub fn file_path(&self) -> Option<&PathBuf> {
124 match &self.kind {
125 BufferKind::File { path, .. } => Some(path),
126 BufferKind::Virtual { .. } => None,
127 }
128 }
129
130 /// Get the file URI if this is a file-backed buffer
131 pub fn file_uri(&self) -> Option<&lsp_types::Uri> {
132 match &self.kind {
133 BufferKind::File { uri, .. } => uri.as_ref(),
134 BufferKind::Virtual { .. } => None,
135 }
136 }
137
138 /// Check if this is a virtual buffer
139 pub fn is_virtual(&self) -> bool {
140 matches!(self.kind, BufferKind::Virtual { .. })
141 }
142
143 /// Get the mode name for virtual buffers
144 pub fn virtual_mode(&self) -> Option<&str> {
145 match &self.kind {
146 BufferKind::Virtual { mode } => Some(mode),
147 BufferKind::File { .. } => None,
148 }
149 }
150}
151
152impl Default for BufferMetadata {
153 fn default() -> Self {
154 Self::new()
155 }
156}
157
158impl BufferMetadata {
159 /// Create new metadata for a buffer (unnamed, file-backed)
160 pub fn new() -> Self {
161 Self {
162 kind: BufferKind::File {
163 path: PathBuf::new(),
164 uri: None,
165 },
166 display_name: t!("buffer.no_name").to_string(),
167 lsp_enabled: true,
168 lsp_disabled_reason: None,
169 read_only: false,
170 binary: false,
171 lsp_opened_with: HashSet::new(),
172 hidden_from_tabs: false,
173 recovery_id: None,
174 }
175 }
176
177 /// Create new metadata for an unnamed buffer with a custom display name
178 /// Used for buffers created from stdin or other non-file sources
179 pub fn new_unnamed(display_name: String) -> Self {
180 Self {
181 kind: BufferKind::File {
182 path: PathBuf::new(),
183 uri: None,
184 },
185 display_name,
186 lsp_enabled: false, // No file path, so no LSP
187 lsp_disabled_reason: Some(t!("lsp.disabled.unnamed").to_string()),
188 read_only: false,
189 binary: false,
190 lsp_opened_with: HashSet::new(),
191 hidden_from_tabs: false,
192 recovery_id: None,
193 }
194 }
195
196 /// Create metadata for a file-backed buffer
197 ///
198 /// # Arguments
199 /// * `path` - The canonical absolute path to the file
200 /// * `working_dir` - The canonical working directory for computing relative display name
201 pub fn with_file(path: PathBuf, working_dir: &Path) -> Self {
202 // Compute URI from the absolute path
203 let file_uri = url::Url::from_file_path(&path)
204 .ok()
205 .and_then(|u| u.as_str().parse::<lsp_types::Uri>().ok());
206
207 // Compute display name (project-relative when under working_dir, else absolute path).
208 // Use canonicalized forms first to handle macOS /var -> /private/var differences.
209 let display_name = Self::display_name_for_path(&path, working_dir);
210
211 Self {
212 kind: BufferKind::File {
213 path,
214 uri: file_uri,
215 },
216 display_name,
217 lsp_enabled: true,
218 lsp_disabled_reason: None,
219 read_only: false,
220 binary: false,
221 lsp_opened_with: HashSet::new(),
222 hidden_from_tabs: false,
223 recovery_id: None,
224 }
225 }
226
227 /// Compute display name relative to working_dir when possible, otherwise absolute
228 pub fn display_name_for_path(path: &Path, working_dir: &Path) -> String {
229 // Canonicalize working_dir to normalize platform-specific prefixes
230 let canonical_working_dir = working_dir
231 .canonicalize()
232 .unwrap_or_else(|_| working_dir.to_path_buf());
233
234 // Try to canonicalize the file path; if it fails (e.g., new file), fall back to absolute
235 let absolute_path = if path.is_absolute() {
236 path.to_path_buf()
237 } else {
238 // If we were given a relative path, anchor it to working_dir
239 canonical_working_dir.join(path)
240 };
241 let canonical_path = absolute_path
242 .canonicalize()
243 .unwrap_or_else(|_| absolute_path.clone());
244
245 // Prefer canonical comparison first, then raw prefix as a fallback
246 let relative = canonical_path
247 .strip_prefix(&canonical_working_dir)
248 .or_else(|_| path.strip_prefix(working_dir))
249 .ok()
250 .and_then(|rel| rel.to_str().map(|s| s.to_string()));
251
252 relative
253 .or_else(|| canonical_path.to_str().map(|s| s.to_string()))
254 .unwrap_or_else(|| t!("buffer.unknown").to_string())
255 }
256
257 /// Create metadata for a virtual buffer (not backed by a file)
258 ///
259 /// # Arguments
260 /// * `name` - Display name (e.g., "*Diagnostics*")
261 /// * `mode` - Buffer mode for keybindings (e.g., "diagnostics-list")
262 /// * `read_only` - Whether the buffer should be read-only
263 pub fn virtual_buffer(name: String, mode: String, read_only: bool) -> Self {
264 Self {
265 kind: BufferKind::Virtual { mode },
266 display_name: name,
267 lsp_enabled: false, // Virtual buffers don't use LSP
268 lsp_disabled_reason: Some(t!("lsp.disabled.virtual").to_string()),
269 read_only,
270 binary: false,
271 lsp_opened_with: HashSet::new(),
272 hidden_from_tabs: false,
273 recovery_id: None,
274 }
275 }
276
277 /// Create metadata for a hidden virtual buffer (for composite source buffers)
278 /// These buffers are not shown in tabs and are managed by their parent composite buffer.
279 /// Hidden buffers are always read-only to prevent accidental edits.
280 pub fn hidden_virtual_buffer(name: String, mode: String) -> Self {
281 Self {
282 kind: BufferKind::Virtual { mode },
283 display_name: name,
284 lsp_enabled: false,
285 lsp_disabled_reason: Some(t!("lsp.disabled.virtual").to_string()),
286 read_only: true, // Hidden buffers are always read-only
287 binary: false,
288 lsp_opened_with: HashSet::new(),
289 hidden_from_tabs: true,
290 recovery_id: None,
291 }
292 }
293
294 /// Disable LSP for this buffer with a reason
295 pub fn disable_lsp(&mut self, reason: String) {
296 self.lsp_enabled = false;
297 self.lsp_disabled_reason = Some(reason);
298 }
299}
300
301/// State for macro recording
302#[derive(Debug, Clone)]
303pub(super) struct MacroRecordingState {
304 /// The register key for this macro
305 pub key: char,
306 /// Actions recorded so far
307 pub actions: Vec<Action>,
308}
309
310/// LSP progress information
311#[derive(Debug, Clone)]
312pub(super) struct LspProgressInfo {
313 pub language: String,
314 pub title: String,
315 pub message: Option<String>,
316 pub percentage: Option<u32>,
317}
318
319/// LSP message entry (for window messages and logs)
320#[derive(Debug, Clone)]
321#[allow(dead_code)]
322pub(super) struct LspMessageEntry {
323 pub language: String,
324 pub message_type: LspMessageType,
325 pub message: String,
326 pub timestamp: std::time::Instant,
327}
328
329/// Types of UI elements that can be hovered over
330#[derive(Debug, Clone, PartialEq)]
331pub enum HoverTarget {
332 /// Hovering over a split separator (split_id, direction)
333 SplitSeparator(SplitId, SplitDirection),
334 /// Hovering over a scrollbar thumb (split_id)
335 ScrollbarThumb(SplitId),
336 /// Hovering over a scrollbar track (split_id)
337 ScrollbarTrack(SplitId),
338 /// Hovering over a menu bar item (menu_index)
339 MenuBarItem(usize),
340 /// Hovering over a menu dropdown item (menu_index, item_index)
341 MenuDropdownItem(usize, usize),
342 /// Hovering over a submenu item (depth, item_index) - depth 1+ for nested submenus
343 SubmenuItem(usize, usize),
344 /// Hovering over a popup list item (popup_index in stack, item_index)
345 PopupListItem(usize, usize),
346 /// Hovering over a suggestion item (item_index)
347 SuggestionItem(usize),
348 /// Hovering over the file explorer border (for resize)
349 FileExplorerBorder,
350 /// Hovering over a file browser navigation shortcut
351 FileBrowserNavShortcut(usize),
352 /// Hovering over a file browser file/directory entry
353 FileBrowserEntry(usize),
354 /// Hovering over a file browser column header
355 FileBrowserHeader(SortMode),
356 /// Hovering over the file browser scrollbar
357 FileBrowserScrollbar,
358 /// Hovering over the file browser "Show Hidden" checkbox
359 FileBrowserShowHiddenCheckbox,
360 /// Hovering over a tab name (buffer_id, split_id) - for non-active tabs
361 TabName(BufferId, SplitId),
362 /// Hovering over a tab close button (buffer_id, split_id)
363 TabCloseButton(BufferId, SplitId),
364 /// Hovering over a close split button (split_id)
365 CloseSplitButton(SplitId),
366 /// Hovering over a maximize/unmaximize split button (split_id)
367 MaximizeSplitButton(SplitId),
368 /// Hovering over the file explorer close button
369 FileExplorerCloseButton,
370 /// Hovering over a file explorer item's status indicator (path)
371 FileExplorerStatusIndicator(std::path::PathBuf),
372 /// Hovering over the status bar LSP indicator
373 StatusBarLspIndicator,
374 /// Hovering over the status bar warning badge
375 StatusBarWarningBadge,
376 /// Hovering over the status bar line ending indicator
377 StatusBarLineEndingIndicator,
378 /// Hovering over the search options "Case Sensitive" checkbox
379 SearchOptionCaseSensitive,
380 /// Hovering over the search options "Whole Word" checkbox
381 SearchOptionWholeWord,
382 /// Hovering over the search options "Regex" checkbox
383 SearchOptionRegex,
384 /// Hovering over the search options "Confirm Each" checkbox
385 SearchOptionConfirmEach,
386 /// Hovering over a tab context menu item (item_index)
387 TabContextMenuItem(usize),
388}
389
390/// Tab context menu items
391#[derive(Debug, Clone, Copy, PartialEq, Eq)]
392pub enum TabContextMenuItem {
393 /// Close this tab
394 Close,
395 /// Close all other tabs
396 CloseOthers,
397 /// Close tabs to the right
398 CloseToRight,
399 /// Close tabs to the left
400 CloseToLeft,
401 /// Close all tabs
402 CloseAll,
403}
404
405impl TabContextMenuItem {
406 /// Get all menu items in order
407 pub fn all() -> &'static [Self] {
408 &[
409 Self::Close,
410 Self::CloseOthers,
411 Self::CloseToRight,
412 Self::CloseToLeft,
413 Self::CloseAll,
414 ]
415 }
416
417 /// Get the display label for this menu item
418 pub fn label(&self) -> String {
419 match self {
420 Self::Close => t!("tab.close").to_string(),
421 Self::CloseOthers => t!("tab.close_others").to_string(),
422 Self::CloseToRight => t!("tab.close_to_right").to_string(),
423 Self::CloseToLeft => t!("tab.close_to_left").to_string(),
424 Self::CloseAll => t!("tab.close_all").to_string(),
425 }
426 }
427}
428
429/// State for tab context menu (right-click popup on tabs)
430#[derive(Debug, Clone)]
431pub struct TabContextMenu {
432 /// The buffer ID this context menu is for
433 pub buffer_id: BufferId,
434 /// The split ID where the tab is located
435 pub split_id: SplitId,
436 /// Screen position where the menu should appear (x, y)
437 pub position: (u16, u16),
438 /// Currently highlighted menu item index
439 pub highlighted: usize,
440}
441
442impl TabContextMenu {
443 /// Create a new tab context menu
444 pub fn new(buffer_id: BufferId, split_id: SplitId, x: u16, y: u16) -> Self {
445 Self {
446 buffer_id,
447 split_id,
448 position: (x, y),
449 highlighted: 0,
450 }
451 }
452
453 /// Get the currently highlighted item
454 pub fn highlighted_item(&self) -> TabContextMenuItem {
455 TabContextMenuItem::all()[self.highlighted]
456 }
457
458 /// Move highlight down
459 pub fn next_item(&mut self) {
460 let items = TabContextMenuItem::all();
461 self.highlighted = (self.highlighted + 1) % items.len();
462 }
463
464 /// Move highlight up
465 pub fn prev_item(&mut self) {
466 let items = TabContextMenuItem::all();
467 self.highlighted = if self.highlighted == 0 {
468 items.len() - 1
469 } else {
470 self.highlighted - 1
471 };
472 }
473}
474
475/// Drop zone for tab drag-and-drop
476/// Indicates where a dragged tab will be placed when released
477#[derive(Debug, Clone, Copy, PartialEq, Eq)]
478pub enum TabDropZone {
479 /// Drop into an existing split's tab bar (before tab at index, or at end if None)
480 /// (target_split_id, insert_index)
481 TabBar(SplitId, Option<usize>),
482 /// Create a new split on the left edge of the target split
483 SplitLeft(SplitId),
484 /// Create a new split on the right edge of the target split
485 SplitRight(SplitId),
486 /// Create a new split on the top edge of the target split
487 SplitTop(SplitId),
488 /// Create a new split on the bottom edge of the target split
489 SplitBottom(SplitId),
490 /// Drop into the center of a split (switch to that split's tab bar)
491 SplitCenter(SplitId),
492}
493
494impl TabDropZone {
495 /// Get the split ID this drop zone is associated with
496 pub fn split_id(&self) -> SplitId {
497 match self {
498 Self::TabBar(id, _)
499 | Self::SplitLeft(id)
500 | Self::SplitRight(id)
501 | Self::SplitTop(id)
502 | Self::SplitBottom(id)
503 | Self::SplitCenter(id) => *id,
504 }
505 }
506}
507
508/// State for a tab being dragged
509#[derive(Debug, Clone)]
510pub struct TabDragState {
511 /// The buffer being dragged
512 pub buffer_id: BufferId,
513 /// The split the tab was dragged from
514 pub source_split_id: SplitId,
515 /// Starting mouse position when drag began
516 pub start_position: (u16, u16),
517 /// Current mouse position
518 pub current_position: (u16, u16),
519 /// Currently detected drop zone (if any)
520 pub drop_zone: Option<TabDropZone>,
521}
522
523impl TabDragState {
524 /// Create a new tab drag state
525 pub fn new(buffer_id: BufferId, source_split_id: SplitId, start_position: (u16, u16)) -> Self {
526 Self {
527 buffer_id,
528 source_split_id,
529 start_position,
530 current_position: start_position,
531 drop_zone: None,
532 }
533 }
534
535 /// Check if the drag has moved enough to be considered a real drag (not just a click)
536 pub fn is_dragging(&self) -> bool {
537 let dx = (self.current_position.0 as i32 - self.start_position.0 as i32).abs();
538 let dy = (self.current_position.1 as i32 - self.start_position.1 as i32).abs();
539 dx > 3 || dy > 3 // Threshold of 3 pixels before drag activates
540 }
541}
542
543/// Mouse state tracking
544#[derive(Debug, Clone, Default)]
545pub(super) struct MouseState {
546 /// Whether we're currently dragging a scrollbar
547 pub dragging_scrollbar: Option<SplitId>,
548 /// Last mouse position
549 pub last_position: Option<(u16, u16)>,
550 /// Mouse hover for LSP: byte position being hovered, timer start, and screen position
551 /// Format: (byte_position, hover_start_instant, screen_x, screen_y)
552 pub lsp_hover_state: Option<(usize, std::time::Instant, u16, u16)>,
553 /// Whether we've already sent a hover request for the current position
554 pub lsp_hover_request_sent: bool,
555 /// Initial mouse row when starting to drag the scrollbar thumb
556 /// Used to calculate relative movement rather than jumping
557 pub drag_start_row: Option<u16>,
558 /// Initial viewport top_byte when starting to drag the scrollbar thumb
559 pub drag_start_top_byte: Option<usize>,
560 /// Whether we're currently dragging a split separator
561 /// Stores (split_id, direction) for the separator being dragged
562 pub dragging_separator: Option<(SplitId, SplitDirection)>,
563 /// Initial mouse position when starting to drag a separator
564 pub drag_start_position: Option<(u16, u16)>,
565 /// Initial split ratio when starting to drag a separator
566 pub drag_start_ratio: Option<f32>,
567 /// Whether we're currently dragging the file explorer border
568 pub dragging_file_explorer: bool,
569 /// Initial file explorer width percentage when starting to drag
570 pub drag_start_explorer_width: Option<f32>,
571 /// Current hover target (if any)
572 pub hover_target: Option<HoverTarget>,
573 /// Whether we're currently doing a text selection drag
574 pub dragging_text_selection: bool,
575 /// The split where text selection started
576 pub drag_selection_split: Option<SplitId>,
577 /// The buffer byte position where the selection anchor is
578 pub drag_selection_anchor: Option<usize>,
579 /// Tab drag state (for drag-to-split functionality)
580 pub dragging_tab: Option<TabDragState>,
581 /// Whether we're currently dragging a popup scrollbar (popup index)
582 pub dragging_popup_scrollbar: Option<usize>,
583 /// Initial scroll offset when starting to drag popup scrollbar
584 pub drag_start_popup_scroll: Option<usize>,
585 /// Whether we're currently selecting text in a popup (popup index)
586 pub selecting_in_popup: Option<usize>,
587}
588
589/// Mapping from visual row to buffer positions for mouse click handling
590/// Each entry represents one visual row with byte position info for click handling
591#[derive(Debug, Clone, Default)]
592pub struct ViewLineMapping {
593 /// Source byte offset for each character (None for injected/virtual content)
594 pub char_source_bytes: Vec<Option<usize>>,
595 /// Character index at each visual column (for O(1) mouse clicks)
596 pub visual_to_char: Vec<usize>,
597 /// Last valid byte position in this visual row (newline for real lines, last char for wrapped)
598 /// Clicks past end of visible text position cursor here
599 pub line_end_byte: usize,
600}
601
602impl ViewLineMapping {
603 /// Get source byte at a given visual column (O(1) for mouse clicks)
604 #[inline]
605 pub fn source_byte_at_visual_col(&self, visual_col: usize) -> Option<usize> {
606 let char_idx = self.visual_to_char.get(visual_col).copied()?;
607 self.char_source_bytes.get(char_idx).copied().flatten()
608 }
609}
610
611/// Type alias for popup area layout information used in mouse hit testing.
612/// Fields: (popup_index, rect, inner_rect, scroll_offset, num_items, scrollbar_rect, total_lines)
613pub(crate) type PopupAreaLayout = (usize, Rect, Rect, usize, usize, Option<Rect>, usize);
614
615/// Cached layout information for mouse hit testing
616#[derive(Debug, Clone, Default)]
617pub(crate) struct CachedLayout {
618 /// File explorer area (if visible)
619 pub file_explorer_area: Option<Rect>,
620 /// Editor content area (excluding file explorer)
621 pub editor_content_area: Option<Rect>,
622 /// Individual split areas with their scrollbar areas and thumb positions
623 /// (split_id, buffer_id, content_rect, scrollbar_rect, thumb_start, thumb_end)
624 pub split_areas: Vec<(SplitId, BufferId, Rect, Rect, usize, usize)>,
625 /// Split separator positions for drag resize
626 /// (split_id, direction, x, y, length)
627 pub separator_areas: Vec<(SplitId, SplitDirection, u16, u16, u16)>,
628 /// Popup areas for mouse hit testing
629 /// scrollbar_rect is Some if popup has a scrollbar
630 pub popup_areas: Vec<PopupAreaLayout>,
631 /// Suggestions area for mouse hit testing
632 /// (inner_rect, scroll_start_idx, visible_count, total_count)
633 pub suggestions_area: Option<(Rect, usize, usize, usize)>,
634 /// Tab hit areas for mouse interaction
635 /// (split_id, buffer_id, tab_row, tab_start_col, tab_end_col, close_button_start_col)
636 /// The close button spans from close_button_start_col to tab_end_col
637 pub tab_areas: Vec<(SplitId, BufferId, u16, u16, u16, u16)>,
638 /// Close split button hit areas
639 /// (split_id, row, start_col, end_col)
640 pub close_split_areas: Vec<(SplitId, u16, u16, u16)>,
641 /// Maximize split button hit areas
642 /// (split_id, row, start_col, end_col)
643 pub maximize_split_areas: Vec<(SplitId, u16, u16, u16)>,
644 /// View line mappings for accurate mouse click positioning per split
645 /// Maps visual row index to character position mappings
646 /// Used to translate screen coordinates to buffer byte positions
647 pub view_line_mappings: HashMap<SplitId, Vec<ViewLineMapping>>,
648 /// Settings modal layout for hit testing
649 pub settings_layout: Option<crate::view::settings::SettingsLayout>,
650 /// Status bar area (row, x, width)
651 pub status_bar_area: Option<(u16, u16, u16)>,
652 /// Status bar LSP indicator area (row, start_col, end_col)
653 pub status_bar_lsp_area: Option<(u16, u16, u16)>,
654 /// Status bar warning badge area (row, start_col, end_col)
655 pub status_bar_warning_area: Option<(u16, u16, u16)>,
656 /// Status bar line ending indicator area (row, start_col, end_col)
657 pub status_bar_line_ending_area: Option<(u16, u16, u16)>,
658 /// Search options layout for checkbox hit testing
659 pub search_options_layout: Option<crate::view::ui::status_bar::SearchOptionsLayout>,
660}