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 #13 from suri-codes/feat/goto

feat: goto-defenition for links!

authored by

Surendra Jammishetti and committed by
GitHub
8aaf1a82 dd56f4d5

+79 -5
+79 -5
src/lsp/mod.rs
··· 1 + use std::fs; 2 + 1 3 use color_eyre::eyre::eyre; 2 - use dto::{TagEntity, ZettelEntity}; 4 + use dto::{NanoId, TagEntity, ZettelEntity}; 3 5 use tower_lsp::{ 4 6 Client, LanguageServer, 5 7 jsonrpc::{self, Result}, 6 8 lsp_types::{ 7 - CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, InitializeParams, 8 - InitializeResult, InitializedParams, MessageType, ServerCapabilities, 9 - TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, 9 + CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, 10 + GotoDefinitionParams, GotoDefinitionResponse, InitializeParams, InitializeResult, 11 + InitializedParams, Location, MessageType, OneOf, Range, ServerCapabilities, 12 + TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Url, 10 13 }, 11 14 }; 12 15 ··· 41 44 trigger_characters: Some(vec!["[[".to_string(), "@".to_string()]), 42 45 ..Default::default() 43 46 }), 47 + definition_provider: Some(OneOf::Left(true)), 48 + 44 49 ..ServerCapabilities::default() 45 50 }, 46 51 ..Default::default() ··· 54 59 } 55 60 56 61 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { 57 - eprintln!("WHATTDAFUCKKK"); 58 62 let Some(trigger) = params 59 63 .context 60 64 .as_ref() ··· 127 131 async fn shutdown(&self) -> Result<()> { 128 132 Ok(()) 129 133 } 134 + 135 + async fn goto_definition( 136 + &self, 137 + params: GotoDefinitionParams, 138 + ) -> Result<Option<GotoDefinitionResponse>> { 139 + let Ok(file_contents) = params 140 + .text_document_position_params 141 + .text_document 142 + .uri 143 + .to_file_path() 144 + .and_then(|path| fs::read_to_string(path).map_err(|_| ())) 145 + else { 146 + return Ok(None); 147 + }; 148 + 149 + let line_idx = params.text_document_position_params.position.line; 150 + let char_idx = params.text_document_position_params.position.character; 151 + 152 + let Some(line) = file_contents.lines().nth(line_idx as usize) else { 153 + return Ok(None); 154 + }; 155 + 156 + let id = extract_wikilink_id_at(line, char_idx as usize); 157 + 158 + match id { 159 + Some(id) => { 160 + let zod = self.kt.index.get_zod(&ZettelId::from(NanoId::from(id))); 161 + 162 + let uri = Url::parse(&format!("file:///{}", zod.path.display())) 163 + .map_err(|e| tower_lsp::jsonrpc::Error::invalid_params(e.to_string()))?; 164 + 165 + Ok(Some(GotoDefinitionResponse::Scalar(Location { 166 + uri, 167 + range: Range::default(), 168 + }))) 169 + } 170 + None => Ok(None), 171 + } 172 + } 173 + } 174 + 175 + /// Returns the `<id>` portion of a `[[<id> | <title>]]` wikilink if the 176 + /// cursor (0-based char index) falls anywhere inside the `[[ ... ]]` span. 177 + /// Also handles `[[<id>]]` (no pipe / title). 178 + /// 179 + // helper function written by claude. I was lowkey too lazy to write this logic xd 180 + fn extract_wikilink_id_at(line: &str, char_idx: usize) -> Option<&str> { 181 + let mut search_start = 0; 182 + 183 + while let Some(open) = line[search_start..].find("[[") { 184 + let open = open + search_start; 185 + let rest = &line[open + 2..]; 186 + 187 + let Some(close) = rest.find("]]") else { 188 + break; // unclosed bracket — stop 189 + }; 190 + let close_abs = open + 2 + close; // index of first `]` 191 + 192 + // Is the cursor inside [[ ... ]] ? 193 + if char_idx >= open && char_idx < close_abs + 2 { 194 + let inner = &line[open + 2..close_abs]; // text between [[ and ]] 195 + // id is everything before the first `|`, trimmed 196 + let id = inner.split('|').next().unwrap_or(inner).trim(); 197 + return Some(id); 198 + } 199 + 200 + search_start = close_abs + 2; // advance past this `]]` 201 + } 202 + 203 + None 130 204 }