diff --git a/Cargo.lock b/Cargo.lock index 5d4a7dc2..52f07dbc 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.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -2559,8 +2559,10 @@ 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", @@ -2620,6 +2622,16 @@ 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" @@ -3827,6 +3839,7 @@ dependencies = [ "livi", "notify-debouncer-full", "palette", + "parking_lot 0.12.5", "proptest", "proptest-derive", "quanta", @@ -3836,6 +3849,7 @@ dependencies = [ "tek_proc", "tengri", "toml 0.9.12+spec-1.1.0", + "tracing-mutex", "uuid", "wavers", "winit", @@ -3905,7 +3919,7 @@ dependencies = [ "parking_lot 0.12.5", "rustix 1.1.4", "signal-hook", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4246,6 +4260,17 @@ 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 25e2ece0..bd1cb9ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,8 @@ 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 new file mode 100644 index 00000000..560d7d4e --- /dev/null +++ b/src/config.rs @@ -0,0 +1,496 @@ +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")?; + modes.0.write().unwrap().insert( + name.into(), + Arc::new(body.each( + Mode::default(), + move|mut mode: Mode, item: &str|{ + mode_add(&mut mode, item)?; + Ok(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 head = tail.head()?.ok_or("mode: missing name")?; + let body = tail.tail()?.ok_or("mode: missing body")?; + let modes = mode.modes.clone(); + let submode = Mode::default(); + let submode = body.each(submode, move|mut submode: Mode, item: &str|{ + mode_add(&mut submode, &item); + Ok(submode) + })?; + modes.0.write().unwrap().insert(head.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) -> Usually<()> { + 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 2b92c56e..cc8913fd 100644 --- a/src/device/dialog.rs +++ b/src/device/dialog.rs @@ -1,8 +1,8 @@ use crate::{*, device::*}; -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> { +pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App) + -> 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, L: Language + ?Sized> ( } else { None }.draw(to), - _ => unimplemented!("App::interpret_word: {dsl:?} ({frags:?})"), + _ => unimplemented!("draw_dialog: ({frags:?})"), } } diff --git a/src/tek.rs b/src/tek.rs index 270b49cd..e401a3c3 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 <'a> (config: Arc>) -> UsuallyRef<'a, ()> { + pub fn run_with_config (config: Arc) -> Usually<()> { Cli::parse().run(Some(config)) } /// Command-line configuration. impl Cli { - pub fn run <'a> (&self, mut config: Option>>) -> UsuallyRef<'a, ()> { + pub fn run (&self, mut config: Option>) -> Usually<()> { if config.is_none() { config = Some(Config::init_new(None)?); } @@ -231,308 +231,7 @@ fn run_new_plain (config: Config) -> Usually<()> { } pub use self::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()); - })) - } - } -} +mod config; pub use self::app::*; mod app { @@ -606,7 +305,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 @@ -632,7 +331,10 @@ 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(), @@ -790,47 +492,6 @@ 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); @@ -983,89 +644,6 @@ 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. /// /// ``` @@ -1309,17 +887,6 @@ 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. /// @@ -1350,15 +917,22 @@ 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() { - 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; + 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 !error { @@ -1368,15 +942,8 @@ mod draw { Ok(()) } - 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_mode <'a, L: Language + 'a> (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> { + self.interpret(to, dsl) } fn draw_debug (&self, to: &mut Tui) -> Drawn { @@ -1388,64 +955,60 @@ mod draw { } impl<'a> Interpret<'a, Tui, Option>> for App { - - fn interpret_expr (&'a self, to: &mut Tui, src: &'a L) -> Drawn { - if let Some(src) = src.src()? { - interpret_keyword!(self, to, src, [ + fn interpret (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> { + if let Some(expr) = dsl.expr()? { + interpret_keyword!(self, to, expr, [ 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: {src:?}").into()) + 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!() + } } else { - Err(format!("interpret_expr: no keyword: {src:?}").into()) + Err(format!("not word/expr:\n{dsl:?}").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); @@ -1458,11 +1021,13 @@ mod draw { fn width_dec (&mut self); } - pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &App) -> impl Draw<'a, Tui> { + pub fn view_templates <'a> (_frags: std::str::Split<&str>, state: &'a App) + -> impl Draw<'a, Tui> + use<'a> + { let height = (state.config.modes.len() * 2) as u16; draw(move |to: &mut Tui|{ let mut index = 0; - state.config.modes.for_each(|id, profile| { + state.config.modes.for_each(&mut |id: &str, profile: &Mode| { 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(""); @@ -1597,53 +1162,6 @@ 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);