use crate::types::LspServerConfig;
use rust_i18n::t;
use crate::config::{Config, FileExplorerSide};
use crate::config_io::{ConfigLayer, ConfigResolver};
use crate::input::keybindings::KeybindingResolver;
use super::Editor;
impl Editor {
pub fn toggle_line_numbers(&mut self) {
let active_split = self
.windows
.get(&self.active_window)
.and_then(|w| w.buffers.splits())
.map(|(mgr, _)| mgr)
.expect("active window must have a populated split layout")
.active_split();
if let Some(vs) = self
.windows
.get_mut(&self.active_window)
.and_then(|w| w.split_view_states_mut())
.expect("active window must have a populated split layout")
.get_mut(&active_split)
{
let currently_shown = vs.show_line_numbers;
vs.show_line_numbers = !currently_shown;
if currently_shown {
self.set_status_message(t!("toggle.line_numbers_hidden").to_string());
} else {
self.set_status_message(t!("toggle.line_numbers_shown").to_string());
}
}
}
pub fn toggle_menu_bar(&mut self) {
let new_value = !self.active_window_mut().menu_bar_visible;
self.config_mut().editor.show_menu_bar = new_value;
self.active_window_mut().menu_bar_visible = new_value;
self.active_window_mut().menu_bar_auto_shown = false;
if !self.active_window_mut().menu_bar_visible {
self.menu_state.close_menu();
}
self.persist_config_change("/editor/show_menu_bar", serde_json::Value::Bool(new_value));
let status = if self.active_window_mut().menu_bar_visible {
t!("toggle.menu_bar_shown")
} else {
t!("toggle.menu_bar_hidden")
};
self.set_status_message(status.to_string());
}
pub fn toggle_file_explorer_side(&mut self) {
let new_side = match self.config.file_explorer.side {
FileExplorerSide::Left => FileExplorerSide::Right,
FileExplorerSide::Right => FileExplorerSide::Left,
};
self.config_mut().file_explorer.side = new_side;
self.active_window_mut().file_explorer_side = new_side;
self.persist_config_change(
"/file_explorer/side",
serde_json::json!(match new_side {
FileExplorerSide::Left => "left",
FileExplorerSide::Right => "right",
}),
);
let status = match new_side {
FileExplorerSide::Left => t!("toggle.file_explorer_side_left"),
FileExplorerSide::Right => t!("toggle.file_explorer_side_right"),
};
self.set_status_message(status.to_string());
}
pub fn toggle_vertical_scrollbar(&mut self) {
let new_value = !self.config.editor.show_vertical_scrollbar;
self.config_mut().editor.show_vertical_scrollbar = new_value;
let status = if self.config.editor.show_vertical_scrollbar {
t!("toggle.vertical_scrollbar_shown")
} else {
t!("toggle.vertical_scrollbar_hidden")
};
self.set_status_message(status.to_string());
}
pub fn toggle_horizontal_scrollbar(&mut self) {
let new_value = !self.config.editor.show_horizontal_scrollbar;
self.config_mut().editor.show_horizontal_scrollbar = new_value;
let status = if self.config.editor.show_horizontal_scrollbar {
t!("toggle.horizontal_scrollbar_shown")
} else {
t!("toggle.horizontal_scrollbar_hidden")
};
self.set_status_message(status.to_string());
}
pub fn reset_buffer_settings(&mut self) {
use crate::config::WhitespaceVisibility;
let buffer_id = self.active_buffer();
let mut whitespace = WhitespaceVisibility::from_editor_config(&self.config.editor);
let mut auto_close = self.config.editor.auto_close;
let mut word_characters = String::new();
let (tab_size, use_tabs) = if let Some(state) = self
.windows
.get(&self.active_window)
.map(|w| &w.buffers)
.expect("active window present")
.get(&buffer_id)
{
let language = &state.language;
if let Some(lang_config) = self.config.languages.get(language) {
whitespace =
whitespace.with_language_tab_override(lang_config.show_whitespace_tabs);
if auto_close {
if let Some(lang_auto_close) = lang_config.auto_close {
auto_close = lang_auto_close;
}
}
if let Some(ref wc) = lang_config.word_characters {
word_characters = wc.clone();
}
(
lang_config.tab_size.unwrap_or(self.config.editor.tab_size),
lang_config.use_tabs.unwrap_or(self.config.editor.use_tabs),
)
} else {
(self.config.editor.tab_size, self.config.editor.use_tabs)
}
} else {
(self.config.editor.tab_size, self.config.editor.use_tabs)
};
if let Some(state) = self
.windows
.get_mut(&self.active_window)
.map(|w| &mut w.buffers)
.expect("active window present")
.get_mut(&buffer_id)
{
state.buffer_settings.tab_size = tab_size;
state.buffer_settings.use_tabs = use_tabs;
state.buffer_settings.auto_close = auto_close;
state.buffer_settings.whitespace = whitespace;
state.buffer_settings.word_characters = word_characters;
}
self.set_status_message(t!("toggle.buffer_settings_reset").to_string());
}
pub fn toggle_mouse_capture(&mut self) {
use std::io::stdout;
self.active_window_mut().mouse_enabled = !self.active_window_mut().mouse_enabled;
if self.active_window_mut().mouse_enabled {
#[allow(clippy::let_underscore_must_use)]
let _ = crossterm::execute!(stdout(), crossterm::event::EnableMouseCapture);
self.set_status_message(t!("toggle.mouse_capture_enabled").to_string());
} else {
#[allow(clippy::let_underscore_must_use)]
let _ = crossterm::execute!(stdout(), crossterm::event::DisableMouseCapture);
self.set_status_message(t!("toggle.mouse_capture_disabled").to_string());
}
}
pub fn is_mouse_enabled(&self) -> bool {
self.active_window().mouse_enabled
}
pub fn toggle_mouse_hover(&mut self) {
let new_value = !self.config.editor.mouse_hover_enabled;
self.config_mut().editor.mouse_hover_enabled = new_value;
if self.config.editor.mouse_hover_enabled {
self.set_status_message(t!("toggle.mouse_hover_enabled").to_string());
} else {
self.active_window_mut().mouse_state.lsp_hover_state = None;
self.active_window_mut().mouse_state.lsp_hover_request_sent = false;
self.set_status_message(t!("toggle.mouse_hover_disabled").to_string());
}
#[cfg(windows)]
{
let mode = if self.config.editor.mouse_hover_enabled {
fresh_winterm::MouseMode::AllMotion
} else {
fresh_winterm::MouseMode::CellMotion
};
if let Err(e) = fresh_winterm::set_mouse_mode(mode) {
tracing::error!("Failed to switch mouse mode: {}", e);
}
}
}
pub fn is_mouse_hover_enabled(&self) -> bool {
self.config.editor.mouse_hover_enabled
}
pub fn set_gpm_active(&mut self, active: bool) {
self.active_window_mut().gpm_active = active;
}
pub fn toggle_inlay_hints(&mut self) {
let new_value = !self.config.editor.enable_inlay_hints;
self.config_mut().editor.enable_inlay_hints = new_value;
self.sync_windows_config();
if self.config.editor.enable_inlay_hints {
self.request_inlay_hints_for_active_buffer();
self.set_status_message(t!("toggle.inlay_hints_enabled").to_string());
} else {
for (_, state) in self
.windows
.get_mut(&self.active_window)
.map(|w| &mut w.buffers)
.expect("active window present")
{
state.virtual_texts.clear(&mut state.marker_list);
}
self.set_status_message(t!("toggle.inlay_hints_disabled").to_string());
}
}
pub fn dump_config(&mut self) {
if let Err(e) = self
.authority
.filesystem
.create_dir_all(&self.dir_context.config_dir)
{
self.set_status_message(
t!("error.config_dir_failed", error = e.to_string()).to_string(),
);
return;
}
let config_path = self.dir_context.config_path();
let resolver =
ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
match resolver.save_to_layer(&self.config, ConfigLayer::User) {
Ok(()) => {
match self.open_file(&config_path) {
Ok(_buffer_id) => {
self.set_status_message(
t!("config.saved", path = config_path.display().to_string())
.to_string(),
);
}
Err(e) => {
if let Some(confirmation) =
e.downcast_ref::<crate::model::buffer::LargeFileEncodingConfirmation>()
{
self.start_large_file_encoding_confirmation(confirmation);
} else {
self.set_status_message(
t!("config.saved_failed_open", error = e.to_string()).to_string(),
);
}
}
}
}
Err(e) => {
self.set_status_message(
t!("error.config_save_failed", error = e.to_string()).to_string(),
);
}
}
}
pub fn save_config(&self) -> Result<(), String> {
self.authority
.filesystem
.create_dir_all(&self.dir_context.config_dir)
.map_err(|e| format!("Failed to create config directory: {}", e))?;
let resolver =
ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
resolver
.save_to_layer(&self.config, ConfigLayer::User)
.map_err(|e| format!("Failed to save config: {}", e))
}
pub fn reload_config(&mut self) {
let old_theme = self.config.theme.clone();
self.set_config(Config::load_with_layers(
&self.dir_context,
self.working_dir(),
));
self.set_user_config_raw(Config::read_user_config_raw(self.working_dir()));
if old_theme != self.config.theme {
if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
*self.theme.write().unwrap() = theme;
tracing::info!("Theme changed to '{}'", self.config.theme.0);
} else {
tracing::error!("Theme '{}' not found", self.config.theme.0);
}
}
*self.keybindings.write().unwrap() = KeybindingResolver::new(&self.config);
self.clipboard.apply_config(&self.config.clipboard);
self.active_window_mut().menu_bar_visible = self.config.editor.show_menu_bar;
self.active_window_mut().tab_bar_visible = self.config.editor.show_tab_bar;
self.active_window_mut().status_bar_visible = self.config.editor.show_status_bar;
self.active_window_mut().prompt_line_visible = self.config.editor.show_prompt_line;
let __active_id = self.active_window;
if let Some(lsp) = self
.windows
.get_mut(&__active_id)
.and_then(|w| w.lsp.as_mut())
{
for (language, lsp_configs) in &self.config.lsp {
lsp.set_language_configs(language.clone(), lsp_configs.as_slice().to_vec());
}
let universal_servers: Vec<LspServerConfig> = self
.config
.universal_lsp
.values()
.flat_map(|lc| lc.as_slice().to_vec())
.filter(|c| c.enabled)
.collect();
lsp.set_universal_configs(universal_servers);
}
let config_path = Config::find_config_path(self.working_dir());
self.emit_event(
"config_changed",
serde_json::json!({
"path": config_path.map(|p| p.to_string_lossy().into_owned()),
}),
);
}
pub fn reload_themes(&mut self) {
use crate::view::theme::ThemeLoader;
let theme_loader = ThemeLoader::new(self.dir_context.themes_dir());
self.theme_registry = std::sync::Arc::new(theme_loader.load_all(&[]));
self.expanded_menus_cache.invalidate();
for w in self.windows.values_mut() {
w.resources.theme_registry = self.theme_registry.clone();
}
*self.theme_cache.write().unwrap() = self.theme_registry.to_json_map();
if let Some(theme) = self.theme_registry.get_cloned(&self.config.theme) {
*self.theme.write().unwrap() = theme;
}
tracing::info!(
"Theme registry reloaded ({} themes)",
self.theme_registry.len()
);
self.emit_event("themes_changed", serde_json::json!({}));
}
pub(super) fn persist_config_change(&self, json_pointer: &str, value: serde_json::Value) {
let resolver =
ConfigResolver::new(self.dir_context.clone(), self.working_dir().to_path_buf());
let changes = std::collections::HashMap::from([(json_pointer.to_string(), value)]);
let deletions = std::collections::HashSet::new();
if let Err(e) = resolver.save_changes_to_layer(&changes, &deletions, ConfigLayer::User) {
tracing::error!("Failed to persist config change {}: {}", json_pointer, e);
}
}
}