diff --git a/TestFiles/Test.md b/TestFiles/Test.md index a632e02f..96f5ff26 100644 --- a/TestFiles/Test.md +++ b/TestFiles/Test.md @@ -12,6 +12,16 @@ Configure your daily note formatting in the config file today is 2024-03-27 +[[testTest.md#Heading 1 + +[[Test#Heading 1]] + +[[TestTesttest]] + +[[Test#Heading 1]] + +[[Random File#Heading 1]] + [next Tuesday](2024-04-02) [tomorrow](2024-03-28) diff --git a/crates/completions/src/lib.rs b/crates/completions/src/lib.rs index d449ecac..07de570b 100644 --- a/crates/completions/src/lib.rs +++ b/crates/completions/src/lib.rs @@ -1,168 +1,107 @@ #![feature(anonymous_lifetime_in_impl_trait)] -use std::{ops::{Deref, Range}, path::{Path, PathBuf}}; +mod parser; +mod querier; +use std::{ + ops::{Deref, Range}, + path::{Path, PathBuf}, +}; + +use moxide_config::Settings; +use parser::{LinkInfo, Parser}; +use querier::{NamedEntity, NamedEntityInfo, Querier}; use rayon::prelude::*; -use regex::Regex; -use tower_lsp::lsp_types::{CompletionItem, CompletionList, CompletionParams, CompletionResponse}; +use tower_lsp::lsp_types::{ + CompletionItem, CompletionList, CompletionParams, CompletionResponse, CompletionTextEdit, + TextEdit, +}; use vault::Vault; -use moxide_config::Settings; - pub fn get_completions( vault: &Vault, - _files: &Box<[PathBuf]>, + _files: &[PathBuf], params: &CompletionParams, path: &Path, - _settings: &Settings + _settings: &Settings, ) -> Option { - let parser = Parser::new(vault); let querier = Querier::new(vault); let location = Location { path, line: params.text_document_position.position.line as usize, - character: params.text_document_position.position.character as usize + character: params.text_document_position.position.character as usize, }; - dbg!(completions(&parser, &querier, location)) -} - -fn completions(parser: &Parser, querier: &Querier, location: Location) -> Option { - let (file_ref, link_info) = parser.parse_link(location)?; - let files = querier.query(file_ref); - Some(to_completion_response(&link_info, files.into_par_iter())) + completions(&parser, &querier, location) } +fn completions( + parser: &Parser, + querier: &Querier, + location: Location, +) -> Option { + let (link, link_info) = parser.parse_link(location)?; -struct Parser<'a> { - vault: &'a Vault -} - -impl<'a> Parser<'a> { - fn new(vault: &'a Vault) -> Self { - Self { vault } - } -} - -impl Parser<'_> { - fn parse_link(&self, location: Location) -> Option<(FileRef, LinkInfo)> { - - let chars = self.vault.select_line(location.path, location.line as isize)?; - let line_string = String::from_iter(chars); - - let re = Regex::new(r"\[\[(?.*?)\]\]").expect("Regex failed to compile"); - - let c = re.captures_iter(&line_string) - .next()?; - let file_ref = c.name("file_ref")?.as_str(); - let char_range = c.get(0)?.start()..c.get(0)?.end(); - - Some((file_ref.to_string(), LinkInfo {char_range, line: location.line})) + let named_entities = querier.query(link); - } -} - -type FileRef = String; -struct LinkInfo { - line: usize, - char_range: Range + Some(to_completion_response(&link_info, named_entities)) } struct Location<'fs> { path: &'fs Path, line: usize, - character: usize -} - -struct Querier<'a> { - vault: &'a Vault -} - -impl<'a> Querier<'a> { - fn new(vault: &'a Vault) -> Self { - Self { vault } - } -} - -impl<'a> Querier<'a> { - fn query(&self, file_ref: FileRef) -> Vec<&'a Path> { - let paths = self.vault.md_files - .keys() - .map(|key| (key.file_name().unwrap().to_str().unwrap().to_string(), key)) - .collect::>(); - - let matched = fuzzy_match(&file_ref, paths); - matched.into_par_iter() - .map(|((_, path), _)| path as &Path) - .collect() - } -} - -impl<'a> Matchable for (String, &'a PathBuf) { - fn match_string(&self) -> &str { - self.0.as_str() - } -} + character: usize, +} + +fn to_completion_response( + info: &LinkInfo, + named_entities: impl IndexedParallelIterator, +) -> CompletionResponse { + let items = named_entities + .enumerate() + .flat_map(|(i, entity)| { + let label = entity_to_label(&entity); + + Some(CompletionItem { + label: label.clone(), + sort_text: Some(i.to_string()), + text_edit: Some(CompletionTextEdit::Edit(TextEdit { + range: tower_lsp::lsp_types::Range { + start: tower_lsp::lsp_types::Position { + line: info.line as u32, + character: info.char_range.start as u32, + }, + end: tower_lsp::lsp_types::Position { + line: info.line as u32, + character: info.char_range.end as u32, + }, + }, + new_text: format!("[[{label}]]"), + })), + filter_text: Some(format!("[[{label}")), + ..Default::default() + }) + }) + .collect::>(); + dbg!(&items); -pub trait Matchable { - fn match_string(&self) -> &str; + CompletionResponse::List(CompletionList { + is_incomplete: true, + items, + }) } - -struct NucleoMatchable(T); -impl Deref for NucleoMatchable { - type Target = T; - fn deref(&self) -> &Self::Target { - &self.0 - } +fn named_entity_file_ref(entity: &NamedEntity) -> String { + entity.0.file_stem().unwrap().to_str().unwrap().to_string() } -impl AsRef for NucleoMatchable { - fn as_ref(&self) -> &str { - self.match_string() +fn entity_to_label(entity: &NamedEntity) -> String { + let file_ref = named_entity_file_ref(entity); // TODO: abstract this better; there is possible duplication in querier + match entity { + NamedEntity(_, querier::NamedEntityInfo::File) => file_ref.to_string(), + NamedEntity(_, NamedEntityInfo::Heading(heading)) => format!("{file_ref}#{heading}"), + NamedEntity(_, NamedEntityInfo::IndexedBlock(index)) => format!("{file_ref}#^{index}"), } } - - -use nucleo_matcher::{pattern::{self, Normalization}, Matcher}; -pub fn fuzzy_match<'a, T: Matchable>( - filter_text: &str, - items: impl IntoIterator, -) -> Vec<(T, u32)> { - let items = items.into_iter().map(NucleoMatchable); - - let mut matcher = Matcher::new(nucleo_matcher::Config::DEFAULT); - let matches = pattern::Pattern::parse( - filter_text, - pattern::CaseMatching::Smart, - Normalization::Smart, - ) - .match_list(items, &mut matcher); - - matches - .into_iter() - .map(|(item, score)| (item.0, score)) - .collect() -} - - - - -fn to_completion_response(info: &LinkInfo, files: impl IndexedParallelIterator) -> CompletionResponse { - let items = files.enumerate() - .flat_map(|(i, path)| Some((i, path.file_name()?.to_str()?))) - .flat_map(|(i, name)| Some(CompletionItem { - label: name.to_string(), - sort_text: Some(i.to_string()), - ..Default::default() - })) - .collect::>(); - - CompletionResponse::List(CompletionList { - is_incomplete: true, - items - }) -} - diff --git a/crates/markdown-oxide/src/codeactions.rs b/crates/markdown-oxide/src/codeactions.rs index 2bb3c784..aa23152d 100644 --- a/crates/markdown-oxide/src/codeactions.rs +++ b/crates/markdown-oxide/src/codeactions.rs @@ -7,11 +7,7 @@ use tower_lsp::lsp_types::{ Position, Range, ResourceOp, TextDocumentEdit, TextEdit, Url, WorkspaceEdit, }; -use crate::{ - daily::filename_is_formatted, - diagnostics::path_unresolved_references, - -}; +use crate::{daily::filename_is_formatted, diagnostics::path_unresolved_references}; use moxide_config::Settings; diff --git a/crates/markdown-oxide/src/commands.rs b/crates/markdown-oxide/src/commands.rs index f0c7b61f..ae3b197f 100644 --- a/crates/markdown-oxide/src/commands.rs +++ b/crates/markdown-oxide/src/commands.rs @@ -1,10 +1,10 @@ use std::fs::File; use std::path::Path; -use moxide_config::Settings; use chrono::offset::Local; use chrono::NaiveDateTime; use fuzzydate::parse; +use moxide_config::Settings; use serde_json::Value; use tower_lsp::jsonrpc::{Error, Result}; use tower_lsp::lsp_types::{MessageType, ShowDocumentParams, Url}; diff --git a/crates/markdown-oxide/src/hover.rs b/crates/markdown-oxide/src/hover.rs index f955fe50..3390b3b1 100644 --- a/crates/markdown-oxide/src/hover.rs +++ b/crates/markdown-oxide/src/hover.rs @@ -2,9 +2,7 @@ use std::path::Path; use tower_lsp::lsp_types::{Hover, HoverContents, HoverParams}; -use crate::{ - ui::{preview_reference, preview_referenceable}, -}; +use crate::ui::{preview_reference, preview_referenceable}; use vault::Vault; diff --git a/crates/markdown-oxide/src/main.rs b/crates/markdown-oxide/src/main.rs index 16ddbc8e..383adfcf 100644 --- a/crates/markdown-oxide/src/main.rs +++ b/crates/markdown-oxide/src/main.rs @@ -3,9 +3,9 @@ use std::ops::{Deref, DerefMut}; use std::path::PathBuf; use std::sync::Arc; -use moxide_config::Settings; use diagnostics::diagnostics; use itertools::Itertools; +use moxide_config::Settings; use rayon::prelude::*; use references::references; use serde_json::Value; @@ -19,7 +19,6 @@ use tower_lsp::lsp_types::*; use tower_lsp::{Client, LanguageServer, LspService, Server}; use vault::Vault; - mod codeactions; mod codelens; mod commands; @@ -554,7 +553,11 @@ impl LanguageServer for Backend { }; // TODO: this is bad let res = self - .bind_vault(|vault| Ok(completions::get_completions(vault, &files, ¶ms, &path, &settings))) + .bind_vault(|vault| { + Ok(completions::get_completions( + vault, &files, ¶ms, &path, &settings, + )) + }) .await; let elapsed = timer.elapsed(); diff --git a/crates/markdown-oxide/src/symbol.rs b/crates/markdown-oxide/src/symbol.rs index 83d6d771..9e1c5053 100644 --- a/crates/markdown-oxide/src/symbol.rs +++ b/crates/markdown-oxide/src/symbol.rs @@ -128,9 +128,7 @@ fn map_to_lsp_tree(tree: Vec) -> Vec { #[cfg(test)] mod test { - use crate::{ - symbol, - }; + use crate::symbol; use vault::{HeadingLevel, MDHeading}; diff --git a/crates/vault/src/lib.rs b/crates/vault/src/lib.rs index 476f2bf0..c3291540 100644 --- a/crates/vault/src/lib.rs +++ b/crates/vault/src/lib.rs @@ -317,6 +317,13 @@ impl Vault { .map(|slice| slice.chars().collect_vec()) } + pub fn select_line_str<'a>(&'a self, path: &Path, line: usize) -> Option<&'a str> { + let rope = self.ropes.get(path)?; + + let line = rope.get_line(line)?; + line.as_str() + } + pub fn select_headings(&self, path: &Path) -> Option<&Vec> { let md_file = self.md_files.get(path)?; let headings = &md_file.headings; diff --git a/flake.lock b/flake.lock index fa8830b5..5e3ba699 100644 --- a/flake.lock +++ b/flake.lock @@ -1,33 +1,36 @@ { "nodes": { - "flake-utils": { + "fenix": { "inputs": { - "systems": "systems" + "nixpkgs": [ + "nixpkgs" + ], + "rust-analyzer-src": "rust-analyzer-src" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "lastModified": 1717827974, + "narHash": "sha256-ixopuTeTouxqTxfMuzs6IaRttbT8JqRW5C9Q/57WxQw=", + "owner": "nix-community", + "repo": "fenix", + "rev": "ab655c627777ab5f9964652fe23bbb1dfbd687a8", "type": "github" }, "original": { - "owner": "numtide", - "repo": "flake-utils", + "owner": "nix-community", + "repo": "fenix", "type": "github" } }, - "flake-utils_2": { + "flake-utils": { "inputs": { - "systems": "systems_2" + "systems": "systems" }, "locked": { - "lastModified": 1705309234, - "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", + "lastModified": 1710146030, + "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26", + "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", "type": "github" }, "original": { @@ -52,45 +55,27 @@ "type": "github" } }, - "nixpkgs_2": { - "locked": { - "lastModified": 1706487304, - "narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "90f456026d284c22b3e3497be980b2e47d0b28ac", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, "root": { "inputs": { + "fenix": "fenix", "flake-utils": "flake-utils", - "nixpkgs": "nixpkgs", - "rust-overlay": "rust-overlay" + "nixpkgs": "nixpkgs" } }, - "rust-overlay": { - "inputs": { - "flake-utils": "flake-utils_2", - "nixpkgs": "nixpkgs_2" - }, + "rust-analyzer-src": { + "flake": false, "locked": { - "lastModified": 1716257780, - "narHash": "sha256-R+NjvJzKEkTVCmdrKRfPE4liX/KMGVqGUwwS5H8ET8A=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "4e5e3d2c5c9b2721bd266f9e43c14e96811b89d2", + "lastModified": 1717583671, + "narHash": "sha256-+lRAmz92CNUxorqWusgJbL9VE1eKCnQQojglRemzwkw=", + "owner": "rust-lang", + "repo": "rust-analyzer", + "rev": "48bbdd6a74f3176987d5c809894ac33957000d19", "type": "github" }, "original": { - "owner": "oxalica", - "repo": "rust-overlay", + "owner": "rust-lang", + "ref": "nightly", + "repo": "rust-analyzer", "type": "github" } }, @@ -108,21 +93,6 @@ "repo": "default", "type": "github" } - }, - "systems_2": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 02740d17..f291e78a 100644 --- a/flake.nix +++ b/flake.nix @@ -3,25 +3,33 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; - rust-overlay.url = "github:oxalica/rust-overlay"; flake-utils.url = "github:numtide/flake-utils"; + fenix = { + url = "github:nix-community/fenix"; + inputs.nixpkgs.follows = "nixpkgs"; + }; }; - outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }: + outputs = { self, nixpkgs, flake-utils, fenix, ... }: flake-utils.lib.eachDefaultSystem (system: let - overlays = [ (import rust-overlay) ]; + overlays = [ fenix.overlays.default ]; pkgs = import nixpkgs { inherit system overlays; }; in - with pkgs; { - devShells.default = mkShell { + devShells.default = pkgs.mkShell { buildInputs = [ - openssl - pkg-config - rust-bin.nightly."2024-05-20".default + pkgs.openssl + pkgs.pkg-config + (pkgs.fenix.complete.withComponents [ + "cargo" + "clippy" + "rust-src" + "rustfmt" + ]) + pkgs.rust-analyzer-nightly ]; shellHook = ''