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
|
build
|
||||||
|
target
|
||||||
.cache
|
.cache
|
||||||
compile_commands.json
|
compile_commands.json
|
||||||
.luacheckcache
|
.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 library_path = (function()
|
||||||
local dirname = string.sub(debug.getinfo(1).source, 2, #"/fzf_lib.lua" * -1)
|
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)()
|
end)()
|
||||||
|
|
||||||
local ffi = require "ffi"
|
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