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_scroll_sync(&mut self) {
26 self.same_buffer_scroll_sync = !self.same_buffer_scroll_sync;
27 if self.same_buffer_scroll_sync {
28 self.set_status_message(t!("toggle.scroll_sync_enabled").to_string());
29 } else {
30 self.set_status_message(t!("toggle.scroll_sync_disabled").to_string());
31 }
32 }
33
34 pub fn toggle_line_numbers(&mut self) {
35 let active_split = self.split_manager.active_split();
36 if let Some(vs) = self.split_view_states.get_mut(&active_split) {
37 let currently_shown = vs.show_line_numbers;
38 vs.show_line_numbers = !currently_shown;
39 if currently_shown {
40 self.set_status_message(t!("toggle.line_numbers_hidden").to_string());
41 } else {
42 self.set_status_message(t!("toggle.line_numbers_shown").to_string());
43 }
44 }
45 }
46
47 pub fn toggle_debug_highlights(&mut self) {
50 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
51 state.debug_highlight_mode = !state.debug_highlight_mode;
52 if state.debug_highlight_mode {
53 self.set_status_message(t!("toggle.debug_mode_on").to_string());
54 } else {
55 self.set_status_message(t!("toggle.debug_mode_off").to_string());
56 }
57 }
58 }
59
60 pub fn toggle_menu_bar(&mut self) {
66 let new_value = !self.menu_bar_visible;
67 self.config_mut().editor.show_menu_bar = new_value;
68 self.menu_bar_visible = new_value;
69 self.menu_bar_auto_shown = false;
71 if !self.menu_bar_visible {
73 self.menu_state.close_menu();
74 }
75 self.persist_config_change("/editor/show_menu_bar", serde_json::Value::Bool(new_value));
76 let status = if self.menu_bar_visible {
77 t!("toggle.menu_bar_shown")
78 } else {
79 t!("toggle.menu_bar_hidden")
80 };
81 self.set_status_message(status.to_string());
82 }
83
84 pub fn toggle_tab_bar(&mut self) {
86 self.tab_bar_visible = !self.tab_bar_visible;
87 let status = if self.tab_bar_visible {
88 t!("toggle.tab_bar_shown")
89 } else {
90 t!("toggle.tab_bar_hidden")
91 };
92 self.set_status_message(status.to_string());
93 }
94
95 pub fn tab_bar_visible(&self) -> bool {
97 self.tab_bar_visible
98 }
99
100 pub fn toggle_status_bar(&mut self) {
102 self.status_bar_visible = !self.status_bar_visible;
103 let status = if self.status_bar_visible {
104 t!("toggle.status_bar_shown")
105 } else {
106 t!("toggle.status_bar_hidden")
107 };
108 self.set_status_message(status.to_string());
109 }
110
111 pub fn status_bar_visible(&self) -> bool {
113 self.status_bar_visible
114 }
115
116 pub fn toggle_prompt_line(&mut self) {
118 self.prompt_line_visible = !self.prompt_line_visible;
119 let status = if self.prompt_line_visible {
120 t!("toggle.prompt_line_shown")
121 } else {
122 t!("toggle.prompt_line_hidden")
123 };
124 self.set_status_message(status.to_string());
125 }
126
127 pub fn prompt_line_visible(&self) -> bool {
129 self.prompt_line_visible
130 }
131
132 pub fn toggle_vertical_scrollbar(&mut self) {
134 let new_value = !self.config.editor.show_vertical_scrollbar;
135 self.config_mut().editor.show_vertical_scrollbar = new_value;
136 let status = if self.config.editor.show_vertical_scrollbar {
137 t!("toggle.vertical_scrollbar_shown")
138 } else {
139 t!("toggle.vertical_scrollbar_hidden")
140 };
141 self.set_status_message(status.to_string());
142 }
143
144 pub fn toggle_horizontal_scrollbar(&mut self) {
146 let new_value = !self.config.editor.show_horizontal_scrollbar;
147 self.config_mut().editor.show_horizontal_scrollbar = new_value;
148 let status = if self.config.editor.show_horizontal_scrollbar {
149 t!("toggle.horizontal_scrollbar_shown")
150 } else {
151 t!("toggle.horizontal_scrollbar_hidden")
152 };
153 self.set_status_message(status.to_string());
154 }
155
156 pub fn reset_buffer_settings(&mut self) {
158 use crate::config::WhitespaceVisibility;
159 let buffer_id = self.active_buffer();
160
161 let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
163 let mut auto_close = self.config.editor.auto_close;
164 let mut word_characters = String::new();
165 let (tab_size, use_tabs) = if let Some(state) = self.buffers.get(&buffer_id) {
166 let language = &state.language;
167 if let Some(lang_config) = self.config.languages.get(language) {
168 whitespace =
169 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
170 if auto_close {
172 if let Some(lang_auto_close) = lang_config.auto_close {
173 auto_close = lang_auto_close;
174 }
175 }
176 if let Some(ref wc) = lang_config.word_characters {
177 word_characters = wc.clone();
178 }
179 (
180 lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
181 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
182 )
183 } else {
184 (self.config.editor.tab_size, self.config.editor.use_tabs)
185 }
186 } else {
187 (self.config.editor.tab_size, self.config.editor.use_tabs)
188 };
189
190 if let Some(state) = self.buffers.get_mut(&buffer_id) {
192 state.buffer_settings.tab_size = tab_size;
193 state.buffer_settings.use_tabs = use_tabs;
194 state.buffer_settings.auto_close = auto_close;
195 state.buffer_settings.whitespace = whitespace;
196 state.buffer_settings.word_characters = word_characters;
197 }
198
199 self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
200 }
201
202 pub fn toggle_mouse_capture(&mut self) {
204 use std::io::stdout;
205
206 self.mouse_enabled = !self.mouse_enabled;
207
208 if self.mouse_enabled {
209 #[allow(clippy::let_underscore_must_use)]
211 let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
212 self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
213 } else {
214 #[allow(clippy::let_underscore_must_use)]
216 let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
217 self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
218 }
219 }
220
221 pub fn is_mouse_enabled(&self) -> bool {
223 self.mouse_enabled
224 }
225
226 pub fn toggle_mouse_hover(&mut self) {
231 let new_value = !self.config.editor.mouse_hover_enabled;
232 self.config_mut().editor.mouse_hover_enabled = new_value;
233
234 if self.config.editor.mouse_hover_enabled {
235 self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
236 } else {
237 self.mouse_state.lsp_hover_state = None;
239 self.mouse_state.lsp_hover_request_sent = false;
240 self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
241 }
242
243 #[cfg(windows)]
245 {
246 let mode = if self.config.editor.mouse_hover_enabled {
247 fresh_winterm::MouseMode::AllMotion
248 } else {
249 fresh_winterm::MouseMode::CellMotion
250 };
251 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
252 tracing::error!("Failed to switch mouse mode: {}", e);
253 }
254 }
255 }
256
257 pub fn is_mouse_hover_enabled(&self) -> bool {
259 self.config.editor.mouse_hover_enabled
260 }
261
262 pub fn set_gpm_active(&mut self, active: bool) {
268 self.gpm_active = active;
269 }
270
271 pub fn toggle_inlay_hints(&mut self) {
273 let new_value = !self.config.editor.enable_inlay_hints;
274 self.config_mut().editor.enable_inlay_hints = new_value;
275
276 if self.config.editor.enable_inlay_hints {
277 self.request_inlay_hints_for_active_buffer();
279 self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
280 } else {
281 for state in self.buffers.values_mut() {
283 state.virtual_texts.clear(&mut state.marker_list);
284 }
285 self.set_status_message(t!("toggle.inlay_hints_disabled").to_string());
286 }
287 }
288
289 pub fn dump_config(&mut self) {
291 if let Err(e) = self
293 .authority
294 .filesystem
295 .create_dir_all(&self.dir_context.config_dir)
296 {
297 self.set_status_message(
298 t!("error.config_dir_failed", error = e.to_string()).to_string(),
299 );
300 return;
301 }
302
303 let config_path = self.dir_context.config_path();
304 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
305
306 match resolver.save_to_layer(&self.config, ConfigLayer::User) {
308 Ok(()) => {
309 match self.open_file(&config_path) {
311 Ok(_buffer_id) => {
312 self.set_status_message(
313 t!("config.saved", path = config_path.display().to_string())
314 .to_string(),
315 );
316 }
317 Err(e) => {
318 if let Some(confirmation) =
320 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
321 {
322 self.start_large_file_encoding_confirmation(confirmation);
323 } else {
324 self.set_status_message(
325 t!("config.saved_failed_open", error = e.to_string()).to_string(),
326 );
327 }
328 }
329 }
330 }
331 Err(e) => {
332 self.set_status_message(
333 t!("error.config_save_failed", error = e.to_string()).to_string(),
334 );
335 }
336 }
337 }
338
339 pub fn save_config(&self) -> Result<(), String> {
343 self.authority
345 .filesystem
346 .create_dir_all(&self.dir_context.config_dir)
347 .map_err(|e| format!("Failed to create config directory: {}", e))?;
348
349 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
350 resolver
351 .save_to_layer(&self.config, ConfigLayer::User)
352 .map_err(|e| format!("Failed to save config: {}", e))
353 }
354
355 pub fn reload_config(&mut self) {
361 let old_theme = self.config.theme.clone();
362 self.set_config(Config::load_with_layers(
363 &self.dir_context,
364 &self.working_dir,
365 ));
366
367 self.set_user_config_raw(Config::read_user_config_raw(&self.working_dir));
369
370 if old_theme != self.config.theme {
372 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
373 self.theme = theme;
374 tracing::info!("Theme changed to '{}'", self.config.theme.0);
375 } else {
376 tracing::error!("Theme '{}' not found", self.config.theme.0);
377 }
378 }
379
380 *self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);
382
383 self.clipboard.apply_config(&self.config.clipboard);
385
386 self.menu_bar_visible = self.config.editor.show_menu_bar;
388 self.tab_bar_visible = self.config.editor.show_tab_bar;
389 self.status_bar_visible = self.config.editor.show_status_bar;
390 self.prompt_line_visible = self.config.editor.show_prompt_line;
391
392 if let Some(ref mut lsp) = self.lsp {
394 for (language, lsp_configs) in &self.config.lsp {
395 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
396 }
397 let universal_servers: Vec<LspServerConfig> = self
399 .config
400 .universal_lsp
401 .values()
402 .flat_map(|lc| lc.as_slice().to_vec())
403 .filter(|c| c.enabled)
404 .collect();
405 lsp.set_universal_configs(universal_servers);
406 }
407
408 let config_path = Config::find_config_path(&self.working_dir);
410 self.emit_event(
411 "config_changed",
412 serde_json::json!({
413 "path": config_path.map(|p| p.to_string_lossy().into_owned()),
414 }),
415 );
416 }
417
418 pub fn reload_themes(&mut self) {
423 use crate::view::theme::ThemeLoader;
424
425 let theme_loader = ThemeLoader::new(self.dir_context.themes_dir());
426 self.theme_registry = std::sync::Arc::new(theme_loader.load_all(&[]));
427 self.expanded_menus_cache.invalidate();
428
429 *self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();
431
432 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
434 self.theme = theme;
435 }
436
437 tracing::info!(
438 "Theme registry reloaded ({} themes)",
439 self.theme_registry.len()
440 );
441
442 self.emit_event("themes_changed", serde_json::json!({}));
444 }
445
446 pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
451 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
452 let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
453 let deletions = std::collections::HashSet::new();
454 if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
455 tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
456 }
457 }
458}