···11use eframe::egui;
22use egui_graphs::{SettingsInteraction, SettingsNavigation};
33+use futures::executor::block_on;
44+use tokio::sync::mpsc::UnboundedReceiver;
55+use tracing::debug;
3644-use crate::types::{Filaments, Index};
77+use crate::{
88+ tui::Signal,
99+ types::{Filaments, Index, KastenHandle},
1010+};
511612/// The `Filaments Visualizer`, which is an instance of `eframe`, which uses `egui`
713pub struct FilViz {
1414+ kh: KastenHandle,
1515+ signal_rx: UnboundedReceiver<Signal>,
816 filaments: Filaments,
917}
10181119impl FilViz {
1220 /// Create a new instance of the `FiLViz`
1313- fn new(_cc: &eframe::CreationContext<'_>, idx: &Index) -> Self {
2121+ fn new(
2222+ _cc: &eframe::CreationContext<'_>,
2323+ kh: KastenHandle,
2424+ signal_rx: UnboundedReceiver<Signal>,
2525+ index: &Index,
2626+ ) -> Self {
1427 // Customize egui here with cc.egui_ctx.set_fonts and cc.egui_ctx.set_global_style.
1528 // Restore app state using cc.storage (requires the "persistence" feature).
1629 // Use the cc.gl (a glow::Context) to create graphics shaders and buffers that you can use
1730 // for e.g. egui::PaintCallback.
1831 Self {
1919- filaments: Filaments::from(idx),
3232+ kh,
3333+ signal_rx,
3434+ filaments: index.into(),
2035 }
2136 }
22372338 /// Create and run the `FilViz`.
2424- pub fn run(idx: &Index) -> color_eyre::Result<()> {
3939+ pub fn run(
4040+ kh: KastenHandle,
4141+ signal_rx: UnboundedReceiver<Signal>,
4242+ index: &Index,
4343+ ) -> color_eyre::Result<()> {
2544 let native_options = eframe::NativeOptions::default();
2645 eframe::run_native(
2746 "Filaments Visualizer",
2847 native_options,
2929- Box::new(|cc| Ok(Box::new(Self::new(cc, idx)))),
4848+ Box::new(|cc| Ok(Box::new(Self::new(cc, kh, signal_rx, index)))),
3049 )?;
31503251 Ok(())
···3958impl eframe::App for FilViz {
4059 fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
4160 egui::CentralPanel::default().show_inside(ui, |ui| {
6161+ // let g = &mut self.fh.lock().expect("Lock must not be poisoned").graph;
4262 let g = &mut self.filaments.graph;
43634464 let mut view = egui_graphs::GraphView::<_, _, _, _, _, _, S, L>::new(g)
···5272 .with_edge_selection_multi_enabled(false),
5373 )
5474 .with_navigations(
5555- &SettingsNavigation::new().with_fit_to_screen_padding(0.5), // .with_zoom_and_pan_enabled(true)
5656- // .with_fit_to_screen_enabled(false),
7575+ &SettingsNavigation::new().with_fit_to_screen_padding(0.2), // .with_zoom_and_pan_enabled(true)
7676+ // .with_fit_to_screen_enabled(false)
7777+ // ,
5778 );
58795980 ui.add(&mut view);
···6485 egui::warn_if_debug_build(ui);
6586 });
6687 });
8888+ }
8989+9090+ fn logic(&mut self, _ctx: &egui::Context, _frame: &mut eframe::Frame) {
9191+ if let Ok(signal) = self.signal_rx.try_recv() {
9292+ debug!("received signal in filaments: {signal}");
9393+9494+ #[allow(clippy::single_match)]
9595+ match signal {
9696+ Signal::CreatedZettel { zid } => {
9797+ block_on(async {
9898+ let index = &self.kh.read().await.index;
9999+ self.filaments.insert_zettel(zid, index);
100100+ });
101101+ }
102102+103103+ Signal::SetLinks { zid, links } => {
104104+ self.filaments.set_links_for_zid(&zid, links);
105105+ }
106106+107107+ _ => {}
108108+ }
109109+ }
67110 }
68111}
69112
+20-6
src/main.rs
···77use crate::{
88 cli::Cli,
99 config::Config,
1010- gui::FilViz,
1110 tui::TuiApp,
1212- types::{Kasten, KastenHandle},
1111+ types::{Deimos, Kasten, KastenHandle},
1212+ viz::FilViz,
1313};
1414use clap::Parser;
1515-use tokio::sync::RwLock;
1515+use tokio::sync::{RwLock, mpsc};
1616use tracing::debug;
17171818mod cli;
1919mod config;
2020mod errors;
2121-mod gui;
2221mod logging;
2322mod lsp;
2423mod tui;
2524mod types;
2525+mod viz;
26262727fn main() -> color_eyre::Result<()> {
2828 errors::init()?;
···48484949 debug!("Kasten Handle: {kh:#?}");
50505151+ let (signal_tx, signal_rx) = mpsc::unbounded_channel();
5252+5153 // then we spawn the tui on its own thread
5254 let tui_handle = std::thread::spawn({
5355 // arc stuff
5456 let tui_rt = rt.clone();
5557 let kh = kh.clone();
5858+ let signal_tx = signal_tx.clone();
56595760 // closure to run the tui
5861 move || -> color_eyre::Result<()> {
5962 // block the tui on the same runtime as above
6063 tui_rt.block_on(async {
6161- let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh).await?;
6464+ let mut tui = TuiApp::new(args.tick_rate, args.frame_rate, kh, signal_tx).await?;
6265 tui.run().await?;
6366 // just close everything as soon as the tui is done running
6467 process::exit(0);
···7073 if args.visualizer {
7174 // enter the guard so egui_async works properly
7275 let _rt_guard = rt.enter();
7676+7777+ // spawn deimos
7878+ {
7979+ let kh = kh.clone();
8080+ rt.spawn(async {
8181+ let deimos = Deimos::new(kh, signal_tx);
8282+ deimos.watch().await
8383+ });
8484+ }
8585+7386 let index = rt.block_on(async { kh.read().await.index.clone() });
7474- FilViz::run(&index)?;
8787+8888+ FilViz::run(kh, signal_rx, &index)?;
7589 }
76907791 // join on the tui
+12-3
src/tui/app.rs
···2929 kh: KastenHandle,
3030 signal_tx: UnboundedSender<Signal>,
3131 signal_rx: UnboundedReceiver<Signal>,
3232+ viz_signal_tx: UnboundedSender<Signal>,
3233}
33343435/// The different regions of the application that the user can
···45464647impl App {
4748 /// Construct a new `App` instance.
4848- pub async fn new(tick_rate: f64, frame_rate: f64, kh: KastenHandle) -> Result<Self> {
4949+ pub async fn new(
5050+ tick_rate: f64,
5151+ frame_rate: f64,
5252+ kh: KastenHandle,
5353+ viz_signal_tx: UnboundedSender<Signal>,
5454+ ) -> Result<Self> {
4955 let (signal_tx, signal_rx) = mpsc::unbounded_channel();
50565157 Ok(Self {
···6167 kh,
6268 signal_tx,
6369 signal_rx,
7070+ viz_signal_tx,
6471 })
6572 }
6673···162169 while let Ok(signal) = self.signal_rx.try_recv() {
163170 if signal != Signal::Tick && signal != Signal::Render {
164171 debug!("handling signal: {signal:?}");
172172+ // we dont care if the receiver is dropped, its fine
173173+ let _ = self.viz_signal_tx.send(signal.clone());
165174 }
166175167176 match signal.clone() {
···176185177186 let hx = spawn({
178187 let file_path = path.clone();
179179- let fil_dir = self.config.fil_dir.clone();
188188+ let _fil_dir = self.config.fil_dir.clone();
180189 move || -> Result<()> {
181190 Command::new("hx")
182191 .stdin(std::process::Stdio::inherit())
183192 .stdout(std::process::Stdio::inherit())
184193 .stderr(std::process::Stdio::inherit())
185194 .arg(file_path)
186186- .arg(format!("-w {}", fil_dir.display()))
195195+ // .arg(format!("-w {}", fil_dir.display()))
187196 .status()?;
188197189198 Ok(())
+16-1
src/tui/components/zk/mod.rs
···240240 .await
241241 .with_context(|| "Failed to create a new Zettel!")?;
242242243243- let path = z.absolute_path(&kt.index).to_path_buf();
243243+ // let path = z.absolute_path(&kt.index).to_path_buf();
244244+245245+ drop(kt);
246246+247247+ return Ok(Some(Signal::CreatedZettel { zid: z.id }));
248248+249249+ // return Ok(Some(Signal::Helix { path }));
250250+ }
251251+ Signal::CreatedZettel { zid } => {
252252+ // what the fuck am i going to do in here
253253+254254+ let kt = self.kh.read().await;
255255+256256+ let path = kt.index.get_zod(&zid).path.clone();
257257+258258+ // let path = z.absolute_path(&kt.index).to_path_buf();
244259245260 drop(kt);
246261
+17-2
src/tui/signal.rs
···5566use serde::{Deserialize, Serialize};
7788-use crate::{tui::Region, types::ZettelId};
88+use crate::{
99+ tui::Region,
1010+ types::{Link, ZettelId},
1111+};
9121013/// The varying signals that can be emitted.
1114#[derive(Debug, Clone, PartialEq, Eq, Display, Serialize, Deserialize)]
···2831 MoveDown,
2932 MoveUp,
30333131- /// New `Zettel`
3434+ /// Create a New `Zettel`
3235 NewZettel,
3636+3737+ /// This zettel was created (filaments specific)
3838+ CreatedZettel {
3939+ zid: ZettelId,
4040+ },
4141+4242+ /// Set the links for this `ZettelId` (filaments specific)
4343+ SetLinks {
4444+ zid: ZettelId,
4545+ links: Vec<Link>,
4646+ },
4747+3348 /// User asks to open a `Zettel`
3449 OpenZettel,
3550 /// The user is done editing a `Zettel`
+66
src/types/deimos.rs
···11+use color_eyre::eyre::Context;
22+use notify::{Config, Event, EventKind, RecommendedWatcher, RecursiveMode, Result, Watcher as _};
33+use tracing::{error, info};
44+55+use tokio::sync::mpsc::UnboundedSender;
66+77+use crate::{
88+ tui::Signal,
99+ types::{KastenHandle, ZettelId},
1010+};
1111+1212+#[derive(Debug)]
1313+pub struct Deimos {
1414+ kh: KastenHandle,
1515+ signal_tx: UnboundedSender<Signal>,
1616+}
1717+1818+impl Deimos {
1919+ pub const fn new(kh: KastenHandle, signal_tx: UnboundedSender<Signal>) -> Self {
2020+ Self { kh, signal_tx }
2121+ }
2222+2323+ /// Watches the `Filaments Directory` for file changes and updates
2424+ /// the internal state of the kasten and the `Filaments` displayed in the
2525+ /// gui.
2626+ ///
2727+ /// NOTE: This function must be spawned as a top level await
2828+ pub async fn watch(&self) -> color_eyre::Result<()> {
2929+ info!("deimos spawned!");
3030+3131+ let (tx, mut rx) = tokio::sync::mpsc::channel::<Result<Event>>(10);
3232+ let mut watcher = RecommendedWatcher::new(
3333+ move |res| tx.blocking_send(res).expect("failed to send event"),
3434+ Config::default(),
3535+ )?;
3636+3737+ watcher
3838+ .watch(&self.kh.read().await.root, RecursiveMode::Recursive)
3939+ .with_context(|| "failed to start the FS watcher")?;
4040+4141+ while let Some(res) = rx.recv().await {
4242+ let Ok(event) = res.inspect_err(|e| error!("watcher error: {e:?}")) else {
4343+ continue;
4444+ };
4545+4646+ if let EventKind::Modify(notify::event::ModifyKind::Data(_)) = event.kind {
4747+ for path in event.paths {
4848+ let Ok(zid) = ZettelId::try_from(path.clone())
4949+ .inspect_err(|e| error!("Failed to convert path into zettel id! : {e}"))
5050+ else {
5151+ continue;
5252+ };
5353+5454+ let kt = &mut *self.kh.write().await;
5555+ kt.process_path(path).await?;
5656+5757+ let links = kt.index.get_links(&zid).clone();
5858+5959+ self.signal_tx.send(Signal::SetLinks { zid, links })?;
6060+ }
6161+ }
6262+ }
6363+6464+ Ok(())
6565+ }
6666+}
+56-14
src/types/filaments.rs
···11-#![expect(dead_code)]
21use std::{cmp::max, collections::HashMap};
3243use eframe::emath;
54use egui_graphs::{
66- Graph,
55+ Graph, Node,
76 petgraph::{Directed, graph::NodeIndex, prelude::StableGraph},
87};
981010-use crate::types::{Index, Link, ZettelId};
99+use crate::types::{Index, Link, ZettelId, index::ZettelOnDisk};
11101211pub type ZkGraph = Graph<ZettelId, Link, Directed>;
1312···1615/// Arbitrarily chosen minimum number of edges
1716const GRAPH_MIN_EDGES: usize = GRAPH_MIN_NODES * 3;
18171818+#[derive(Debug)]
1919pub struct Filaments {
2020 pub graph: ZkGraph,
2121 /// simple conversions
2222 zid_to_gid: HashMap<ZettelId, NodeIndex>,
2323}
24242525+impl Filaments {
2626+ pub fn get_gid(&self, zid: &ZettelId) -> NodeIndex {
2727+ *self
2828+ .zid_to_gid
2929+ .get(zid)
3030+ .expect("Invariant broken, any zid we ask for must have a corresponding gid")
3131+ }
3232+3333+ /// Inserts a `Zettel` into the graph.
3434+ pub fn insert_zettel(&mut self, zid: ZettelId, index: &Index) {
3535+ let zod = index.get_zod(&zid);
3636+3737+ let node_idx = self
3838+ .graph
3939+ .add_node_custom(zid.clone(), |node| Self::custom_node_closure(zod, node));
4040+4141+ let _ = self.zid_to_gid.insert(zid, node_idx);
4242+ }
4343+4444+ fn custom_node_closure(zod: &ZettelOnDisk, node: &mut Node<ZettelId, Link>) {
4545+ node.set_label(zod.fm.title.clone());
4646+ let disp = node.display_mut();
4747+ disp.radius = 75.0;
4848+4949+ // randomize position
5050+ let x = rand::random_range(0.0..=100.0);
5151+ let y = rand::random_range(0.0..=100.0);
5252+ node.set_location(emath::Pos2 { x, y });
5353+ node.set_hovered(true);
5454+ }
5555+5656+ /// Sets the `Links` for the given `ZettelId`
5757+ pub fn set_links_for_zid(&mut self, zid: &ZettelId, links: Vec<Link>) {
5858+ let gid = self.get_gid(zid);
5959+ self.graph
6060+ .g()
6161+ .edges(gid)
6262+ .map(|e| e.weight().id())
6363+ .collect::<Vec<_>>()
6464+ .iter()
6565+ .for_each(|edge_index| {
6666+ self.graph.remove_edge(*edge_index);
6767+ });
6868+6969+ for link in links {
7070+ let dest = self.get_gid(&link.dest);
7171+ self.graph.add_edge(gid, dest, link);
7272+ }
7373+ }
7474+}
7575+2576impl From<&Index> for Filaments {
2677 fn from(value: &Index) -> Self {
2778 let number_of_zettels = value.zods().len();
···3485 ));
35863687 for (zid, zod) in value.zods() {
3737- let node_idx = graph.add_node_custom(zid.clone(), |node| {
3838- node.set_label(zod.fm.title.clone());
3939- let disp = node.display_mut();
4040- disp.radius = 50.0;
4141-4242- // randomize position
4343- let x = rand::random_range(0.0..=100.0);
4444- let y = rand::random_range(0.0..=100.0);
4545- node.set_location(emath::Pos2 { x, y });
4646- node.set_hovered(true);
4747- });
8888+ let node_idx =
8989+ graph.add_node_custom(zid.clone(), |node| Self::custom_node_closure(zod, node));
48904991 let _ = zid_to_gid.insert(zid.clone(), node_idx);
5092 }
+14-2
src/types/index.rs
···5353 let zid: ZettelId = path.as_path().try_into()?;
5454 let (fm, body) = FrontMatter::extract_from_file(&path)?;
55555656- let outgoing_links = Self::parse_outgoing_links(&zid, &body);
5656+ let outgoing_links = Self::_parse_outgoing_links(&zid, &body);
57575858 Ok((
5959 zid,
···9494 })
9595 }
96969797- pub fn parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec<Link> {
9797+ pub fn parse_outgoing_links(&mut self, zid: &ZettelId) {
9898+ let body = self.get_zod(zid).body.as_str();
9999+ let links = Self::_parse_outgoing_links(zid, body);
100100+ self.outgoing_links.insert(zid.clone(), links);
101101+ }
102102+103103+ fn _parse_outgoing_links(zid: &ZettelId, body: &str) -> Vec<Link> {
98104 let parser = Parser::new_ext(body, Options::ENABLE_WIKILINKS);
99105100106 // let mut links = vec![];
···230236231237 fn get_zod_mut(&mut self, zid: &ZettelId) -> &mut ZettelOnDisk {
232238 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.")
239239+ }
240240+241241+ pub fn get_links(&self, zid: &ZettelId) -> &Vec<Link> {
242242+ self.outgoing_links
243243+ .get(zid)
244244+ .expect("Invariant broken. Any zid we look up exist inside this map")
233245 }
234246235247 pub const fn zods(&self) -> &HashMap<ZettelId, ZettelOnDisk> {
+1
src/types/kasten.rs
···112112 // and then we sync tags
113113 self.index.sync_tags_with_db(&zid, &self.db).await?;
114114 self.index.sync_zettel_title_with_db(&zid, &self.db).await?;
115115+ self.index.parse_outgoing_links(&zid);
115116116117 Ok(())
117118 }
+3
src/types/mod.rs
···33333434mod frontmatter;
3535pub use frontmatter::FrontMatter;
3636+3737+mod deimos;
3838+pub use deimos::Deimos;