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: Update Zettel after editing file

+134 -25
+1 -1
.config/config.kdl
··· 7 7 q Quit // Quit the application 8 8 <Ctrl-c> Quit // Another way to quit 9 9 <Ctrl-z> Suspend // Suspend the application 10 - h Helix 11 10 j MoveDown 12 11 k MoveUp 12 + o OpenZettel 13 13 } 14 14 }
+23 -12
src/tui/app.rs
··· 11 11 use crate::{ 12 12 config::Config, 13 13 tui::{Event, Tui, components::Zk}, 14 - types::KastenHandle, 14 + types::{KastenHandle, ZettelId}, 15 15 }; 16 16 17 17 use super::{components::Component, signal::Signal}; ··· 26 26 #[allow(dead_code)] 27 27 region: Region, 28 28 last_tick_key_events: Vec<KeyEvent>, 29 - _kh: KastenHandle, 29 + kh: KastenHandle, 30 30 signal_tx: UnboundedSender<Signal>, 31 31 signal_rx: UnboundedReceiver<Signal>, 32 32 } ··· 57 57 config: Config::parse()?, 58 58 region: Region::default(), 59 59 last_tick_key_events: Vec::new(), 60 - _kh: kh, 60 + kh, 61 61 signal_tx, 62 62 signal_rx, 63 63 }) ··· 160 160 debug!("handling signal: {signal:?}"); 161 161 } 162 162 163 - match signal { 163 + match signal.clone() { 164 164 Signal::Tick => { 165 165 self.last_tick_key_events.drain(..); 166 166 } 167 167 168 168 Signal::Quit => self.should_quit = true, 169 169 170 - Signal::Helix => { 170 + Signal::Helix { path } => { 171 171 tui.exit()?; 172 172 173 - let hx = spawn(move || -> Result<()> { 174 - Command::new("hx") 175 - .stdin(std::process::Stdio::inherit()) 176 - .stdout(std::process::Stdio::inherit()) 177 - .stderr(std::process::Stdio::inherit()) 178 - .status()?; 173 + let hx = spawn({ 174 + let path = path.clone(); 175 + move || -> Result<()> { 176 + Command::new("hx") 177 + .stdin(std::process::Stdio::inherit()) 178 + .stdout(std::process::Stdio::inherit()) 179 + .stderr(std::process::Stdio::inherit()) 180 + .arg(path) 181 + .status()?; 179 182 180 - Ok(()) 183 + Ok(()) 184 + } 181 185 }); 182 186 183 187 hx.join().unwrap().unwrap(); 188 + // once we get out of the edit, we need to update the zettel for this 189 + // path and then update the db and the kasten for this stuff 190 + let zid = ZettelId::try_from(path)?; 191 + 192 + self.kh.write().await.process_zid(&zid).await?; 193 + 194 + self.signal_tx.send(Signal::ClosedZettel)?; 184 195 185 196 tui.terminal.clear()?; 186 197 tui.enter()?;
+50 -4
src/tui/components/zk/mod.rs
··· 101 101 .expect("must exist"); 102 102 103 103 let zettel = kt 104 - .get_by_zettel_id(selected_zettel) 104 + .get_node_by_zettel_id(selected_zettel) 105 105 .expect("must exist, handle case where it doesnt later...") 106 106 .payload(); 107 107 ··· 114 114 115 115 // okay now that we have the zettel we need to construct the zettel out of this id 116 116 let zettel_view: ZettelView = kt 117 - .get_by_zettel_id(selected_zettel) 117 + .get_node_by_zettel_id(selected_zettel) 118 118 .expect("must exist, handle case where it doesnt later...") 119 119 .payload() 120 120 .into(); ··· 147 147 let kh = self.kh.read().await; 148 148 149 149 self.zettel_view = kh 150 - .get_by_zettel_id(z_id) 150 + .get_node_by_zettel_id(z_id) 151 151 .expect("this should be valid unless the kasten changed out underneath us") 152 152 .payload() 153 153 .into(); 154 154 155 155 self.preview = kh 156 - .get_by_zettel_id(z_id) 156 + .get_node_by_zettel_id(z_id) 157 157 .expect("this should be valid unless the kasten changed out underneath us") 158 158 .payload() 159 159 .content(&kh.ws) ··· 182 182 self.zettel_list.state.select_previous(); 183 183 self.update_views_from_zettel_list_selection().await?; 184 184 } 185 + 186 + Signal::OpenZettel => { 187 + let Some(selcted) = self.zettel_list.state.selected() else { 188 + return Ok(None); 189 + }; 190 + 191 + let Some(zid) = self.zettel_list.id_list.get(selcted) else { 192 + return Ok(None); 193 + }; 194 + 195 + let kh = self.kh.read().await; 196 + let path = kh 197 + .get_node_by_zettel_id(zid) 198 + .expect( 199 + "This should not have 200 + change dout underneath us", 201 + ) 202 + .payload() 203 + .absolute_path(&kh.ws); 204 + 205 + drop(kh); 206 + 207 + return Ok(Some(Signal::Helix { path })); 208 + } 209 + 210 + Signal::ClosedZettel => { 211 + let selected = self.zettel_list.state.selected().expect( 212 + "still have to 213 + figure out what to do if this doesnt exist", 214 + ); 215 + 216 + let Some(id) = self.zettel_list.id_list.get(selected) else { 217 + return Ok(None); 218 + }; 219 + 220 + let kh = self.kh.read().await; 221 + 222 + let node = kh 223 + .get_node_by_zettel_id(id) 224 + .expect("Invariant broken, this must exist."); 225 + 226 + self.zettel_view = ZettelView::from(node.payload()); 227 + self.preview = Preview::from(node.payload().content(&kh.ws).await?); 228 + drop(kh); 229 + } 230 + 185 231 _ => {} 186 232 } 187 233 Ok(None)
+13 -3
src/tui/signal.rs
··· 1 - use std::str::FromStr; 1 + use std::{path::PathBuf, str::FromStr}; 2 2 3 3 use color_eyre::eyre::eyre; 4 4 use strum::Display; ··· 17 17 ClearScreen, 18 18 Error(String), 19 19 Help, 20 + 21 + // movement 20 22 MoveDown, 21 23 MoveUp, 22 24 25 + /// User asks to open a `Zettel` 26 + OpenZettel, 27 + 28 + /// The user is done editing a `Zettel` 29 + ClosedZettel, 30 + 23 31 /// this is fucking temporary 24 - Helix, 32 + Helix { 33 + path: PathBuf, 34 + }, 25 35 } 26 36 27 37 impl FromStr for Signal { ··· 32 42 "suspend" => Self::Suspend, 33 43 "resume" => Self::Resume, 34 44 "quit" => Self::Quit, 35 - "helix" => Self::Helix, 36 45 "movedown" => Self::MoveDown, 37 46 "moveup" => Self::MoveUp, 47 + "openzettel" => Self::OpenZettel, 38 48 _ => { 39 49 return Err(eyre!(format!( 40 50 "Attempt to construct a non-user Signal from str: {s}"
+31 -1
src/types/kasten.rs
··· 104 104 most_recently_edited: None, 105 105 }) 106 106 } 107 - pub fn get_by_zettel_id(&self, id: &ZettelId) -> Option<&Node<Zettel, Link>> { 107 + 108 + /// processes the `Zettel` for the provided `ZettelId`, 109 + /// meaning it updates the internal state of the `Kasten` 110 + /// with the changes in `Zettel`. 111 + pub async fn process_zid(&mut self, zid: &ZettelId) -> Result<()> { 112 + //NOTE: need to clone to get around borrowing rules but 113 + // ideally we dont have to do this, kind of cringe imo. 114 + let ws = self.ws.clone(); 115 + 116 + let zettel = self 117 + .get_node_by_zettel_id_mut(zid) 118 + .expect("this should not happen ever") 119 + .payload_mut(); 120 + 121 + zettel.sync_with_file(&ws).await?; 122 + 123 + Ok(()) 124 + } 125 + 126 + pub fn get_node_by_zettel_id(&self, id: &ZettelId) -> Option<&Node<Zettel, Link>> { 108 127 let idx = self.zid_to_gid.get(id)?; 109 128 110 129 let node = self.graph.node(*idx).expect( 111 130 "invariant broken if internal hashmap is not uptodate with 112 131 the state of the graph...", 113 132 ); 133 + Some(node) 134 + } 135 + 136 + pub fn get_node_by_zettel_id_mut(&mut self, id: &ZettelId) -> Option<&mut Node<Zettel, Link>> { 137 + let idx = self.zid_to_gid.get(id)?; 138 + 139 + let node = self.graph.node_mut(*idx).expect( 140 + "invariant broken if internal hashmap is not uptodate with the 141 + state of the graph...", 142 + ); 143 + 114 144 Some(node) 115 145 } 116 146 }
+16 -4
src/types/zettel.rs
··· 1 1 use dto::{ 2 2 ActiveModelTrait, ActiveValue, ColumnTrait, DateTime, EntityTrait as _, IntoActiveModel, 3 - QueryFilter, TagActiveModel, TagEntity, ZettelActiveModel, ZettelEntity, ZettelModelEx, 4 - ZettelTagActiveModel, ZettelTagColumns, ZettelTagEntity, 3 + QueryFilter, TagActiveModel, TagEntity, ZettelActiveModel, ZettelActiveModelEx, ZettelEntity, 4 + ZettelModelEx, ZettelTagActiveModel, ZettelTagColumns, ZettelTagEntity, 5 5 }; 6 6 use pulldown_cmark::{Event, Parser, Tag as MkTag}; 7 7 use serde::{Deserialize, Serialize}; ··· 15 15 use dto::NanoId; 16 16 use tokio::{fs::File, io::AsyncWriteExt}; 17 17 18 - use crate::types::{FrontMatter, Link, Tag, Workspace, frontmatter}; 18 + use crate::types::{FrontMatter, Kasten, Link, Tag, Workspace, frontmatter}; 19 19 20 20 /// A `Zettel` is a note about a single idea. 21 21 /// It can have many `Tag`s, just meaning it can fall under many ··· 79 79 Ok(zettel.into()) 80 80 } 81 81 82 + pub async fn sync_with_file(&mut self, ws: &Workspace) -> Result<()> { 83 + let (fm, content) = FrontMatter::extract_from_file(self.absolute_path(ws)).await?; 84 + 85 + self.title = fm.title; 86 + 87 + // it could have new tags and stuff... 88 + 89 + // todo!(); 90 + 91 + Ok(()) 92 + } 93 + 82 94 /// Returns the most up-to-date `FrontMatter` for this 83 95 /// `Zettel` 84 96 pub async fn front_matter(&self, ws: &Workspace) -> Result<FrontMatter> { ··· 101 113 Ok(File::open(path).await?) 102 114 } 103 115 104 - fn absolute_path(&self, ws: &Workspace) -> PathBuf { 116 + pub fn absolute_path(&self, ws: &Workspace) -> PathBuf { 105 117 ws.root.clone().join(&self.file_path) 106 118 } 107 119