-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a9bd6b5
commit d395f66
Showing
13 changed files
with
599 additions
and
215 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
use std::path::Path; | ||
|
||
use nucleo_matcher::{pattern::{self, Normalization}, Matcher}; | ||
use vault::Vault; | ||
use rayon::prelude::*; | ||
|
||
pub(crate) 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::<Vec<_>>(); | ||
|
||
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() | ||
} | ||
} | ||
|
||
|
||
pub trait Matchable { | ||
fn match_string(&self) -> &str; | ||
} | ||
|
||
|
||
struct NucleoMatchable<T: Matchable>(T); | ||
impl<T: Matchable> Deref for NucleoMatchable<T> { | ||
type Target = T; | ||
fn deref(&self) -> &Self::Target { | ||
&self.0 | ||
} | ||
} | ||
|
||
impl<T: Matchable> AsRef<str> for NucleoMatchable<T> { | ||
fn as_ref(&self) -> &str { | ||
self.match_string() | ||
} | ||
} | ||
|
||
|
||
|
||
use crate::parser::{FileRef, Parser}; | ||
pub fn fuzzy_match<'a, T: Matchable>( | ||
filter_text: &str, | ||
items: impl IntoIterator<Item = T>, | ||
) -> 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() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,168 +1,108 @@ | ||
#![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<CompletionResponse> { | ||
|
||
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<CompletionResponse> { | ||
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<CompletionResponse> { | ||
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"\[\[(?<file_ref>.*?)\]\]").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<usize> | ||
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::<Vec<_>>(); | ||
|
||
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<Item = NamedEntity>, | ||
) -> CompletionResponse { | ||
let items = named_entities | ||
.take(20) | ||
.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::<Vec<_>>(); | ||
|
||
dbg!(&items); | ||
|
||
pub trait Matchable { | ||
fn match_string(&self) -> &str; | ||
CompletionResponse::List(CompletionList { | ||
is_incomplete: true, | ||
items, | ||
}) | ||
} | ||
|
||
|
||
struct NucleoMatchable<T: Matchable>(T); | ||
impl<T: Matchable> Deref for NucleoMatchable<T> { | ||
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<T: Matchable> AsRef<str> for NucleoMatchable<T> { | ||
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<Item = T>, | ||
) -> 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<Item = &Path>) -> 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::<Vec<_>>(); | ||
|
||
CompletionResponse::List(CompletionList { | ||
is_incomplete: true, | ||
items | ||
}) | ||
} | ||
|
Oops, something went wrong.