feat: experimental first rust implementation of libivy

This commit is contained in:
Ade Attwood 2022-08-13 15:09:48 +01:00
parent 7439ff98b0
commit e7b7dc1e4c
9 changed files with 462 additions and 1 deletions

1
.gitignore vendored
View file

@ -1,4 +1,5 @@
build
target
.cache
compile_commands.json
.luacheckcache

196
Cargo.lock generated Normal file
View file

@ -0,0 +1,196 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
dependencies = [
"memchr",
]
[[package]]
name = "bstr"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
dependencies = [
"memchr",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam-utils"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51887d4adc7b564537b15adcfb307936f8075dfcd5f00dde9a9f1d29383682bc"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "fuzzy-matcher"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94"
dependencies = [
"thread_local",
]
[[package]]
name = "globset"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a"
dependencies = [
"aho-corasick",
"bstr",
"fnv",
"log",
"regex",
]
[[package]]
name = "ignore"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d"
dependencies = [
"crossbeam-utils",
"globset",
"lazy_static",
"log",
"memchr",
"regex",
"same-file",
"thread_local",
"walkdir",
"winapi-util",
]
[[package]]
name = "ivy"
version = "0.0.1"
dependencies = [
"fuzzy-matcher",
"ignore",
"lazy_static",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "thread_local"
version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
dependencies = [
"once_cell",
]
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

17
Cargo.toml Normal file
View file

@ -0,0 +1,17 @@
[package]
name = "ivy"
version = "0.0.1"
edition = "2021"
[lib]
name = "ivyrs"
crate-type = ["cdylib"]
path = "rust/lib.rs"
[dependencies]
ignore = "0.4"
fuzzy-matcher = "0.3.7"
lazy_static = "1.4.0"
[profile.release]
opt-level = 3

View file

@ -1,6 +1,6 @@
local library_path = (function()
local dirname = string.sub(debug.getinfo(1).source, 2, #"/fzf_lib.lua" * -1)
return dirname .. "/../../build/Release/lib/libivy.so"
return dirname .. "/../../target/release/libivyrs.so"
end)()
local ffi = require "ffi"

26
rust/finder.rs Normal file
View file

@ -0,0 +1,26 @@
use ignore::WalkBuilder;
use std::fs;
pub struct Options {
pub directory: String,
}
pub fn find_files(options: Options) -> Vec<String> {
let mut files: Vec<String> = Vec::new();
let base_path = &fs::canonicalize(options.directory).unwrap();
let mut builder = WalkBuilder::new(base_path);
builder.ignore(true).hidden(true);
for result in builder.build() {
let absolute_candidate = result.unwrap();
let candidate_path = absolute_candidate.path().strip_prefix(base_path).unwrap();
if candidate_path.is_dir() {
continue;
}
files.push(candidate_path.to_str().unwrap().to_string());
}
return files;
}

68
rust/lib.rs Normal file
View file

@ -0,0 +1,68 @@
mod matcher;
mod finder;
mod sorter;
mod thread_pool;
use std::sync::Mutex;
use std::collections::HashMap;
use std::os::raw::{c_int, c_char};
use std::ffi::CString;
use std::ffi::CStr;
#[macro_use]
extern crate lazy_static;
lazy_static! {
static ref GLOBAL_FILE_CACHE: Mutex<HashMap<String, Vec<String>>> = return Mutex::new(HashMap::new()) ;
}
fn to_string(input: *const c_char) -> String {
return unsafe { CStr::from_ptr(input) }.to_str().unwrap().to_string();
}
fn get_files(directory: &String) -> Vec<String> {
let mut cache = GLOBAL_FILE_CACHE.lock().unwrap();
if !cache.contains_key(directory) {
let finder_options = finder::Options{ directory: directory.clone() };
cache.insert( directory.clone(), finder::find_files(finder_options));
}
return cache.get(directory).unwrap().to_vec();
}
#[no_mangle]
pub extern "C" fn ivy_init() {}
#[no_mangle]
pub extern "C" fn ivy_match(c_pattern: *const c_char, c_text: *const c_char) -> c_int {
let pattern = to_string(c_pattern);
let text = to_string(c_text);
let m = matcher::Matcher{ pattern };
return m.score(text) as i32;
}
#[no_mangle]
pub extern "C" fn ivy_files(c_pattern: *const c_char, c_base_dir: *const c_char) -> *const c_char {
let pattern = to_string(c_pattern);
let directory = to_string(c_base_dir);
// Bail out early if the pattern is empty its never going to find anything
if pattern.is_empty() {
return CString::new("").unwrap().into_raw()
}
let files = get_files(&directory);
let mut output = String::new();
let sorter_options = sorter::Options::new(pattern);
let files = sorter::sort_strings(sorter_options, files);
for file in files.lock().unwrap().iter() {
output.push_str(&file.content);
output.push('\n');
}
return CString::new(output).unwrap().into_raw()
}

18
rust/matcher.rs Normal file
View file

@ -0,0 +1,18 @@
use fuzzy_matcher::FuzzyMatcher;
use fuzzy_matcher::skim::SkimMatcherV2;
pub struct Matcher {
/// The search pattern that we want to match against some text
pub pattern: String,
}
impl Matcher {
pub fn score(self: &Self, text: String) -> i64 {
let matcher = SkimMatcherV2::default();
if let Some((score, _indices)) = matcher.fuzzy_indices(&text, &self.pattern) {
return score;
}
return 0;
}
}

48
rust/sorter.rs Normal file
View file

@ -0,0 +1,48 @@
use super::matcher;
use super::thread_pool;
use std::sync::Mutex;
use std::sync::Arc;
pub struct Match {
pub score: i64,
pub content: String,
}
pub struct Options {
pub pattern: String,
pub minimun_score: i64,
}
impl Options {
pub fn new(pattern: String) -> Self {
return Self { pattern, minimun_score: 20 };
}
}
pub fn sort_strings(options: Options, strings: Vec<String>) -> Arc<Mutex<Vec<Match>>> {
let matches: Arc<Mutex<Vec<Match>>> = Arc::new(Mutex::new(Vec::new()));
let matcher = Arc::new(Mutex::new(matcher::Matcher{ pattern: options.pattern }));
let pool = thread_pool::ThreadPool::new(std::thread::available_parallelism().unwrap().get());
for string in strings {
let thread_matcher = Arc::clone(&matcher);
let thread_matches = Arc::clone(&matches);
pool.execute(move || {
let score = thread_matcher.lock().unwrap().score(string.to_string());
if score > 25 {
let mut tmp = thread_matches.lock().unwrap();
let content = string.clone();
tmp.push(Match{ score, content });
}
})
}
drop(pool);
matches.lock().unwrap().sort_by(|a, b| a.score.cmp(&b.score));
return matches;
}

87
rust/thread_pool.rs Normal file
View file

@ -0,0 +1,87 @@
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
enum Message {
NewJob(Job),
Terminate,
}
pub struct ThreadPool {
jobs: mpsc::Sender<Message>,
threads: Vec<Worker>,
}
trait FnBox {
fn call_box(self: Box<Self>);
}
impl<F: FnOnce()> FnBox for F {
fn call_box(self: Box<F>) {
(*self)()
}
}
type Job = Box<dyn FnBox + Send + 'static>;
impl ThreadPool {
pub fn new(thread_count: usize) -> Self {
let (jobs, receiver) = mpsc::channel();
let receiver = Arc::new(Mutex::new(receiver));
let mut threads: Vec<Worker> = Vec::new();
for id in 1..thread_count {
threads.push(Worker::new(id, Arc::clone(&receiver)));
}
return ThreadPool { jobs, threads };
}
pub fn execute<F>(&self, f: F)
where
F: FnOnce() + Send + 'static,
{
let job = Box::new(f);
self.jobs.send(Message::NewJob(job)).unwrap();
}
}
impl Drop for ThreadPool {
fn drop(&mut self) {
for _ in &mut self.threads {
self.jobs.send(Message::Terminate).unwrap();
}
for worker in &mut self.threads {
if let Some(thread) = worker.thread.take() {
thread.join().unwrap();
}
}
}
}
struct Worker {
id: usize,
thread: Option<thread::JoinHandle<()>>,
}
impl Worker {
fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {
let thread = thread::spawn(move || loop {
let message = receiver.lock().unwrap().recv().unwrap();
match message {
Message::NewJob(job) => job.call_box(),
Message::Terminate => {
break;
}
}
});
return Worker {
id,
thread: Some(thread),
};
}
}