mirror of
https://codeberg.org/unspeaker/tek.git
synced 2026-08-28 04:46:58 +02:00
3e...
This commit is contained in:
parent
816150125d
commit
4fcbcdda86
5 changed files with 607 additions and 566 deletions
29
Cargo.lock
generated
29
Cargo.lock
generated
|
|
@ -2137,7 +2137,7 @@ version = "0.50.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2559,8 +2559,10 @@ version = "0.9.12"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"libc",
|
"libc",
|
||||||
|
"petgraph",
|
||||||
"redox_syscall 0.5.18",
|
"redox_syscall 0.5.18",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"windows-link",
|
"windows-link",
|
||||||
|
|
@ -2620,6 +2622,16 @@ dependencies = [
|
||||||
"pest",
|
"pest",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf"
|
name = "phf"
|
||||||
version = "0.11.3"
|
version = "0.11.3"
|
||||||
|
|
@ -3827,6 +3839,7 @@ dependencies = [
|
||||||
"livi",
|
"livi",
|
||||||
"notify-debouncer-full",
|
"notify-debouncer-full",
|
||||||
"palette",
|
"palette",
|
||||||
|
"parking_lot 0.12.5",
|
||||||
"proptest",
|
"proptest",
|
||||||
"proptest-derive",
|
"proptest-derive",
|
||||||
"quanta",
|
"quanta",
|
||||||
|
|
@ -3836,6 +3849,7 @@ dependencies = [
|
||||||
"tek_proc",
|
"tek_proc",
|
||||||
"tengri",
|
"tengri",
|
||||||
"toml 0.9.12+spec-1.1.0",
|
"toml 0.9.12+spec-1.1.0",
|
||||||
|
"tracing-mutex",
|
||||||
"uuid",
|
"uuid",
|
||||||
"wavers",
|
"wavers",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
@ -3905,7 +3919,7 @@ dependencies = [
|
||||||
"parking_lot 0.12.5",
|
"parking_lot 0.12.5",
|
||||||
"rustix 1.1.4",
|
"rustix 1.1.4",
|
||||||
"signal-hook",
|
"signal-hook",
|
||||||
"windows-sys 0.60.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -4246,6 +4260,17 @@ dependencies = [
|
||||||
"tracing-core",
|
"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]]
|
[[package]]
|
||||||
name = "tracing-subscriber"
|
name = "tracing-subscriber"
|
||||||
version = "0.3.23"
|
version = "0.3.23"
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ gtk = { optional = true, version = "0.18.1" }
|
||||||
|
|
||||||
notify-debouncer-full = "0.7.0"
|
notify-debouncer-full = "0.7.0"
|
||||||
hotpath = "0.23"
|
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"
|
#once_cell = "1.19.0"
|
||||||
#no_deadlocks = "1.3.2"
|
#no_deadlocks = "1.3.2"
|
||||||
|
|
|
||||||
496
src/config.rs
Normal file
496
src/config.rs
Normal file
|
|
@ -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<std::sync::Arc<str>> = 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<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)?;
|
||||||
|
Self::watch(config.clone(), None)?;
|
||||||
|
let result = callback(config);
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create and initialize a new configuration.
|
||||||
|
pub fn init_new (_dirs: Option<BaseDirectories>) -> UsuallyRef<'static, Arc<Self>> {
|
||||||
|
let config = Arc::new(Self::new(None));
|
||||||
|
config_init(&config)?;
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Watch a config's file for changes.
|
||||||
|
pub fn watch (config: Arc<Config>, poll: Option<Duration>) -> 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<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 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!();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::{*, device::*};
|
use crate::{*, device::*};
|
||||||
|
|
||||||
pub fn draw_dialog <'a, L: Language + ?Sized> (
|
pub fn draw_dialog <'a> (to: &mut Tui, mut frags: std::str::Split<&str>, state: &App)
|
||||||
to: &mut Tui, mut frags: std::str::Split<&str>, state: &App, dsl: &'a L
|
-> Drawn<'a, u16>
|
||||||
) -> Drawn<'a, u16> {
|
{
|
||||||
match frags.next() {
|
match frags.next() {
|
||||||
Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog {
|
Some("menu") => if let Dialog::Menu(selected, items) = &state.dialog {
|
||||||
//Some(iter_south(move||items.0.iter().enumerate().map(move|(index, MenuItem(item, _))|{
|
//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 {
|
} else {
|
||||||
None
|
None
|
||||||
}.draw(to),
|
}.draw(to),
|
||||||
_ => unimplemented!("App::interpret_word: {dsl:?} ({frags:?})"),
|
_ => unimplemented!("draw_dialog: ({frags:?})"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
638
src/tek.rs
638
src/tek.rs
|
|
@ -76,13 +76,13 @@ fn run_new_plain (config: Config) -> Usually<()> {
|
||||||
#[cfg(feature = "cli")] pub mod cli {
|
#[cfg(feature = "cli")] pub mod cli {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
pub fn run_with_config <'a> (config: Arc<Config<'a>>) -> UsuallyRef<'a, ()> {
|
pub fn run_with_config (config: Arc<Config>) -> Usually<()> {
|
||||||
Cli::parse().run(Some(config))
|
Cli::parse().run(Some(config))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Command-line configuration.
|
/// Command-line configuration.
|
||||||
impl Cli {
|
impl Cli {
|
||||||
pub fn run <'a> (&self, mut config: Option<Arc<Config<'a>>>) -> UsuallyRef<'a, ()> {
|
pub fn run (&self, mut config: Option<Arc<Config>>) -> Usually<()> {
|
||||||
if config.is_none() {
|
if config.is_none() {
|
||||||
config = Some(Config::init_new(None)?);
|
config = Some(Config::init_new(None)?);
|
||||||
}
|
}
|
||||||
|
|
@ -231,308 +231,7 @@ fn run_new_plain (config: Config) -> Usually<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub use self::config::*;
|
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<Option<Arc<str>>>,
|
|
||||||
/// Timestamp
|
|
||||||
pub stamp: AtomicU64,
|
|
||||||
/// Watcher
|
|
||||||
pub watch: RwLock<Option<PollWatcher>>
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collection of custom view definitions.
|
|
||||||
pub type Views = Arc<RwLock<BTreeMap<Arc<str>, Arc<str>>>>;
|
|
||||||
|
|
||||||
/// 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<RwLock<BTreeMap<Arc<str>, Arc<Mode<'a, Arc<str>>>>>>);
|
|
||||||
|
|
||||||
/// Group of view and keys definitions.
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// let mode = tek::Mode::<std::sync::Arc<str>>::default();
|
|
||||||
/// ```
|
|
||||||
#[derive(Default, Debug)]
|
|
||||||
pub struct Mode<'a, D: Language + Ord> {
|
|
||||||
pub path: PathBuf,
|
|
||||||
pub name: Vec<D>,
|
|
||||||
pub info: Vec<D>,
|
|
||||||
pub view: Vec<D>,
|
|
||||||
pub keys: Vec<D>,
|
|
||||||
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<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> (callback: impl FnOnce(Arc<Self>)->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<BaseDirectories>) -> UsuallyRef<'a, Arc<Self>> {
|
|
||||||
let config = Arc::new(Self::new(None));
|
|
||||||
config.init()?;
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
/// Watch a config's file for changes.
|
|
||||||
pub fn watch (config: Arc<Self>, poll: Option<Duration>) -> 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<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)
|
|
||||||
}
|
|
||||||
/// 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 <F: FnMut(&Self, &str)->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::<Option<()>, Box<dyn Error>>(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<str>) -> Option<Arc<Mode<'a, Arc<str>>>> {
|
|
||||||
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<str> + ?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<str>) -> Option<Arc<Mode<'a, Arc<str>>>> {
|
|
||||||
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<Arc<str>>)->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<str>> {
|
|
||||||
/// 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 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<dyn Error>>(())
|
|
||||||
})?))
|
|
||||||
}
|
|
||||||
/// 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::*;
|
pub use self::app::*;
|
||||||
mod app {
|
mod app {
|
||||||
|
|
@ -606,7 +305,7 @@ mod app {
|
||||||
/// Performance counter
|
/// Performance counter
|
||||||
pub perf: PerfModel,
|
pub perf: PerfModel,
|
||||||
/// Available view modes and input bindings
|
/// Available view modes and input bindings
|
||||||
pub config: Arc<Config<'static>>,
|
pub config: Arc<Config>,
|
||||||
/// Currently selected mode
|
/// Currently selected mode
|
||||||
pub mode: Option<Arc<str>>,
|
pub mode: Option<Arc<str>>,
|
||||||
/// Undo history
|
/// Undo history
|
||||||
|
|
@ -632,7 +331,10 @@ mod app {
|
||||||
/// let tek = tek::App::new(None, proj, conf, "hello");
|
/// let tek = tek::App::new(None, proj, conf, "hello");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn new (
|
pub fn new (
|
||||||
exit: Option<Exit>, project: Arrangement, config: Arc<Config>, mode: impl AsRef<str>
|
exit: Option<Exit>,
|
||||||
|
project: Arrangement,
|
||||||
|
config: Arc<Config>,
|
||||||
|
mode: impl AsRef<str>
|
||||||
) -> Self {
|
) -> Self {
|
||||||
App {
|
App {
|
||||||
exit: exit.unwrap_or_default(),
|
exit: exit.unwrap_or_default(),
|
||||||
|
|
@ -790,47 +492,6 @@ pub use self::bind::*;
|
||||||
mod bind {
|
mod bind {
|
||||||
use crate::*;
|
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>>);
|
|
||||||
|
|
||||||
tui_keys!(self: App, input {
|
tui_keys!(self: App, input {
|
||||||
let name = self.mode.as_ref();
|
let name = self.mode.as_ref();
|
||||||
let mode = name.and_then(|m|self.config.get_mode(m)).as_ref().map(Arc::clone);
|
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<str>, body: impl Language
|
|
||||||
) -> Usually<()> {
|
|
||||||
binds.write().unwrap().insert(name.as_ref().into(), Bind::load(&body)?);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Bind<TuiEvent, Arc<str>> {
|
|
||||||
pub fn load <'a> (lang: &impl Language) -> Usually<Self> {
|
|
||||||
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<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, "*") });
|
|
||||||
|
|
||||||
/// A control axis.
|
/// A control axis.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
|
@ -1309,17 +887,6 @@ pub use self::draw::*;
|
||||||
mod draw {
|
mod draw {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
/// Load custom view definition.
|
|
||||||
pub(crate) fn load_view <'a> (
|
|
||||||
views: &Views, name: impl AsRef<str>, 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,
|
/// The [Draw] implementation for [App] handles the loaded view,
|
||||||
/// which is defined in terms of [dizzle] DSL.
|
/// 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)) {
|
if let Some(mode) = self.mode.as_ref().and_then(|m|self.config.get_mode(m)) {
|
||||||
let mut error = false;
|
let mut error = false;
|
||||||
for (index, dsl) in mode.view.iter().enumerate() {
|
for (index, dsl) in mode.view.iter().enumerate() {
|
||||||
if let Err(e) = self.draw_mode(to, dsl) {
|
match self.draw_mode(to, dsl) {
|
||||||
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
Ok(None) => {},
|
||||||
let message = format!(
|
Ok(Some(XYWH(.., w, h))) => {
|
||||||
"Mode: {:?}\n\nLayer: #{index}\n\nError: {e}\n\nSource:\n{src}",
|
self.size.0.store(w as usize, Relaxed);
|
||||||
&mode.name
|
self.size.1.store(h as usize, Relaxed);
|
||||||
);
|
},
|
||||||
*self.error.write().unwrap() = Some(message.into());
|
Err(e) => {
|
||||||
error = true;
|
let src = &dsl.src().unwrap_or(Some("<source error>")).unwrap_or("<no source>");
|
||||||
break;
|
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 {
|
if !error {
|
||||||
|
|
@ -1368,15 +942,8 @@ mod draw {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_mode <'a> (&'a self, to: &mut Tui, dsl: &'a impl Language) -> UsuallyRef<'a, ()> {
|
fn draw_mode <'a, L: Language + 'a> (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> {
|
||||||
Ok(match self.interpret(to, dsl) {
|
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<u16> {
|
fn draw_debug (&self, to: &mut Tui) -> Drawn<u16> {
|
||||||
|
|
@ -1388,64 +955,60 @@ mod draw {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Interpret<'a, Tui, Option<XYWH<u16>>> for App {
|
impl<'a> Interpret<'a, Tui, Option<XYWH<u16>>> for App {
|
||||||
|
fn interpret <L: Language + 'a> (&'a self, to: &mut Tui, dsl: L) -> Drawn<'a, u16> {
|
||||||
fn interpret_expr <L: Language + ?Sized> (&'a self, to: &mut Tui, src: &'a L) -> Drawn<u16> {
|
if let Some(expr) = dsl.expr()? {
|
||||||
if let Some(src) = src.src()? {
|
interpret_keyword!(self, to, expr, [
|
||||||
interpret_keyword!(self, to, src, [
|
|
||||||
kw_when, kw_either, kw_split, kw_align, kw_exact, kw_min, kw_max, kw_push, kw_full,
|
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
|
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 {
|
} else {
|
||||||
Err(format!("interpret_expr: no keyword: {src:?}").into())
|
Err(format!("not word/expr:\n{dsl:?}").into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn interpret_word <L: Language + ?Sized> (&'a self, to: &mut Tui, lang: &'a L) -> Drawn<u16> {
|
|
||||||
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);
|
impl_has!(Sizer: |self: App|self.size);
|
||||||
|
|
@ -1458,11 +1021,13 @@ mod draw {
|
||||||
fn width_dec (&mut self);
|
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;
|
let height = (state.config.modes.len() * 2) as u16;
|
||||||
draw(move |to: &mut Tui|{
|
draw(move |to: &mut Tui|{
|
||||||
let mut index = 0;
|
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 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("<no name>");
|
let name = profile.name.get(0).map(|x|x.as_ref()).unwrap_or("<no name>");
|
||||||
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
let info = profile.info.get(0).map(|x|x.as_ref()).unwrap_or("<no info>");
|
||||||
|
|
@ -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) {
|
pub fn print_status (project: &Arrangement) {
|
||||||
println!("Name: {:?}", &project.name);
|
println!("Name: {:?}", &project.name);
|
||||||
println!("JACK: {:?}", &project.jack);
|
println!("JACK: {:?}", &project.jack);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue