···11-22-filaments_dir "./ZettelKasten"
33-44-keymap {
55-66- Home {
77- <Ctrl-c> Quit // Another way to quit
88- <Ctrl-z> Suspend // Suspend the application
99- up MoveUp
1010- down MoveDown
1111- enter OpenZettel
1212- <Ctrl-n> NewZettel
1313- }
1414-}
···11-scroll_offset 4
22-33-keybindings {
44- Explorer {
55- "<q>" Quit // Quit the application
66- "<Ctrl-d>" Quit // Another way to quit
77- "<Ctrl-c>" Quit // Yet another way to quit
88- "<Ctrl-z>" Suspend // Suspend the application
99- "<2>" switch-to="TodoList"
1010- "<3>" switch-to="Inspector"
1111- f ToggleShowFinished
1212- x Delete
1313- t NewTask
1414- g NewSubGroup
1515- "<Shift-g>" NewGroup
1616- j MoveDown
1717- k MoveUp
1818- l MoveInto
1919- h MoveOutOf
2020- }
2121-2222- TodoList {
2323- "<q>" Quit // Quit the application
2424- "<Ctrl-d>" Quit // Another way to quit
2525- "<Ctrl-c>" Quit // Yet another way to quit
2626- "<Ctrl-z>" Suspend // Suspend the application
2727- "<1>" switch-to="Explorer"
2828- "<3>" switch-to="Inspector"
2929- j MoveDown
3030- k MoveUp
3131- }
3232-3333- Inspector {
3434- "<q>" Quit // Quit the application
3535- "<Ctrl-d>" Quit // Another way to quit
3636- "<Ctrl-c>" Quit // Yet another way to quit
3737- "<Ctrl-z>" Suspend // Suspend the application
3838- "<1>" switch-to="Explorer"
3939- "<2>" switch-to="TodoList"
4040- r RandomColor
4141- n EditName
4242- c EditColor
4343- p EditPriority
4444- u EditDue
4545- d EditDescription
4646- f ToggleFinishTask
4747- t NewTask
4848- g NewSubGroup
4949- }
5050-}
···124124 # Set any environment variables for your dev shell
125125 env = {
126126 RUST_SRC_PATH = "${pkgs.rustToolchain}/lib/rustlib/src/rust/library";
127127+128128+ # project specific vars
127129 FIL_CONFIG = "./.config";
128130 FIL_DATA = "./.data";
131131+ FIL_LOG_LEVEL = "DEBUG";
129132 };
130133131134 # Add any shell logic you want executed any time the environment is activated
+4-4
src/cli/process.rs
···2424 Workspace::initialize(dir.clone()).await?;
25252626 // write config that sets the filaments directory to current dir!
2727- let config_kdl = dbg! {Config::generate(&dir)};
2727+ let config_str = dbg! {Config::generate(&dir)}?;
28282929 // create the config dir
3030 let config_dir = get_config_dir();
31313232 create_dir_all(config_dir).expect("creating the config dir should not error");
33333434- let mut config_file = File::create(get_config_dir().join("config.kdl"))
3434+ let mut config_file = File::create(get_config_dir().join("config.ron"))
3535 .context("Failed to create config file")?;
36363737- write!(config_file, "{config_kdl}")?;
3737+ write!(config_file, "{config_str}")?;
38383939 println!("wrote config to {config_file:#?}");
4040···44444545 Self::Zettel(zettel_sub_command) => {
4646 let conf = Config::parse()?;
4747- let ws = Workspace::instansiate(conf.app_config.workspace).await?;
4747+ let ws = Workspace::instansiate(conf.fil_dir).await?;
48484949 match zettel_sub_command {
5050 ZettelSubcommand::New { title } => {
-152
src/config.rs
···11-use color_eyre::eyre::Context;
22-use directories::ProjectDirs;
33-use kdl::KdlDocument;
44-use serde::Deserialize;
55-use std::{
66- env::{self, home_dir},
77- fmt::Debug,
88- fs::File,
99- io::Read,
1010- path::{Path, PathBuf},
1111- sync::LazyLock,
1212-};
1313-1414-use crate::tui::KeyMap;
1515-1616-/// Project Name: Filaments
1717-pub static PROJECT_NAME: LazyLock<String> =
1818- LazyLock::new(|| env!("CARGO_CRATE_NAME").to_uppercase());
1919-2020-/// The OS-agnostic data directory for the project.
2121-pub static DATA_DIRECTORY: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
2222- env::var(format!("{}_DATA", PROJECT_NAME.clone()))
2323- .ok()
2424- .map(PathBuf::from)
2525-});
2626-2727-/// The OS-agnostic config directory for the project.
2828-pub static CONFIG_DIRECTORY: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
2929- env::var(format!("{}_CONFIG", PROJECT_NAME.clone()))
3030- .ok()
3131- .map(PathBuf::from)
3232-});
3333-3434-const DEFAULT_CONFIG: &str = include_str!("../.config/config.kdl");
3535-3636-/// The App Config and Data locations.
3737-#[derive(Clone, Debug, Deserialize, Default)]
3838-#[expect(dead_code)]
3939-pub struct AppConfig {
4040- /// The directory where the single instance of the filaments exists.
4141- pub workspace: PathBuf,
4242- #[serde(default)]
4343- pub data: PathBuf,
4444- #[serde(default)]
4545- pub config: PathBuf,
4646-}
4747-4848-/// Configuration for the App
4949-#[derive(Debug, Clone)]
5050-pub struct Config {
5151- pub app_config: AppConfig,
5252- pub keymap: KeyMap,
5353- // pub styles: Styles,
5454-}
5555-5656-impl Config {
5757- /// generates a new config with the provided `filaments_dir`
5858- pub fn generate(filaments_dir: &Path) -> KdlDocument {
5959-6060-6161- let mut default_config: KdlDocument = DEFAULT_CONFIG
6262- .parse()
6363- .expect("Default config should always be a valid KDL document.");
6464-6565- if let Some(node) = default_config
6666- .nodes_mut()
6767- .iter_mut()
6868- .find(|n| n.name().value() == "filaments_dir")
6969- && let Some(entry) = node.entries_mut().get_mut(0)
7070- {
7171- *entry.value_mut() = kdl::KdlValue::String(filaments_dir.to_string_lossy().to_string());
7272- entry.clear_format();
7373- }
7474-7575- default_config
7676- }
7777-7878- /// Parse the config from `~/.config/filametns`
7979- ///
8080- /// # Errors
8181- ///
8282- /// Will error if the config doesn't exist or if there
8383- /// is a problem parsing it.
8484- pub fn parse() -> color_eyre::Result<Self> {
8585- let config: KdlDocument = {
8686- let file_path = get_config_dir().join("config.kdl");
8787-8888- let mut file = File::open(file_path).context("Failed to find file!")?;
8989-9090- let mut str = String::new();
9191-9292- file.read_to_string(&mut str)
9393- .context("Failed to read file!")?;
9494-9595- str.parse().context("Expected to be valid kdl")?
9696- };
9797-9898- let keymap = KeyMap::try_from(
9999- config
100100- .get("keymap")
101101- .expect("Keymap must exist in the config"),
102102- )
103103- .context("Keymap is not valid!")?;
104104-105105- let filaments_dir_str = config
106106- .get("filaments_dir")
107107- .expect("config should always have this specified")
108108- .get(0)
109109- .and_then(|arg| arg.as_string())
110110- .expect("filaments_dir must be a string");
111111-112112- let filaments_dir = PathBuf::from(filaments_dir_str)
113113- .canonicalize()
114114- .context("Filaments directory does not exist!")?;
115115-116116- Ok(Self {
117117- app_config: AppConfig {
118118- workspace: filaments_dir,
119119- data: get_data_dir(),
120120- config: get_config_dir(),
121121- },
122122- keymap,
123123- })
124124- }
125125-}
126126-127127-/// Returns the path to the OS-agnostic data directory.
128128-pub fn get_data_dir() -> PathBuf {
129129- DATA_DIRECTORY.clone().unwrap_or_else(|| {
130130- project_directory().map_or_else(
131131- || PathBuf::from(".").join(".data"),
132132- |proj_dirs| proj_dirs.data_local_dir().to_path_buf(),
133133- )
134134- })
135135-}
136136-137137-/// Returns the path to the OS-agnostic config directory.
138138-pub fn get_config_dir() -> PathBuf {
139139- CONFIG_DIRECTORY.clone().unwrap_or_else(|| {
140140- home_dir().map_or_else(
141141- || PathBuf::from(".").join(".config"),
142142- |mut path| {
143143- path.push(".config");
144144- path.push("filaments");
145145- path
146146- },
147147- )
148148- })
149149-}
150150-fn project_directory() -> Option<ProjectDirs> {
151151- ProjectDirs::from("com", "suri-codes", env!("CARGO_PKG_NAME"))
152152-}
···11+use color_eyre::eyre::{Result, eyre};
22+use crossterm::event::{KeyCode, KeyModifiers};
33+use std::{
44+ collections::HashMap,
55+ ops::{Deref, DerefMut},
66+};
77+use strum::IntoEnumIterator;
88+99+use crossterm::event::KeyEvent;
1010+1111+use crate::{
1212+ config::file::RonConfig,
1313+ tui::{Region, Signal},
1414+};
1515+#[derive(Debug, Clone)]
1616+pub struct KeyMap(pub HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>);
1717+1818+impl TryFrom<&RonConfig> for KeyMap {
1919+ type Error = color_eyre::Report;
2020+2121+ fn try_from(value: &RonConfig) -> Result<Self, Self::Error> {
2222+ let mut binds = HashMap::new();
2323+2424+ for region in Region::iter() {
2525+ let mut region_binds = HashMap::new();
2626+2727+ let mut parse_and_insert = |str: &str, bind: &Signal| -> Result<()> {
2828+ let key_seq = parse_key_sequence(str).map_err(|e| {
2929+ eyre!(format!(
3030+ "Failed to parse the following keybind as valid keybind: {e}"
3131+ ))
3232+ })?;
3333+3434+ region_binds.insert(key_seq, bind.clone());
3535+ Ok(())
3636+ };
3737+3838+ // first thing we have to do is insert the global binds for this region
3939+4040+ for (str, bind) in &value.global_key_binds {
4141+ parse_and_insert(str, bind)?;
4242+ }
4343+4444+ // now we insert the region specific binds
4545+ for (str, bind) in match region {
4646+ Region::Zk => value.zk.keybinds.iter(),
4747+ Region::Todo => value.todo.keybinds.iter(),
4848+ } {
4949+ parse_and_insert(str, bind)?;
5050+ }
5151+5252+ binds.insert(region, region_binds);
5353+ }
5454+5555+ Ok(Self(binds))
5656+ }
5757+}
5858+5959+impl Deref for KeyMap {
6060+ type Target = HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>;
6161+6262+ fn deref(&self) -> &Self::Target {
6363+ &self.0
6464+ }
6565+}
6666+6767+impl DerefMut for KeyMap {
6868+ fn deref_mut(&mut self) -> &mut Self::Target {
6969+ &mut self.0
7070+ }
7171+}
7272+7373+pub fn parse_key_sequence(raw: &str) -> color_eyre::Result<Vec<KeyEvent>, String> {
7474+ if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() {
7575+ return Err(format!("Unable to parse `{raw}`"));
7676+ }
7777+ let raw = if raw.contains("><") {
7878+ raw
7979+ } else {
8080+ let raw = raw.strip_prefix('<').unwrap_or(raw);
8181+8282+ raw.strip_prefix('>').unwrap_or(raw)
8383+ };
8484+8585+ raw.split("><")
8686+ .map(|seq| {
8787+ seq.strip_prefix('<')
8888+ .unwrap_or_else(|| seq.strip_suffix('>').map_or(seq, |s| s))
8989+ })
9090+ .map(parse_key_event)
9191+ .collect()
9292+}
9393+9494+fn parse_key_event(raw: &str) -> color_eyre::Result<KeyEvent, String> {
9595+ let raw_lower = raw.to_ascii_lowercase();
9696+ let (remaining, modifiers) = extract_modifiers(&raw_lower);
9797+ parse_key_code_with_modifiers(remaining, modifiers)
9898+}
9999+100100+fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
101101+ let mut modifiers = KeyModifiers::empty();
102102+ let mut current = raw;
103103+104104+ loop {
105105+ match current {
106106+ rest if rest.starts_with("ctrl-") => {
107107+ modifiers.insert(KeyModifiers::CONTROL);
108108+ current = &rest[5..];
109109+ }
110110+ rest if rest.starts_with("alt-") => {
111111+ modifiers.insert(KeyModifiers::ALT);
112112+ current = &rest[4..];
113113+ }
114114+ rest if rest.starts_with("shift-") => {
115115+ modifiers.insert(KeyModifiers::SHIFT);
116116+ current = &rest[6..];
117117+ }
118118+ _ => break, // break out of the loop if no known prefix is detected
119119+ }
120120+ }
121121+122122+ (current, modifiers)
123123+}
124124+125125+fn parse_key_code_with_modifiers(
126126+ raw: &str,
127127+ mut modifiers: KeyModifiers,
128128+) -> color_eyre::Result<KeyEvent, String> {
129129+ let c = match raw {
130130+ "esc" => KeyCode::Esc,
131131+ "enter" => KeyCode::Enter,
132132+ "left" => KeyCode::Left,
133133+ "right" => KeyCode::Right,
134134+ "up" => KeyCode::Up,
135135+ "down" => KeyCode::Down,
136136+ "home" => KeyCode::Home,
137137+ "end" => KeyCode::End,
138138+ "pageup" => KeyCode::PageUp,
139139+ "pagedown" => KeyCode::PageDown,
140140+ "backtab" => {
141141+ modifiers.insert(KeyModifiers::SHIFT);
142142+ KeyCode::BackTab
143143+ }
144144+ "backspace" => KeyCode::Backspace,
145145+ "delete" => KeyCode::Delete,
146146+ "insert" => KeyCode::Insert,
147147+ "f1" => KeyCode::F(1),
148148+ "f2" => KeyCode::F(2),
149149+ "f3" => KeyCode::F(3),
150150+ "f4" => KeyCode::F(4),
151151+ "f5" => KeyCode::F(5),
152152+ "f6" => KeyCode::F(6),
153153+ "f7" => KeyCode::F(7),
154154+ "f8" => KeyCode::F(8),
155155+ "f9" => KeyCode::F(9),
156156+ "f10" => KeyCode::F(10),
157157+ "f11" => KeyCode::F(11),
158158+ "f12" => KeyCode::F(12),
159159+ "space" => KeyCode::Char(' '),
160160+ "hyphen" | "minuc" => KeyCode::Char('-'),
161161+ "tab" => KeyCode::Tab,
162162+ c if c.len() == 1 => {
163163+ let mut c = c.chars().next().unwrap();
164164+ if modifiers.contains(KeyModifiers::SHIFT) {
165165+ c = c.to_ascii_uppercase();
166166+ }
167167+ KeyCode::Char(c)
168168+ }
169169+ _ => return Err(format!("Unable to parse {raw}")),
170170+ };
171171+ Ok(KeyEvent::new(c, modifiers))
172172+}
173173+174174+#[cfg(test)]
175175+mod test {
176176+ // use crossterm::event::{KeyEvent, KeyModifiers};
177177+ // use kdl::KdlNode;
178178+179179+ // use crate::tui::{KeyMap, Region, Signal};
180180+181181+ use crossterm::event::{KeyEvent, KeyModifiers};
182182+183183+ use crate::{
184184+ config::{file::RonConfig, keymap::KeyMap},
185185+ tui::{Region, Signal},
186186+ };
187187+188188+ #[test]
189189+ fn test_quit() {
190190+ let conf_str = r#"
191191+(
192192+ directory: "./ZettelKasten",
193193+ global_key_binds: {
194194+ "ctrl-c": Quit,
195195+ "ctrl-z": Suspend,
196196+ "up": MoveUp,
197197+ "down": MoveDown,
198198+ },
199199+ zk: (
200200+ keybinds: {
201201+ "<Ctrl-n>": NewZettel,
202202+ "enter": OpenZettel,
203203+ "tab": SwitchTo (
204204+ region: Todo
205205+ ),
206206+207207+ },
208208+ ),
209209+ todo: (
210210+ keybinds: {
211211+ "j": MoveDown,
212212+ "k": MoveUp,
213213+ "tab": SwitchTo (
214214+ region: Zk
215215+ ),
216216+217217+ },
218218+ ),
219219+)
220220+ "#;
221221+222222+ let config: RonConfig = ron::from_str(conf_str).unwrap();
223223+ let keymap: KeyMap = (&config).try_into().unwrap();
224224+225225+ let map = keymap
226226+ .get(&Region::Todo)
227227+ .expect("Home region must exist in keymap");
228228+229229+ let signal = map
230230+ .get(&vec![KeyEvent::new_with_kind(
231231+ crossterm::event::KeyCode::Char('c'),
232232+ KeyModifiers::CONTROL,
233233+ crossterm::event::KeyEventKind::Press,
234234+ )])
235235+ .expect("Must resolve to a signal");
236236+237237+ assert_eq!(*signal, Signal::Quit);
238238+239239+ let map = keymap
240240+ .get(&Region::Zk)
241241+ .expect("Home region must exist in keymap");
242242+243243+ let signal = map
244244+ .get(&vec![KeyEvent::new_with_kind(
245245+ crossterm::event::KeyCode::Char('c'),
246246+ KeyModifiers::CONTROL,
247247+ crossterm::event::KeyEventKind::Press,
248248+ )])
249249+ .expect("Must resolve to a signal");
250250+251251+ assert_eq!(*signal, Signal::Quit);
252252+ }
253253+}
+100
src/config/mod.rs
···11+use std::{
22+ env::{self, home_dir},
33+ fs::read_to_string,
44+ path::{Path, PathBuf},
55+ sync::LazyLock,
66+};
77+88+use color_eyre::eyre::Result;
99+use directories::ProjectDirs;
1010+1111+use crate::config::{file::RonConfig, keymap::KeyMap};
1212+1313+mod file;
1414+mod keymap;
1515+1616+/// Project Name: Filaments
1717+pub static PROJECT_NAME: LazyLock<String> =
1818+ LazyLock::new(|| env!("CARGO_CRATE_NAME").to_uppercase());
1919+2020+/// The OS-agnostic data directory for the project.
2121+pub static DATA_DIRECTORY: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
2222+ env::var(format!("{}_DATA", PROJECT_NAME.clone()))
2323+ .ok()
2424+ .map(PathBuf::from)
2525+});
2626+2727+/// The OS-agnostic config directory for the project.
2828+pub static CONFIG_DIRECTORY: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
2929+ env::var(format!("{}_CONFIG", PROJECT_NAME.clone()))
3030+ .ok()
3131+ .map(PathBuf::from)
3232+});
3333+3434+const DEFAULT_CONFIG: &str = include_str!("../../.config/config.ron");
3535+3636+#[derive(Debug, Clone)]
3737+pub struct Config {
3838+ pub fil_dir: PathBuf,
3939+ pub keymap: KeyMap,
4040+}
4141+4242+impl Config {
4343+ /// generates a new config with the provided `filaments_dir`
4444+ pub fn generate(fil_dir: &Path) -> Result<String> {
4545+ let mut default_conf: RonConfig = ron::from_str(DEFAULT_CONFIG)
4646+ .expect("The default config must always be a valid RonConfig");
4747+4848+ default_conf.directory = fil_dir.canonicalize()?;
4949+5050+ Ok(ron::to_string(&default_conf)?)
5151+ }
5252+ /// Parse the config from `~/.config/filaments`, but will prioritize
5353+ /// `FIL_CONFIG_DIR`.
5454+ ///
5555+ /// # Errors
5656+ ///
5757+ /// Will error if the config doesn't exist or if there
5858+ /// is a problem parsing it.
5959+ pub fn parse() -> Result<Self> {
6060+ let ron: RonConfig = {
6161+ let file_path = get_config_dir().join("config.ron");
6262+ ron::from_str(&read_to_string(file_path)?)?
6363+ };
6464+6565+ let keymap = KeyMap::try_from(&ron)?;
6666+6767+ Ok(Self {
6868+ fil_dir: ron.directory.canonicalize()?,
6969+ keymap,
7070+ })
7171+ }
7272+}
7373+7474+/// Returns the path to the OS-agnostic data directory.
7575+pub fn get_data_dir() -> PathBuf {
7676+ DATA_DIRECTORY.clone().unwrap_or_else(|| {
7777+ project_directory().map_or_else(
7878+ || PathBuf::from(".").join(".data"),
7979+ |proj_dirs| proj_dirs.data_local_dir().to_path_buf(),
8080+ )
8181+ })
8282+}
8383+8484+/// Returns the path to the OS-agnostic config directory.
8585+pub fn get_config_dir() -> PathBuf {
8686+ CONFIG_DIRECTORY.clone().unwrap_or_else(|| {
8787+ home_dir().map_or_else(
8888+ || PathBuf::from(".").join(".config"),
8989+ |mut path| {
9090+ path.push(".config");
9191+ path.push("filaments");
9292+ path
9393+ },
9494+ )
9595+ })
9696+}
9797+9898+fn project_directory() -> Option<ProjectDirs> {
9999+ ProjectDirs::from("com", "suri-codes", env!("CARGO_PKG_NAME"))
100100+}
+2-2
src/main.rs
···4141 // create the kasten handle
4242 let kh: KastenHandle = rt.block_on(async {
4343 let cfg = Config::parse()?;
4444- let ws = Workspace::instansiate(cfg.app_config.workspace).await?;
4444+ let ws = Workspace::instansiate(cfg.fil_dir).await?;
4545 Ok::<KastenHandle, color_eyre::Report>(Arc::new(RwLock::new(Kasten::index(ws).await?)))
4646 })?;
47474848- debug!("{kh:#?}");
4848+ debug!("Kasten Handle: {kh:#?}");
49495050 // then we spawn the tui on its own thread
5151 let tui_handle = std::thread::spawn({
···13131414/// The zk component
1515mod zk;
1616+pub use zk::*;
1717+1818+/// The todo component
1919+mod todo;
2020+pub use todo::*;
16211717-pub use zk::*;
2222+/// The main view into the app
2323+mod viewport;
2424+pub use viewport::*;
18251926/// `Component` is a trait that represents a visual and interactive element of the user interface.
2027///
···100100101101 let kt = kh.read().await;
102102103103+ info!("{selected_zettel:#?}");
104104+ info!("{kt:#?}");
105105+103106 let zettel = kt
104107 .get_node_by_zettel_id(selected_zettel)
105105- .expect("")
108108+ .expect("kasten should have the selected zettel")
106109 .payload();
107110108111 let preview = Preview::from(
···315318 async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result<Option<Signal>> {
316319 // NOTE: this is hardcoded for now, but I honestly think people should not
317320 // be able to change these binds, opinionated software or something...
318318- if !(key.code.is_up() || key.code.is_down() || key.code.is_enter()) {
321321+ if !(key.code.is_up() || key.code.is_down() || key.code.is_enter() || key.code.is_tab()) {
319322 self.search.query.input(key);
320323 self.update_with_respect_to_query().await?;
321324 }
···11-use std::{ collections::HashMap,
22- ops::{Deref, DerefMut},
33-};
44-55-use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
66-use kdl::KdlNode;
77-use strum::IntoEnumIterator;
88-99-use crate::tui::{Signal, app::Region};
1010-1111-#[derive(Debug, Clone)]
1212-pub struct KeyMap(pub HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>);
1313-1414-impl TryFrom<&KdlNode> for KeyMap {
1515- type Error = color_eyre::Report;
1616-1717- fn try_from(value: &KdlNode) -> std::result::Result<Self, Self::Error> {
1818- let mut all_binds = HashMap::new();
1919-2020- for region in Region::iter() {
2121- let mut region_binds = HashMap::new();
2222- let Some(binds) = value
2323- .children()
2424- .expect("Keymap must have children.")
2525- .get(®ion.to_string())
2626- else {
2727- continue;
2828- };
2929-3030- // now we iter through the things children
3131- for child in binds.iter_children() {
3232- let key_combo_str = child.name().to_string();
3333- let key_combo_str = key_combo_str.trim();
3434-3535- let signal_str = child
3636- .entries()
3737- .first()
3838- .expect("A bind must map to an entry")
3939- .to_string();
4040- let signal_str = signal_str.trim();
4141-4242- let signal: Signal = signal_str.parse().expect("Must be a \"bindable\" Signal");
4343- let key_combo = parse_key_sequence(key_combo_str).unwrap();
4444-4545- let _ = region_binds.insert(key_combo, signal);
4646- }
4747-4848- let _ = all_binds.insert(region, region_binds);
4949- }
5050-5151- Ok(Self(all_binds))
5252- }
5353-}
5454-5555-impl Deref for KeyMap {
5656- type Target = HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>;
5757-5858- fn deref(&self) -> &Self::Target {
5959- &self.0
6060- }
6161-}
6262-6363-impl DerefMut for KeyMap {
6464- fn deref_mut(&mut self) -> &mut Self::Target {
6565- &mut self.0
6666- }
6767-}
6868-6969-pub fn parse_key_sequence(raw: &str) -> color_eyre::Result<Vec<KeyEvent>, String> {
7070- if raw.chars().filter(|c| *c == '>').count() != raw.chars().filter(|c| *c == '<').count() {
7171- return Err(format!("Unable to parse `{raw}`"));
7272- }
7373- let raw = if raw.contains("><") {
7474- raw
7575- } else {
7676- let raw = raw.strip_prefix('<').unwrap_or(raw);
7777-7878- raw.strip_prefix('>').unwrap_or(raw)
7979- };
8080-8181- raw.split("><")
8282- .map(|seq| {
8383- seq.strip_prefix('<')
8484- .unwrap_or_else(|| seq.strip_suffix('>').map_or(seq, |s| s))
8585- })
8686- .map(parse_key_event)
8787- .collect()
8888-}
8989-9090-fn parse_key_event(raw: &str) -> color_eyre::Result<KeyEvent, String> {
9191- let raw_lower = raw.to_ascii_lowercase();
9292- let (remaining, modifiers) = extract_modifiers(&raw_lower);
9393- parse_key_code_with_modifiers(remaining, modifiers)
9494-}
9595-9696-fn extract_modifiers(raw: &str) -> (&str, KeyModifiers) {
9797- let mut modifiers = KeyModifiers::empty();
9898- let mut current = raw;
9999-100100- loop {
101101- match current {
102102- rest if rest.starts_with("ctrl-") => {
103103- modifiers.insert(KeyModifiers::CONTROL);
104104- current = &rest[5..];
105105- }
106106- rest if rest.starts_with("alt-") => {
107107- modifiers.insert(KeyModifiers::ALT);
108108- current = &rest[4..];
109109- }
110110- rest if rest.starts_with("shift-") => {
111111- modifiers.insert(KeyModifiers::SHIFT);
112112- current = &rest[6..];
113113- }
114114- _ => break, // break out of the loop if no known prefix is detected
115115- }
116116- }
117117-118118- (current, modifiers)
119119-}
120120-121121-fn parse_key_code_with_modifiers(
122122- raw: &str,
123123- mut modifiers: KeyModifiers,
124124-) -> color_eyre::Result<KeyEvent, String> {
125125- let c = match raw {
126126- "esc" => KeyCode::Esc,
127127- "enter" => KeyCode::Enter,
128128- "left" => KeyCode::Left,
129129- "right" => KeyCode::Right,
130130- "up" => KeyCode::Up,
131131- "down" => KeyCode::Down,
132132- "home" => KeyCode::Home,
133133- "end" => KeyCode::End,
134134- "pageup" => KeyCode::PageUp,
135135- "pagedown" => KeyCode::PageDown,
136136- "backtab" => {
137137- modifiers.insert(KeyModifiers::SHIFT);
138138- KeyCode::BackTab
139139- }
140140- "backspace" => KeyCode::Backspace,
141141- "delete" => KeyCode::Delete,
142142- "insert" => KeyCode::Insert,
143143- "f1" => KeyCode::F(1),
144144- "f2" => KeyCode::F(2),
145145- "f3" => KeyCode::F(3),
146146- "f4" => KeyCode::F(4),
147147- "f5" => KeyCode::F(5),
148148- "f6" => KeyCode::F(6),
149149- "f7" => KeyCode::F(7),
150150- "f8" => KeyCode::F(8),
151151- "f9" => KeyCode::F(9),
152152- "f10" => KeyCode::F(10),
153153- "f11" => KeyCode::F(11),
154154- "f12" => KeyCode::F(12),
155155- "space" => KeyCode::Char(' '),
156156- "hyphen" | "minuc" => KeyCode::Char('-'),
157157- "tab" => KeyCode::Tab,
158158- c if c.len() == 1 => {
159159- let mut c = c.chars().next().unwrap();
160160- if modifiers.contains(KeyModifiers::SHIFT) {
161161- c = c.to_ascii_uppercase();
162162- }
163163- KeyCode::Char(c)
164164- }
165165- _ => return Err(format!("Unable to parse {raw}")),
166166- };
167167- Ok(KeyEvent::new(c, modifiers))
168168-}
169169-170170-#[cfg(test)]
171171-mod test {
172172- use crossterm::event::{KeyEvent, KeyModifiers};
173173- use kdl::KdlNode;
174174-175175- use crate::tui::{KeyMap, Signal, app::Region};
176176-177177- #[test]
178178- fn test_quit_in_home_region() {
179179- let keymap_str = "
180180- keymap {
181181- Home {
182182- q Quit
183183- <Ctrl-C> Quit
184184- }
185185- }
186186- ";
187187-188188- let kdl: &KdlNode = &keymap_str
189189- .parse()
190190- .expect("Keymap_str should be a valid KDL document");
191191-192192- let keymap: KeyMap = kdl.try_into().expect("Must be a valid keymap");
193193-194194- let map = keymap
195195- .get(&Region::Home)
196196- .expect("Home region must exist in keymap");
197197-198198- let signal = map
199199- .get(&vec![KeyEvent::new_with_kind(
200200- crossterm::event::KeyCode::Char('q'),
201201- KeyModifiers::empty(),
202202- crossterm::event::KeyEventKind::Press,
203203- )])
204204- .expect("Must resolve to a signal");
205205-206206- assert_eq!(*signal, Signal::Quit);
207207- }
208208-}
+1-4
src/tui/mod.rs
···11/// The tui app
22mod app;
33pub use app::App as TuiApp;
44+pub use app::Region;
4556/// Tui components
67mod components;
···910mod raw_tui;
1011pub use raw_tui::Event;
1112pub use raw_tui::Tui;
1212-1313-/// Keymap for mapping keybinds to regions
1414-mod keymap;
1515-pub use keymap::KeyMap;
16131714/// Singals for commands needing to be processed
1815mod signal;
+6
src/tui/signal.rs
···5566use serde::{Deserialize, Serialize};
7788+use crate::tui::Region;
99+810/// The varying signals that can be emitted.
911#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
1012pub enum Signal {
···1719 ClearScreen,
1820 Error(String),
1921 Help,
2222+2323+ SwitchTo {
2424+ region: Region,
2525+ },
20262127 // movement
2228 MoveDown,
+17-1
src/types/kasten.rs
···99use rayon::iter::{ParallelBridge as _, ParallelIterator as _};
1010use std::{cmp::max, collections::HashMap, path::Path, sync::Arc};
1111use tokio::sync::RwLock;
1212+use tracing::{debug, error};
12131314use crate::types::Workspace;
1415···5657 .map(|entry| entry.path())
5758 .collect::<Vec<_>>();
58596060+ debug!(
6161+ "indexing the following paths {paths:#?} at root {:#?}",
6262+ ws.root
6363+ );
6464+5965 let zettel_tasks = paths
6066 .into_iter()
6167 .map(|path| {
···6874 let zettels = futures::future::join_all(zettel_tasks)
6975 .await
7076 .into_iter()
7171- .filter_map(|result| result.ok()?.ok())
7777+ .filter_map(|result| {
7878+ result
7979+ .inspect_err(|e| error!("Failed to join on zettel task parsing: {e:#?}"))
8080+ .ok()?
8181+ .inspect_err(|e| error!("Failed to parse file into zettel: {e:#?}"))
8282+ .ok()
8383+ })
7284 .collect::<Vec<Zettel>>();
8585+8686+ debug!("parsed zettels: {zettels:#?}");
73877488 // capacity!
7589 let mut graph: ZkGraph = ZkGraph::from(&StableGraph::with_capacity(
···96110 graph.add_edge(*src, *dst, link.clone());
97111 }
98112 }
113113+114114+ debug!("parsed graph: {graph:#?}");
99115100116 Ok(Self {
101117 _private: (),