My personal-knowledge-system, with deeply integrated task tracking and long term goal planning capabilities.
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: config update for todo

+226 -105
+36 -15
.config/config.ron
··· 1 1 ( 2 2 directory: "/Users/suri/dev/projects/filaments/ZettelKasten", 3 3 global_key_binds: { 4 - "up": MoveUp, 4 + "up": MoveUp, 5 + "down": MoveDown, 5 6 "ctrl-z": Suspend, 6 7 "ctrl-c": Quit, 7 - "down": MoveDown, 8 8 }, 9 9 zk: ( 10 10 keybinds: { 11 - "tab": SwitchTo( 12 - region: Todo, 13 - ), 14 - "<Ctrl-n>": NewZettel, 15 - "enter": OpenZettel, 11 + "tab": SwitchTo(page: Todo(Explorer)), 12 + "ctrl-n": NewZettel, 13 + "enter": OpenZettel, 16 14 }, 17 15 ), 18 16 todo: ( 19 - keybinds: { 20 - "j": MoveDown, 21 - "tab": SwitchTo( 22 - region: Zk, 23 - ), 24 - "k": MoveUp, 25 - }, 17 + explorer: ( 18 + keybinds: { 19 + "tab": SwitchTo(page: Zk), 20 + "1": SwitchTo(page: Todo(Explorer)), 21 + "2": SwitchTo(page: Todo(Inspector)), 22 + "3": SwitchTo(page: Todo(TaskList)), 23 + "j": MoveDown, 24 + "k": MoveUp, 25 + }, 26 + ), 27 + inspector: ( 28 + keybinds: { 29 + "tab": SwitchTo(page: Zk), 30 + "1": SwitchTo(page: Todo(Explorer)), 31 + "2": SwitchTo(page: Todo(Inspector)), 32 + "3": SwitchTo(page: Todo(TaskList)), 33 + "j": MoveDown, 34 + "k": MoveUp, 35 + }, 36 + ), 37 + tasklist: ( 38 + keybinds: { 39 + "tab": SwitchTo(page: Zk), 40 + "1": SwitchTo(page: Todo(Explorer)), 41 + "2": SwitchTo(page: Todo(Inspector)), 42 + "3": SwitchTo(page: Todo(TaskList)), 43 + "j": MoveDown, 44 + "k": MoveUp, 45 + }, 46 + ), 26 47 ), 27 - ) 48 + )
+2 -2
.config/default_config.ron
··· 10 10 keybinds: { 11 11 "enter": OpenZettel, 12 12 "tab": SwitchTo( 13 - region: Todo, 13 + page: Todo(Explorer), 14 14 ), 15 15 "<Ctrl-n>": NewZettel, 16 16 }, ··· 20 20 "k": MoveUp, 21 21 "j": MoveDown, 22 22 "tab": SwitchTo( 23 - region: Zk, 23 + page: Zk, 24 24 ), 25 25 }, 26 26 ),
+33 -4
src/config/file.rs
··· 19 19 20 20 #[derive(Debug, Deserialize, Serialize)] 21 21 pub struct TodoConfig { 22 + pub explorer: ExplorerConfig, 23 + pub inspector: InspectorConfig, 24 + pub tasklist: TaskListConfig, 25 + } 26 + 27 + #[derive(Debug, Deserialize, Serialize)] 28 + pub struct ExplorerConfig { 29 + pub keybinds: HashMap<String, Signal>, 30 + } 31 + #[derive(Debug, Deserialize, Serialize)] 32 + pub struct InspectorConfig { 33 + pub keybinds: HashMap<String, Signal>, 34 + } 35 + #[derive(Debug, Deserialize, Serialize)] 36 + pub struct TaskListConfig { 22 37 pub keybinds: HashMap<String, Signal>, 23 38 } 24 39 ··· 40 55 ]), 41 56 }, 42 57 todo: TodoConfig { 43 - keybinds: HashMap::from([ 44 - ("<Space>".to_string(), Signal::NewZettel), 45 - ("<Esc>".to_string(), Signal::MoveUp), 46 - ]), 58 + explorer: ExplorerConfig { 59 + keybinds: HashMap::from([ 60 + ("<Space>".to_string(), Signal::NewZettel), 61 + ("<Esc>".to_string(), Signal::MoveUp), 62 + ]), 63 + }, 64 + inspector: InspectorConfig { 65 + keybinds: HashMap::from([ 66 + ("<Space>".to_string(), Signal::NewZettel), 67 + ("<Esc>".to_string(), Signal::MoveUp), 68 + ]), 69 + }, 70 + tasklist: TaskListConfig { 71 + keybinds: HashMap::from([ 72 + ("<Space>".to_string(), Signal::NewZettel), 73 + ("<Esc>".to_string(), Signal::MoveUp), 74 + ]), 75 + }, 47 76 }, 48 77 }; 49 78
+31 -28
src/config/keymap.rs
··· 10 10 11 11 use crate::{ 12 12 config::file::RonConfig, 13 - tui::{Region, Signal}, 13 + tui::{Page, Signal, TodoRegion}, 14 14 }; 15 + 15 16 #[derive(Debug, Clone)] 16 - pub struct KeyMap(pub HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>); 17 + pub struct KeyMap(pub HashMap<Page, HashMap<Vec<KeyEvent>, Signal>>); 17 18 18 19 impl TryFrom<&RonConfig> for KeyMap { 19 20 type Error = color_eyre::Report; ··· 21 22 fn try_from(value: &RonConfig) -> Result<Self, Self::Error> { 22 23 let mut binds = HashMap::new(); 23 24 24 - for region in Region::iter() { 25 - let mut region_binds = HashMap::new(); 25 + let all_pages = std::iter::once(Page::Zk).chain(TodoRegion::iter().map(Page::Todo)); 26 26 27 - let mut parse_and_insert = |str: &str, bind: &Signal| -> Result<()> { 28 - let key_seq = parse_key_sequence(str).map_err(|e| { 29 - eyre!(format!( 30 - "Failed to parse the following keybind as valid keybind: {e}" 31 - )) 32 - })?; 33 - 34 - region_binds.insert(key_seq, bind.clone()); 35 - Ok(()) 36 - }; 37 - 38 - // first thing we have to do is insert the global binds for this region 27 + for page in all_pages { 28 + let mut page_binds = HashMap::new(); 39 29 40 30 for (str, bind) in &value.global_key_binds { 41 - parse_and_insert(str, bind)?; 31 + parse_and_insert(str, bind, &mut page_binds)?; 42 32 } 43 33 44 - // now we insert the region specific binds 45 - for (str, bind) in match region { 46 - Region::Zk => value.zk.keybinds.iter(), 47 - Region::Todo => value.todo.keybinds.iter(), 48 - } { 49 - parse_and_insert(str, bind)?; 34 + let page_specific: &HashMap<String, Signal> = match &page { 35 + Page::Zk => &value.zk.keybinds, 36 + Page::Todo(TodoRegion::Inspector) => &value.todo.inspector.keybinds, 37 + Page::Todo(TodoRegion::Explorer) => &value.todo.explorer.keybinds, 38 + Page::Todo(TodoRegion::TaskList) => &value.todo.tasklist.keybinds, 39 + }; 40 + 41 + for (str, bind) in page_specific { 42 + parse_and_insert(str, bind, &mut page_binds)?; 50 43 } 51 44 52 - binds.insert(region, region_binds); 45 + binds.insert(page, page_binds); 53 46 } 54 47 55 48 Ok(Self(binds)) 56 49 } 57 50 } 51 + fn parse_and_insert( 52 + str: &str, 53 + bind: &Signal, 54 + page_binds: &mut HashMap<Vec<KeyEvent>, Signal>, 55 + ) -> Result<()> { 56 + let key_seq = parse_key_sequence(str) 57 + .map_err(|e| eyre!("Failed to parse the following keybind as valid keybind: {e}"))?; 58 + page_binds.insert(key_seq, bind.clone()); 59 + Ok(()) 60 + } 58 61 59 62 impl Deref for KeyMap { 60 - type Target = HashMap<Region, HashMap<Vec<KeyEvent>, Signal>>; 63 + type Target = HashMap<Page, HashMap<Vec<KeyEvent>, Signal>>; 61 64 62 65 fn deref(&self) -> &Self::Target { 63 66 &self.0 ··· 182 185 183 186 use crate::{ 184 187 config::{file::RonConfig, keymap::KeyMap}, 185 - tui::{Region, Signal}, 188 + tui::{Page, Signal, TodoRegion}, 186 189 }; 187 190 188 191 #[test] ··· 223 226 let keymap: KeyMap = (&config).try_into().unwrap(); 224 227 225 228 let map = keymap 226 - .get(&Region::Todo) 229 + .get(&Page::Todo(TodoRegion::Inspector)) 227 230 .expect("Home region must exist in keymap"); 228 231 229 232 let signal = map ··· 237 240 assert_eq!(*signal, Signal::Quit); 238 241 239 242 let map = keymap 240 - .get(&Region::Zk) 243 + .get(&Page::Zk) 241 244 .expect("Home region must exist in keymap"); 242 245 243 246 let signal = map
+3
src/main.rs
··· 41 41 // create the kasten handle 42 42 let kh: KastenHandle = rt.block_on(async { 43 43 let cfg = Config::parse()?; 44 + 45 + debug!("Config: {cfg:#?}"); 46 + 44 47 Ok::<KastenHandle, color_eyre::Report>(Arc::new(RwLock::new( 45 48 Kasten::instansiate(cfg.fil_dir).await?, 46 49 )))
+17 -11
src/tui/app.rs
··· 6 6 use serde::{Deserialize, Serialize}; 7 7 use strum::{Display, EnumIter}; 8 8 use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; 9 - use tracing::{debug, trace}; 9 + use tracing::{debug, info, trace}; 10 10 11 11 use crate::{ 12 12 config::Config, 13 13 tui::{Event, Tui, components::Viewport}, 14 14 types::{KastenHandle, ZettelId}, 15 15 }; 16 + 17 + pub use crate::tui::components::TodoRegion; 16 18 17 19 use super::{components::Component, signal::Signal}; 18 20 ··· 24 26 should_quit: bool, 25 27 should_suspend: bool, 26 28 #[allow(dead_code)] 27 - region: Region, 29 + page: Page, 28 30 last_tick_key_events: Vec<KeyEvent>, 29 31 kh: KastenHandle, 30 32 signal_tx: UnboundedSender<Signal>, ··· 38 40 #[derive( 39 41 Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, Display, 40 42 )] 41 - pub enum Region { 43 + pub enum Page { 44 + #[default] 42 45 Zk, 43 - #[default] 44 - Todo, 46 + Todo(TodoRegion), 45 47 } 46 48 47 49 impl App { ··· 62 64 should_quit: false, 63 65 should_suspend: false, 64 66 config: Config::parse()?, 65 - region: Region::default(), 67 + page: Page::default(), 66 68 last_tick_key_events: Vec::new(), 67 69 kh, 68 70 signal_tx, ··· 148 150 149 151 let signal_tx = self.signal_tx.clone(); 150 152 151 - let Some(region_keymap) = self.config.keymap.get(&self.region) else { 153 + let Some(page_keymap) = self.config.keymap.get(&self.page) else { 152 154 return Ok(()); 153 155 }; 154 156 155 - if let Some(signal) = region_keymap.get(&vec![key]) { 157 + info!("page: {:#?}, page_keymap: {page_keymap:#?}", self.page); 158 + 159 + if let Some(signal) = page_keymap.get(&vec![key]) { 156 160 signal_tx.send(signal.clone())?; 157 161 } else { 158 162 self.last_tick_key_events.push(key); 159 - if let Some(signal) = region_keymap.get(&self.last_tick_key_events) { 163 + if let Some(signal) = page_keymap.get(&self.last_tick_key_events) { 160 164 debug!("Got signal: {signal:?}"); 161 165 signal_tx.send(signal.clone())?; 162 166 } ··· 167 171 168 172 async fn handle_signals(&mut self, tui: &mut Tui) -> Result<()> { 169 173 while let Ok(signal) = self.signal_rx.try_recv() { 174 + // debug!("handling signal: {signal:?}"); 170 175 if signal != Signal::Tick && signal != Signal::Render { 171 176 debug!("handling signal: {signal:?}"); 172 177 // we dont care if the receiver is dropped, its fine ··· 226 231 tui.enter()?; 227 232 } 228 233 229 - Signal::SwitchTo { region } => { 230 - self.region = region; 234 + Signal::SwitchTo { page } => { 235 + info!("Switched page to {page:#?}"); 236 + self.page = page; 231 237 } 232 238 233 239 Signal::Suspend => self.should_suspend = true,
-1
src/tui/components/todo/explorer.rs
··· 51 51 } 52 52 } 53 53 54 - #[expect(dead_code)] 55 54 pub fn set_active(&mut self) { 56 55 self.render_list = self.render_list.clone().block( 57 56 Block::new()
+31 -8
src/tui/components/todo/inspector/mod.rs
··· 18 18 pub struct Inspector<'text> { 19 19 render_data: RenderData<'text>, 20 20 margins: Layout, 21 + block: Block<'text>, 22 + } 23 + 24 + impl Inspector<'_> { 25 + pub fn set_active(&mut self) { 26 + self.block = Block::new() 27 + .title("[2]") 28 + .title("Inspector") 29 + .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM) 30 + .border_style(Style::new().fg(Color::Green)) 31 + .border_type(BorderType::Rounded); 32 + } 33 + 34 + pub fn set_inactive(&mut self) { 35 + self.block = Block::new() 36 + .title("[2]") 37 + .title("Inspector") 38 + .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM) 39 + .border_style(Style::new().fg(Color::Gray)) 40 + .border_type(BorderType::Rounded); 41 + } 21 42 } 22 43 23 44 enum RenderData<'text> { ··· 32 53 .horizontal_margin(3) 33 54 .vertical_margin(2); 34 55 56 + let block = Block::new() 57 + .title("[2]") 58 + .title("Inspector") 59 + .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM) 60 + .border_style(Style::new().fg(Color::Gray)) 61 + .border_type(BorderType::Rounded); 35 62 match value.kind { 36 63 TodoNodeKind::Root => Self { 37 64 render_data: RenderData::Root { 38 65 widget: Box::new(RootView::default()), 39 66 }, 40 67 margins, 68 + block, 41 69 }, 42 70 TodoNodeKind::Group(ref group) => Self { 43 71 render_data: RenderData::Group { 44 72 widget: Box::new(GroupView::from(&**group)), 45 73 }, 46 74 margins, 75 + block, 47 76 }, 48 77 TodoNodeKind::Task(ref task) => Self { 49 78 render_data: RenderData::Task { 50 79 widget: Box::new(TaskView::from(&**task)), 51 80 }, 52 81 margins, 82 + block, 53 83 }, 54 84 } 55 85 } ··· 60 90 where 61 91 Self: Sized, 62 92 { 63 - let block = Block::new() 64 - .title("[3]") 65 - .title("Inspector") 66 - .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM) 67 - .border_style(Style::new().fg(Color::Gray)) 68 - .border_type(BorderType::Rounded); 69 - 70 - block.render(area, buf); 93 + self.block.clone().render(area, buf); 71 94 72 95 let area = self.margins.split(area)[0]; 73 96
+40 -4
src/tui/components/todo/mod.rs
··· 2 2 use ratatui::{ 3 3 Frame, 4 4 layout::{Constraint, Layout, Rect, Size}, 5 - style::{Color, Stylize}, 6 - widgets::{Block, ListState}, 5 + widgets::ListState, 7 6 }; 7 + use serde::{Deserialize, Serialize}; 8 + use strum::{Display, EnumIter}; 8 9 use tokio::sync::mpsc::UnboundedSender; 9 10 10 11 use crate::{ 11 - tui::{Signal, components::Component}, 12 + tui::{Page, Signal, components::Component}, 12 13 types::KastenHandle, 13 14 }; 14 15 ··· 29 30 explorer: Option<Explorer<'text>>, 30 31 task_list: Option<TaskList<'text>>, 31 32 inspector: Option<Inspector<'text>>, 33 + } 34 + 35 + /// The different regions inside the `Todo` component 36 + #[derive( 37 + Default, Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, EnumIter, Display, 38 + )] 39 + pub enum TodoRegion { 40 + Inspector, 41 + TaskList, 42 + #[default] 43 + Explorer, 32 44 } 33 45 34 46 impl Todo<'_> { ··· 121 133 } 122 134 123 135 async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> { 124 - let _explorer = self 136 + 137 + let explorer = self 125 138 .explorer 126 139 .as_mut() 127 140 .expect("This should have already been initialized"); 128 141 129 142 let task_list = self 130 143 .task_list 144 + .as_mut() 145 + .expect("This should have already been initialized"); 146 + let inspector = self 147 + .inspector 131 148 .as_mut() 132 149 .expect("This should have already been initialized"); 133 150 134 151 match signal { 152 + Signal::SwitchTo { 153 + page: Page::Todo(region), 154 + } => match region { 155 + TodoRegion::Inspector => { 156 + inspector.set_active(); 157 + explorer.set_inactive(); 158 + task_list.set_inactive(); 159 + } 160 + TodoRegion::TaskList => { 161 + inspector.set_inactive(); 162 + explorer.set_inactive(); 163 + task_list.set_active(); 164 + } 165 + TodoRegion::Explorer => { 166 + explorer.set_active(); 167 + task_list.set_inactive(); 168 + inspector.set_inactive(); 169 + } 170 + }, 135 171 Signal::MoveDown => { 136 172 // explorer.state.select_next(); 137 173 task_list.state.select_next();
+4 -4
src/tui/components/todo/tasklist.rs
··· 56 56 pub fn set_active(&mut self) { 57 57 self.render_list = self.render_list.clone().block( 58 58 Block::new() 59 - .title("[2]") 60 - .title("TodoList") 59 + .title("[3]") 60 + .title("TaskList") 61 61 .borders(Borders::TOP | Borders::LEFT) 62 62 .border_style(Style::new().fg(Color::Green)) 63 63 .border_type(BorderType::Rounded), ··· 67 67 pub fn set_inactive(&mut self) { 68 68 self.render_list = self.render_list.clone().block( 69 69 Block::new() 70 - .title("[2]") 71 - .title("TodoList") 70 + .title("[3]") 71 + .title("TaskList") 72 72 .borders(Borders::TOP | Borders::LEFT) 73 73 .border_style(Style::new().fg(Color::Gray)) 74 74 .border_type(BorderType::Rounded),
+21 -21
src/tui/components/viewport/mod.rs
··· 11 11 use crate::{ 12 12 tui::{ 13 13 Signal, 14 - app::Region, 14 + app::Page, 15 15 components::{Component, Todo, Zk}, 16 16 }, 17 17 types::KastenHandle, ··· 23 23 kh: KastenHandle, 24 24 _layouts: Layouts, 25 25 switcher: Switcher<'text>, 26 - active_region: Region, 26 + active_page: Page, 27 27 zk: Zk<'text>, 28 28 todo: Todo<'text>, 29 29 } ··· 34 34 impl Viewport<'_> { 35 35 pub async fn new(kh: KastenHandle) -> Result<Self> { 36 36 let mut switcher = Switcher::default(); 37 - switcher.select_region(Region::default()); 37 + switcher.select_region(Page::default()); 38 38 39 39 Ok(Self { 40 40 signal_tx: None, ··· 42 42 switcher, 43 43 zk: Zk::new(kh.clone()).await?, 44 44 todo: Todo::new(kh.clone()), 45 - active_region: Region::default(), 45 + active_page: Page::default(), 46 46 kh, 47 47 }) 48 48 } ··· 63 63 #[async_trait] 64 64 impl Component for Viewport<'_> { 65 65 async fn init(&mut self, area: Size) -> color_eyre::Result<()> { 66 - match self.active_region { 67 - Region::Zk => self.zk.init(area).await, 68 - Region::Todo => self.todo.init(area).await, 69 - } 66 + self.zk.init(area).await?; 67 + self.todo.init(area).await?; 68 + Ok(()) 70 69 } 71 70 72 71 fn register_signal_handler(&mut self, tx: UnboundedSender<Signal>) -> Result<()> { ··· 78 77 79 78 async fn update(&mut self, signal: Signal) -> color_eyre::Result<Option<Signal>> { 80 79 // switch active region 81 - if let Signal::SwitchTo { region } = signal { 82 - self.active_region = region; 83 - self.switcher.select_region(region); 84 - debug!("active region switched to : {region}"); 80 + if let Signal::SwitchTo { page } = signal { 81 + self.active_page = page; 82 + self.switcher.select_region(page); 83 + debug!("active page switched to : {page}"); 85 84 } 86 85 87 - match self.active_region { 88 - Region::Zk => self.zk.update(signal).await, 89 - Region::Todo => self.todo.update(signal).await, 86 + match self.active_page { 87 + Page::Zk => self.zk.update(signal).await, 88 + Page::Todo(_) => self.todo.update(signal).await, 90 89 } 91 90 } 92 91 93 92 async fn handle_key_event(&mut self, key: KeyEvent) -> color_eyre::Result<Option<Signal>> { 94 - match self.active_region { 95 - Region::Zk => self.zk.handle_key_event(key).await, 96 - Region::Todo => self.todo.handle_key_event(key).await, 93 + match self.active_page { 94 + Page::Zk => self.zk.handle_key_event(key).await, 95 + Page::Todo(_) => self.todo.handle_key_event(key).await, 97 96 } 98 97 } 99 98 ··· 103 102 // let rects = self.layouts.main_switcher.split(area); 104 103 // (rects[0], rects[1]) 105 104 // }; 105 + // 106 106 107 - match self.active_region { 108 - Region::Zk => self.zk.draw(frame, area), 109 - Region::Todo => self.todo.draw(frame, area), 107 + match self.active_page { 108 + Page::Zk => self.zk.draw(frame, area), 109 + Page::Todo(_) => self.todo.draw(frame, area), 110 110 }?; 111 111 112 112 Ok(())
+4 -4
src/tui/components/viewport/switcher.rs
··· 6 6 }; 7 7 use strum::IntoEnumIterator; 8 8 9 - use crate::tui::app::Region; 9 + use crate::tui::app::Page; 10 10 11 11 #[derive(Debug, Clone)] 12 12 pub struct Switcher<'text> { ··· 15 15 } 16 16 17 17 impl Switcher<'_> { 18 - pub fn select_region(&mut self, region: Region) { 19 - self.line = Region::iter() 18 + pub fn select_region(&mut self, region: Page) { 19 + self.line = Page::iter() 20 20 .map(|r| { 21 21 Span::from(format!(" {r} ")).style({ 22 22 if r == region { ··· 45 45 46 46 impl Default for Switcher<'_> { 47 47 fn default() -> Self { 48 - let line = Region::iter() 48 + let line = Page::iter() 49 49 .map(|r| Span::from(format!(" {r} ")).style(Style::default().bg(Color::DarkGray))) 50 50 .collect::<Line>(); 51 51
+2 -1
src/tui/mod.rs
··· 1 1 /// The tui app 2 2 mod app; 3 3 pub use app::App as TuiApp; 4 - pub use app::Region; 4 + pub use app::Page; 5 + pub use app::TodoRegion; 5 6 6 7 /// Tui components 7 8 mod components;
+2 -2
src/tui/signal.rs
··· 6 6 use serde::{Deserialize, Serialize}; 7 7 8 8 use crate::{ 9 - tui::Region, 9 + tui::Page, 10 10 types::{Link, ZettelId}, 11 11 }; 12 12 ··· 24 24 Help, 25 25 26 26 SwitchTo { 27 - region: Region, 27 + page: Page, 28 28 }, 29 29 30 30 // movement