feat: experimental first rust implementation of libivy
This commit is contained in:
parent
7439ff98b0
commit
e7b7dc1e4c
9 changed files with 462 additions and 1 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,4 +1,5 @@
|
|||
build
|
||||
target
|
||||
.cache
|
||||
compile_commands.json
|
||||
.luacheckcache
|
||||
196
Cargo.lock
generated
Normal file
196
Cargo.lock
generated
Normal 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
17
Cargo.toml
Normal 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
|
||||
|
|
@ -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
26
rust/finder.rs
Normal 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
68
rust/lib.rs
Normal 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
18
rust/matcher.rs
Normal 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
48
rust/sorter.rs
Normal 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
87
rust/thread_pool.rs
Normal 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),
|
||||
};
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue