Skip to main content

fresh/app/
menu_context.rs

1//! Menu context computation.
2//!
3//! This module provides methods to compute menu context values that determine
4//! when menu items and commands should be enabled or disabled. Each context
5//! value has a dedicated method that encapsulates the logic for checking
6//! whether that feature is available.
7
8use super::Editor;
9use crate::view::ui::context_keys;
10
11impl Editor {
12    /// Return a clone of the current menu context (boolean state flags).
13    ///
14    /// This is used by the GUI layer to sync native menu item states
15    /// (enabled/disabled, checkmarks) without knowing about the editor's
16    /// internal state.
17    pub fn menu_context(&self) -> crate::view::ui::MenuContext {
18        self.menu_state.context.clone()
19    }
20
21    /// Return the fully-expanded menu definitions (with `DynamicSubmenu`
22    /// items resolved to `Submenu`).  Used by the GUI layer to build
23    /// platform-native menus.
24    pub fn expanded_menu_definitions(&self) -> Vec<fresh_core::menu::Menu> {
25        use crate::config::{MenuConfig, MenuExt};
26
27        let mut menus = MenuConfig::translated_menus();
28        let themes_dir = self.menu_state.themes_dir.clone();
29        for menu in &mut menus {
30            menu.expand_dynamic_items(&themes_dir);
31        }
32        menus
33    }
34
35    /// Update all menu context values based on current editor state.
36    /// This should be called before rendering the menu bar.
37    pub fn update_menu_context(&mut self) {
38        // Simple state lookups
39        let line_numbers = self.is_line_numbers_visible();
40        let line_wrap = self.is_line_wrap_enabled();
41        let compose_mode = self.is_compose_mode();
42        let file_explorer_visible = self.file_explorer_visible;
43        let file_explorer_focused = self.is_file_explorer_focused();
44        let mouse_capture = self.mouse_enabled;
45        let mouse_hover = self.config.editor.mouse_hover_enabled;
46        let inlay_hints = self.config.editor.enable_inlay_hints;
47        let has_selection = self.has_active_selection();
48        let menu_bar = self.menu_bar_visible;
49        let vertical_scrollbar = self.config.editor.show_vertical_scrollbar;
50        let horizontal_scrollbar = self.config.editor.show_horizontal_scrollbar;
51
52        // File explorer state
53        let show_hidden = self.is_file_explorer_showing_hidden();
54        let show_gitignored = self.is_file_explorer_showing_gitignored();
55
56        // Language-dependent context values
57        let lsp_available = self.is_lsp_available();
58        let formatter_available = self.is_formatter_available();
59
60        // Session mode (for detach command availability)
61        let session_mode = self.session_mode;
62
63        // Scroll sync state
64        let scroll_sync = self.same_buffer_scroll_sync;
65        let has_same_buffer_splits = self.has_same_buffer_splits();
66
67        // Apply all context values
68        self.menu_state
69            .context
70            .set(context_keys::LINE_NUMBERS, line_numbers)
71            .set(context_keys::LINE_WRAP, line_wrap)
72            .set(context_keys::COMPOSE_MODE, compose_mode)
73            .set(context_keys::FILE_EXPLORER, file_explorer_visible)
74            .set(context_keys::FILE_EXPLORER_FOCUSED, file_explorer_focused)
75            .set(context_keys::MOUSE_CAPTURE, mouse_capture)
76            .set(context_keys::MOUSE_HOVER, mouse_hover)
77            .set(context_keys::INLAY_HINTS, inlay_hints)
78            .set(context_keys::LSP_AVAILABLE, lsp_available)
79            .set(context_keys::FILE_EXPLORER_SHOW_HIDDEN, show_hidden)
80            .set(context_keys::FILE_EXPLORER_SHOW_GITIGNORED, show_gitignored)
81            .set(context_keys::HAS_SELECTION, has_selection)
82            .set(context_keys::MENU_BAR, menu_bar)
83            .set(context_keys::FORMATTER_AVAILABLE, formatter_available)
84            .set(context_keys::SESSION_MODE, session_mode)
85            .set(context_keys::VERTICAL_SCROLLBAR, vertical_scrollbar)
86            .set(context_keys::HORIZONTAL_SCROLLBAR, horizontal_scrollbar)
87            .set(context_keys::SCROLL_SYNC, scroll_sync)
88            .set(context_keys::HAS_SAME_BUFFER_SPLITS, has_same_buffer_splits);
89    }
90
91    /// Check if line numbers are visible in the active split.
92    fn is_line_numbers_visible(&self) -> bool {
93        let active_split = self.split_manager.active_split();
94        self.split_view_states
95            .get(&active_split)
96            .map(|vs| vs.show_line_numbers)
97            .unwrap_or(true)
98    }
99
100    /// Check if line wrap is enabled in the active split.
101    fn is_line_wrap_enabled(&self) -> bool {
102        let active_split = self.split_manager.active_split();
103        self.split_view_states
104            .get(&active_split)
105            .map(|vs| vs.viewport.line_wrap_enabled)
106            .unwrap_or(false)
107    }
108
109    /// Check if compose mode is active in the current buffer.
110    fn is_compose_mode(&self) -> bool {
111        let active_split = self.split_manager.active_split();
112        self.split_view_states
113            .get(&active_split)
114            .map(|vs| vs.view_mode == crate::state::ViewMode::Compose)
115            .unwrap_or(false)
116    }
117
118    /// Check if the file explorer is currently focused.
119    fn is_file_explorer_focused(&self) -> bool {
120        self.key_context == crate::input::keybindings::KeyContext::FileExplorer
121    }
122
123    /// Check if the file explorer is showing hidden files.
124    fn is_file_explorer_showing_hidden(&self) -> bool {
125        self.file_explorer
126            .as_ref()
127            .map(|fe| fe.ignore_patterns().show_hidden())
128            .unwrap_or(false)
129    }
130
131    /// Check if the file explorer is showing gitignored files.
132    fn is_file_explorer_showing_gitignored(&self) -> bool {
133        self.file_explorer
134            .as_ref()
135            .map(|fe| fe.ignore_patterns().show_gitignored())
136            .unwrap_or(false)
137    }
138
139    /// Check if an LSP server is available and ready for the current buffer's language.
140    fn is_lsp_available(&self) -> bool {
141        let buffer_id = self.active_buffer();
142
143        // Check if LSP is enabled for this buffer
144        if let Some(metadata) = self.buffer_metadata.get(&buffer_id) {
145            if !metadata.lsp_enabled {
146                return false;
147            }
148        } else {
149            return false;
150        }
151
152        // Use buffer's stored language
153        self.buffers
154            .get(&buffer_id)
155            .and_then(|state| {
156                self.lsp
157                    .as_ref()
158                    .map(|lsp| lsp.is_server_ready(&state.language))
159            })
160            .unwrap_or(false)
161    }
162
163    /// Check if the active buffer is shown in more than one visible split.
164    fn has_same_buffer_splits(&self) -> bool {
165        let active_split = self.split_manager.active_split();
166        let active_buf_id = self.split_manager.buffer_for_split(active_split);
167        if let Some(buf_id) = active_buf_id {
168            self.split_view_states
169                .keys()
170                .filter(|&&s| {
171                    s != active_split && self.split_manager.buffer_for_split(s) == Some(buf_id)
172                })
173                .next()
174                .is_some()
175        } else {
176            false
177        }
178    }
179
180    /// Check if a formatter is configured for the current buffer's language.
181    fn is_formatter_available(&self) -> bool {
182        let buffer_id = self.active_buffer();
183
184        // Use buffer's stored language
185        self.buffers
186            .get(&buffer_id)
187            .and_then(|state| {
188                self.config
189                    .languages
190                    .get(&state.language)
191                    .and_then(|lc| lc.formatter.as_ref())
192                    .map(|_| true)
193            })
194            .unwrap_or(false)
195    }
196}