fresh/app/
toggle_actions.rs1use crate::types::LspServerConfig;
10use rust_i18n::t;
11
12use crate::config::Config;
13use crate::config_io::{ConfigLayer, ConfigResolver};
14use crate::input::keybindings::KeybindingResolver;
15
16use super::Editor;
17
18impl Editor {
19 pub fn toggle_line_numbers(&mut self) {
25 let active_split = self
26 .windows
27 .get(&self.active_window)
28 .and_then(|w| w.buffers.splits())
29 .map(|(mgr, _)| mgr)
30 .expect("active window must have a populated split layout")
31 .active_split();
32 if let Some(vs) = self
33 .windows
34 .get_mut(&self.active_window)
35 .and_then(|w| w.split_view_states_mut())
36 .expect("active window must have a populated split layout")
37 .get_mut(&active_split)
38 {
39 let currently_shown = vs.show_line_numbers;
40 vs.show_line_numbers = !currently_shown;
41 if currently_shown {
42 self.set_status_message(t!("toggle.line_numbers_hidden").to_string());
43 } else {
44 self.set_status_message(t!("toggle.line_numbers_shown").to_string());
45 }
46 }
47 }
48
49 pub fn toggle_menu_bar(&mut self) {
55 let new_value = !self.active_window_mut().menu_bar_visible;
56 self.config_mut().editor.show_menu_bar = new_value;
57 self.active_window_mut().menu_bar_visible = new_value;
58 self.active_window_mut().menu_bar_auto_shown = false;
60 if !self.active_window_mut().menu_bar_visible {
62 self.menu_state.close_menu();
63 }
64 self.persist_config_change("/editor/show_menu_bar", serde_json::Value::Bool(new_value));
65 let status = if self.active_window_mut().menu_bar_visible {
66 t!("toggle.menu_bar_shown")
67 } else {
68 t!("toggle.menu_bar_hidden")
69 };
70 self.set_status_message(status.to_string());
71 }
72
73 pub fn toggle_vertical_scrollbar(&mut self) {
80 let new_value = !self.config.editor.show_vertical_scrollbar;
81 self.config_mut().editor.show_vertical_scrollbar = new_value;
82 let status = if self.config.editor.show_vertical_scrollbar {
83 t!("toggle.vertical_scrollbar_shown")
84 } else {
85 t!("toggle.vertical_scrollbar_hidden")
86 };
87 self.set_status_message(status.to_string());
88 }
89
90 pub fn toggle_horizontal_scrollbar(&mut self) {
92 let new_value = !self.config.editor.show_horizontal_scrollbar;
93 self.config_mut().editor.show_horizontal_scrollbar = new_value;
94 let status = if self.config.editor.show_horizontal_scrollbar {
95 t!("toggle.horizontal_scrollbar_shown")
96 } else {
97 t!("toggle.horizontal_scrollbar_hidden")
98 };
99 self.set_status_message(status.to_string());
100 }
101
102 pub fn reset_buffer_settings(&mut self) {
104 use crate::config::WhitespaceVisibility;
105 let buffer_id = self.active_buffer();
106
107 let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
109 let mut auto_close = self.config.editor.auto_close;
110 let mut word_characters = String::new();
111 let (tab_size, use_tabs) = if let Some(state) = self
112 .windows
113 .get(&self.active_window)
114 .map(|w| &w.buffers)
115 .expect("active window present")
116 .get(&buffer_id)
117 {
118 let language = &state.language;
119 if let Some(lang_config) = self.config.languages.get(language) {
120 whitespace =
121 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
122 if auto_close {
124 if let Some(lang_auto_close) = lang_config.auto_close {
125 auto_close = lang_auto_close;
126 }
127 }
128 if let Some(ref wc) = lang_config.word_characters {
129 word_characters = wc.clone();
130 }
131 (
132 lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
133 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
134 )
135 } else {
136 (self.config.editor.tab_size, self.config.editor.use_tabs)
137 }
138 } else {
139 (self.config.editor.tab_size, self.config.editor.use_tabs)
140 };
141
142 if let Some(state) = self
144 .windows
145 .get_mut(&self.active_window)
146 .map(|w| &mut w.buffers)
147 .expect("active window present")
148 .get_mut(&buffer_id)
149 {
150 state.buffer_settings.tab_size = tab_size;
151 state.buffer_settings.use_tabs = use_tabs;
152 state.buffer_settings.auto_close = auto_close;
153 state.buffer_settings.whitespace = whitespace;
154 state.buffer_settings.word_characters = word_characters;
155 }
156
157 self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
158 }
159
160 pub fn toggle_mouse_capture(&mut self) {
162 use std::io::stdout;
163
164 self.active_window_mut().mouse_enabled = !self.active_window_mut().mouse_enabled;
165
166 if self.active_window_mut().mouse_enabled {
167 #[allow(clippy::let_underscore_must_use)]
169 let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
170 self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
171 } else {
172 #[allow(clippy::let_underscore_must_use)]
174 let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
175 self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
176 }
177 }
178
179 pub fn is_mouse_enabled(&self) -> bool {
181 self.active_window().mouse_enabled
182 }
183
184 pub fn toggle_mouse_hover(&mut self) {
189 let new_value = !self.config.editor.mouse_hover_enabled;
190 self.config_mut().editor.mouse_hover_enabled = new_value;
191
192 if self.config.editor.mouse_hover_enabled {
193 self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
194 } else {
195 self.active_window_mut().mouse_state.lsp_hover_state = None;
197 self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
198 self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
199 }
200
201 #[cfg(windows)]
203 {
204 let mode = if self.config.editor.mouse_hover_enabled {
205 fresh_winterm::MouseMode::AllMotion
206 } else {
207 fresh_winterm::MouseMode::CellMotion
208 };
209 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
210 tracing::error!("Failed to switch mouse mode: {}", e);
211 }
212 }
213 }
214
215 pub fn is_mouse_hover_enabled(&self) -> bool {
217 self.config.editor.mouse_hover_enabled
218 }
219
220 pub fn set_gpm_active(&mut self, active: bool) {
226 self.active_window_mut().gpm_active = active;
227 }
228
229 pub fn toggle_inlay_hints(&mut self) {
231 let new_value = !self.config.editor.enable_inlay_hints;
232 self.config_mut().editor.enable_inlay_hints = new_value;
233 self.sync_windows_config();
237
238 if self.config.editor.enable_inlay_hints {
239 self.request_inlay_hints_for_active_buffer();
241 self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
242 } else {
243 for (_, state) in self
245 .windows
246 .get_mut(&self.active_window)
247 .map(|w| &mut w.buffers)
248 .expect("active window present")
249 {
250 state.virtual_texts.clear(&mut state.marker_list);
251 }
252 self.set_status_message(t!("toggle.inlay_hints_disabled").to_string());
253 }
254 }
255
256 pub fn dump_config(&mut self) {
258 if let Err(e) = self
260 .authority
261 .filesystem
262 .create_dir_all(&self.dir_context.config_dir)
263 {
264 self.set_status_message(
265 t!("error.config_dir_failed", error = e.to_string()).to_string(),
266 );
267 return;
268 }
269
270 let config_path = self.dir_context.config_path();
271 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
272
273 match resolver.save_to_layer(&self.config, ConfigLayer::User) {
275 Ok(()) => {
276 match self.open_file(&config_path) {
278 Ok(_buffer_id) => {
279 self.set_status_message(
280 t!("config.saved", path = config_path.display().to_string())
281 .to_string(),
282 );
283 }
284 Err(e) => {
285 if let Some(confirmation) =
287 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
288 {
289 self.start_large_file_encoding_confirmation(confirmation);
290 } else {
291 self.set_status_message(
292 t!("config.saved_failed_open", error = e.to_string()).to_string(),
293 );
294 }
295 }
296 }
297 }
298 Err(e) => {
299 self.set_status_message(
300 t!("error.config_save_failed", error = e.to_string()).to_string(),
301 );
302 }
303 }
304 }
305
306 pub fn save_config(&self) -> Result<(), String> {
310 self.authority
312 .filesystem
313 .create_dir_all(&self.dir_context.config_dir)
314 .map_err(|e| format!("Failed to create config directory: {}", e))?;
315
316 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
317 resolver
318 .save_to_layer(&self.config, ConfigLayer::User)
319 .map_err(|e| format!("Failed to save config: {}", e))
320 }
321
322 pub fn reload_config(&mut self) {
328 let old_theme = self.config.theme.clone();
329 self.set_config(Config::load_with_layers(
330 &self.dir_context,
331 &self.working_dir,
332 ));
333
334 self.set_user_config_raw(Config::read_user_config_raw(&self.working_dir));
336
337 if old_theme != self.config.theme {
339 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
340 *self.theme.write().unwrap() = theme;
341 tracing::info!("Theme changed to '{}'", self.config.theme.0);
342 } else {
343 tracing::error!("Theme '{}' not found", self.config.theme.0);
344 }
345 }
346
347 *self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);
349
350 self.clipboard.apply_config(&self.config.clipboard);
352
353 self.active_window_mut().menu_bar_visible = self.config.editor.show_menu_bar;
355 self.active_window_mut().tab_bar_visible = self.config.editor.show_tab_bar;
356 self.active_window_mut().status_bar_visible = self.config.editor.show_status_bar;
357 self.active_window_mut().prompt_line_visible = self.config.editor.show_prompt_line;
358
359 let __active_id = self.active_window;
361 if let Some(lsp) = self
362 .windows
363 .get_mut(&__active_id)
364 .and_then(|w| w.lsp.as_mut())
365 {
366 for (language, lsp_configs) in &self.config.lsp {
367 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
368 }
369 let universal_servers: Vec<LspServerConfig> = self
371 .config
372 .universal_lsp
373 .values()
374 .flat_map(|lc| lc.as_slice().to_vec())
375 .filter(|c| c.enabled)
376 .collect();
377 lsp.set_universal_configs(universal_servers);
378 }
379
380 let config_path = Config::find_config_path(&self.working_dir);
382 self.emit_event(
383 "config_changed",
384 serde_json::json!({
385 "path": config_path.map(|p| p.to_string_lossy().into_owned()),
386 }),
387 );
388 }
389
390 pub fn reload_themes(&mut self) {
395 use crate::view::theme::ThemeLoader;
396
397 let theme_loader = ThemeLoader::new(self.dir_context.themes_dir());
398 self.theme_registry = std::sync::Arc::new(theme_loader.load_all(&[]));
399 self.expanded_menus_cache.invalidate();
400
401 for w in self.windows.values_mut() {
406 w.resources.theme_registry = self.theme_registry.clone();
407 }
408
409 *self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();
411
412 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
414 *self.theme.write().unwrap() = theme;
415 }
416
417 tracing::info!(
418 "Theme registry reloaded ({} themes)",
419 self.theme_registry.len()
420 );
421
422 self.emit_event("themes_changed", serde_json::json!({}));
424 }
425
426 pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
431 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
432 let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
433 let deletions = std::collections::HashSet::new();
434 if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
435 tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
436 }
437 }
438}