#![feature(if_let_guard)] use ::{ std::{ sync::{Arc, RwLock}, collections::BTreeMap, path::PathBuf }, tengri::{ Usually, impl_debug, tui::TuiEvent, dsl::{Dsl, DslWord, DslExpr} }, xdg::BaseDirectories, }; /// Configuration. /// /// Contains mode, view, and bind definitions. #[derive(Default, Debug)] pub struct Config { pub dirs: BaseDirectories, pub modes: Modes, pub views: Views, pub binds: Binds, } type Modes = Arc, Arc>>>>>; type Binds = Arc, EventMap>>>>; type Views = Arc, Arc>>>; /// A set of currently active view and keys definitions, /// with optional name and description. #[derive(Default, Debug)] pub struct Mode { pub path: PathBuf, pub name: Vec, pub info: Vec, pub view: Vec, pub keys: Vec, pub modes: Modes, } /// A collection of input bindings. #[derive(Debug)] pub struct EventMap( /// Map of each event (e.g. key combination) to /// all command expressions bound to it by /// all loaded input layers. pub BTreeMap>> ); /// An input binding. #[derive(Debug, Clone)] pub struct Binding { pub commands: Arc<[C]>, pub condition: Option, pub description: Option>, pub source: Option>, } /// Input bindings are only returned if this evaluates to true #[derive(Clone)] pub struct Condition(Arcbool + Send + Sync>>); impl Config { const CONFIG: &'static str = "tek.edn"; const DEFAULTS: &'static str = include_str!("../../tek.edn"); pub fn new (dirs: Option) -> Self { Self { dirs: dirs.unwrap_or_else(||BaseDirectories::with_profile("tek", "v0")), ..Default::default() } } pub fn init (&mut self) -> Usually<()> { self.init_file(Self::CONFIG, Self::DEFAULTS, |cfgs, dsl|cfgs.load(dsl))?; Ok(()) } pub fn init_file ( &mut self, path: &str, defaults: &str, mut each: impl FnMut(&mut Self, &str)->Usually<()> ) -> Usually<()> { if self.dirs.find_config_file(path).is_none() { println!("Creating {path:?}"); std::fs::write(self.dirs.place_config_file(path)?, defaults); } Ok(if let Some(path) = self.dirs.find_config_file(path) { println!("Loading {path:?}"); let src = std::fs::read_to_string(&path)?; src.as_str().each(move|item|each(self, item))?; } else { return Err(format!("{path}: not found").into()) }) } pub fn load (&mut self, dsl: impl Dsl) -> Usually<()> { dsl.each(|item|if let Some(expr) = item.expr()? { let head = expr.head()?; let tail = expr.tail()?; let name = tail.head()?; let body = tail.tail()?; println!("Config::load: {} {} {}", head.unwrap_or_default(), name.unwrap_or_default(), body.unwrap_or_default()); match head { Some("mode") if let Some(name) = name => Mode::>::load_into(&self.modes, &name, &body)?, Some("keys") if let Some(name) = name => EventMap::>::load_into(&self.binds, &name, &body)?, Some("view") if let Some(name) = name => { self.views.write().unwrap().insert(name.into(), body.src()?.unwrap_or_default().into()); }, _ => return Err(format!("Config::load: expected view/keys/mode, got: {item:?}").into()) } Ok(()) } else { return Err(format!("Config::load: expected expr, got: {item:?}").into()) }) } } impl Mode> { pub fn load_into (modes: &Modes, name: &impl AsRef, body: &impl Dsl) -> Usually<()> { let mut mode = Self::default(); println!("Mode::load_into: {}: {body:?}", name.as_ref()); body.each(|item|mode.load_one(item))?; modes.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); Ok(()) } pub fn load_one (&mut self, dsl: impl Dsl) -> Usually<()> { Ok(if let Ok(Some(expr)) = dsl.expr() && let Ok(Some(head)) = expr.head() { println!("Mode::load_one: {head} {:?}", expr.tail()); let tail = expr.tail()?.map(|x|x.trim()).unwrap_or(""); match head { "name" => self.name.push(tail.into()), "info" => self.info.push(tail.into()), "view" => self.view.push(tail.into()), "keys" => tail.each(|expr|{self.keys.push(expr.trim().into()); Ok(())})?, "mode" => if let Some(id) = tail.head()? { Self::load_into(&self.modes, &id, &tail.tail())?; } else { return Err(format!("Mode::load_one: self: incomplete: {expr:?}").into()); }, _ => { return Err(format!("Mode::load_one: unexpected expr: {head:?} {tail:?}").into()) }, }; } else if let Ok(Some(word)) = dsl.word() { self.view.push(word.into()); } else { return Err(format!("Mode::load_one: unexpected: {dsl:?}").into()); }) } } /// Default is always empty map regardless if `E` and `C` implement [Default]. impl Default for EventMap { fn default () -> Self { Self(Default::default()) } } impl EventMap { /// 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) -> Self { self.add(event, binding); self } /// Add a binding to an event map. pub fn add (&mut self, event: E, binding: Binding) -> &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]> { 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> { self.query(event) .map(|bb|bb.iter().filter(|b|b.condition.as_ref().map(|c|(c.0)()).unwrap_or(true)).next()) .flatten() } } impl EventMap> { pub fn load_into (binds: &Binds, name: &impl AsRef, body: &impl Dsl) -> Usually<()> { println!("EventMap::load_into: {}: {body:?}", name.as_ref()); let mut map = Self::new(); body.each(|item|if item.expr().head() == Ok(Some("see")) { // TODO Ok(()) } else if let Ok(Some(word)) = item.expr().head().word() { if let Some(key) = TuiEvent::from_dsl(item.expr()?.head()?)? { map.add(key, 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()) })?; binds.write().unwrap().insert(name.as_ref().into(), map); Ok(()) } } impl Binding { pub fn from_dsl (dsl: impl Dsl) -> Usually { let command: Option = None; let condition: Option = None; let description: Option> = None; let source: Option> = None; if let Some(command) = command { Ok(Self { commands: [command].into(), condition, description, source }) } else { Err(format!("no command in {dsl:?}").into()) } } } impl_debug!(Condition |self, w| { write!(w, "*") });