use std::path::Path;
use crate::{DiagnosticsPrinter, Error};
const DEFAULT_MOUSE_CAPTURE: bool = true;
const DEFAULT_COPY_ON_SELECT: bool = false;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct Layers {
pub(crate) mouse: Option<bool>,
pub(crate) copy_on_select: Option<bool>,
}
impl Layers {
fn or(self, below: Self) -> Self {
Self {
mouse: self.mouse.or(below.mouse),
copy_on_select: self.copy_on_select.or(below.copy_on_select),
}
}
fn is_complete(self) -> bool {
self.mouse.is_some() && self.copy_on_select.is_some()
}
fn from_project(ui: µmux::UiConfig) -> Self {
Self {
mouse: ui.mouse,
copy_on_select: ui.copy_on_select,
}
}
}
fn resolve(flags: Layers, project: Layers, user: Layers) -> micromux_tui::MouseOptions {
let settings = flags.or(project).or(user);
micromux_tui::MouseOptions {
capture: settings.mouse.unwrap_or(DEFAULT_MOUSE_CAPTURE),
copy_on_select: settings.copy_on_select.unwrap_or(DEFAULT_COPY_ON_SELECT),
}
}
pub(crate) async fn mouse_options(
flags: Layers,
project: Option<µmux::UiConfig>,
printer: &DiagnosticsPrinter,
) -> Result<micromux_tui::MouseOptions, Error> {
let project = project.map(Layers::from_project).unwrap_or_default();
let user = if flags.or(project).is_complete() {
Layers::default()
} else {
let user = load_user_config(printer).await?;
Layers {
mouse: user.mouse,
copy_on_select: user.copy_on_select,
}
};
Ok(resolve(flags, project, user))
}
async fn load_user_config(printer: &DiagnosticsPrinter) -> Result<micromux::UserConfig, Error> {
let Some(path) = micromux::user_config_path() else {
return Ok(micromux::UserConfig::default());
};
let raw = match micromux::read_config_file_async(&path).await {
Ok(raw) => raw,
Err(err) if err.kind() == std::io::ErrorKind::NotFound => {
return Ok(micromux::UserConfig::default());
}
Err(err) => {
return Err(Error::Message(format!(
"cannot read the user config {}: {err}",
path.display()
)));
}
};
let file_id = printer.add_source_file(&path, raw.clone());
let mut diagnostics = Vec::new();
let parsed = micromux::parse_user_config(&raw, file_id, &mut diagnostics);
if let Err(err) = &parsed {
diagnostics.extend(micromux::ToDiagnostics::to_diagnostics(err, file_id));
}
for diagnostic in &diagnostics {
printer.emit(diagnostic)?;
}
parsed.map_err(|_| Error::DiagnosticsEmitted)
}
pub(crate) async fn project_ui_config(config_path: &Path) -> Option<micromux::UiConfig> {
let parsed = match micromux::read_config_file_async(config_path).await {
Ok(raw) => micromux::parse_ui_section(&raw).map_err(|err| err.to_string()),
Err(err) => Err(err.to_string()),
};
match parsed {
Ok(ui) => Some(ui),
Err(err) => {
eprintln!(
"warning: ignoring the ui settings of {}: {err}",
config_path.display()
);
tracing::warn!(err, path = %config_path.display(), "ignoring the project's ui settings");
None
}
}
}
#[cfg(test)]
mod tests {
use super::{Layers, resolve};
use similar_asserts::assert_eq;
fn mouse(mouse: Option<bool>) -> Layers {
Layers {
mouse,
copy_on_select: None,
}
}
#[test]
fn the_flag_beats_the_project_which_beats_the_user() {
let capture = |flags, project, user| resolve(mouse(flags), mouse(project), mouse(user));
assert!(!capture(Some(false), Some(true), Some(true)).capture);
assert!(capture(Some(true), Some(false), Some(false)).capture);
assert!(!capture(None, Some(false), Some(true)).capture);
assert!(capture(None, Some(true), Some(false)).capture);
assert!(!capture(None, None, Some(false)).capture);
assert!(capture(None, None, None).capture);
}
#[test]
fn copy_on_select_is_off_unless_a_layer_turns_it_on() {
let copy_on_select = |flags, project, user| {
let layer = |copy_on_select| Layers {
mouse: None,
copy_on_select,
};
resolve(layer(flags), layer(project), layer(user)).copy_on_select
};
assert!(!copy_on_select(None, None, None));
assert!(copy_on_select(None, None, Some(true)));
assert!(!copy_on_select(None, Some(false), Some(true)));
assert!(copy_on_select(Some(true), Some(false), None));
let settings = resolve(
mouse(Some(false)),
Layers::default(),
Layers {
mouse: Some(true),
copy_on_select: Some(true),
},
);
assert_eq!((settings.capture, settings.copy_on_select), (false, true));
}
}