Skip to content

Commit

Permalink
support headings and indexed blocks
Browse files Browse the repository at this point in the history
  • Loading branch information
Feel-ix-343 committed Jun 13, 2024
1 parent a9bd6b5 commit d395f66
Show file tree
Hide file tree
Showing 13 changed files with 599 additions and 215 deletions.
78 changes: 78 additions & 0 deletions '
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()
}
10 changes: 10 additions & 0 deletions TestFiles/Test.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
206 changes: 73 additions & 133 deletions crates/completions/src/lib.rs
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
})
}

Loading

0 comments on commit d395f66

Please sign in to comment.