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: tag completions!

+76 -98
+5 -5
.config/config.ron
··· 1 1 ( 2 2 directory: "/Users/suri/dev/projects/filaments/ZettelKasten", 3 3 global_key_binds: { 4 - "down": MoveDown, 4 + "ctrl-c": Quit, 5 + "ctrl-z": Suspend, 5 6 "up": MoveUp, 6 - "ctrl-z": Suspend, 7 - "ctrl-c": Quit, 7 + "down": MoveDown, 8 8 }, 9 9 zk: ( 10 10 keybinds: { 11 11 "tab": SwitchTo( 12 12 region: Todo, 13 13 ), 14 - "<Ctrl-n>": NewZettel, 15 14 "enter": OpenZettel, 15 + "<Ctrl-n>": NewZettel, 16 16 }, 17 17 ), 18 18 todo: ( 19 19 keybinds: { 20 - "j": MoveDown, 21 20 "k": MoveUp, 21 + "j": MoveDown, 22 22 "tab": SwitchTo( 23 23 region: Zk, 24 24 ),
+1 -2
src/cli/process.rs
··· 65 65 let stdin = tokio::io::stdin(); 66 66 let stdout = tokio::io::stdout(); 67 67 68 - let (service, socket) = 69 - LspService::new(|client| Backend::new(client, kt.db.clone())); 68 + let (service, socket) = LspService::new(|client| Backend::new(client, kt)); 70 69 71 70 Server::new(stdin, stdout, socket).serve(service).await; 72 71 }
+56 -86
src/lsp/mod.rs
··· 1 - use dto::{DatabaseConnection, TagEntity, ZettelEntity}; 2 - use nucleo_matcher::{ 3 - Matcher, Utf32Str, 4 - pattern::{CaseMatching, Normalization, Pattern}, 5 - }; 1 + use color_eyre::eyre::eyre; 2 + use dto::{TagEntity, ZettelEntity}; 6 3 use tower_lsp::{ 7 4 Client, LanguageServer, 8 5 jsonrpc::{self, Result}, 9 6 lsp_types::{ 10 7 CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, InitializeParams, 11 8 InitializeResult, InitializedParams, MessageType, ServerCapabilities, 12 - TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Url, 9 + TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, 13 10 }, 14 11 }; 15 12 16 - use crate::types::{Zettel, ZettelId}; 13 + use crate::types::{Kasten, Tag, Zettel, ZettelId}; 17 14 18 15 #[derive(Debug)] 19 16 pub struct Backend { 20 17 client: Client, 21 - db: DatabaseConnection, 18 + kt: Kasten, 22 19 } 23 20 24 21 impl Backend { 25 - pub const fn new(client: Client, db: DatabaseConnection) -> Self { 26 - Self { client, db } 22 + pub const fn new(client: Client, kt: Kasten) -> Self { 23 + Self { client, kt } 27 24 } 28 25 } 29 26 ··· 41 38 )), 42 39 completion_provider: Some(CompletionOptions { 43 40 resolve_provider: Some(false), 44 - trigger_characters: Some(vec!["[".to_string()]), 41 + trigger_characters: Some(vec!["[[".to_string(), "@".to_string()]), 45 42 ..Default::default() 46 43 }), 47 44 ..ServerCapabilities::default() ··· 57 54 } 58 55 59 56 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { 60 - let file_path = params 57 + let Some(trigger) = params 58 + .context 59 + .as_ref() 60 + .and_then(|c| c.trigger_character.as_deref()) 61 + else { 62 + return Ok(None); 63 + }; 64 + 65 + let Ok::<ZettelId, color_eyre::eyre::Error>(zid) = params 61 66 .text_document_position 62 67 .text_document 63 68 .uri 64 69 .to_file_path() 65 - .expect("The uri should be in file format."); 66 - 67 - eprintln!( 68 - "Hello, processing completion in file: {}", 69 - file_path.display() 70 - ); 71 - 72 - let Ok(file) = tokio::fs::read_to_string(&file_path).await else { 70 + .map_err(|()| eyre!("failed to turn into file path")) 71 + .and_then(TryInto::try_into) 72 + else { 73 73 return Ok(None); 74 74 }; 75 - eprintln!("here are the file contents: {file}"); 76 75 77 - let Ok::<ZettelId, color_eyre::eyre::Error>(src_zid) = file_path.try_into() else { 78 - return Ok(None); 79 - }; 80 - 81 - let position = params.text_document_position.position; 82 - 83 - eprintln!("here is the position we are given: {position:#?}"); 84 - 85 - // for some weird reason we have to add +1 here?? 86 - let line = file.lines().nth(position.line as usize + 1).unwrap_or(""); 87 - 88 - eprintln!("params: {params:#?}"); 89 - 90 - // this means they are looking for a link completion 91 - if let Some(context) = params.context 92 - && Some("[") == context.trigger_character.as_deref() 93 - { 94 - eprintln!("we are looking for link stuff"); 95 - // let mut matcher = Matcher::default(); 96 - // // the query is going to be anything between the '[' character and the last known cursor position 76 + let responses = match trigger { 77 + // Link completion 78 + "[[" => ZettelEntity::load() 79 + .with(TagEntity) 80 + .all(&self.kt.db) 81 + .await 82 + .map_err(|_| jsonrpc::Error::internal_error())? 83 + .into_iter() 84 + .filter_map(|model| { 85 + let z: Zettel = model.into(); 97 86 98 - // let query = { 99 - // let Some(start) = line.rfind('[') else { 100 - // eprintln!( 101 - // "could not find the start of the link thingy: here is the line: {line}" 102 - // ); 103 - // return Ok(None); 104 - // }; 87 + // we dont want to return links to ourself 88 + if z.id == zid { 89 + return None; 90 + } 105 91 106 - // // .expect("it has to exist in the line for this completion request to be fired"); 107 - // let end = position.character as usize; 92 + let item = format!("{}|{}", z.id, z.title); 93 + let detail = z 94 + .front_matter(&self.kt.index) 95 + .to_string() 96 + .trim() 97 + .to_string(); 108 98 109 - // Pattern::parse( 110 - // &line[start..=end], 111 - // CaseMatching::Ignore, 112 - // Normalization::Smart, 113 - // ) 114 - // }; 99 + Some(CompletionItem::new_simple(item, detail)) 100 + }) 101 + .collect(), 115 102 116 - let responses = ZettelEntity::load() 117 - .with(TagEntity) 118 - .all(&self.db) 103 + // Tag completion 104 + "@" => TagEntity::load() 105 + .all(&self.kt.db) 119 106 .await 120 107 .map_err(|_| jsonrpc::Error::internal_error())? 121 108 .into_iter() 122 - .map(Into::<Zettel>::into) 123 - .filter_map(|z| { 124 - // let mut buf = Vec::new(); 109 + .map(|model| { 110 + let t: Tag = model.into(); 125 111 126 - // let score = query 127 - // .score(Utf32Str::new(&z.title, &mut buf), &mut matcher) 128 - // .unwrap_or_default(); 112 + let item = t.name.clone(); 129 113 130 - // if score > 0 { 131 - Some(CompletionItem::new_simple( 132 - z.title, 133 - "The title of the thing".to_owned(), 134 - )) 135 - // } else { 136 - // None 137 - // } 114 + let detail = format!("{}\ncolor: {}", t.name, t.color); 115 + 116 + CompletionItem::new_simple(item, detail) 138 117 }) 139 - .collect(); 118 + .collect(), 140 119 141 - return Ok(Some(CompletionResponse::Array(responses))); 142 - } 120 + _ => return Ok(None), 121 + }; 143 122 144 - return Ok(None); 145 - 146 - // // multi threaded zig 147 - // // async rust with tokio 148 - 149 - // // this is for what completion again? 150 - // Ok(Some(CompletionResponse::Array(vec![ 151 - // CompletionItem::new_simple("Hello".to_string(), "Some detail".to_string()), 152 - // CompletionItem::new_simple("Bye".to_string(), "More detail".to_string()), 153 - // ]))) 123 + return Ok(Some(CompletionResponse::Array(responses))); 154 124 } 155 125 156 126 async fn shutdown(&self) -> Result<()> {
+9
src/types/color.rs
··· 1 + use std::fmt::Display; 2 + 1 3 use dto::ColorDTO; 2 4 use ratatui::style::Color as RatColor; 3 5 ··· 5 7 /// internally represented as rgb 6 8 #[derive(Debug, Copy, Clone, Default)] 7 9 pub struct Color(ColorDTO); 10 + 11 + impl Display for Color { 12 + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 13 + let rgb = self.0.to_rgb8(); 14 + write!(f, "#{:02X}{:02X}{:02X}", rgb.r, rgb.g, rgb.b) 15 + } 16 + } 8 17 9 18 impl From<ColorDTO> for Color { 10 19 fn from(value: ColorDTO) -> Self {
+5 -5
src/types/frontmatter.rs
··· 49 49 /// --- 50 50 /// Title: LOL 51 51 /// Date: 2025-01-01 12:50:19 AM 52 - /// Tags: Daily barber 52 + /// Tags: @Daily @barber 53 53 /// --- 54 54 /// ``` 55 55 pub fn extract_from_file(path: impl AsRef<Path>) -> Result<(Self, Body)> { ··· 64 64 /// --- 65 65 /// Title: LOL 66 66 /// Date: 2025-01-01 12:50:19 AM 67 - /// Tags: Daily barber 67 + /// Tags: @Daily @barber 68 68 /// --- 69 69 /// ``` 70 70 pub fn extract_from_str(string: impl Into<String>) -> Result<(Self, Body)> { ··· 108 108 .strip_prefix("Tags: ") 109 109 .ok_or_else(|| eyre!("Tag line doesn't start with \"Tags: \" ".to_owned(),))? 110 110 .split_whitespace() 111 - .map(ToOwned::to_owned) 111 + .filter_map(|tag| tag.strip_prefix("@").map(ToOwned::to_owned)) 112 112 .collect::<Vec<_>>(); 113 113 114 114 delim_check(4)?; ··· 127 127 write!(f, "Tags: ")?; 128 128 129 129 for tag in &self.tag_strings { 130 - write!(f, "{tag} ")?; 130 + write!(f, "@{tag} ")?; 131 131 } 132 132 133 133 writeln!(f, "\n---") ··· 149 149 r"--- 150 150 Title: LOL 151 151 Date: 2025-01-01 12:50:19 AM 152 - Tags: whoa barber 152 + Tags: @whoa @barber 153 153 --- 154 154 ", 155 155 (