use {
super::*,
crate::{
app::Mode,
display::{
ColsConf,
LayoutInstructions,
},
errors::*,
kitty::KittyGraphicsDisplay,
kitty::TransmissionMedium,
path::*,
preview::PreviewTransformerConf,
skin::SkinEntry,
syntactic::SyntaxTheme,
verb::ExecPattern,
},
crokey::crossterm::style::Attribute,
rustc_hash::FxHashMap,
serde::Deserialize,
std::{
collections::HashMap,
num::NonZeroUsize,
path::PathBuf,
},
};
macro_rules! overwrite {
($dst: ident, $prop: ident, $src: ident) => {
if $src.$prop.is_some() {
$dst.$prop = $src.$prop.take();
}
};
}
macro_rules! overwrite_map {
($dst: ident, $prop: ident, $src: ident) => {
for (k, v) in $src.$prop {
$dst.$prop.insert(k, v);
}
};
}
macro_rules! overwrite_vec {
($dst: ident, $prop: ident, $src: ident) => {
for v in $src.$prop {
$dst.$prop.push(v);
}
};
}
#[derive(Default, Clone, Debug, Deserialize)]
pub struct Conf {
#[serde(alias = "capture-mouse")]
pub capture_mouse: Option<bool>,
#[serde(alias = "cols-order")]
pub cols_order: Option<ColsConf>,
#[serde(
alias = "content-search-max-file-size",
deserialize_with = "file_size::deserialize",
default
)]
pub content_search_max_file_size: Option<u64>,
#[serde(alias = "date-time-format")]
pub date_time_format: Option<String>,
#[serde(alias = "default-flags")]
pub default_flags: Option<String>,
#[serde(alias = "disable-mouse-capture")]
pub disable_mouse_capture: Option<bool>,
#[serde(alias = "enable-keyboard-enhancements")]
pub enable_kitty_keyboard: Option<bool>,
#[serde(default, alias = "ext-colors")]
pub ext_colors: FxHashMap<String, String>,
pub file_sum_threads_count: Option<usize>,
#[serde(skip)]
pub files: Vec<PathBuf>,
#[serde(alias = "icon-theme")]
pub icon_theme: Option<String>,
#[serde(default)]
pub imports: Vec<Import>,
#[serde(alias = "initial-mode")]
pub initial_mode: Option<Mode>,
#[serde(alias = "kitty-graphics-transmission")]
pub kitty_graphics_transmission: Option<TransmissionMedium>,
#[serde(alias = "kitty-graphics-display")]
pub kitty_graphics_display: Option<KittyGraphicsDisplay>,
#[serde(default, alias = "kept-kitty-temp-files")]
pub kept_kitty_temp_files: Option<NonZeroUsize>,
#[serde(default, alias = "preview-transformers")]
pub preview_transformers: Vec<PreviewTransformerConf>,
#[serde(alias = "lines-after-match-in-preview")]
pub lines_after_match_in_preview: Option<usize>,
#[serde(alias = "lines-before-match-in-preview")]
pub lines_before_match_in_preview: Option<usize>,
pub max_panels_count: Option<usize>,
#[serde(alias = "max_staged_count")]
pub max_staged_count: Option<usize>,
#[serde(alias = "auto-open-staging-area")]
pub auto_open_staging_area: Option<bool>,
pub modal: Option<bool>,
#[serde(alias = "quit-on-last-cancel")]
pub quit_on_last_cancel: Option<bool>,
#[serde(alias = "search-modes")]
pub search_modes: Option<FxHashMap<String, String>>,
#[serde(alias = "show-matching-characters-on-path-searches")]
pub show_matching_characters_on_path_searches: Option<bool>,
#[serde(alias = "show-selection-mark")]
pub show_selection_mark: Option<bool>,
pub skin: Option<FxHashMap<String, SkinEntry>>,
#[serde(default, alias = "special-paths")]
pub special_paths: HashMap<GlobConf, SpecialHandlingConf>,
#[serde(alias = "syntax-theme")]
pub syntax_theme: Option<SyntaxTheme>,
#[serde(alias = "terminal-title")]
pub terminal_title: Option<ExecPattern>,
#[serde(alias = "reset-terminal-title-on-exit")]
pub reset_terminal_title_on_exit: Option<bool>,
#[serde(alias = "true-colors")]
pub true_colors: Option<bool>,
#[serde(alias = "update-work-dir")]
pub update_work_dir: Option<bool>,
#[serde(default)]
pub verbs: Vec<VerbConf>,
#[serde(alias = "layout-instructions")]
pub layout_instructions: Option<LayoutInstructions>,
}
impl Conf {
pub fn default_location() -> PathBuf {
let hjson_file = super::dir().join("conf.hjson");
if !hjson_file.exists() {
let toml_file = super::dir().join("conf.toml");
if toml_file.exists() {
return toml_file;
}
}
hjson_file
}
pub fn from_default_location() -> Result<Conf, ProgramError> {
let conf_dir = super::dir();
let conf_filepath = Conf::default_location();
if !conf_filepath.exists() {
write_default_conf_in(conf_dir)?;
println!(
"New Configuration files written in {}{:?}{}.",
Attribute::Bold,
&conf_dir,
Attribute::Reset,
);
println!(
"You should have a look at them: their comments will help you configure broot."
);
println!("You should especially set up your favourite editor in verbs.hjson.");
}
let mut conf = Conf::default();
conf.read_file(conf_filepath)?;
Ok(conf)
}
pub fn solve_conf_path(
&self,
path: &str,
) -> Option<PathBuf> {
if path_has_ext(path, "toml") || path_has_ext(path, "hjson") {
for conf_file in self.files.iter().rev() {
let solved = path_from(conf_file, PathAnchor::Parent, path);
if solved.exists() {
return Some(solved);
}
}
}
None
}
pub fn read_file(
&mut self,
path: PathBuf,
) -> Result<(), ProgramError> {
debug!("reading conf file: {:?}", &path);
let mut conf: Conf = SerdeFormat::read_file(&path)?;
overwrite!(self, default_flags, conf);
overwrite!(self, date_time_format, conf);
overwrite!(self, icon_theme, conf);
overwrite!(self, syntax_theme, conf);
overwrite!(self, disable_mouse_capture, conf);
overwrite!(self, capture_mouse, conf);
overwrite!(self, true_colors, conf);
overwrite!(self, show_selection_mark, conf);
overwrite!(self, cols_order, conf);
overwrite!(self, skin, conf);
overwrite!(self, search_modes, conf);
overwrite!(self, max_panels_count, conf);
overwrite!(self, modal, conf);
overwrite!(self, initial_mode, conf);
overwrite!(self, quit_on_last_cancel, conf);
overwrite!(self, file_sum_threads_count, conf);
overwrite!(self, max_staged_count, conf);
overwrite!(self, auto_open_staging_area, conf);
overwrite!(self, show_matching_characters_on_path_searches, conf);
overwrite!(self, content_search_max_file_size, conf);
overwrite!(self, terminal_title, conf);
overwrite!(self, reset_terminal_title_on_exit, conf);
overwrite!(self, update_work_dir, conf);
overwrite!(self, enable_kitty_keyboard, conf);
overwrite!(self, kitty_graphics_transmission, conf);
overwrite!(self, kitty_graphics_display, conf);
overwrite!(self, kept_kitty_temp_files, conf);
overwrite!(self, lines_after_match_in_preview, conf);
overwrite!(self, lines_before_match_in_preview, conf);
overwrite!(self, layout_instructions, conf);
self.verbs.append(&mut conf.verbs);
overwrite_map!(self, special_paths, conf);
overwrite_map!(self, ext_colors, conf);
overwrite_vec!(self, preview_transformers, conf);
self.files.push(path);
for import in &conf.imports {
let file = import.file().trim();
if !import.applies() {
debug!("skipping not applying conf file : {file:?}");
continue;
}
let import_path =
self.solve_conf_path(file)
.ok_or_else(|| ConfError::ImportNotFound {
path: file.to_string(),
})?;
if self.files.contains(&import_path) {
debug!("skipping import already read: {import_path:?}");
continue;
}
self.read_file(import_path)?;
}
Ok(())
}
}