Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 863af1ce7b | |||
| a4e674fddf |
7 changed files with 185 additions and 30 deletions
|
|
@ -20,8 +20,31 @@ ffi.cdef [[
|
|||
char* ivy_cwd();
|
||||
int ivy_match(const char*, const char*);
|
||||
char* ivy_files(const char*, const char*);
|
||||
|
||||
int ivy_files_iter(const char*, const char*);
|
||||
int ivy_files_iter_len(int);
|
||||
char* ivy_files_iter_at(int, int);
|
||||
void ivy_files_iter_delete(int);
|
||||
]]
|
||||
|
||||
local iter_mt = {
|
||||
__len = function(self)
|
||||
return self.length
|
||||
end,
|
||||
__index = function(self, index)
|
||||
-- Pass in our index -1. This will map lua's one based indexing to zero
|
||||
-- based indexing that we are using in the rust lib.
|
||||
local item = ffi.string(ivy_c.ivy_files_iter_at(self.id, index - 1))
|
||||
return { content = item }
|
||||
end,
|
||||
__newindex = function(_, _, _)
|
||||
error("attempt to update a read-only table", 2)
|
||||
end,
|
||||
__gc = function(self)
|
||||
ivy_c.ivy_files_iter_delete(self.id)
|
||||
end,
|
||||
}
|
||||
|
||||
local libivy = {}
|
||||
|
||||
libivy.ivy_init = function(dir)
|
||||
|
|
@ -37,7 +60,12 @@ libivy.ivy_match = function(pattern, text)
|
|||
end
|
||||
|
||||
libivy.ivy_files = function(pattern, base_dir)
|
||||
return ffi.string(ivy_c.ivy_files(pattern, base_dir))
|
||||
local iter_id = ivy_c.ivy_files_iter(pattern, base_dir)
|
||||
local iter_len = ivy_c.ivy_files_iter_len(iter_id)
|
||||
local iter = { id = iter_id, length = iter_len }
|
||||
setmetatable(iter, iter_mt)
|
||||
|
||||
return iter
|
||||
end
|
||||
|
||||
return libivy
|
||||
|
|
|
|||
|
|
@ -10,18 +10,37 @@ end)
|
|||
|
||||
it("should find a dot file", function(t)
|
||||
local current_dir = libivy.ivy_cwd()
|
||||
local matches = libivy.ivy_files(".github/workflows/ci.yml", current_dir)
|
||||
local results = libivy.ivy_files(".github/workflows/ci.yml", current_dir)
|
||||
|
||||
local results = {}
|
||||
for line in string.gmatch(matches, "[^\r\n]+") do
|
||||
table.insert(results, line)
|
||||
if results.length ~= 2 then
|
||||
t.error("Incorrect number of results found " .. results.length)
|
||||
end
|
||||
|
||||
if #results ~= 2 then
|
||||
t.error "Incorrect number of results"
|
||||
end
|
||||
|
||||
if results[2] ~= ".github/workflows/ci.yml" then
|
||||
t.error("Invalid matches: " .. results[2])
|
||||
if results[2].content ~= ".github/workflows/ci.yml" then
|
||||
t.error("Invalid matches: " .. results[2].content)
|
||||
end
|
||||
end)
|
||||
|
||||
it("will allow you to access the length via the metatable", function(t)
|
||||
local current_dir = libivy.ivy_cwd()
|
||||
local results = libivy.ivy_files(".github/workflows/ci.yml", current_dir)
|
||||
|
||||
local mt = getmetatable(results)
|
||||
|
||||
if results.length ~= mt.__len(results) then
|
||||
t.error "The `length` property does not match the __len metamethod"
|
||||
end
|
||||
end)
|
||||
|
||||
it("will create an iterator", function(t)
|
||||
local iter = libivy.ivy_files(".github/workflows/ci.yml", libivy.ivy_cwd())
|
||||
local mt = getmetatable(iter)
|
||||
|
||||
if type(mt["__index"]) ~= "function" then
|
||||
t.error "The iterator does not have an __index metamethod"
|
||||
end
|
||||
|
||||
if type(mt["__len"]) ~= "function" then
|
||||
t.error "The iterator does not have an __len metamethod"
|
||||
end
|
||||
end)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,22 @@ local function string_to_table(lines)
|
|||
return matches
|
||||
end
|
||||
|
||||
local function get_items_length(items)
|
||||
local mt = getmetatable(items)
|
||||
if mt ~= nil and mt.__len ~= nil then
|
||||
return mt.__len(items)
|
||||
end
|
||||
|
||||
return #items
|
||||
end
|
||||
|
||||
local function call_gc(items)
|
||||
local mt = getmetatable(items)
|
||||
if mt ~= nil and mt.__gc ~= nil then
|
||||
return mt.__gc(items)
|
||||
end
|
||||
end
|
||||
|
||||
local window = {}
|
||||
|
||||
window.index = 0
|
||||
|
|
@ -106,15 +122,17 @@ window.set_items = function(items)
|
|||
items = string_to_table(items)
|
||||
end
|
||||
|
||||
local items_length = get_items_length(items)
|
||||
|
||||
-- TODO(ade): Validate the items are in the correct format. This also need to
|
||||
-- come with some descriptive messages and possible help.
|
||||
|
||||
-- Display no items text if there are no items to dispaly
|
||||
if #items == 0 then
|
||||
if items_length == 0 then
|
||||
items_length = 1
|
||||
items = { { content = "-- No Items --" } }
|
||||
end
|
||||
|
||||
local items_length = #items
|
||||
window.index = items_length - 1
|
||||
|
||||
for index = 1, items_length do
|
||||
|
|
@ -130,6 +148,8 @@ window.set_items = function(items)
|
|||
|
||||
vim.api.nvim_win_set_height(window.window, line_count)
|
||||
window.update()
|
||||
|
||||
call_gc(items)
|
||||
end
|
||||
|
||||
window.destroy = function()
|
||||
|
|
|
|||
|
|
@ -21,3 +21,13 @@ it("can set items", function(t)
|
|||
window.set_items { { content = "Line one" } }
|
||||
t.assert_equal("Line one", window.get_current_selection())
|
||||
end)
|
||||
|
||||
it("will set the items when a string is passed in", function(t)
|
||||
window.initialize()
|
||||
|
||||
local items = table.concat({ "One", "Two", "Three" }, '\n')
|
||||
window.set_items(items)
|
||||
|
||||
local lines = table.concat(vim_mock.get_lines()[window.buffer], "\n");
|
||||
t.assert_equal(items, lines)
|
||||
end)
|
||||
|
|
|
|||
94
rust/lib.rs
94
rust/lib.rs
|
|
@ -9,21 +9,35 @@ use std::os::raw::{c_char, c_int};
|
|||
use std::sync::Mutex;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
struct Ivy {
|
||||
pub file_cache: HashMap<String, Vec<String>>,
|
||||
}
|
||||
|
||||
// A store to the singleton instance of the ivy struct. This must not be accessed directly it must
|
||||
// be use via the Ivy::global() function. Accessing this directly may cause a panic if its been
|
||||
// initialized correctly.
|
||||
static INSTANCE: OnceLock<Mutex<Ivy>> = OnceLock::new();
|
||||
|
||||
impl Ivy {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
file_cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
struct Ivy {
|
||||
// The file cache so we don't have to keep iterating the filesystem. The map key is the root
|
||||
// directory that has been search and the value an a vector containing all of the files that as
|
||||
// in the root. The value will be relative from the root.
|
||||
pub file_cache: HashMap<String, Vec<String>>,
|
||||
// The sequence number of the last iterator created. This will use as a pointer value to the
|
||||
// iterator so we can access it though lua and rust without having to copy strings.
|
||||
pub iter_sequence: i32,
|
||||
// A store of all the iterators that have been created. The key is the sequence number and the
|
||||
// value is the vector of matches that were matched in the search.
|
||||
pub iter_map: HashMap<i32, Vec<CString>>,
|
||||
}
|
||||
|
||||
impl Ivy {
|
||||
// Get the global instance of the ivy struct. This will initialize the struct if it has not
|
||||
// initialized yet.
|
||||
pub fn global() -> &'static Mutex<Ivy> {
|
||||
INSTANCE.get_or_init(|| Mutex::new(Ivy::new()))
|
||||
INSTANCE.get_or_init(|| {
|
||||
Mutex::new(Ivy {
|
||||
file_cache: HashMap::new(),
|
||||
iter_sequence: 0,
|
||||
iter_map: HashMap::new(),
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -75,6 +89,64 @@ pub fn inner_match(pattern: String, text: String) -> i32 {
|
|||
m.score(text.as_str()) as i32
|
||||
}
|
||||
|
||||
// Create a new iterator that will iterate over all the files in the given directory that match a
|
||||
// pattern. It will return the pointer to the iterator so it can be retrieve later. The iterator
|
||||
// can be deleted with `ivy_files_iter_delete`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ivy_files_iter(c_pattern: *const c_char, c_base_dir: *const c_char) -> i32 {
|
||||
let directory = to_string(c_base_dir);
|
||||
let pattern = to_string(c_pattern);
|
||||
|
||||
let files = get_files(&directory);
|
||||
|
||||
let mut ivy = Ivy::global().lock().unwrap();
|
||||
|
||||
// Convert the matches into CStrings so we can pass the pointers out while still maintaining
|
||||
// ownership. If we didn't do this the CString would be dropped and the pointer would be freed
|
||||
// while its being used externally.
|
||||
let sorter_options = sorter::Options::new(pattern);
|
||||
let matches = sorter::sort_strings(sorter_options, files)
|
||||
.into_iter()
|
||||
.map(|m| CString::new(m.content.as_str()).unwrap())
|
||||
.collect::<Vec<CString>>();
|
||||
|
||||
ivy.iter_sequence += 1;
|
||||
let new_sequence = ivy.iter_sequence;
|
||||
ivy.iter_map.insert(new_sequence, matches);
|
||||
|
||||
new_sequence
|
||||
}
|
||||
|
||||
// Delete the iterator with the given id. This will free the memory used by the iterator that was
|
||||
// created with `ivy_files_iter`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ivy_files_iter_delete(iter_id: i32) {
|
||||
let mut ivy = Ivy::global().lock().unwrap();
|
||||
ivy.iter_map.remove(&iter_id);
|
||||
}
|
||||
|
||||
// Returns the length of a given iterator. This will return the number of items that were matched
|
||||
// when the iterator was created with `ivy_files_iter`
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ivy_files_iter_len(iter_id: i32) -> i32 {
|
||||
let ivy = Ivy::global().lock().unwrap();
|
||||
|
||||
let items = ivy.iter_map.get(&iter_id).unwrap();
|
||||
items.len() as i32
|
||||
}
|
||||
|
||||
// Returns the item at the given index in the iterator. This will return the full match that was
|
||||
// given in the iterator. This will return a pointer to the string so it can be used in lua.
|
||||
#[no_mangle]
|
||||
pub extern "C" fn ivy_files_iter_at(iter_id: i32, index: i32) -> *const c_char {
|
||||
let ivy = Ivy::global().lock().unwrap();
|
||||
|
||||
let items = ivy.iter_map.get(&iter_id).unwrap();
|
||||
let item = items.get(index as usize).unwrap();
|
||||
|
||||
item.as_ptr()
|
||||
}
|
||||
|
||||
#[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);
|
||||
|
|
|
|||
|
|
@ -17,8 +17,7 @@ impl Matcher {
|
|||
|
||||
pub fn score(&self, text: &str) -> i64 {
|
||||
self.matcher
|
||||
.fuzzy_indices(text, &self.pattern)
|
||||
.map(|(score, _indices)| score)
|
||||
.fuzzy_match(text, &self.pattern)
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,12 +25,19 @@ pub fn sort_strings(options: Options, strings: Vec<String>) -> Vec<Match> {
|
|||
|
||||
let mut matches = strings
|
||||
.into_par_iter()
|
||||
.map(|candidate| Match {
|
||||
score: matcher.score(candidate.as_str()),
|
||||
content: candidate,
|
||||
.filter_map(|candidate| {
|
||||
let score = matcher.score(candidate.as_str());
|
||||
if score > options.minimum_score {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Match {
|
||||
score,
|
||||
content: candidate,
|
||||
})
|
||||
})
|
||||
.filter(|m| m.score > options.minimum_score)
|
||||
.collect::<Vec<Match>>();
|
||||
|
||||
matches.par_sort_unstable_by(|a, b| a.score.cmp(&b.score));
|
||||
matches
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue