fresh/app/
toggle_actions.rs1use crate::types::LspServerConfig;
10use rust_i18n::t;
11
12use crate::config::{Config, FileExplorerSide};
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_file_explorer_side(&mut self) {
84 let new_side = match self.config.file_explorer.side {
85 FileExplorerSide::Left => FileExplorerSide::Right,
86 FileExplorerSide::Right => FileExplorerSide::Left,
87 };
88 self.config_mut().file_explorer.side = new_side;
89 self.active_window_mut().file_explorer_side = new_side;
90 self.persist_config_change(
91 "/file_explorer/side",
92 serde_json::json!(match new_side {
93 FileExplorerSide::Left => "left",
94 FileExplorerSide::Right => "right",
95 }),
96 );
97 let status = match new_side {
98 FileExplorerSide::Left => t!("toggle.file_explorer_side_left"),
99 FileExplorerSide::Right => t!("toggle.file_explorer_side_right"),
100 };
101 self.set_status_message(status.to_string());
102 }
103
104 pub fn toggle_vertical_scrollbar(&mut self) {
106 let new_value = !self.config.editor.show_vertical_scrollbar;
107 self.config_mut().editor.show_vertical_scrollbar = new_value;
108 let status = if self.config.editor.show_vertical_scrollbar {
109 t!("toggle.vertical_scrollbar_shown")
110 } else {
111 t!("toggle.vertical_scrollbar_hidden")
112 };
113 self.set_status_message(status.to_string());
114 }
115
116 pub fn toggle_horizontal_scrollbar(&mut self) {
118 let new_value = !self.config.editor.show_horizontal_scrollbar;
119 self.config_mut().editor.show_horizontal_scrollbar = new_value;
120 let status = if self.config.editor.show_horizontal_scrollbar {
121 t!("toggle.horizontal_scrollbar_shown")
122 } else {
123 t!("toggle.horizontal_scrollbar_hidden")
124 };
125 self.set_status_message(status.to_string());
126 }
127
128 pub fn reset_buffer_settings(&mut self) {
130 use crate::config::WhitespaceVisibility;
131 let buffer_id = self.active_buffer();
132
133 let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
135 let mut auto_close = self.config.editor.auto_close;
136 let mut word_characters = String::new();
137 let (tab_size, use_tabs) = if let Some(state) = self
138 .windows
139 .get(&self.active_window)
140 .map(|w| &w.buffers)
141 .expect("active window present")
142 .get(&buffer_id)
143 {
144 let language = &state.language;
145 if let Some(lang_config) = self.config.languages.get(language) {
146 whitespace =
147 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
148 if auto_close {
150 if let Some(lang_auto_close) = lang_config.auto_close {
151 auto_close = lang_auto_close;
152 }
153 }
154 if let Some(ref wc) = lang_config.word_characters {
155 word_characters = wc.clone();
156 }
157 (
158 lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
159 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
160 )
161 } else {
162 (self.config.editor.tab_size, self.config.editor.use_tabs)
163 }
164 } else {
165 (self.config.editor.tab_size, self.config.editor.use_tabs)
166 };
167
168 if let Some(state) = self
170 .windows
171 .get_mut(&self.active_window)
172 .map(|w| &mut w.buffers)
173 .expect("active window present")
174 .get_mut(&buffer_id)
175 {
176 state.buffer_settings.tab_size = tab_size;
177 state.buffer_settings.use_tabs = use_tabs;
178 state.buffer_settings.auto_close = auto_close;
179 state.buffer_settings.whitespace = whitespace;
180 state.buffer_settings.word_characters = word_characters;
181 }
182
183 self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
184 }
185
186 pub fn toggle_mouse_capture(&mut self) {
188 use std::io::stdout;
189
190 self.active_window_mut().mouse_enabled = !self.active_window_mut().mouse_enabled;
191
192 if self.active_window_mut().mouse_enabled {
193 #[allow(clippy::let_underscore_must_use)]
195 let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
196 self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
197 } else {
198 #[allow(clippy::let_underscore_must_use)]
200 let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
201 self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
202 }
203 }
204
205 pub fn is_mouse_enabled(&self) -> bool {
207 self.active_window().mouse_enabled
208 }
209
210 pub fn toggle_mouse_hover(&mut self) {
215 let new_value = !self.config.editor.mouse_hover_enabled;
216 self.config_mut().editor.mouse_hover_enabled = new_value;
217
218 if self.config.editor.mouse_hover_enabled {
219 self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
220 } else {
221 self.active_window_mut().mouse_state.lsp_hover_state = None;
223 self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
224 self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
225 }
226
227 #[cfg(windows)]
229 {
230 let mode = if self.config.editor.mouse_hover_enabled {
231 fresh_winterm::MouseMode::AllMotion
232 } else {
233 fresh_winterm::MouseMode::CellMotion
234 };
235 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
236 tracing::error!("Failed to switch mouse mode: {}", e);
237 }
238 }
239 }
240
241 pub fn is_mouse_hover_enabled(&self) -> bool {
243 self.config.editor.mouse_hover_enabled
244 }
245
246 pub fn set_gpm_active(&mut self, active: bool) {
252 self.active_window_mut().gpm_active = active;
253 }
254
255 pub fn toggle_inlay_hints(&mut self) {
257 let new_value = !self.config.editor.enable_inlay_hints;
258 self.config_mut().editor.enable_inlay_hints = new_value;
259 self.sync_windows_config();
263
264 if self.config.editor.enable_inlay_hints {
265 self.request_inlay_hints_for_active_buffer();
267 self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
268 } else {
269 for (_, state) in self
271 .windows
272 .get_mut(&self.active_window)
273 .map(|w| &mut w.buffers)
274 .expect("active window present")
275 {
276 state.virtual_texts.clear(&mut state.marker_list);
277 }
278 self.set_status_message(t!("toggle.inlay_hints_disabled").to_string());
279 }
280 }
281
282 pub fn dump_config(&mut self) {
284 if let Err(e) = self
286 .authority
287 .filesystem
288 .create_dir_all(&self.dir_context.config_dir)
289 {
290 self.set_status_message(
291 t!("error.config_dir_failed", error = e.to_string()).to_string(),
292 );
293 return;
294 }
295
296 let config_path = self.dir_context.config_path();
297 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
298
299 match resolver.save_to_layer(&self.config, ConfigLayer::User) {
301 Ok(()) => {
302 match self.open_file(&config_path) {
304 Ok(_buffer_id) => {
305 self.set_status_message(
306 t!("config.saved", path = config_path.display().to_string())
307 .to_string(),
308 );
309 }
310 Err(e) => {
311 if let Some(confirmation) =
313 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
314 {
315 self.start_large_file_encoding_confirmation(confirmation);
316 } else {
317 self.set_status_message(
318 t!("config.saved_failed_open", error = e.to_string()).to_string(),
319 );
320 }
321 }
322 }
323 }
324 Err(e) => {
325 self.set_status_message(
326 t!("error.config_save_failed", error = e.to_string()).to_string(),
327 );
328 }
329 }
330 }
331
332 pub fn save_config(&self) -> Result<(), String> {
336 self.authority
338 .filesystem
339 .create_dir_all(&self.dir_context.config_dir)
340 .map_err(|e| format!("Failed to create config directory: {}", e))?;
341
342 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
343 resolver
344 .save_to_layer(&self.config, ConfigLayer::User)
345 .map_err(|e| format!("Failed to save config: {}", e))
346 }
347
348 pub fn reload_config(&mut self) {
354 let old_theme = self.config.theme.clone();
355 self.set_config(Config::load_with_layers(
356 &self.dir_context,
357 &self.working_dir,
358 ));
359
360 self.set_user_config_raw(Config::read_user_config_raw(&self.working_dir));
362
363 if old_theme != self.config.theme {
365 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
366 *self.theme.write().unwrap() = theme;
367 tracing::info!("Theme changed to '{}'", self.config.theme.0);
368 } else {
369 tracing::error!("Theme '{}' not found", self.config.theme.0);
370 }
371 }
372
373 *self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);
375
376 self.clipboard.apply_config(&self.config.clipboard);
378
379 self.active_window_mut().menu_bar_visible = self.config.editor.show_menu_bar;
381 self.active_window_mut().tab_bar_visible = self.config.editor.show_tab_bar;
382 self.active_window_mut().status_bar_visible = self.config.editor.show_status_bar;
383 self.active_window_mut().prompt_line_visible = self.config.editor.show_prompt_line;
384
385 let __active_id = self.active_window;
387 if let Some(lsp) = self
388 .windows
389 .get_mut(&__active_id)
390 .and_then(|w| w.lsp.as_mut())
391 {
392 for (language, lsp_configs) in &self.config.lsp {
393 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
394 }
395 let universal_servers: Vec<LspServerConfig> = self
397 .config
398 .universal_lsp
399 .values()
400 .flat_map(|lc| lc.as_slice().to_vec())
401 .filter(|c| c.enabled)
402 .collect();
403 lsp.set_universal_configs(universal_servers);
404 }
405
406 let config_path = Config::find_config_path(&self.working_dir);
408 self.emit_event(
409 "config_changed",
410 serde_json::json!({
411 "path": config_path.map(|p| p.to_string_lossy().into_owned()),
412 }),
413 );
414 }
415
416 pub fn reload_themes(&mut self) {
421 use crate::view::theme::ThemeLoader;
422
423 let theme_loader = ThemeLoader::new(self.dir_context.themes_dir());
424 self.theme_registry = std::sync::Arc::new(theme_loader.load_all(&[]));
425 self.expanded_menus_cache.invalidate();
426
427 for w in self.windows.values_mut() {
432 w.resources.theme_registry = self.theme_registry.clone();
433 }
434
435 *self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();
437
438 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
440 *self.theme.write().unwrap() = theme;
441 }
442
443 tracing::info!(
444 "Theme registry reloaded ({} themes)",
445 self.theme_registry.len()
446 );
447
448 self.emit_event("themes_changed", serde_json::json!({}));
450 }
451
452 pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
457 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
458 let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
459 let deletions = std::collections::HashSet::new();
460 if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
461 tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
462 }
463 }
464}