tek/src/config.rs
2026-08-23 06:18:04 +03:00

494 lines
17 KiB
Rust

use crate::*;
use std::path::PathBuf;
use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher};
impl AsRef<Config> for Config {
fn as_ref (&self) -> &Config {
self
}
}
/// Write initial contents of configuration.
pub fn config_init <C: AsRef<Config>> (config: C) -> Usually<C> {
if config.as_ref().find_file().is_none() {
std::fs::write(config.as_ref().place_file()?, Config::DEFAULTS)?;
}
Ok(if let Some(path) = config.as_ref().find_file() {
config_load(config, std::fs::read_to_string(&path)?)?
} else {
return Err(format!("config_init: not found").into())
})
}
pub fn config_load <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
config.as_ref().clear();
config.as_ref().stamp.store(quanta::Clock::new().raw(), Relaxed);
src.each(config, |c, s|config_load_item(c, s))
}
pub fn config_load_item <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
if let Some(expr) = src.expr()? {
config_load_kind(config, expr)
} else {
Err(format!("Config::add_one: tried to add empty item").into())
}
}
pub fn config_load_kind <C: AsRef<Config>, L: Language> (config: C, src: L) -> Usually<C> {
//if let Some(tail) = src.tail()? {
match src.head()? {
Some("mode") => modes_add(&config.as_ref().modes, src.tail()),
Some("keys") => load_bind(&config.as_ref().binds, src.tail()),
Some("view") => load_view(&config.as_ref().views, src.tail()),
_ => return Err(format!("Config::load: expected view/keys/mode expr, got: {src:?}").into())
}?;
//}
Ok(config)
}
/// Watch a config's file for changes.
pub fn config_watch (
config: Arc<Config>, poll: Option<Duration>
) -> Usually<()> {
let poll = poll.unwrap_or(Duration::from_millis(250));
let opts = NotifyConfig::default().with_poll_interval(poll);
let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new({
let config = config.clone();
move|result|{
match result {
Ok(_events) => if let Err(e) = config_init(config.as_ref()) {
*config.as_ref().error.write().unwrap() = Some(format!("{e:?}").into());
panic!("{e:?}");
} else {
//println!("config updated");
},
Err(errors) => {
panic!("{errors:?}");
}
}
}
}, opts)?;
if let Some(path) = config.as_ref().get_file() {
//println!("watching: {path:?}");
watcher.watch(&path, RecursiveMode::NonRecursive)?;
*config.as_ref().watch.write().unwrap() = Some(watcher);
Ok(())
} else {
Err(format!("no config path").into())
}
}
/// Register a mode.
pub fn modes_add <'a> (modes: &Modes, expr: impl Language) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("mode: missing name")?;
let body = expr.tail()?.ok_or("mode: missing body")?;
let mode = Mode::default();
let mode = body.each(mode, |c,s|mode_add(c,s))?;
modes.0.write().unwrap().insert(name.into(), Arc::new(mode));
Ok(())
}
/// Add a definition to the mode.
///
/// Supported definitions:
///
/// - (name ...) -> name
/// - (info ...) -> description
/// - (keys ...) -> key bindings
/// - (mode ...) -> submode
/// - ... -> view
///
/// ```
/// let mut mode: tek::Mode<std::sync::Arc<str>> = Default::default();
/// mode.add("(name hello)").unwrap();
/// ```
pub fn mode_add (mut mode: Mode, dsl: impl Language) -> Usually<Mode> {
Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() {
//println!("Mode::add: {head} {:?}", expr.tail());
let tail = expr.tail()?.map(|x|x.trim()).unwrap_or("");
match head {
"mode" => {
let name = tail.head()?.ok_or("submode: missing name")?;
let body = tail.tail()?.ok_or("submode: missing body")?;
let submode = Mode::default();
let submode = body.each(submode, |c,s|mode_add(c,s))?;
let modes = mode.modes.clone();
modes.0.write().unwrap().insert(name.into(), Arc::new(submode));
mode
},
"keys" => {
dsl.each(mode, |mut mode: Mode, expr: &str|{
mode.keys.push(expr.trim().into());
Ok(mode)
})?
},
"name" => { mode.name.push(tail.into()); mode },
"info" => { mode.info.push(tail.into()); mode },
"view" => { mode.view.push(tail.into()); mode },
_ => { mode.view.push(expr.into()); mode },
}
} else if let Ok(Some(word)) = dsl.word() {
mode.view.push(word.into());
mode
} else {
return Err(format!("Mode::add: unexpected: {dsl:?}").into());
})
}
/// Load custom view definition.
pub fn load_view <'a> (views: &Views, expr: impl Language) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("view: missing name")?;
let body = expr.tail()?.ok_or("view: missing body")?;
views.write().unwrap().insert(
name.into(),
body.src()?.unwrap_or_default().into()
);
Ok(())
}
pub fn load_bind <'a> (binds: &Binds, expr: impl Language) -> UsuallyRef<'a, ()> {
let name = expr.head()?.ok_or("bind: missing name")?;
let body = expr.tail()?.ok_or("bind: missing body")?;
binds.write().unwrap().insert(name.into(), {
let mut map = Bind::new();
body.each((), |_, item: &str|if item.expr().head() == Ok(Some("see")) {
// TODO
Ok(())
} else if let Ok(Some(_word)) = item.expr().head().word() {
let expr = item.expr()?;
let head = expr.head()?;
if let Some(event) = TuiKey::from_dsl(&head)?.to_crossterm() {
map.add(TuiEvent(event), Binding {
commands: [item.expr()?.tail()?.unwrap_or_default().into()].into(),
condition: None,
description: None,
source: None
});
Ok(())
} else if Some(":char") == item.expr()?.head()? {
// TODO
return Ok(())
} else {
return Err(format!("Config::load_bind: invalid key: {:?}", item.expr()?.head()?).into())
}
} else {
return Err(format!("Config::load_bind: unexpected: {item:?}").into())
})?;
map
});
Ok(())
}
/// Configuration: mode, view, and bind definitions.
///
/// ```
/// let config = tek::Config::default();
/// ```
///
/// ```
/// // Some dizzle.
/// // What indentation to use here lol?
/// let source = stringify!((mode :menu (name Menu)
/// (info Mode selector.) (keys :axis/y :confirm)
/// (view (bg (g 0) (bsp/s :ports/out
/// (bsp/n :ports/in
/// (bg (g 30) (bsp/s (fixed/y 7 :logo)
/// (fill :dialog/menu)))))))));
/// // Add this definition to the config and try to load it.
/// // A "mode" is basically a state machine
/// // with associated input and output definitions.
/// tek::Config::default().add(&source).unwrap().get_mode(":menu").unwrap();
/// ```
#[derive(Default, Debug)]
pub struct Config {
/// XDG base directories of running user.
pub dirs: BaseDirectories,
/// Active collection of interaction modes.
pub modes: Modes,
/// Active collection of event bindings.
pub binds: Binds,
/// Active collection of view definitions.
pub views: Views,
/// Error caught during reloading
pub error: RwLock<Option<Arc<str>>>,
/// Timestamp
pub stamp: AtomicU64,
/// Watcher
pub watch: RwLock<Option<PollWatcher>>
}
/// Collection of UI modes.
///
/// ```
/// let mut modes = tek::Modes::default();
/// assert_eq!(modes.len(), 0);
/// let _ = modes.add(&":foo", &"(mode (name Foo) (info Bar))");
/// assert_eq!(modes.len(), 1);
/// ```
#[derive(Default, Debug, Clone)]
pub struct Modes(Arc<RwLock<BTreeMap<Arc<str>, Arc<Mode>>>>);
/// Group of view and keys definitions.
///
/// ```
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
/// ```
#[derive(Default, Debug)]
pub struct Mode {
pub path: PathBuf,
pub name: Vec<Arc<str>>,
pub info: Vec<Arc<str>>,
pub view: Vec<Arc<str>>,
pub keys: Vec<Arc<str>>,
pub modes: Modes,
}
impl Config {
/// Default configuration directory.
const CONFIG_DIR: &'static str = "tek";
/// Default configuration subdirectory.
const CONFIG_SUB: &'static str = "v0";
/// Default configuration file name.
const CONFIG: &'static str = "tek.edn";
/// Default configuration contents.
const DEFAULTS: &'static str = include_str!("tek.edn");
/// Create a new app configuration from a set of XDG base directories,
pub fn new (dirs: Option<BaseDirectories>) -> Self {
Self {
dirs: dirs.unwrap_or_else(||{
BaseDirectories::with_profile(Self::CONFIG_DIR, Self::CONFIG_SUB)
}),
stamp: quanta::Clock::new().raw().into(),
..Default::default()
}
}
/// Create, initialize, and watch a new configuration.
pub fn watched <T: 'static> (callback: impl FnOnce(Arc<Config>)->T) -> Usually<T> {
let config = Self::init_new(None)?;
config_watch(config.clone(), None)?;
let result = callback(config);
Ok(result)
}
/// Create and initialize a new configuration.
pub fn init_new (_dirs: Option<BaseDirectories>) -> Usually<Arc<Self>> {
config_init(Arc::new(Self::new(None)))
}
/// Find the config file.
fn find_file (&self) -> Option<PathBuf> {
self.dirs.find_config_file(Self::CONFIG)
}
/// Place config file in default location.
fn place_file (&self) -> Result<PathBuf, std::io::Error> {
self.dirs.place_config_file(Self::CONFIG)
}
/// Get path to config file.
fn get_file (&self) -> Option<PathBuf> {
self.dirs.get_config_file(Self::CONFIG)
}
/// Get a mode by name.
pub fn get_mode (&self, mode: impl AsRef<str>) -> Option<Arc<Mode>> {
self.modes.get(mode)
}
/// Print the configuration.
pub fn print (&self) {
print_config(self)
}
/// Make this configuration empty.
fn clear (&self) {
*self.modes.0.write().unwrap() = Default::default();
*self.views.write().unwrap() = Default::default();
*self.binds.write().unwrap() = Default::default();
}
pub fn get_view (&self, name: impl AsRef<str>) -> Option<Arc<str>> {
self.views.read().unwrap().get(name.as_ref()).cloned()
}
}
pub use self::view::*;
mod view {
use crate::*;
/// Collection of custom view definitions.
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
}
pub use self::mode::*;
mod mode {
use crate::*;
impl Modes {
/// Get a mode by name.
pub fn get (&self, name: impl AsRef<str>) -> Option<Arc<Mode>> {
self.0.read().unwrap().get(name.as_ref()).cloned()
}
/// Run something for each mode.
pub fn for_each <T> (&self, mut ator: impl FnMut(&str, &Mode)->T) {
for (k, v) in self.0.read().unwrap().iter() {
let _ = ator(k.as_ref(), v.as_ref());
}
}
/// Count modes.
pub fn len (&self) -> usize {
self.0.read().unwrap().len()
}
}
}
pub use self::bind::*;
mod bind {
use crate::*;
/// Collection of input bindings.
pub type Binds = Arc<RwLock<BTreeMap<Arc<str>, Bind<TuiEvent, Arc<str>>>>>;
/// An map of input events (e.g. [TuiEvent]) to [Binding]s.
///
/// ```
/// let lang = "(@x (nop)) (@y (nop) (nop))";
/// let bind = tek::Bind::<tek::tengri::TuiEvent, std::sync::Arc<str>>::load(&lang).unwrap();
/// assert_eq!(bind.query(&'x'.into()).map(|x|x.len()), Some(1));
/// //assert_eq!(bind.query(&'y'.into()).map(|x|x.len()), Some(2));
/// ```
#[derive(Debug)]
pub struct Bind<E, C>(
/// Map of each event (e.g. key combination) to
/// all command expressions bound to it by
/// all loaded input layers.
pub BTreeMap<E, Vec<Binding<C>>>
);
/// A sequence of zero or more commands (e.g. [AppCommand]),
/// optionally filtered by [Condition] to form layers.
///
/// ```
/// //FIXME: Why does it overflow?
/// //let binding: Binding<()> = tek::Binding { ..Default::default() };
/// ```
#[derive(Debug, Clone)] pub struct Binding<C> {
pub commands: Arc<[C]>,
pub condition: Option<Condition>,
pub description: Option<Arc<str>>,
pub source: Option<Arc<PathBuf>>,
}
/// Condition that must evaluate to true in order to enable an input layer.
///
/// ```
/// let condition = tek::Condition(std::sync::Arc::new(Box::new(||{true})));
/// ```
#[derive(Clone)]
pub struct Condition(pub Arc<Box<dyn Fn()->bool + Send + Sync>>);
impl Bind<TuiEvent, Arc<str>> {
}
/// Default is always empty map regardless if `E` and `C` implement [Default].
impl<E, C> Default for Bind<E, C> {
fn default () -> Self { Self(Default::default()) }
}
impl<C: Default> Default for Binding<C> {
fn default () -> Self {
Self {
commands: Default::default(),
condition: Default::default(),
description: Default::default(),
source: Default::default(),
}
}
}
impl<E: Clone + Ord, C> Bind<E, C> {
/// Create a new event map
pub fn new () -> Self {
Default::default()
}
/// Add a binding to an owned event map.
pub fn def (mut self, event: E, binding: Binding<C>) -> Self {
self.add(event, binding);
self
}
/// Add a binding to an event map.
pub fn add (&mut self, event: E, binding: Binding<C>) -> &mut Self {
if !self.0.contains_key(&event) {
self.0.insert(event.clone(), Default::default());
}
self.0.get_mut(&event).unwrap().push(binding);
self
}
/// Return the binding(s) that correspond to an event.
pub fn query (&self, event: &E) -> Option<&[Binding<C>]> {
self.0.get(event).map(|x|x.as_slice())
}
/// Return the first binding that corresponds to an event, considering conditions.
pub fn dispatch (&self, event: &E) -> Option<&Binding<C>> {
self.query(event)
.map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next())
.flatten()
}
}
impl_debug!(Condition |self, w| { write!(w, "*") });
}
pub fn print_config (config: &Config) {
use ::ansi_term::Color::*;
println!("{:?}", config.dirs);
for (k, v) in config.views.read().unwrap().iter() {
println!("{} {} {v}", Green.paint("VIEW"), Green.bold().paint(format!("{k:<16}")));
}
for (k, v) in config.binds.read().unwrap().iter() {
println!("{} {}", Green.paint("BIND"), Green.bold().paint(format!("{k:<16}")));
for (k, v) in v.0.iter() {
print!("{} ", &Yellow.paint(match &k.0 {
Event::Key(KeyEvent { modifiers, .. }) =>
format!("{:>16}", format!("{modifiers}")),
_ => unimplemented!()
}));
print!("{}", &Yellow.bold().paint(match &k.0 {
Event::Key(KeyEvent { code, .. }) =>
format!("{:<10}", format!("{code}")),
_ => unimplemented!()
}));
for v in v.iter() {
print!(" => {:?}", v.commands);
print!(" {}", v.condition.as_ref().map(|x|format!("{x:?}")).unwrap_or_default());
println!(" {}", v.description.as_ref().map(|x|x.as_ref()).unwrap_or_default());
//println!(" {:?}", v.source);
}
}
}
config.modes.for_each(|k, v|{
println!();
for v in v.name.iter() { print!("{}", Green.bold().paint(format!("{v} "))); }
for v in v.info.iter() { print!("\n{}", Green.paint(format!("{v}"))); }
print!("\n{} {}", Blue.paint("TOOL"), Green.bold().paint(format!("{k:<16}")));
print!("\n{}", Blue.paint("KEYS"));
for v in v.keys.iter() { print!("{}", Green.paint(format!(" {v}"))); }
println!();
v.modes.for_each(|k, v|{
print!("{} {} {:?}", Blue.paint("MODE"), Green.bold().paint(format!("{k:<16}")), v.name);
print!( " INFO={:?}", v.info);
print!( " VIEW={:?}", v.view);
println!(" KEYS={:?}", v.keys);
});
print!("{}", Blue.paint("VIEW"));
for v in v.view.iter() { print!("{}", Green.paint(format!(" {v}"))); }
println!();
});
}