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) {
62 self.menu_bar_visible = !self.menu_bar_visible;
63 self.menu_bar_auto_shown = false;
65 if !self.menu_bar_visible {
67 self.menu_state.close_menu();
68 }
69 let status = if self.menu_bar_visible {
70 t!("toggle.menu_bar_shown")
71 } else {
72 t!("toggle.menu_bar_hidden")
73 };
74 self.set_status_message(status.to_string());
75 }
76
77 pub fn toggle_tab_bar(&mut self) {
79 self.tab_bar_visible = !self.tab_bar_visible;
80 let status = if self.tab_bar_visible {
81 t!("toggle.tab_bar_shown")
82 } else {
83 t!("toggle.tab_bar_hidden")
84 };
85 self.set_status_message(status.to_string());
86 }
87
88 pub fn tab_bar_visible(&self) -> bool {
90 self.tab_bar_visible
91 }
92
93 pub fn toggle_status_bar(&mut self) {
95 self.status_bar_visible = !self.status_bar_visible;
96 let status = if self.status_bar_visible {
97 t!("toggle.status_bar_shown")
98 } else {
99 t!("toggle.status_bar_hidden")
100 };
101 self.set_status_message(status.to_string());
102 }
103
104 pub fn status_bar_visible(&self) -> bool {
106 self.status_bar_visible
107 }
108
109 pub fn toggle_prompt_line(&mut self) {
111 self.prompt_line_visible = !self.prompt_line_visible;
112 let status = if self.prompt_line_visible {
113 t!("toggle.prompt_line_shown")
114 } else {
115 t!("toggle.prompt_line_hidden")
116 };
117 self.set_status_message(status.to_string());
118 }
119
120 pub fn prompt_line_visible(&self) -> bool {
122 self.prompt_line_visible
123 }
124
125 pub fn toggle_vertical_scrollbar(&mut self) {
127 let new_value = !self.config.editor.show_vertical_scrollbar;
128 self.config_mut().editor.show_vertical_scrollbar = new_value;
129 let status = if self.config.editor.show_vertical_scrollbar {
130 t!("toggle.vertical_scrollbar_shown")
131 } else {
132 t!("toggle.vertical_scrollbar_hidden")
133 };
134 self.set_status_message(status.to_string());
135 }
136
137 pub fn toggle_horizontal_scrollbar(&mut self) {
139 let new_value = !self.config.editor.show_horizontal_scrollbar;
140 self.config_mut().editor.show_horizontal_scrollbar = new_value;
141 let status = if self.config.editor.show_horizontal_scrollbar {
142 t!("toggle.horizontal_scrollbar_shown")
143 } else {
144 t!("toggle.horizontal_scrollbar_hidden")
145 };
146 self.set_status_message(status.to_string());
147 }
148
149 pub fn reset_buffer_settings(&mut self) {
151 use crate::config::WhitespaceVisibility;
152 let buffer_id = self.active_buffer();
153
154 let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
156 let mut auto_close = self.config.editor.auto_close;
157 let mut word_characters = String::new();
158 let (tab_size, use_tabs) = if let Some(state) = self.buffers.get(&buffer_id) {
159 let language = &state.language;
160 if let Some(lang_config) = self.config.languages.get(language) {
161 whitespace =
162 whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
163 if auto_close {
165 if let Some(lang_auto_close) = lang_config.auto_close {
166 auto_close = lang_auto_close;
167 }
168 }
169 if let Some(ref wc) = lang_config.word_characters {
170 word_characters = wc.clone();
171 }
172 (
173 lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
174 lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
175 )
176 } else {
177 (self.config.editor.tab_size, self.config.editor.use_tabs)
178 }
179 } else {
180 (self.config.editor.tab_size, self.config.editor.use_tabs)
181 };
182
183 if let Some(state) = self.buffers.get_mut(&buffer_id) {
185 state.buffer_settings.tab_size = tab_size;
186 state.buffer_settings.use_tabs = use_tabs;
187 state.buffer_settings.auto_close = auto_close;
188 state.buffer_settings.whitespace = whitespace;
189 state.buffer_settings.word_characters = word_characters;
190 }
191
192 self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
193 }
194
195 pub fn toggle_mouse_capture(&mut self) {
197 use std::io::stdout;
198
199 self.mouse_enabled = !self.mouse_enabled;
200
201 if self.mouse_enabled {
202 #[allow(clippy::let_underscore_must_use)]
204 let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
205 self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
206 } else {
207 #[allow(clippy::let_underscore_must_use)]
209 let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
210 self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
211 }
212 }
213
214 pub fn is_mouse_enabled(&self) -> bool {
216 self.mouse_enabled
217 }
218
219 pub fn toggle_mouse_hover(&mut self) {
224 let new_value = !self.config.editor.mouse_hover_enabled;
225 self.config_mut().editor.mouse_hover_enabled = new_value;
226
227 if self.config.editor.mouse_hover_enabled {
228 self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
229 } else {
230 self.mouse_state.lsp_hover_state = None;
232 self.mouse_state.lsp_hover_request_sent = false;
233 self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
234 }
235
236 #[cfg(windows)]
238 {
239 let mode = if self.config.editor.mouse_hover_enabled {
240 fresh_winterm::MouseMode::AllMotion
241 } else {
242 fresh_winterm::MouseMode::CellMotion
243 };
244 if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
245 tracing::error!("Failed to switch mouse mode: {}", e);
246 }
247 }
248 }
249
250 pub fn is_mouse_hover_enabled(&self) -> bool {
252 self.config.editor.mouse_hover_enabled
253 }
254
255 pub fn set_gpm_active(&mut self, active: bool) {
261 self.gpm_active = active;
262 }
263
264 pub fn toggle_inlay_hints(&mut self) {
266 let new_value = !self.config.editor.enable_inlay_hints;
267 self.config_mut().editor.enable_inlay_hints = new_value;
268
269 if self.config.editor.enable_inlay_hints {
270 self.request_inlay_hints_for_active_buffer();
272 self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
273 } else {
274 for state in self.buffers.values_mut() {
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.filesystem.create_dir_all(&self.dir_context.config_dir) {
286 self.set_status_message(
287 t!("error.config_dir_failed", error = e.to_string()).to_string(),
288 );
289 return;
290 }
291
292 let config_path = self.dir_context.config_path();
293 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
294
295 match resolver.save_to_layer(&self.config, ConfigLayer::User) {
297 Ok(()) => {
298 match self.open_file(&config_path) {
300 Ok(_buffer_id) => {
301 self.set_status_message(
302 t!("config.saved", path = config_path.display().to_string())
303 .to_string(),
304 );
305 }
306 Err(e) => {
307 if let Some(confirmation) =
309 e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
310 {
311 self.start_large_file_encoding_confirmation(confirmation);
312 } else {
313 self.set_status_message(
314 t!("config.saved_failed_open", error = e.to_string()).to_string(),
315 );
316 }
317 }
318 }
319 }
320 Err(e) => {
321 self.set_status_message(
322 t!("error.config_save_failed", error = e.to_string()).to_string(),
323 );
324 }
325 }
326 }
327
328 pub fn save_config(&self) -> Result<(), String> {
332 self.filesystem
334 .create_dir_all(&self.dir_context.config_dir)
335 .map_err(|e| format!("Failed to create config directory: {}", e))?;
336
337 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
338 resolver
339 .save_to_layer(&self.config, ConfigLayer::User)
340 .map_err(|e| format!("Failed to save config: {}", e))
341 }
342
343 pub fn reload_config(&mut self) {
349 let old_theme = self.config.theme.clone();
350 self.set_config(Config::load_with_layers(
351 &self.dir_context,
352 &self.working_dir,
353 ));
354
355 self.set_user_config_raw(Config::read_user_config_raw(&self.working_dir));
357
358 if old_theme != self.config.theme {
360 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
361 self.theme = theme;
362 tracing::info!("Theme changed to '{}'", self.config.theme.0);
363 } else {
364 tracing::error!("Theme '{}' not found", self.config.theme.0);
365 }
366 }
367
368 *self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);
370
371 self.clipboard.apply_config(&self.config.clipboard);
373
374 self.menu_bar_visible = self.config.editor.show_menu_bar;
376 self.tab_bar_visible = self.config.editor.show_tab_bar;
377 self.status_bar_visible = self.config.editor.show_status_bar;
378 self.prompt_line_visible = self.config.editor.show_prompt_line;
379
380 if let Some(ref mut lsp) = self.lsp {
382 for (language, lsp_configs) in &self.config.lsp {
383 lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
384 }
385 let universal_servers: Vec<LspServerConfig> = self
387 .config
388 .universal_lsp
389 .values()
390 .flat_map(|lc| lc.as_slice().to_vec())
391 .filter(|c| c.enabled)
392 .collect();
393 lsp.set_universal_configs(universal_servers);
394 }
395
396 let config_path = Config::find_config_path(&self.working_dir);
398 self.emit_event(
399 "config_changed",
400 serde_json::json!({
401 "path": config_path.map(|p| p.to_string_lossy().into_owned()),
402 }),
403 );
404 }
405
406 pub fn reload_themes(&mut self) {
411 use crate::view::theme::ThemeLoader;
412
413 let theme_loader = ThemeLoader::new(self.dir_context.themes_dir());
414 self.theme_registry = theme_loader.load_all(&[]);
415
416 *self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();
418
419 if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
421 self.theme = theme;
422 }
423
424 tracing::info!(
425 "Theme registry reloaded ({} themes)",
426 self.theme_registry.len()
427 );
428
429 self.emit_event("themes_changed", serde_json::json!({}));
431 }
432
433 pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
438 let resolver = ConfigResolver::new(self.dir_context.clone(), self.working_dir.clone());
439 let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
440 let deletions = std::collections::HashSet::new();
441 if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
442 tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
443 }
444 }
445}