diff --git a/Cargo.lock b/Cargo.lock index 52f07dbc..5d4a7dc2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2137,7 +2137,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.59.0", ] [[package]] @@ -2559,10 +2559,8 @@ version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ - "backtrace", "cfg-if", "libc", - "petgraph", "redox_syscall 0.5.18", "smallvec", "windows-link", @@ -2622,16 +2620,6 @@ dependencies = [ "pest", ] -[[package]] -name = "petgraph" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" -dependencies = [ - "fixedbitset", - "indexmap", -] - [[package]] name = "phf" version = "0.11.3" @@ -3839,7 +3827,6 @@ dependencies = [ "livi", "notify-debouncer-full", "palette", - "parking_lot 0.12.5", "proptest", "proptest-derive", "quanta", @@ -3849,7 +3836,6 @@ dependencies = [ "tek_proc", "tengri", "toml 0.9.12+spec-1.1.0", - "tracing-mutex", "uuid", "wavers", "winit", @@ -3919,7 +3905,7 @@ dependencies = [ "parking_lot 0.12.5", "rustix 1.1.4", "signal-hook", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4260,17 +4246,6 @@ dependencies = [ "tracing-core", ] -[[package]] -name = "tracing-mutex" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64aed473bc9271d160dce1fda5daebec76bb740f9db452bf6c9682344a5e44c0" -dependencies = [ - "autocfg", - "lock_api", - "parking_lot 0.12.5", -] - [[package]] name = "tracing-subscriber" version = "0.3.23" diff --git a/Cargo.toml b/Cargo.toml index bd1cb9ae..25e2ece0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,8 +36,6 @@ gtk = { optional = true, version = "0.18.1" } notify-debouncer-full = "0.7.0" hotpath = "0.23" -parking_lot = { version = "0.12.5", features = ["deadlock_detection"] } -tracing-mutex = { version = "0.3.3", features = ["parking_lot"] } #once_cell = "1.19.0" #no_deadlocks = "1.3.2" diff --git a/src/config.rs b/src/config.rs deleted file mode 100644 index 5a13144c..00000000 --- a/src/config.rs +++ /dev/null @@ -1,493 +0,0 @@ -use crate::*; -use std::path::PathBuf; -use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher}; - -/// Write initial contents of configuration. -pub fn config_init (config: &Config) -> UsuallyRef<'static, ()> { - //println!("\r\ninit {}", quanta::Clock::new().raw()); - config.clear(); - let path = Config::CONFIG; - let defaults = Config::DEFAULTS; - config.stamp.store(quanta::Clock::new().raw(), Relaxed); - if config.find_file().is_none() { - //println!("Creating {path:?}"); - std::fs::write(config.place_file()?, defaults)?; - } - Ok(if let Some(path) = config.find_file() { - //println!("Loading {path:?}"); - let src = std::fs::read_to_string(&path)?; - src.as_str().each((), &mut move|ctx, item: &str|{ - config_add(config, item); - Ok(ctx) - }); - } else { - return Err(format!("{path}: not found").into()) - }) -} - -/// Add statements to configuration from [Dsl] source. -pub fn config_add <'a> (config: &'a Config, dsl: &'a str) -> UsuallyRef<'a, &'a Config> { - dsl.each(config, &mut move|ctx: &'a Config, item: &'a str|if let Some(expr) = item.expr()? { - //if let (Some(name), Some(body)) = (expr.tail()?.head()?, expr.tail()?.tail()?,) { - match expr.head()? { - Some("mode") => expr.tail()?.map(|tail|modes_add(&config.modes, tail)), - Some("keys") => expr.tail()?.map(|tail|load_bind(&config.binds, tail)), - Some("view") => expr.tail()?.map(|tail|load_view(&config.views, tail)), - _ => return Err(format!("Config::load: expected view/keys/mode expr, got: {item:?}").into()) - }.transpose()?; - //} - Ok(config) - } else { - Err(format!("Config::add_one: tried to add empty item").into()) - }) -} - -/// Register a mode. -pub fn modes_add <'a> (modes: &Modes, expr: &'a str) -> 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, move|mut submode: Mode, item: &str|{ - mode_add(&mut submode, &item); - Ok(submode) - })?; - 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> = Default::default(); -/// mode.add("(name hello)").unwrap(); -/// ``` -pub fn mode_add <'a> (mode: &'a mut Mode, dsl: &'a str) -> PerhapsRef<'a, &'a mut Mode> { - Ok(Some(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, move|mut submode: Mode, item: &str|{ - mode_add(&mut submode, &item); - Ok(submode) - })?; - let modes = mode.modes.clone(); - modes.0.write().unwrap().insert(name.into(), Arc::new(submode)); - mode - }, - "keys" => { - dsl.each(mode, &mut |mode: &'a mut Mode, expr: &'a 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: &'a str,) -> 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: &'a str) -> 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((), &mut |_, 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>>, - /// Timestamp - pub stamp: AtomicU64, - /// Watcher - pub watch: RwLock> -} - -/// 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, Arc>>>); - -/// Group of view and keys definitions. -/// -/// ``` -/// let mode = tek::Mode::>::default(); -/// ``` -#[derive(Default, Debug)] -pub struct Mode { - pub path: PathBuf, - pub name: Vec>, - pub info: Vec>, - pub view: Vec>, - pub keys: Vec>, - 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) -> 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 (callback: impl FnOnce(Arc)->T) -> Usually { - let config = Self::init_new(None)?; - Self::watch(config.clone(), None)?; - let result = callback(config); - Ok(result) - } - - /// Create and initialize a new configuration. - pub fn init_new (_dirs: Option) -> UsuallyRef<'static, Arc> { - let config = Arc::new(Self::new(None)); - config_init(&config)?; - Ok(config) - } - - /// Watch a config's file for changes. - pub fn watch (config: Arc, poll: Option) -> UsuallyRef<'static, ()> { - let handler = { - let config = config.clone(); - move |result|match result { - Ok(_events) => if let Err(e) = config_init(&config) { - *config.error.write().unwrap() = Some(format!("{e:?}").into()); - panic!("{e:?}"); - } else { - //println!("config updated"); - }, - Err(errors) => { - panic!("{errors:?}"); - } - } - }; - let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new( - handler, - NotifyConfig::default() - .with_poll_interval(poll.unwrap_or(Duration::from_millis(250))) - )?; - if let Some(path) = config.get_file() { - //println!("watching: {path:?}"); - watcher.watch(&path, RecursiveMode::NonRecursive)?; - *config.watch.write().unwrap() = Some(watcher); - Ok(()) - } else { - Err(format!("no config path").into()) - } - } - - /// Find the config file. - fn find_file (&self) -> Option { - self.dirs.find_config_file(Self::CONFIG) - } - - /// Place config file in default location. - fn place_file (&self) -> Result { - self.dirs.place_config_file(Self::CONFIG) - } - - /// Get path to config file. - fn get_file (&self) -> Option { - self.dirs.get_config_file(Self::CONFIG) - } - - /// Get a mode by name. - pub fn get_mode (&self, mode: impl AsRef) -> Option> { - 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 use self::view::*; -mod view { - use crate::*; - /// Collection of custom view definitions. - pub type Views = Arc, Arc>>>; -} - -pub use self::mode::*; -mod mode { - use crate::*; - - impl Modes { - /// Get a mode by name. - pub fn get (&self, name: impl AsRef) -> Option> { - self.0.read().unwrap().get(name.as_ref()).cloned() - } - /// Run something for each mode. - pub fn for_each (&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, Bind>>>>; - - /// An map of input events (e.g. [TuiEvent]) to [Binding]s. - /// - /// ``` - /// let lang = "(@x (nop)) (@y (nop) (nop))"; - /// let bind = tek::Bind::>::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( - /// Map of each event (e.g. key combination) to - /// all command expressions bound to it by - /// all loaded input layers. - pub BTreeMap>> - ); - - /// 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 { - pub commands: Arc<[C]>, - pub condition: Option, - pub description: Option>, - pub source: Option>, - } - - /// 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 Arcbool + Send + Sync>>); - - impl Bind> { - - } - - /// Default is always empty map regardless if `E` and `C` implement [Default]. - impl Default for Bind { - fn default () -> Self { Self(Default::default()) } - } - - impl Default for Binding { - fn default () -> Self { - Self { - commands: Default::default(), - condition: Default::default(), - description: Default::default(), - source: Default::default(), - } - } - } - - impl Bind { - /// 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_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!(); - }); -} diff --git a/src/device/dialog.rs b/src/device/dialog.rs index cc8913fd..2b92c56e 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -1,8 +1,8 @@ use crate::{*, device::*}; -pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App) - -> Drawn<'a, u16> -{ +pub fn draw_dialog <'a, L: Language + ?Sized> ( + to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &'a L +) -> Drawn<'a, u16> { match frags.next() { Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog { //Some(iter_south(move||items.0.iter().enumerate().map(move|(index, MenuItem(item, _))|{ @@ -28,7 +28,7 @@ pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: } else { None }.draw(to), - _ => unimplemented!("draw_dialog: ({frags:?})"), + _ => unimplemented!("App::interpret_word: {dsl:?} ({frags:?})"), } } diff --git a/src/tek.rs b/src/tek.rs index e401a3c3..270b49cd 100644 --- a/src/tek.rs +++ b/src/tek.rs @@ -76,13 +76,13 @@ fn run_new_plain (config: Config) -> Usually<()> { #[cfg(feature = "cli")] pub mod cli { use crate::*; - pub fn run_with_config (config: Arc) -> Usually<()> { + pub fn run_with_config <'a> (config: Arc>) -> UsuallyRef<'a, ()> { Cli::parse().run(Some(config)) } /// Command-line configuration. impl Cli { - pub fn run (&self, mut config: Option>) -> Usually<()> { + pub fn run <'a> (&self, mut config: Option>>) -> UsuallyRef<'a, ()> { if config.is_none() { config = Some(Config::init_new(None)?); } @@ -231,7 +231,308 @@ fn run_new_plain (config: Config) -> Usually<()> { } pub use self::config::*; -mod config; +mod config { + use crate::*; + use std::path::PathBuf; + use notify_debouncer_full::notify::{RecursiveMode, PollWatcher, Config as NotifyConfig, Watcher}; + + /// 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<'a> { + /// XDG base directories of running user. + pub dirs: BaseDirectories, + /// Active collection of interaction modes. + pub modes: Modes<'a>, + /// Active collection of event bindings. + pub binds: Binds, + /// Active collection of view definitions. + pub views: Views, + /// Error caught during reloading + pub error: RwLock>>, + /// Timestamp + pub stamp: AtomicU64, + /// Watcher + pub watch: RwLock> + } + + /// Collection of custom view definitions. + pub type Views = Arc, Arc>>>; + + /// 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<'a>(Arc, Arc>>>>>); + + /// Group of view and keys definitions. + /// + /// ``` + /// let mode = tek::Mode::>::default(); + /// ``` + #[derive(Default, Debug)] + pub struct Mode<'a, D: Language + Ord> { + pub path: PathBuf, + pub name: Vec, + pub info: Vec, + pub view: Vec, + pub keys: Vec, + pub modes: Modes<'a>, + } + + impl<'a> Config<'a> { + /// 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) -> 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 (callback: impl FnOnce(Arc)->T) -> UsuallyRef<'a, T> { + let config = Self::init_new(None)?; + Self::watch(config.clone(), None)?; + let result = callback(config); + Ok(result) + } + /// Create and initialize a new configuration. + pub fn init_new (_dirs: Option) -> UsuallyRef<'a, Arc> { + let config = Arc::new(Self::new(None)); + config.init()?; + Ok(config) + } + /// Watch a config's file for changes. + pub fn watch (config: Arc, poll: Option) -> UsuallyRef<'a, ()> { + let handler = { + let config = config.clone(); + move |result|match result { + Ok(_events) => if let Err(e) = config.init() { + *config.error.write().unwrap() = Some(format!("{e:?}").into()); + panic!("{e:?}"); + } else { + //println!("config updated"); + }, + Err(errors) => { + panic!("{errors:?}"); + } + } + }; + let mut watcher = ::notify_debouncer_full::notify::poll::PollWatcher::new( + handler, + NotifyConfig::default() + .with_poll_interval(poll.unwrap_or(Duration::from_millis(250))) + )?; + if let Some(path) = config.get_file() { + //println!("watching: {path:?}"); + watcher.watch(&path, RecursiveMode::NonRecursive)?; + *config.watch.write().unwrap() = Some(watcher); + Ok(()) + } else { + Err(format!("no config path").into()) + } + } + /// Find the config file. + fn find_file (&self) -> Option { + self.dirs.find_config_file(Self::CONFIG) + } + /// Place config file in default location. + fn place_file (&self) -> Result { + self.dirs.place_config_file(Self::CONFIG) + } + /// Get path to config file. + fn get_file (&self) -> Option { + self.dirs.get_config_file(Self::CONFIG) + } + /// Write initial contents of configuration. + pub fn init (&self) -> UsuallyRef<'a, ()> { + //println!("\r\ninit {}", quanta::Clock::new().raw()); + self.clear(); + self.load(Self::CONFIG, Self::DEFAULTS, move|cfgs, dsl|{ + cfgs.add(&dsl)?; + Ok(()) + }) + } + /// Write initial contents of a configuration file. + pub fn load UsuallyRef<'a, ()>> ( + &self, path: &str, defaults: &str, mut each: F + ) -> UsuallyRef<'a, ()> { + self.stamp.store(quanta::Clock::new().raw(), Relaxed); + if self.find_file().is_none() { + //println!("Creating {path:?}"); + std::fs::write(self.place_file()?, defaults)?; + } + Ok(if let Some(path) = self.find_file() { + //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()) + }) + } + /// Add statements to configuration from [Dsl] source. + pub fn add (&'a self, dsl: impl Language) -> UsuallyRef<'a, &Self> { + dsl.each(|item|self.add_one(item))?; + Ok(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(); + } + /// Add one entry to the configuration. + fn add_one (&'a self, item: &'a (impl Language + ?Sized)) -> UsuallyRef<'a, ()> { + item.expr()? + .map(|expr|{ + let head = expr.head()?; + let tail = expr.tail()?; + let name = tail.head()?; + let body = tail.tail()?; + match head { + Some("mode") if let Some(name) = name => self.modes.add(name, body)?, + Some("keys") if let Some(name) = name => load_bind(&self.binds, name, body)?, + Some("view") if let Some(name) = name => load_view(&self.views, name, body)?, + _ => return Ok::, Box>(None) + } + Ok(Some(())) + }) + .transpose()? + .flatten() + .ok_or_else(||format!("Config::load: expected view/keys/mode expr, got: {item:?}").into()) + } + /// Get a mode by name. + pub fn get_mode (&'a self, mode: impl AsRef) -> Option>>> { + self.modes.get(mode) + } + /// Print the configuration. + pub fn print (&self) { + print_config(self) + } + } + + impl<'a> Modes<'a> { + /// Register a mode. + pub fn add ( + &self, name: &'a (impl AsRef + ?Sized), body: impl Language + ) + -> UsuallyRef<'a, ()> + { + let mut mode = Mode::default(); + body.each(|item|mode.add(item))?; + self.0.write().unwrap().insert(name.as_ref().into(), Arc::new(mode)); + Ok(()) + } + /// Get a mode by name. + pub fn get (&self, name: impl AsRef) -> Option>>> { + self.0.read().unwrap().get(name.as_ref()).cloned() + } + /// Run something for each mode. + pub fn for_each (&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() + } + } + + impl<'a> Mode<'a, Arc> { + /// Add a definition to the mode. + /// + /// Supported definitions: + /// + /// - (name ...) -> name + /// - (info ...) -> description + /// - (keys ...) -> key bindings + /// - (mode ...) -> submode + /// - ... -> view + /// + /// ``` + /// let mut mode: tek::Mode> = Default::default(); + /// mode.add("(name hello)").unwrap(); + /// ``` + pub fn add (&mut self, dsl: &'a (impl Language + ?Sized)) -> UsuallyRef<'a, ()> { + 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 { + "name" => self.add_name(tail)?, + "info" => self.add_info(tail)?, + "keys" => self.add_keys(tail)?, + "mode" => self.add_mode(tail)?, + _ => self.add_view(tail)?, + }; + } else if let Ok(Some(word)) = dsl.word() { + self.add_view(word)?; + } else { + return Err(format!("Mode::add: unexpected: {dsl:?}").into()); + }) + } + /// Add a name to the mode. + fn add_name (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> { + Ok(dsl.text()?.map(|text|self.name.push(text.into()))) + } + /// Add a description to the mode. + fn add_info (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> { + Ok(dsl.text()?.map(|text|self.info.push(text.into()))) + } + /// Add a view definition to the mode. + fn add_view (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> { + Ok(dsl.text()?.map(|text|self.view.push(text.into()))) + } + /// Add a keyboard input bindin to the mode. + fn add_keys (&mut self, dsl: &'a (impl Language + ?Sized)) -> PerhapsRef<'a, ()> { + Ok(Some(dsl.src()?.each(|expr|{ + self.keys.push(expr.trim().into()); + Ok::<(), Box>(()) + })?)) + } + /// Add a submode to the mode. + fn add_mode (&mut self, dsl: &'a (impl Language + ?Sized + 'a)) -> PerhapsRef<'a, ()> { + Ok(Some(if let Some(id) = dsl.head()? { + self.modes.add(id, &dsl.tail())?; + } else { + return Err(format!("Mode::add: self: incomplete: {dsl:?}").into()); + })) + } + } +} pub use self::app::*; mod app { @@ -305,7 +606,7 @@ mod app { /// Performance counter pub perf: PerfModel, /// Available view modes and input bindings - pub config: Arc, + pub config: Arc>, /// Currently selected mode pub mode: Option>, /// Undo history @@ -331,10 +632,7 @@ mod app { /// let tek = tek::App::new(None, proj, conf, "hello"); /// ``` pub fn new ( - exit: Option, - project: Arrangement, - config: Arc, - mode: impl AsRef + exit: Option, project: Arrangement, config: Arc, mode: impl AsRef ) -> Self { App { exit: exit.unwrap_or_default(), @@ -492,6 +790,47 @@ pub use self::bind::*; mod bind { use crate::*; + /// Collection of input bindings. + pub type Binds = Arc, Bind>>>>; + + /// An map of input events (e.g. [TuiEvent]) to [Binding]s. + /// + /// ``` + /// let lang = "(@x (nop)) (@y (nop) (nop))"; + /// let bind = tek::Bind::>::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( + /// Map of each event (e.g. key combination) to + /// all command expressions bound to it by + /// all loaded input layers. + pub BTreeMap>> + ); + + /// 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 { + pub commands: Arc<[C]>, + pub condition: Option, + pub description: Option>, + pub source: Option>, + } + + /// 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 Arcbool + Send + Sync>>); + tui_keys!(self: App, input { let name = self.mode.as_ref(); let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone); @@ -644,6 +983,89 @@ mod bind { } } + pub(crate) fn load_bind <'a> ( + binds: &Binds, name: impl AsRef, body: impl Language + ) -> Usually<()> { + binds.write().unwrap().insert(name.as_ref().into(), Bind::load(&body)?); + Ok(()) + } + + impl Bind> { + pub fn load <'a> (lang: &impl Language) -> Usually { + let mut map = Self::new(); + lang.each(|item|if item.expr().head() == Ok(Some("see")) { + // TODO + Ok(()) + } else if let Ok(Some(_word)) = item.expr().head().word() { + if let Some(event) = TuiKey::from_dsl(item.expr()?.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()) + })?; + Ok(map) + } + } + + /// Default is always empty map regardless if `E` and `C` implement [Default]. + impl Default for Bind { + fn default () -> Self { Self(Default::default()) } + } + + impl Default for Binding { + fn default () -> Self { + Self { + commands: Default::default(), + condition: Default::default(), + description: Default::default(), + source: Default::default(), + } + } + } + + impl Bind { + /// 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_debug!(Condition |self, w| { write!(w, "*") }); + /// A control axis. /// /// ``` @@ -887,6 +1309,17 @@ pub use self::draw::*; mod draw { use crate::*; + /// Load custom view definition. + pub(crate) fn load_view <'a> ( + views: &Views, name: impl AsRef, body: impl Language, + ) -> UsuallyRef<'a, ()> { + views.write().unwrap().insert( + name.as_ref().into(), + body.src()?.unwrap_or_default().into() + ); + Ok(()) + } + /// The [Draw] implementation for [App] handles the loaded view, /// which is defined in terms of [dizzle] DSL. /// @@ -917,22 +1350,15 @@ mod draw { if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) { let mut error = false; for (index, dsl) in mode.view.iter().enumerate() { - match self.draw_mode(to, dsl) { - Ok(None) => {}, - Ok(Some(XYWH(.., w, h))) => { - self.size.0.store(w as usize, Relaxed); - self.size.1.store(h as usize, Relaxed); - }, - Err(e) => { - let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); - let message = format!( - "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}", - &mode.name - ); - *self.error.write().unwrap() = Some(message.into()); - error = true; - break; - } + if let Err(e) = self.draw_mode(to, dsl) { + let src = &dsl.src().unwrap_or(Some("")).unwrap_or(""); + let message = format!( + "Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}", + &mode.name + ); + *self.error.write().unwrap() = Some(message.into()); + error = true; + break; } } if !error { @@ -942,8 +1368,15 @@ mod draw { Ok(()) } - fn draw_mode <'a, L: Language + 'a> (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> { - self.interpret(to, dsl) + fn draw_mode <'a> (&'a self, to: &mut Tui, dsl: &'a impl Language) -> UsuallyRef<'a, ()> { + Ok(match self.interpret(to, dsl) { + Err(e) => return Err(e), + Ok(None) => {}, + Ok(Some(XYWH(.., w, h))) => { + self.size.0.store(w as usize, Relaxed); + self.size.1.store(h as usize, Relaxed); + }, + }) } fn draw_debug (&self, to: &mut Tui) -> Drawn { @@ -955,60 +1388,64 @@ mod draw { } impl<'a> Interpret<'a, Tui, Option>> for App { - fn interpret (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> { - if let Some(expr) = dsl.expr()? { - interpret_keyword!(self, to, expr, [ + + fn interpret_expr (&'a self, to: &mut Tui, src: &'a L) -> Drawn { + if let Some(src) = src.src()? { + interpret_keyword!(self, to, src, [ kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full, kw_tui_text, kw_tui_fg, kw_tui_bg ]); - Err(format!("interpret_expr: unexpected: {expr:?}").into()) - } else if let Some(word) = dsl.word()? { - let mut frags = word.src()?.unwrap().split("/"); - match frags.next() { - //Some(":logo") => view_logo().draw(to), - Some(":meters") => match frags.next() { - Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), - Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), - _ => panic!() - }, - Some(":tracks") => match frags.next() { - None => "TODO tracks".draw(to), - Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), - Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), - Some("devices") => self.project.view_track_devices(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), - Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to), - _ => panic!() - }, - Some(":scenes") => match frags.next() { - None => self.view_scenes_clips().draw(to), - Some("names") => self.view_scenes_names().draw(to), - _ => panic!() - }, - Some(":dialog") => draw_dialog(to, frags, self), - Some(":templates") => view_templates(frags, self).draw(to), - Some(":sessions") => view_sessions().draw(to), - Some(":browse/title") => view_browse_title(self).draw(to), - Some(":device") => view_device(self).draw(to), - Some(":status") => "TODO: Status Bar".draw(to), - Some(":editor") => "TODO Editor".draw(to), - Some(":transport") => view_transport(true, "", "", "").draw(to), - Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to), - Some(_) => { - let views = self.config.views.read().unwrap(); - if let Some(lang) = views.get(word.src()?.unwrap()) { - let lang = lang.clone(); - std::mem::drop(views); - self.draw_mode(to, lang) - } else { - fg(Color::Rgb(128, 32, 32), format!("undefined: {word:?}")).draw(to) - } - }, - _ => unreachable!() - } + Err(format!("interpret_expr: unexpected: {src:?}").into()) } else { - Err(format!("not word/expr:\n{dsl:?}").into()) + Err(format!("interpret_expr: no keyword: {src:?}").into()) } } + + fn interpret_word (&'a self, to: &mut Tui, lang: &'a L) -> Drawn { + let mut frags = lang.src()?.unwrap().split("/"); + match frags.next() { + //Some(":logo") => view_logo().draw(to), + Some(":meters") => match frags.next() { + Some("input") => bg(Rgb(30, 30, 30), "Input Meters".align_s().full_h()).draw(to), + Some("output") => bg(Rgb(30, 30, 30), "Output Meters".align_s().full_h()).draw(to), + _ => panic!() + }, + Some(":tracks") => match frags.next() { + None => "TODO tracks".draw(to), + Some("names") => self.project.view_track_names(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), full_w(align_w("Track Names")))), + Some("inputs") => self.project.view_track_inputs(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Inputs".align_w().full_w()).draw(to), + Some("devices") => self.project.view_track_devices(self.color.clone()).draw(to),//bg(Rgb(40, 40, 40), "Track Devices".align_w().full_w()).draw(to), + Some("outputs") => self.project.view_track_outputs(self.color.clone(), 0).draw(to), + _ => panic!() + }, + Some(":scenes") => match frags.next() { + None => self.view_scenes_clips().draw(to), + Some("names") => self.view_scenes_names().draw(to), + _ => panic!() + }, + Some(":dialog") => draw_dialog(to, frags, self, lang), + Some(":templates") => view_templates(frags, self).draw(to), + Some(":sessions") => view_sessions().draw(to), + Some(":browse/title") => view_browse_title(self).draw(to), + Some(":device") => view_device(self).draw(to), + Some(":status") => "TODO: Status Bar".draw(to), + Some(":editor") => "TODO Editor".draw(to), + Some(":transport") => view_transport(true, "", "", "").draw(to), + Some(":debug") => format!("[{:?}]", to.area()).exact_h(1).draw(to), + Some(_) => { + let views = self.config.views.read().unwrap(); + if let Some(lang) = views.get(lang.src()?.unwrap()) { + let lang = lang.clone(); + std::mem::drop(views); + self.interpret(to, &lang) + } else { + fg(Color::Rgb(128, 32, 32), format!("undefined: {lang:?}")).draw(to) + } + }, + _ => unreachable!() + } + } + } impl_has!(Sizer: |self: App|self.size); @@ -1021,13 +1458,11 @@ mod draw { fn width_dec (&mut self); } - pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &'a App) - -> impl Draw<'a, Tui> + use<'a> - { + pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &App) -> impl Draw<'a, Tui> { let height = (state.config.modes.len() * 2) as u16; draw(move |to: &mut Tui|{ let mut index = 0; - state.config.modes.for_each(&mut |id: &str, profile: &Mode| { + state.config.modes.for_each(|id, profile| { let b = if index == 0 { Rgb(70,70,70) } else { Rgb(50,50,50) }; let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or(""); let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or(""); @@ -1162,6 +1597,53 @@ mod draw { } +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!(); + }); +} + pub fn print_status (project: &Arrangement) { println!("Name: {:?}", &project.name); println!("JACK: {:?}", &project.jack);