fresh/app/
toggle_actions.rs1use rust_i18n::t;
10
11use crate::config::Config;
12use crate::config_io::{ConfigLayer, ConfigResolver};
13use crate::input::keybindings::KeybindingResolver;
14
15use super::Editor;
16
17impl Editor {
18 pub fn toggle_scroll_sync(&mut self) {
25 self.same_buffer_scroll_sync = !self.same_buffer_scroll_sync;
26 if self.same_buffer_scroll_sync {
27 self.set_status_message(t!("toggle.scroll_sync_enabled").to_string());
28 } else {
29 self.set_status_message(t!("toggle.scroll_sync_disabled").to_string());
30 }
31 }
32
33 pub fn toggle_line_numbers(&mut self) {
34 let active_split = self.split_manager.active_split();
35 if let Some(vs) = self.split_view_states.get_mut(&active_split) {
36 let currently_shown = vs.show_line_numbers;
37 vs.show_line_numbers = !currently_shown;
38 if currently_shown {
39 self.set_status_message(t!("toggle.line_numbers_hidden").to_string());
40 } else {
41 self.set_status_message(t!("toggle.line_numbers_shown").to_string());
42 }
43 }
44 }
45
46 pub fn toggle_debug_highlights(&mut self) {
49 if let Some(state) = self.buffers.get_mut(&self.active_buffer()) {
50 state.debug_highlight_mode = !state.debug_highlight_mode;
51 if state.debug_highlight_mode {
52 self.set_status_message(t!("toggle.debug_mode_on").to_string());
53 } else {
54 self.set_status_message(t!("toggle.debug_mode_off").to_string());
55 }
56 }
57 }
58
59 pub fn toggle_menu_bar(&mut self) {
61 self.menu_bar_visible = !self.menu_bar_visible;
62 self.menu_bar_auto_shown = false;
64 if !self.menu_bar_visible {
66 self.menu_state.close_menu();
67 }
68 let status = if self.menu_bar_visible {
69 t!("toggle.menu_bar_shown")
70 } else {
71 t!("toggle.menu_bar_hidden")
72 };
73 self.set_status_message(status.to_string());
74 }
75
76 pub fn toggle_tab_bar(&mut self) {
78 self.tab_bar_visible = !self.tab_bar_visible;
79 let status = if self.tab_bar_visible {
80 t!("toggle.tab_bar_shown")
81 } else {
82 t!("toggle.tab_bar_hidden")
83 };
84 self.set_status_message(status.to_string());
85 }
86
87 pub fn tab_bar_visible(&self) -> bool {
89 self.tab_bar_visible
90 }
91
92 pub fn toggle_status_bar(&mut self) {
94 self.status_bar_visible = !self.status_bar_visible;
95 let status = if self.status_bar_visible {
96 t!("toggle.status_bar_shown")
97 } else {
98 t!("toggle.status_bar_hidden")
99 };
100 self.set_status_message(status.to_string());
101 }
102
103 pub fn status_bar_visible(&self) -> bool {
105 self.status_bar_visible
106 }
107
108 pub fn toggle_prompt_line(&mut self) {
110 self.prompt_line_visible = !self.prompt_line_visible;
111 let status = if self.prompt_line_visible {
112 t!("toggle.prompt_line_shown")
113 } else {
114 t!("toggle.prompt_line_hidden")
115 };
116 self.set_status_message(status.to_string());
117 }
118
119 pub fn prompt_line_visible(&self) -> bool {
121 self.prompt_line_visible
122 }
123
124 pub fn toggle_vertical_scrollbar(&mut self) {
126 self.config.editor.show_vertical_scrollbar = !self.config.editor.show_vertical_scrollbar;
127 let status = if self.config.editor.show_vertical_scrollbar {
128 t!("toggle.vertical_scrollbar_shown")
129 } else {
130 t!("toggle.vertical_scrollbar_hidden")
131 };
132 self.set_status_message(status.to_string());
133 }
134
135 pub fn toggle_horizontal_scrollbar(&mut self) {
137 self.config.editor.show_horizontal_scrollbar =
138 !self.config.editor.show_horizontal_scrollbar;
139 let status = if self.config.editor.show_horizontal_scrollbar {
140 t!("toggle.horizontal_scrollbar_shown")
141 } else {
142 t!("toggle.horizontal_scrollbar_hidden")
143 };
144 self.set_status_message(status.to_string());
145 }
146
147 pub fn reset_buffer_settings(&mut self) {
149 use crate::config::WhitespaceVisibility;
150 let buffer_id = self.active_buffer();
151
152 let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
154 let mut auto_close = self.config.editor.auto_close;
155 let mut word_characters = String::new();
156 let (tab_size, use_tabs) = if let Some(state) = self.buffers.get(&buffer_id) {
157 let language = &state.language;
158 if let Some(lang_config) = self.config.languages.get(language) {
159 whitespace =
160 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
161 if auto_close {
163 if let Some(lang_auto_close) = lang_config.auto_close {
164 auto_close = lang_auto_close;
165 }
166 }
167 if let Some(ref wc) = lang_config.word_characters {
168 word_characters = wc.clone();
169 }
170 (
171 lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
172 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
173 )
174 } else {
175 (self.config.editor.tab_size, self.config.editor.use_tabs)
176 }
177 } else {
178 (self.config.editor.tab_size, self.config.editor.use_tabs)
179 };
180
181 if let Some(state) = self.buffers.get_mut(&buffer_id) {
183 state.buffer_settings.tab_size = tab_size;
184 state.buffer_settings.use_tabs = use_tabs;
185 state.buffer_settings.auto_close = auto_close;
186 state.buffer_settings.whitespace = whitespace;
187 state.buffer_settings.word_characters = word_characters;
188 }
189
190 self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
191 }
192
193 pub fn toggle_mouse_capture(&mut self) {
195 use std::io::stdout;
196
197 self.mouse_enabled = !self.mouse_enabled;
198
199 if self.mouse_enabled {
200 #[allow(clippy::let_underscore_must_use)]
202 let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
203 self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
204 } else {
205 #[allow(clippy::let_underscore_must_use)]
207 let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
208 self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
209 }
210 }
211
212 pub fn is_mouse_enabled(&self) -> bool {
214 self.mouse_enabled
215 }
216
217 pub fn toggle_mouse_hover(&mut self) {
222 self.config.editor.mouse_hover_enabled = !self.config.editor.mouse_hover_enabled;
223
224 if self.config.editor.mouse_hover_enabled {
225 self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
226 } else {
227 self.mouse_state.lsp_hover_state = None;
229 self.mouse_state.lsp_hover_request_sent = false;
230 self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
231 }
232
233 #[cfg(windows)]
235 {
236 let mode = if self.config.editor.mouse_hover_enabled {
237 fresh_winterm::MouseMode::AllMotion
238 } else {
239 fresh_winterm::MouseMode::CellMotion
240 };
241 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
242 tracing::error!("Failed to switch mouse mode: {}", e);
243 }
244 }
245 }
246
247 pub fn is_mouse_hover_enabled(&self) -> bool {
249 self.config.editor.mouse_hover_enabled
250 }
251
252 pub fn set_gpm_active(&mut self, active: bool) {
258 self.gpm_active = active;
259 }
260
261 pub fn toggle_inlay_hints(&mut self) {
263 self.config.editor.enable_inlay_hints = !self.config.editor.enable_inlay_hints;
264
265 if self.config.editor.enable_inlay_hints {
266 self.request_inlay_hints_for_active_buffer();
268 self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
269 } else {
270 for state in self.buffers.values_mut() {
272 state.virtual_texts.clear(&mut state.marker_list);
273 }
274 self.set_status_message(t!("toggle.inlay_hints_disabled").to_string());
275 }
276 }
277
278 pub fn dump_config(&mut self) {
280 if let Err(e) = self.filesystem.create_dir_all(&self.dir_context.config_dir) {
282 self.set_status_message(
283 t!("error.config_dir_failed", error = e.to_string()).to_string(),
284 );
285 return;
286 }
287
288 let config_path = self.dir_context.config_path();
289 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
290
291 match resolver.save_to_layer(&self.config, ConfigLayer::User) {
293 Ok(()) => {
294 match self.open_file(&config_path) {
296 Ok(_buffer_id) => {
297 self.set_status_message(
298 t!("config.saved", path = config_path.display().to_string())
299 .to_string(),
300 );
301 }
302 Err(e) => {
303 if let Some(confirmation) =
305 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
306 {
307 self.start_large_file_encoding_confirmation(confirmation);
308 } else {
309 self.set_status_message(
310 t!("config.saved_failed_open", error = e.to_string()).to_string(),
311 );
312 }
313 }
314 }
315 }
316 Err(e) => {
317 self.set_status_message(
318 t!("error.config_save_failed", error = e.to_string()).to_string(),
319 );
320 }
321 }
322 }
323
324 pub fn save_config(&self) -> Result<(), String> {
328 self.filesystem
330 .create_dir_all(&self.dir_context.config_dir)
331 .map_err(|e| format!("Failed to create config directory: {}", e))?;
332
333 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
334 resolver
335 .save_to_layer(&self.config, ConfigLayer::User)
336 .map_err(|e| format!("Failed to save config: {}", e))
337 }
338
339 pub fn reload_config(&mut self) {
345 let old_theme = self.config.theme.clone();
346 self.config = Config::load_with_layers(&self.dir_context, &self.working_dir);
347
348 self.user_config_raw = Config::read_user_config_raw(&self.working_dir);
350
351 if old_theme != self.config.theme {
353 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
354 self.theme = theme;
355 tracing::info!("Theme changed to '{}'", self.config.theme.0);
356 } else {
357 tracing::error!("Theme '{}' not found", self.config.theme.0);
358 }
359 }
360
361 self.keybindings = KeybindingResolver::new(&self.config);
363
364 self.clipboard.apply_config(&self.config.clipboard);
366
367 self.menu_bar_visible = self.config.editor.show_menu_bar;
369 self.tab_bar_visible = self.config.editor.show_tab_bar;
370 self.status_bar_visible = self.config.editor.show_status_bar;
371 self.prompt_line_visible = self.config.editor.show_prompt_line;
372
373 if let Some(ref mut lsp) = self.lsp {
375 for (language, lsp_configs) in &self.config.lsp {
376 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
377 }
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 = theme_loader.load_all(&[]);
399
400 *self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();
402
403 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
405 self.theme = theme;
406 }
407
408 tracing::info!(
409 "Theme registry reloaded ({} themes)",
410 self.theme_registry.len()
411 );
412
413 self.emit_event("themes_changed", serde_json::json!({}));
415 }
416
417 pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
422 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
423 let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
424 let deletions = std::collections::HashSet::new();
425 if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
426 tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
427 }
428 }
429}