mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-28 04:46:58 +02:00
494 lines
17 KiB
Rust
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!();
|
|
});
|
|
}
|