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 #10 from suri-codes/lsp

feat: init lsp

authored by

Surendra Jammishetti and committed by
GitHub
34b51e0e 7764623a

+291 -24
+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 ),
+106 -1
Cargo.lock
··· 759 759 ] 760 760 761 761 [[package]] 762 + name = "auto_impl" 763 + version = "1.3.0" 764 + source = "registry+https://github.com/rust-lang/crates.io-index" 765 + checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" 766 + dependencies = [ 767 + "proc-macro2", 768 + "quote", 769 + "syn 2.0.117", 770 + ] 771 + 772 + [[package]] 762 773 name = "autocfg" 763 774 version = "1.5.0" 764 775 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 1684 1695 1685 1696 [[package]] 1686 1697 name = "dashmap" 1698 + version = "5.5.3" 1699 + source = "registry+https://github.com/rust-lang/crates.io-index" 1700 + checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" 1701 + dependencies = [ 1702 + "cfg-if", 1703 + "hashbrown 0.14.5", 1704 + "lock_api", 1705 + "once_cell", 1706 + "parking_lot_core", 1707 + ] 1708 + 1709 + [[package]] 1710 + name = "dashmap" 1687 1711 version = "6.1.0" 1688 1712 source = "registry+https://github.com/rust-lang/crates.io-index" 1689 1713 checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" ··· 2315 2339 "tempfile", 2316 2340 "tokio", 2317 2341 "tokio-util", 2342 + "tower-lsp", 2318 2343 "tracing", 2319 2344 "tracing-error", 2320 2345 "tracing-subscriber", ··· 3282 3307 source = "registry+https://github.com/rust-lang/crates.io-index" 3283 3308 checksum = "ad89218e74850f42d364ed3877c7291f0474c8533502df91bb877ecc5cb0dd40" 3284 3309 dependencies = [ 3285 - "dashmap", 3310 + "dashmap 6.1.0", 3286 3311 "gix-fs", 3287 3312 "libc", 3288 3313 "parking_lot", ··· 3669 3694 ] 3670 3695 3671 3696 [[package]] 3697 + name = "httparse" 3698 + version = "1.10.1" 3699 + source = "registry+https://github.com/rust-lang/crates.io-index" 3700 + checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" 3701 + 3702 + [[package]] 3672 3703 name = "human-panic" 3673 3704 version = "2.0.6" 3674 3705 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 4295 4326 checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593" 4296 4327 dependencies = [ 4297 4328 "hashbrown 0.16.1", 4329 + ] 4330 + 4331 + [[package]] 4332 + name = "lsp-types" 4333 + version = "0.94.1" 4334 + source = "registry+https://github.com/rust-lang/crates.io-index" 4335 + checksum = "c66bfd44a06ae10647fe3f8214762e9369fd4248df1350924b4ef9e770a85ea1" 4336 + dependencies = [ 4337 + "bitflags 1.3.2", 4338 + "serde", 4339 + "serde_json", 4340 + "serde_repr", 4341 + "url", 4298 4342 ] 4299 4343 4300 4344 [[package]] ··· 7398 7442 checksum = "ab16f14aed21ee8bfd8ec22513f7287cd4a91aa92e44edfe2c17ddd004e92607" 7399 7443 7400 7444 [[package]] 7445 + name = "tower" 7446 + version = "0.4.13" 7447 + source = "registry+https://github.com/rust-lang/crates.io-index" 7448 + checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 7449 + dependencies = [ 7450 + "futures-core", 7451 + "futures-util", 7452 + "pin-project", 7453 + "pin-project-lite", 7454 + "tower-layer", 7455 + "tower-service", 7456 + ] 7457 + 7458 + [[package]] 7459 + name = "tower-layer" 7460 + version = "0.3.3" 7461 + source = "registry+https://github.com/rust-lang/crates.io-index" 7462 + checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 7463 + 7464 + [[package]] 7465 + name = "tower-lsp" 7466 + version = "0.20.0" 7467 + source = "registry+https://github.com/rust-lang/crates.io-index" 7468 + checksum = "d4ba052b54a6627628d9b3c34c176e7eda8359b7da9acd497b9f20998d118508" 7469 + dependencies = [ 7470 + "async-trait", 7471 + "auto_impl", 7472 + "bytes", 7473 + "dashmap 5.5.3", 7474 + "futures", 7475 + "httparse", 7476 + "lsp-types", 7477 + "memchr", 7478 + "serde", 7479 + "serde_json", 7480 + "tokio", 7481 + "tokio-util", 7482 + "tower", 7483 + "tower-lsp-macros", 7484 + "tracing", 7485 + ] 7486 + 7487 + [[package]] 7488 + name = "tower-lsp-macros" 7489 + version = "0.9.0" 7490 + source = "registry+https://github.com/rust-lang/crates.io-index" 7491 + checksum = "84fd902d4e0b9a4b27f2f440108dc034e1758628a9b702f8ec61ad66355422fa" 7492 + dependencies = [ 7493 + "proc-macro2", 7494 + "quote", 7495 + "syn 2.0.117", 7496 + ] 7497 + 7498 + [[package]] 7499 + name = "tower-service" 7500 + version = "0.3.3" 7501 + source = "registry+https://github.com/rust-lang/crates.io-index" 7502 + checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 7503 + 7504 + [[package]] 7401 7505 name = "tracing" 7402 7506 version = "0.1.44" 7403 7507 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 7600 7704 "idna", 7601 7705 "percent-encoding", 7602 7706 "serde", 7707 + "serde_derive", 7603 7708 ] 7604 7709 7605 7710 [[package]]
+1
Cargo.toml
··· 81 81 ratatui-textarea = "0.8.0" 82 82 nucleo-matcher = "0.3.1" 83 83 ron = "0.12.1" 84 + tower-lsp = "0.20.0" 84 85 85 86 [build-dependencies] 86 87 anyhow = "1.0.102"
+4 -5
flake.nix
··· 139 139 env = { 140 140 RUST_SRC_PATH = "${pkgs.rustToolchain}/lib/rustlib/src/rust/library"; 141 141 142 - # project specific vars 143 - FIL_CONFIG = "./.config"; 144 - FIL_DATA = "./.data"; 145 142 FIL_LOG_LEVEL = "DEBUG"; 146 143 }; 147 144 148 - # Add any shell logic you want executed any time the environment is activated 149 - shellHook = ""; 145 + shellHook = '' 146 + export FIL_CONFIG="$(pwd)/.config" 147 + export FIL_DATA="$(pwd)/.data" 148 + ''; 150 149 }; 151 150 } 152 151 );
+3
src/cli/mod.rs
··· 29 29 #[command(subcommand)] 30 30 Zettel(ZettelSubcommand), 31 31 32 + /// Spawn the `LSP` 33 + Lsp, 34 + 32 35 // / Manage TARS groups. 33 36 // #[command(subcommand)] 34 37 // Group(GroupSubcommand),
+15
src/cli/process.rs
··· 5 5 }; 6 6 7 7 use color_eyre::eyre::{Context, Result}; 8 + use tower_lsp::{LspService, Server}; 8 9 9 10 use crate::{ 10 11 cli::{Commands, ZettelSubcommand}, 11 12 config::{Config, get_config_dir}, 13 + lsp::Backend, 12 14 types::{Kasten, Zettel}, 13 15 }; 14 16 ··· 53 55 } 54 56 ZettelSubcommand::List { by_tag: _by_tag } => {} 55 57 } 58 + } 59 + Self::Lsp => { 60 + let conf = Config::parse().with_context(|| "Failed to parse the config!!")?; 61 + let kt = Kasten::instansiate(conf.fil_dir) 62 + .await 63 + .with_context(|| "Failed to initialize a kasten!!")?; 64 + 65 + let stdin = tokio::io::stdin(); 66 + let stdout = tokio::io::stdout(); 67 + 68 + let (service, socket) = LspService::new(|client| Backend::new(client, kt)); 69 + 70 + Server::new(stdin, stdout, socket).serve(service).await; 56 71 } 57 72 } 58 73
+10 -4
src/config/mod.rs
··· 5 5 sync::LazyLock, 6 6 }; 7 7 8 - use color_eyre::eyre::Result; 8 + use color_eyre::eyre::{Context, Result}; 9 9 use directories::ProjectDirs; 10 10 use ron::ser::PrettyConfig; 11 11 ··· 63 63 pub fn parse() -> Result<Self> { 64 64 let ron: RonConfig = { 65 65 let file_path = get_config_dir().join("config.ron"); 66 - ron::from_str(&read_to_string(file_path)?)? 66 + ron::from_str(&read_to_string(&file_path).with_context(|| { 67 + format!("Failed to read config from path: {}", file_path.display()) 68 + })?)? 67 69 }; 68 70 69 - let keymap = KeyMap::try_from(&ron)?; 71 + let keymap = 72 + KeyMap::try_from(&ron).with_context(|| "Unable to parse keymap from config!")?; 70 73 71 74 Ok(Self { 72 - fil_dir: ron.directory.canonicalize()?, 75 + fil_dir: ron 76 + .directory 77 + .canonicalize() 78 + .with_context(|| "Failed to canonicalize the directory provided in the config!")?, 73 79 keymap, 74 80 }) 75 81 }
+129
src/lsp/mod.rs
··· 1 + use color_eyre::eyre::eyre; 2 + use dto::{TagEntity, ZettelEntity}; 3 + use tower_lsp::{ 4 + Client, LanguageServer, 5 + jsonrpc::{self, Result}, 6 + lsp_types::{ 7 + CompletionItem, CompletionOptions, CompletionParams, CompletionResponse, InitializeParams, 8 + InitializeResult, InitializedParams, MessageType, ServerCapabilities, 9 + TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, 10 + }, 11 + }; 12 + 13 + use crate::types::{Kasten, Tag, Zettel, ZettelId}; 14 + 15 + #[derive(Debug)] 16 + pub struct Backend { 17 + client: Client, 18 + kt: Kasten, 19 + } 20 + 21 + impl Backend { 22 + pub const fn new(client: Client, kt: Kasten) -> Self { 23 + Self { client, kt } 24 + } 25 + } 26 + 27 + #[tower_lsp::async_trait] 28 + impl LanguageServer for Backend { 29 + async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> { 30 + Ok(InitializeResult { 31 + capabilities: ServerCapabilities { 32 + text_document_sync: Some(TextDocumentSyncCapability::Options( 33 + TextDocumentSyncOptions { 34 + open_close: Some(true), 35 + change: Some(TextDocumentSyncKind::FULL), 36 + ..Default::default() 37 + }, 38 + )), 39 + completion_provider: Some(CompletionOptions { 40 + resolve_provider: Some(false), 41 + trigger_characters: Some(vec!["[[".to_string(), "@".to_string()]), 42 + ..Default::default() 43 + }), 44 + ..ServerCapabilities::default() 45 + }, 46 + ..Default::default() 47 + }) 48 + } 49 + 50 + async fn initialized(&self, _: InitializedParams) { 51 + self.client 52 + .log_message(MessageType::INFO, "server initialized!") 53 + .await; 54 + } 55 + 56 + async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> { 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 66 + .text_document_position 67 + .text_document 68 + .uri 69 + .to_file_path() 70 + .map_err(|()| eyre!("failed to turn into file path")) 71 + .and_then(TryInto::try_into) 72 + else { 73 + return Ok(None); 74 + }; 75 + 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(); 86 + 87 + // we dont want to return links to ourself 88 + if z.id == zid { 89 + return None; 90 + } 91 + 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(); 98 + 99 + Some(CompletionItem::new_simple(item, detail)) 100 + }) 101 + .collect(), 102 + 103 + // Tag completion 104 + "@" => TagEntity::load() 105 + .all(&self.kt.db) 106 + .await 107 + .map_err(|_| jsonrpc::Error::internal_error())? 108 + .into_iter() 109 + .map(|model| { 110 + let t: Tag = model.into(); 111 + 112 + let item = t.name.clone(); 113 + 114 + let detail = format!("{}\ncolor: {}", t.name, t.color); 115 + 116 + CompletionItem::new_simple(item, detail) 117 + }) 118 + .collect(), 119 + 120 + _ => return Ok(None), 121 + }; 122 + 123 + return Ok(Some(CompletionResponse::Array(responses))); 124 + } 125 + 126 + async fn shutdown(&self) -> Result<()> { 127 + Ok(()) 128 + } 129 + }
+4 -4
src/main.rs
··· 16 16 use tracing::debug; 17 17 18 18 mod cli; 19 - mod gui; 20 - mod tui; 21 - mod types; 22 - 23 19 mod config; 24 20 mod errors; 21 + mod gui; 25 22 mod logging; 23 + mod lsp; 24 + mod tui; 25 + mod types; 26 26 27 27 fn main() -> color_eyre::Result<()> { 28 28 errors::init()?;
+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 (