diff --git a/.gitignore b/.gitignore index 88eb5a1..e138ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +target .cache compile_commands.json .luacheckcache \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..6c62469 --- /dev/null +++ b/Cargo.lock @@ -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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..cd601b6 --- /dev/null +++ b/Cargo.toml @@ -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 diff --git a/lua/ivy/libivy.lua b/lua/ivy/libivy.lua index d6222fa..0b00c2f 100644 --- a/lua/ivy/libivy.lua +++ b/lua/ivy/libivy.lua @@ -1,29 +1,29 @@ 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" local ivy_c = ffi.load(library_path) ffi.cdef [[ void ivy_init(const char*); int ivy_match(const char*, const char*); char* ivy_files(const char*, const char*); ]] local libivy = {} libivy.ivy_init = function(dir) ivy_c.ivy_init(dir) end libivy.ivy_match = function(pattern, text) return ivy_c.ivy_match(pattern, text) end libivy.ivy_files = function(pattern, base_dir) return ffi.string(ivy_c.ivy_files(pattern, base_dir)) end return libivy diff --git a/rust/finder.rs b/rust/finder.rs new file mode 100644 index 0000000..4a3ccf5 --- /dev/null +++ b/rust/finder.rs @@ -0,0 +1,26 @@ +use ignore::WalkBuilder; +use std::fs; + +pub struct Options { + pub directory: String, +} + +pub fn find_files(options: Options) -> Vec { + let mut files: Vec = 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; +} diff --git a/rust/lib.rs b/rust/lib.rs new file mode 100644 index 0000000..52b861d --- /dev/null +++ b/rust/lib.rs @@ -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>> = 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 { + 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() +} + diff --git a/rust/matcher.rs b/rust/matcher.rs new file mode 100644 index 0000000..43d364d --- /dev/null +++ b/rust/matcher.rs @@ -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; + } +} diff --git a/rust/sorter.rs b/rust/sorter.rs new file mode 100644 index 0000000..3bb5c47 --- /dev/null +++ b/rust/sorter.rs @@ -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) -> Arc>> { + let matches: Arc>> = 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; +} + diff --git a/rust/thread_pool.rs b/rust/thread_pool.rs new file mode 100644 index 0000000..df49872 --- /dev/null +++ b/rust/thread_pool.rs @@ -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, + threads: Vec, +} + +trait FnBox { + fn call_box(self: Box); +} + +impl FnBox for F { + fn call_box(self: Box) { + (*self)() + } +} + +type Job = Box; + +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 = Vec::new(); + for id in 1..thread_count { + threads.push(Worker::new(id, Arc::clone(&receiver))); + } + + return ThreadPool { jobs, threads }; + } + + pub fn execute(&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>, +} + +impl Worker { + fn new(id: usize, receiver: Arc>>) -> 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), + }; + } +}