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: Deimos watcher enables link creations in visualizer

+125 -55
+6 -1
src/gui/mod.rs src/viz/mod.rs
··· 73 73 ) 74 74 .with_navigations( 75 75 &SettingsNavigation::new().with_fit_to_screen_padding(0.2), // .with_zoom_and_pan_enabled(true) 76 - // .with_fit_to_screen_enabled(false), 76 + // .with_fit_to_screen_enabled(false) 77 + // , 77 78 ); 78 79 79 80 ui.add(&mut view); ··· 97 98 let index = &self.kh.read().await.index; 98 99 self.filaments.insert_zettel(zid, index); 99 100 }); 101 + } 102 + 103 + Signal::SetLinks { zid, links } => { 104 + self.filaments.set_links_for_zid(&zid, links); 100 105 } 101 106 102 107 _ => {}
+13 -12
src/main.rs
··· 7 7 use crate::{ 8 8 cli::Cli, 9 9 config::Config, 10 - gui::FilViz, 11 10 tui::TuiApp, 12 - types::{Kasten, KastenHandle}, 11 + types::{Deimos, Kasten, KastenHandle}, 12 + viz::FilViz, 13 13 }; 14 14 use clap::Parser; 15 15 use tokio::sync::{RwLock, mpsc}; ··· 18 18 mod cli; 19 19 mod config; 20 20 mod errors; 21 - mod gui; 22 21 mod logging; 23 22 mod lsp; 24 23 mod tui; 25 24 mod types; 25 + mod viz; 26 26 27 27 fn main() -> color_eyre::Result<()> { 28 28 errors::init()?; ··· 55 55 // arc stuff 56 56 let tui_rt = rt.clone(); 57 57 let kh = kh.clone(); 58 + let signal_tx = signal_tx.clone(); 58 59 59 60 // closure to run the tui 60 61 move || -> color_eyre::Result<()> { ··· 68 69 } 69 70 }); 70 71 71 - // spawn deimos 72 - // { 73 - 74 - // rt.spawn(async { 75 - // let deimos = Deimos::new(kh, fh); 76 - // deimos.watch().await 77 - // }); 78 - // } 79 - 80 72 // if they asked for the visualizer, we give them the visualizer 81 73 if args.visualizer { 82 74 // enter the guard so egui_async works properly 83 75 let _rt_guard = rt.enter(); 76 + 77 + // spawn deimos 78 + { 79 + let kh = kh.clone(); 80 + rt.spawn(async { 81 + let deimos = Deimos::new(kh, signal_tx); 82 + deimos.watch().await 83 + }); 84 + } 84 85 85 86 let index = rt.block_on(async { kh.read().await.index.clone() }); 86 87
+11 -1
src/tui/signal.rs
··· 5 5 6 6 use serde::{Deserialize, Serialize}; 7 7 8 - use crate::{tui::Region, types::ZettelId}; 8 + use crate::{ 9 + tui::Region, 10 + types::{Link, ZettelId}, 11 + }; 9 12 10 13 /// The varying signals that can be emitted. 11 14 #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] ··· 31 34 /// Create a New `Zettel` 32 35 NewZettel, 33 36 37 + /// This zettel was created (filaments specific) 34 38 CreatedZettel { 35 39 zid: ZettelId, 40 + }, 41 + 42 + /// Set the links for this `ZettelId` (filaments specific) 43 + SetLinks { 44 + zid: ZettelId, 45 + links: Vec<Link>, 36 46 }, 37 47 38 48 /// User asks to open a `Zettel`
+52 -37
src/types/deimos.rs
··· 1 - // use color_eyre::eyre::Context; 2 - // use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Result, Watcher as _}; 3 - // use tracing::{error, info}; 1 + use color_eyre::eyre::Context; 2 + use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Result, Watcher as _}; 3 + use tracing::{error, info}; 4 4 5 - use crate::types::KastenHandle; 5 + use tokio::sync::mpsc::UnboundedSender; 6 + 7 + use crate::{ 8 + tui::Signal, 9 + types::{KastenHandle, ZettelId}, 10 + }; 6 11 7 12 #[derive(Debug)] 8 - #[expect(dead_code)] 9 13 pub struct Deimos { 10 14 kh: KastenHandle, 11 - // fh: FilamentsHandle, 15 + signal_tx: UnboundedSender<Signal>, 12 16 } 13 17 14 - // impl Deimos { 15 - // pub const fn new(kh: KastenHandle, fh: FilamentsHandle) -> Self { 16 - // Self { kh, fh } 17 - // } 18 + impl Deimos { 19 + pub const fn new(kh: KastenHandle, signal_tx: UnboundedSender<Signal>) -> Self { 20 + Self { kh, signal_tx } 21 + } 22 + 23 + /// Watches the `Filaments Directory` for file changes and updates 24 + /// the internal state of the kasten and the `Filaments` displayed in the 25 + /// gui. 26 + /// 27 + /// NOTE: This function must be spawned as a top level await 28 + pub async fn watch(&self) -> color_eyre::Result<()> { 29 + info!("deimos spawned!"); 18 30 19 - // /// Watches the `Filaments Directory` for file changes and updates 20 - // /// the internal state of the kasten and the `Filaments` displayed in the 21 - // /// gui. 22 - // /// 23 - // /// NOTE: This function must be spawned as a top level await 24 - // pub async fn watch(&self) -> color_eyre::Result<()> { 25 - // info!("deimos spawned!"); 31 + let (tx, mut rx) = tokio::sync::mpsc::channel::<Result<Event>>(10); 32 + let mut watcher = RecommendedWatcher::new( 33 + move |res| tx.blocking_send(res).expect("failed to send event"), 34 + Config::default(), 35 + )?; 36 + 37 + watcher 38 + .watch(&self.kh.read().await.root, RecursiveMode::Recursive) 39 + .with_context(|| "failed to start the FS watcher")?; 26 40 27 - // let (tx, mut rx) = tokio::sync::mpsc::channel::<Result<Event>>(10); 28 - // let mut watcher = RecommendedWatcher::new( 29 - // move |res| tx.blocking_send(res).expect("failed to send event"), 30 - // Config::default(), 31 - // )?; 41 + while let Some(res) = rx.recv().await { 42 + let Ok(event) = res.inspect_err(|e| error!("watcher error: {e:?}")) else { 43 + continue; 44 + }; 32 45 33 - // watcher 34 - // .watch(&self.kh.read().await.root, RecursiveMode::Recursive) 35 - // .with_context(|| "failed to start the FS watcher")?; 46 + if let EventKind::Modify(notify::event::ModifyKind::Data(_)) = event.kind { 47 + for path in event.paths { 48 + let Ok(zid) = ZettelId::try_from(path.clone()) 49 + .inspect_err(|e| error!("Failed to convert path into zettel id! : {e}")) 50 + else { 51 + continue; 52 + }; 36 53 37 - // while let Some(res) = rx.recv().await { 38 - // let kt = &mut *self.kh.write().await; 39 - // let fh = &mut *self.fh.lock().expect("Lock must not be poisoned"); 54 + let kt = &mut *self.kh.write().await; 55 + kt.process_path(path).await?; 40 56 41 - // match res { 42 - // Ok(event) => info!("event: {event:?}"), 43 - // Err(e) => error!("watch error: {e:?}"), 44 - // } 57 + let links = kt.index.get_links(&zid).clone(); 45 58 46 - // *fh = (&kt.index).into(); 47 - // } 59 + self.signal_tx.send(Signal::SetLinks { zid, links })?; 60 + } 61 + } 62 + } 48 63 49 - // Ok(()) 50 - // } 51 - // } 64 + Ok(()) 65 + } 66 + }
+28 -1
src/types/filaments.rs
··· 23 23 } 24 24 25 25 impl Filaments { 26 + pub fn get_gid(&self, zid: &ZettelId) -> NodeIndex { 27 + *self 28 + .zid_to_gid 29 + .get(zid) 30 + .expect("Invariant broken, any zid we ask for must have a corresponding gid") 31 + } 32 + 33 + /// Inserts a `Zettel` into the graph. 26 34 pub fn insert_zettel(&mut self, zid: ZettelId, index: &Index) { 27 35 let zod = index.get_zod(&zid); 28 36 ··· 36 44 fn custom_node_closure(zod: &ZettelOnDisk, node: &mut Node<ZettelId, Link>) { 37 45 node.set_label(zod.fm.title.clone()); 38 46 let disp = node.display_mut(); 39 - disp.radius = 50.0; 47 + disp.radius = 75.0; 40 48 41 49 // randomize position 42 50 let x = rand::random_range(0.0..=100.0); 43 51 let y = rand::random_range(0.0..=100.0); 44 52 node.set_location(emath::Pos2 { x, y }); 45 53 node.set_hovered(true); 54 + } 55 + 56 + /// Sets the `Links` for the given `ZettelId` 57 + pub fn set_links_for_zid(&mut self, zid: &ZettelId, links: Vec<Link>) { 58 + let gid = self.get_gid(zid); 59 + self.graph 60 + .g() 61 + .edges(gid) 62 + .map(|e| e.weight().id()) 63 + .collect::<Vec<_>>() 64 + .iter() 65 + .for_each(|edge_index| { 66 + self.graph.remove_edge(*edge_index); 67 + }); 68 + 69 + for link in links { 70 + let dest = self.get_gid(&link.dest); 71 + self.graph.add_edge(gid, dest, link); 72 + } 46 73 } 47 74 } 48 75
+14 -2
src/types/index.rs
··· 53 53 let zid: ZettelId = path.as_path().try_into()?; 54 54 let (fm, body) = FrontMatter::extract_from_file(&path)?; 55 55 56 - let outgoing_links = Self::parse_outgoing_links(&zid, &body); 56 + let outgoing_links = Self::_parse_outgoing_links(&zid, &body); 57 57 58 58 Ok(( 59 59 zid, ··· 94 94 }) 95 95 } 96 96 97 - pub fn parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec<Link> { 97 + pub fn parse_outgoing_links(&mut self, zid: &ZettelId) { 98 + let body = self.get_zod(zid).body.as_str(); 99 + let links = Self::_parse_outgoing_links(zid, body); 100 + self.outgoing_links.insert(zid.clone(), links); 101 + } 102 + 103 + fn _parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec<Link> { 98 104 let parser = Parser::new_ext(body, Options::ENABLE_WIKILINKS); 99 105 100 106 // let mut links = vec![]; ··· 230 236 231 237 fn get_zod_mut(&mut self, zid: &ZettelId) -> &mut ZettelOnDisk { 232 238 self.zods.get_mut(zid).expect("Invariant broken. Any zid we lookup must exist in the index, otherwise the db is corrupt or not sync'd.") 239 + } 240 + 241 + pub fn get_links(&self, zid: &ZettelId) -> &Vec<Link> { 242 + self.outgoing_links 243 + .get(zid) 244 + .expect("Invariant broken. Any zid we look up exist inside this map") 233 245 } 234 246 235 247 pub const fn zods(&self) -> &HashMap<ZettelId, ZettelOnDisk> {
+1
src/types/kasten.rs
··· 112 112 // and then we sync tags 113 113 self.index.sync_tags_with_db(&zid, &self.db).await?; 114 114 self.index.sync_zettel_title_with_db(&zid, &self.db).await?; 115 + self.index.parse_outgoing_links(&zid); 115 116 116 117 Ok(()) 117 118 }
-1
src/types/mod.rs
··· 35 35 pub use frontmatter::FrontMatter; 36 36 37 37 mod deimos; 38 - #[expect(unused_imports)] 39 38 pub use deimos::Deimos;