diff --git a/src/main.rs b/src/main.rs index 9582535..5be33d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,9 @@ use std::{process, sync::Arc}; use crate::{ cli::Cli, config::Config, - gui::FilViz, tui::TuiApp, - types::{Kasten, KastenHandle}, + types::{Deimos, Kasten, KastenHandle}, + viz::FilViz, }; use clap::Parser; use tokio::sync::{RwLock, mpsc}; @@ -18,11 +18,11 @@ use tracing::debug; mod cli; mod config; mod errors; -mod gui; mod logging; mod lsp; mod tui; mod types; +mod viz; fn main() -> color_eyre::Result<()> { errors::init()?; @@ -55,6 +55,7 @@ fn main() -> color_eyre::Result<()> { // arc stuff let tui_rt = rt.clone(); let kh = kh.clone(); + let signal_tx = signal_tx.clone(); // closure to run the tui move || -> color_eyre::Result<()> { @@ -68,20 +69,20 @@ fn main() -> color_eyre::Result<()> { } }); - // spawn deimos - // { - - // rt.spawn(async { - // let deimos = Deimos::new(kh, fh); - // deimos.watch().await - // }); - // } - // if they asked for the visualizer, we give them the visualizer if args.visualizer { // enter the guard so egui_async works properly let _rt_guard = rt.enter(); + // spawn deimos + { + let kh = kh.clone(); + rt.spawn(async { + let deimos = Deimos::new(kh, signal_tx); + deimos.watch().await + }); + } + let index = rt.block_on(async { kh.read().await.index.clone() }); FilViz::run(kh, signal_rx, &index)?; diff --git a/src/tui/signal.rs b/src/tui/signal.rs index 0747245..d31a5a5 100644 --- a/src/tui/signal.rs +++ b/src/tui/signal.rs @@ -5,7 +5,10 @@ use strum::Display; use serde::{Deserialize, Serialize}; -use crate::{tui::Region, types::ZettelId}; +use crate::{ + tui::Region, + types::{Link, ZettelId}, +}; /// The varying signals that can be emitted. #[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)] @@ -31,10 +34,17 @@ pub enum Signal { /// Create a New `Zettel` NewZettel, + /// This zettel was created (filaments specific) CreatedZettel { zid: ZettelId, }, + /// Set the links for this `ZettelId` (filaments specific) + SetLinks { + zid: ZettelId, + links: Vec, + }, + /// User asks to open a `Zettel` OpenZettel, /// The user is done editing a `Zettel` diff --git a/src/types/deimos.rs b/src/types/deimos.rs index 80381b9..173f43c 100644 --- a/src/types/deimos.rs +++ b/src/types/deimos.rs @@ -1,51 +1,66 @@ -// use color_eyre::eyre::Context; -// use notify::{Config, Event, RecommendedWatcher, RecursiveMode, Result, Watcher as _}; -// use tracing::{error, info}; +use color_eyre::eyre::Context; +use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Result, Watcher as _}; +use tracing::{error, info}; -use crate::types::KastenHandle; +use tokio::sync::mpsc::UnboundedSender; + +use crate::{ + tui::Signal, + types::{KastenHandle, ZettelId}, +}; #[derive(Debug)] -#[expect(dead_code)] pub struct Deimos { kh: KastenHandle, - // fh: FilamentsHandle, + signal_tx: UnboundedSender, } -// impl Deimos { -// pub const fn new(kh: KastenHandle, fh: FilamentsHandle) -> Self { -// Self { kh, fh } -// } - -// /// Watches the `Filaments Directory` for file changes and updates -// /// the internal state of the kasten and the `Filaments` displayed in the -// /// gui. -// /// -// /// NOTE: This function must be spawned as a top level await -// pub async fn watch(&self) -> color_eyre::Result<()> { -// info!("deimos spawned!"); - -// let (tx, mut rx) = tokio::sync::mpsc::channel::>(10); -// let mut watcher = RecommendedWatcher::new( -// move |res| tx.blocking_send(res).expect("failed to send event"), -// Config::default(), -// )?; - -// watcher -// .watch(&self.kh.read().await.root, RecursiveMode::Recursive) -// .with_context(|| "failed to start the FS watcher")?; - -// while let Some(res) = rx.recv().await { -// let kt = &mut *self.kh.write().await; -// let fh = &mut *self.fh.lock().expect("Lock must not be poisoned"); - -// match res { -// Ok(event) => info!("event: {event:?}"), -// Err(e) => error!("watch error: {e:?}"), -// } - -// *fh = (&kt.index).into(); -// } - -// Ok(()) -// } -// } +impl Deimos { + pub const fn new(kh: KastenHandle, signal_tx: UnboundedSender) -> Self { + Self { kh, signal_tx } + } + + /// Watches the `Filaments Directory` for file changes and updates + /// the internal state of the kasten and the `Filaments` displayed in the + /// gui. + /// + /// NOTE: This function must be spawned as a top level await + pub async fn watch(&self) -> color_eyre::Result<()> { + info!("deimos spawned!"); + + let (tx, mut rx) = tokio::sync::mpsc::channel::>(10); + let mut watcher = RecommendedWatcher::new( + move |res| tx.blocking_send(res).expect("failed to send event"), + Config::default(), + )?; + + watcher + .watch(&self.kh.read().await.root, RecursiveMode::Recursive) + .with_context(|| "failed to start the FS watcher")?; + + while let Some(res) = rx.recv().await { + let Ok(event) = res.inspect_err(|e| error!("watcher error: {e:?}")) else { + continue; + }; + + if let EventKind::Modify(notify::event::ModifyKind::Data(_)) = event.kind { + for path in event.paths { + let Ok(zid) = ZettelId::try_from(path.clone()) + .inspect_err(|e| error!("Failed to convert path into zettel id! : {e}")) + else { + continue; + }; + + let kt = &mut *self.kh.write().await; + kt.process_path(path).await?; + + let links = kt.index.get_links(&zid).clone(); + + self.signal_tx.send(Signal::SetLinks { zid, links })?; + } + } + } + + Ok(()) + } +} diff --git a/src/types/filaments.rs b/src/types/filaments.rs index 8668786..9e82c8b 100644 --- a/src/types/filaments.rs +++ b/src/types/filaments.rs @@ -23,6 +23,14 @@ pub struct Filaments { } impl Filaments { + pub fn get_gid(&self, zid: &ZettelId) -> NodeIndex { + *self + .zid_to_gid + .get(zid) + .expect("Invariant broken, any zid we ask for must have a corresponding gid") + } + + /// Inserts a `Zettel` into the graph. pub fn insert_zettel(&mut self, zid: ZettelId, index: &Index) { let zod = index.get_zod(&zid); @@ -36,7 +44,7 @@ impl Filaments { fn custom_node_closure(zod: &ZettelOnDisk, node: &mut Node) { node.set_label(zod.fm.title.clone()); let disp = node.display_mut(); - disp.radius = 50.0; + disp.radius = 75.0; // randomize position let x = rand::random_range(0.0..=100.0); @@ -44,6 +52,25 @@ impl Filaments { node.set_location(emath::Pos2 { x, y }); node.set_hovered(true); } + + /// Sets the `Links` for the given `ZettelId` + pub fn set_links_for_zid(&mut self, zid: &ZettelId, links: Vec) { + let gid = self.get_gid(zid); + self.graph + .g() + .edges(gid) + .map(|e| e.weight().id()) + .collect::>() + .iter() + .for_each(|edge_index| { + self.graph.remove_edge(*edge_index); + }); + + for link in links { + let dest = self.get_gid(&link.dest); + self.graph.add_edge(gid, dest, link); + } + } } impl From<&Index> for Filaments { diff --git a/src/types/index.rs b/src/types/index.rs index 350d1f9..5f6cef3 100644 --- a/src/types/index.rs +++ b/src/types/index.rs @@ -53,7 +53,7 @@ impl Index { let zid: ZettelId = path.as_path().try_into()?; let (fm, body) = FrontMatter::extract_from_file(&path)?; - let outgoing_links = Self::parse_outgoing_links(&zid, &body); + let outgoing_links = Self::_parse_outgoing_links(&zid, &body); Ok(( zid, @@ -94,7 +94,13 @@ impl Index { }) } - pub fn parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec { + pub fn parse_outgoing_links(&mut self, zid: &ZettelId) { + let body = self.get_zod(zid).body.as_str(); + let links = Self::_parse_outgoing_links(zid, body); + self.outgoing_links.insert(zid.clone(), links); + } + + fn _parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec { let parser = Parser::new_ext(body, Options::ENABLE_WIKILINKS); // let mut links = vec![]; @@ -232,6 +238,12 @@ impl Index { 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.") } + pub fn get_links(&self, zid: &ZettelId) -> &Vec { + self.outgoing_links + .get(zid) + .expect("Invariant broken. Any zid we look up exist inside this map") + } + pub const fn zods(&self) -> &HashMap { &self.zods } diff --git a/src/types/kasten.rs b/src/types/kasten.rs index 897b2be..f819425 100644 --- a/src/types/kasten.rs +++ b/src/types/kasten.rs @@ -112,6 +112,7 @@ impl Kasten { // and then we sync tags self.index.sync_tags_with_db(&zid, &self.db).await?; self.index.sync_zettel_title_with_db(&zid, &self.db).await?; + self.index.parse_outgoing_links(&zid); Ok(()) } diff --git a/src/types/mod.rs b/src/types/mod.rs index a5483e2..54f191f 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -35,5 +35,4 @@ mod frontmatter; pub use frontmatter::FrontMatter; mod deimos; -#[expect(unused_imports)] pub use deimos::Deimos; diff --git a/src/gui/mod.rs b/src/viz/mod.rs similarity index 93% rename from src/gui/mod.rs rename to src/viz/mod.rs index 52629aa..8e8ea92 100644 --- a/src/gui/mod.rs +++ b/src/viz/mod.rs @@ -73,7 +73,8 @@ impl eframe::App for FilViz { ) .with_navigations( &SettingsNavigation::new().with_fit_to_screen_padding(0.2), // .with_zoom_and_pan_enabled(true) - // .with_fit_to_screen_enabled(false), + // .with_fit_to_screen_enabled(false) + // , ); ui.add(&mut view); @@ -99,6 +100,10 @@ impl eframe::App for FilViz { }); } + Signal::SetLinks { zid, links } => { + self.filaments.set_links_for_zid(&zid, links); + } + _ => {} } }