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.

Merge pull request #14 from suri-codes/deimos

Deimos

authored by

Surendra Jammishetti and committed by
GitHub
5734e987 8aaf1a82

+333 -35
+77
Cargo.lock
··· 2326 2326 "egui_graphs", 2327 2327 "futures", 2328 2328 "human-panic", 2329 + "notify", 2329 2330 "nucleo-matcher", 2330 2331 "pulldown-cmark", 2331 2332 "rand 0.10.0", ··· 2475 2476 checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" 2476 2477 dependencies = [ 2477 2478 "percent-encoding", 2479 + ] 2480 + 2481 + [[package]] 2482 + name = "fsevent-sys" 2483 + version = "4.1.0" 2484 + source = "registry+https://github.com/rust-lang/crates.io-index" 2485 + checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" 2486 + dependencies = [ 2487 + "libc", 2478 2488 ] 2479 2489 2480 2490 [[package]] ··· 3924 3934 ] 3925 3935 3926 3936 [[package]] 3937 + name = "inotify" 3938 + version = "0.11.1" 3939 + source = "registry+https://github.com/rust-lang/crates.io-index" 3940 + checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" 3941 + dependencies = [ 3942 + "bitflags 2.11.0", 3943 + "inotify-sys", 3944 + "libc", 3945 + ] 3946 + 3947 + [[package]] 3948 + name = "inotify-sys" 3949 + version = "0.1.5" 3950 + source = "registry+https://github.com/rust-lang/crates.io-index" 3951 + checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" 3952 + dependencies = [ 3953 + "libc", 3954 + ] 3955 + 3956 + [[package]] 3927 3957 name = "instability" 3928 3958 version = "0.3.11" 3929 3959 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4103 4133 version = "3.1.0" 4104 4134 source = "registry+https://github.com/rust-lang/crates.io-index" 4105 4135 checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" 4136 + 4137 + [[package]] 4138 + name = "kqueue" 4139 + version = "1.1.1" 4140 + source = "registry+https://github.com/rust-lang/crates.io-index" 4141 + checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" 4142 + dependencies = [ 4143 + "kqueue-sys", 4144 + "libc", 4145 + ] 4146 + 4147 + [[package]] 4148 + name = "kqueue-sys" 4149 + version = "1.0.4" 4150 + source = "registry+https://github.com/rust-lang/crates.io-index" 4151 + checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" 4152 + dependencies = [ 4153 + "bitflags 1.3.2", 4154 + "libc", 4155 + ] 4106 4156 4107 4157 [[package]] 4108 4158 name = "kstring" ··· 4555 4605 dependencies = [ 4556 4606 "memchr", 4557 4607 "minimal-lexical", 4608 + ] 4609 + 4610 + [[package]] 4611 + name = "notify" 4612 + version = "8.2.0" 4613 + source = "registry+https://github.com/rust-lang/crates.io-index" 4614 + checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" 4615 + dependencies = [ 4616 + "bitflags 2.11.0", 4617 + "fsevent-sys", 4618 + "inotify", 4619 + "kqueue", 4620 + "libc", 4621 + "log", 4622 + "mio", 4623 + "notify-types", 4624 + "walkdir", 4625 + "windows-sys 0.60.2", 4626 + ] 4627 + 4628 + [[package]] 4629 + name = "notify-types" 4630 + version = "2.1.0" 4631 + source = "registry+https://github.com/rust-lang/crates.io-index" 4632 + checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" 4633 + dependencies = [ 4634 + "bitflags 2.11.0", 4558 4635 ] 4559 4636 4560 4637 [[package]]
+1
Cargo.toml
··· 82 82 nucleo-matcher = "0.3.1" 83 83 ron = "0.12.1" 84 84 tower-lsp = "0.20.0" 85 + notify = "8.2.0" 85 86 86 87 [build-dependencies] 87 88 anyhow = "1.0.102"
+50 -7
src/gui/mod.rs src/viz/mod.rs
··· 1 1 use eframe::egui; 2 2 use egui_graphs::{SettingsInteraction, SettingsNavigation}; 3 + use futures::executor::block_on; 4 + use tokio::sync::mpsc::UnboundedReceiver; 5 + use tracing::debug; 3 6 4 - use crate::types::{Filaments, Index}; 7 + use crate::{ 8 + tui::Signal, 9 + types::{Filaments, Index, KastenHandle}, 10 + }; 5 11 6 12 /// The `Filaments Visualizer`, which is an instance of `eframe`, which uses `egui` 7 13 pub struct FilViz { 14 + kh: KastenHandle, 15 + signal_rx: UnboundedReceiver<Signal>, 8 16 filaments: Filaments, 9 17 } 10 18 11 19 impl FilViz { 12 20 /// Create a new instance of the `FiLViz` 13 - fn new(_cc: &eframe::CreationContext<'_>, idx: &Index) -> Self { 21 + fn new( 22 + _cc: &eframe::CreationContext<'_>, 23 + kh: KastenHandle, 24 + signal_rx: UnboundedReceiver<Signal>, 25 + index: &Index, 26 + ) -> Self { 14 27 // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style. 15 28 // Restore app state using cc.storage (requires the "persistence" feature). 16 29 // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use 17 30 // for e.g. egui::PaintCallback. 18 31 Self { 19 - filaments: Filaments::from(idx), 32 + kh, 33 + signal_rx, 34 + filaments: index.into(), 20 35 } 21 36 } 22 37 23 38 /// Create and run the `FilViz`. 24 - pub fn run(idx: &Index) -> color_eyre::Result<()> { 39 + pub fn run( 40 + kh: KastenHandle, 41 + signal_rx: UnboundedReceiver<Signal>, 42 + index: &Index, 43 + ) -> color_eyre::Result<()> { 25 44 let native_options = eframe::NativeOptions::default(); 26 45 eframe::run_native( 27 46 "Filaments Visualizer", 28 47 native_options, 29 - Box::new(|cc| Ok(Box::new(Self::new(cc, idx)))), 48 + Box::new(|cc| Ok(Box::new(Self::new(cc, kh, signal_rx, index)))), 30 49 )?; 31 50 32 51 Ok(()) ··· 39 58 impl eframe::App for FilViz { 40 59 fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) { 41 60 egui::CentralPanel::default().show_inside(ui, |ui| { 61 + // let g = &mut self.fh.lock().expect("Lock must not be poisoned").graph; 42 62 let g = &mut self.filaments.graph; 43 63 44 64 let mut view = egui_graphs::GraphView::<_, _, _, _, _, _, S, L>::new(g) ··· 52 72 .with_edge_selection_multi_enabled(false), 53 73 ) 54 74 .with_navigations( 55 - &SettingsNavigation::new().with_fit_to_screen_padding(0.5), // .with_zoom_and_pan_enabled(true) 56 - // .with_fit_to_screen_enabled(false), 75 + &SettingsNavigation::new().with_fit_to_screen_padding(0.2), // .with_zoom_and_pan_enabled(true) 76 + // .with_fit_to_screen_enabled(false) 77 + // , 57 78 ); 58 79 59 80 ui.add(&mut view); ··· 64 85 egui::warn_if_debug_build(ui); 65 86 }); 66 87 }); 88 + } 89 + 90 + fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) { 91 + if let Ok(signal) = self.signal_rx.try_recv() { 92 + debug!("received signal in filaments: {signal}"); 93 + 94 + #[allow(clippy::single_match)] 95 + match signal { 96 + Signal::CreatedZettel { zid } => { 97 + block_on(async { 98 + let index = &self.kh.read().await.index; 99 + self.filaments.insert_zettel(zid, index); 100 + }); 101 + } 102 + 103 + Signal::SetLinks { zid, links } => { 104 + self.filaments.set_links_for_zid(&zid, links); 105 + } 106 + 107 + _ => {} 108 + } 109 + } 67 110 } 68 111 } 69 112
+20 -6
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 - use tokio::sync::RwLock; 15 + use tokio::sync::{RwLock, mpsc}; 16 16 use tracing::debug; 17 17 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()?; ··· 48 48 49 49 debug!("Kasten Handle: {kh:#?}"); 50 50 51 + let (signal_tx, signal_rx) = mpsc::unbounded_channel(); 52 + 51 53 // then we spawn the tui on its own thread 52 54 let tui_handle = std::thread::spawn({ 53 55 // arc stuff 54 56 let tui_rt = rt.clone(); 55 57 let kh = kh.clone(); 58 + let signal_tx = signal_tx.clone(); 56 59 57 60 // closure to run the tui 58 61 move || -> color_eyre::Result<()> { 59 62 // block the tui on the same runtime as above 60 63 tui_rt.block_on(async { 61 - let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh).await?; 64 + let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx).await?; 62 65 tui.run().await?; 63 66 // just close everything as soon as the tui is done running 64 67 process::exit(0); ··· 70 73 if args.visualizer { 71 74 // enter the guard so egui_async works properly 72 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 + } 85 + 73 86 let index = rt.block_on(async { kh.read().await.index.clone() }); 74 - FilViz::run(&index)?; 87 + 88 + FilViz::run(kh, signal_rx, &index)?; 75 89 } 76 90 77 91 // join on the tui
+12 -3
src/tui/app.rs
··· 29 29 kh: KastenHandle, 30 30 signal_tx: UnboundedSender<Signal>, 31 31 signal_rx: UnboundedReceiver<Signal>, 32 + viz_signal_tx: UnboundedSender<Signal>, 32 33 } 33 34 34 35 /// The different regions of the application that the user can ··· 45 46 46 47 impl App { 47 48 /// Construct a new `App` instance. 48 - pub async fn new(tick_rate: f64, frame_rate: f64, kh: KastenHandle) -> Result<Self> { 49 + pub async fn new( 50 + tick_rate: f64, 51 + frame_rate: f64, 52 + kh: KastenHandle, 53 + viz_signal_tx: UnboundedSender<Signal>, 54 + ) -> Result<Self> { 49 55 let (signal_tx, signal_rx) = mpsc::unbounded_channel(); 50 56 51 57 Ok(Self { ··· 61 67 kh, 62 68 signal_tx, 63 69 signal_rx, 70 + viz_signal_tx, 64 71 }) 65 72 } 66 73 ··· 162 169 while let Ok(signal) = self.signal_rx.try_recv() { 163 170 if signal != Signal::Tick && signal != Signal::Render { 164 171 debug!("handling signal: {signal:?}"); 172 + // we dont care if the receiver is dropped, its fine 173 + let _ = self.viz_signal_tx.send(signal.clone()); 165 174 } 166 175 167 176 match signal.clone() { ··· 176 185 177 186 let hx = spawn({ 178 187 let file_path = path.clone(); 179 - let fil_dir = self.config.fil_dir.clone(); 188 + let _fil_dir = self.config.fil_dir.clone(); 180 189 move || -> Result<()> { 181 190 Command::new("hx") 182 191 .stdin(std::process::Stdio::inherit()) 183 192 .stdout(std::process::Stdio::inherit()) 184 193 .stderr(std::process::Stdio::inherit()) 185 194 .arg(file_path) 186 - .arg(format!("-w {}", fil_dir.display())) 195 + // .arg(format!("-w {}", fil_dir.display())) 187 196 .status()?; 188 197 189 198 Ok(())
+16 -1
src/tui/components/zk/mod.rs
··· 240 240 .await 241 241 .with_context(|| "Failed to create a new Zettel!")?; 242 242 243 - let path = z.absolute_path(&kt.index).to_path_buf(); 243 + // let path = z.absolute_path(&kt.index).to_path_buf(); 244 + 245 + drop(kt); 246 + 247 + return Ok(Some(Signal::CreatedZettel { zid: z.id })); 248 + 249 + // return Ok(Some(Signal::Helix { path })); 250 + } 251 + Signal::CreatedZettel { zid } => { 252 + // what the fuck am i going to do in here 253 + 254 + let kt = self.kh.read().await; 255 + 256 + let path = kt.index.get_zod(&zid).path.clone(); 257 + 258 + // let path = z.absolute_path(&kt.index).to_path_buf(); 244 259 245 260 drop(kt); 246 261
+17 -2
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)] ··· 28 31 MoveDown, 29 32 MoveUp, 30 33 31 - /// New `Zettel` 34 + /// Create a New `Zettel` 32 35 NewZettel, 36 + 37 + /// This zettel was created (filaments specific) 38 + CreatedZettel { 39 + zid: ZettelId, 40 + }, 41 + 42 + /// Set the links for this `ZettelId` (filaments specific) 43 + SetLinks { 44 + zid: ZettelId, 45 + links: Vec<Link>, 46 + }, 47 + 33 48 /// User asks to open a `Zettel` 34 49 OpenZettel, 35 50 /// The user is done editing a `Zettel`
+66
src/types/deimos.rs
··· 1 + use color_eyre::eyre::Context; 2 + use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Result, Watcher as _}; 3 + use tracing::{error, info}; 4 + 5 + use tokio::sync::mpsc::UnboundedSender; 6 + 7 + use crate::{ 8 + tui::Signal, 9 + types::{KastenHandle, ZettelId}, 10 + }; 11 + 12 + #[derive(Debug)] 13 + pub struct Deimos { 14 + kh: KastenHandle, 15 + signal_tx: UnboundedSender<Signal>, 16 + } 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!"); 30 + 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")?; 40 + 41 + while let Some(res) = rx.recv().await { 42 + let Ok(event) = res.inspect_err(|e| error!("watcher error: {e:?}")) else { 43 + continue; 44 + }; 45 + 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 + }; 53 + 54 + let kt = &mut *self.kh.write().await; 55 + kt.process_path(path).await?; 56 + 57 + let links = kt.index.get_links(&zid).clone(); 58 + 59 + self.signal_tx.send(Signal::SetLinks { zid, links })?; 60 + } 61 + } 62 + } 63 + 64 + Ok(()) 65 + } 66 + }
+56 -14
src/types/filaments.rs
··· 1 - #![expect(dead_code)] 2 1 use std::{cmp::max, collections::HashMap}; 3 2 4 3 use eframe::emath; 5 4 use egui_graphs::{ 6 - Graph, 5 + Graph, Node, 7 6 petgraph::{Directed, graph::NodeIndex, prelude::StableGraph}, 8 7 }; 9 8 10 - use crate::types::{Index, Link, ZettelId}; 9 + use crate::types::{Index, Link, ZettelId, index::ZettelOnDisk}; 11 10 12 11 pub type ZkGraph = Graph<ZettelId, Link, Directed>; 13 12 ··· 16 15 /// Arbitrarily chosen minimum number of edges 17 16 const GRAPH_MIN_EDGES: usize = GRAPH_MIN_NODES * 3; 18 17 18 + #[derive(Debug)] 19 19 pub struct Filaments { 20 20 pub graph: ZkGraph, 21 21 /// simple conversions 22 22 zid_to_gid: HashMap<ZettelId, NodeIndex>, 23 23 } 24 24 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. 34 + pub fn insert_zettel(&mut self, zid: ZettelId, index: &Index) { 35 + let zod = index.get_zod(&zid); 36 + 37 + let node_idx = self 38 + .graph 39 + .add_node_custom(zid.clone(), |node| Self::custom_node_closure(zod, node)); 40 + 41 + let _ = self.zid_to_gid.insert(zid, node_idx); 42 + } 43 + 44 + fn custom_node_closure(zod: &ZettelOnDisk, node: &mut Node<ZettelId, Link>) { 45 + node.set_label(zod.fm.title.clone()); 46 + let disp = node.display_mut(); 47 + disp.radius = 75.0; 48 + 49 + // randomize position 50 + let x = rand::random_range(0.0..=100.0); 51 + let y = rand::random_range(0.0..=100.0); 52 + node.set_location(emath::Pos2 { x, y }); 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 + } 73 + } 74 + } 75 + 25 76 impl From<&Index> for Filaments { 26 77 fn from(value: &Index) -> Self { 27 78 let number_of_zettels = value.zods().len(); ··· 34 85 )); 35 86 36 87 for (zid, zod) in value.zods() { 37 - let node_idx = graph.add_node_custom(zid.clone(), |node| { 38 - node.set_label(zod.fm.title.clone()); 39 - let disp = node.display_mut(); 40 - disp.radius = 50.0; 41 - 42 - // randomize position 43 - let x = rand::random_range(0.0..=100.0); 44 - let y = rand::random_range(0.0..=100.0); 45 - node.set_location(emath::Pos2 { x, y }); 46 - node.set_hovered(true); 47 - }); 88 + let node_idx = 89 + graph.add_node_custom(zid.clone(), |node| Self::custom_node_closure(zod, node)); 48 90 49 91 let _ = zid_to_gid.insert(zid.clone(), node_idx); 50 92 }
+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 }
+3
src/types/mod.rs
··· 33 33 34 34 mod frontmatter; 35 35 pub use frontmatter::FrontMatter; 36 + 37 + mod deimos; 38 + pub use deimos::Deimos;