run grep in a different thread to not block UI

plugins
Patrick Cleavelin 2024-01-16 17:00:07 -06:00
parent 670ae631f5
commit 73b35dfece
3 changed files with 103 additions and 26 deletions

View File

@ -1,10 +1,18 @@
use std::{error::Error, ffi::OsString, path::Path, str::FromStr}; use std::{
error::Error,
ffi::OsString,
path::Path,
str::FromStr,
sync::mpsc::{Receiver, Sender},
thread,
};
use grep::{ use grep::{
regex::RegexMatcher, regex::{RegexMatcher, RegexMatcherBuilder},
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError}, searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
}; };
use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, PaletteColor, Plugin}; use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, PaletteColor, Plugin};
use std::sync::mpsc::channel;
use walkdir::WalkDir; use walkdir::WalkDir;
#[derive(Debug)] #[derive(Debug)]
@ -73,7 +81,10 @@ impl Sink for SimpleSink {
} }
fn search(pattern: &str, paths: &[OsString]) -> Result<SimpleSink, Box<dyn Error>> { fn search(pattern: &str, paths: &[OsString]) -> Result<SimpleSink, Box<dyn Error>> {
let matcher = RegexMatcher::new_line_matcher(pattern)?; let matcher = RegexMatcherBuilder::new()
.case_smart(true)
.fixed_strings(true)
.build(pattern)?;
let mut searcher = SearcherBuilder::new() let mut searcher = SearcherBuilder::new()
.binary_detection(BinaryDetection::quit(b'\x00')) .binary_detection(BinaryDetection::quit(b'\x00'))
.line_number(true) .line_number(true)
@ -81,7 +92,15 @@ fn search(pattern: &str, paths: &[OsString]) -> Result<SimpleSink, Box<dyn Error
let mut sink = SimpleSink::default(); let mut sink = SimpleSink::default();
for path in paths { for path in paths {
for result in WalkDir::new(path) { for result in WalkDir::new(path).into_iter().filter_entry(|dent| {
if dent.file_type().is_dir()
&& (dent.path().ends_with("target") || dent.path().ends_with(".git"))
{
return false;
}
true
}) {
let dent = match result { let dent = match result {
Ok(dent) => dent, Ok(dent) => dent,
Err(err) => { Err(err) => {
@ -105,18 +124,24 @@ fn search(pattern: &str, paths: &[OsString]) -> Result<SimpleSink, Box<dyn Error
Ok(sink) Ok(sink)
} }
#[derive(Default)] enum Message {
Search((String, Vec<OsString>)),
Quit,
}
struct GrepWindow { struct GrepWindow {
sink: Option<SimpleSink>, sink: Option<SimpleSink>,
selected_match: usize, selected_match: usize,
top_index: usize, top_index: usize,
input_buffer: Option<Buffer>, input_buffer: Option<Buffer>,
tx: Sender<Message>,
rx: Receiver<SimpleSink>,
} }
#[no_mangle] #[no_mangle]
pub extern "C" fn OnInitialize(plugin: Plugin) { pub extern "C" fn OnInitialize(plugin: Plugin) {
println!("Grep Plugin Initialized"); println!("Grep Plugin Initialized");
plugin.register_hook(Hook::BufferInput, on_buffer_input); plugin.register_hook(Hook::BufferInput, on_buffer_input);
plugin.register_input_group( plugin.register_input_group(
None, None,
@ -126,11 +151,17 @@ pub extern "C" fn OnInitialize(plugin: Plugin) {
input_map, input_map,
Key::R, Key::R,
Closure!((plugin: Plugin) => { Closure!((plugin: Plugin) => {
let (window_tx, thread_rx) = channel();
let (thread_tx, window_rx) = channel();
create_search_thread(thread_tx, thread_rx);
let window = GrepWindow { let window = GrepWindow {
selected_match: 0, selected_match: 0,
top_index: 0, top_index: 0,
input_buffer: Some(plugin.buffer_table.open_virtual_buffer()), input_buffer: Some(plugin.buffer_table.open_virtual_buffer()),
sink: None, sink: None,
tx: window_tx,
rx: window_rx,
}; };
plugin.create_window(window, Closure!((plugin: Plugin, input_map: InputMap) => { plugin.create_window(window, Closure!((plugin: Plugin, input_map: InputMap) => {
@ -212,6 +243,24 @@ pub extern "C" fn OnInitialize(plugin: Plugin) {
); );
} }
fn create_search_thread(tx: Sender<SimpleSink>, rx: Receiver<Message>) {
thread::spawn(move || {
while let Ok(message) = rx.recv() {
match message {
Message::Search((pattern, paths)) => {
if let Ok(sink) = search(&pattern, &paths) {
if let Err(err) = tx.send(sink) {
eprintln!("error getting grep results: {err:?}");
return;
}
}
}
Message::Quit => return,
}
}
});
}
#[no_mangle] #[no_mangle]
pub extern "C" fn OnExit(_plugin: Plugin) { pub extern "C" fn OnExit(_plugin: Plugin) {
println!("Grep Plugin Exiting"); println!("Grep Plugin Exiting");
@ -239,6 +288,13 @@ extern "C" fn draw_window(plugin: Plugin, window: *const std::ffi::c_void) {
let directory = Path::new(dir.as_ref()); let directory = Path::new(dir.as_ref());
(plugin.draw_rect)(x, y, width, height, PaletteColor::Background4); (plugin.draw_rect)(x, y, width, height, PaletteColor::Background4);
(plugin.draw_rect)(
x + font_width,
y + font_height,
width - font_width * 2,
height - font_height * 3,
PaletteColor::Background3,
);
if let Some(buffer) = window.input_buffer { if let Some(buffer) = window.input_buffer {
(plugin.draw_rect)( (plugin.draw_rect)(
@ -258,12 +314,17 @@ extern "C" fn draw_window(plugin: Plugin, window: *const std::ffi::c_void) {
); );
} }
if let Ok(sink) = window.rx.try_recv() {
window.sink = Some(sink);
}
if let Some(sink) = &window.sink { if let Some(sink) = &window.sink {
if !sink.matches.is_empty() { if !sink.matches.is_empty() {
let num_mats_to_draw = std::cmp::min( let num_mats_to_draw = std::cmp::min(
(sink.matches.len() - window.top_index) as i32, (sink.matches.len() - window.top_index) as i32,
(height - font_height * 2) / (font_height) - 1, (height - font_height * 2) / (font_height) - 1,
); );
let max_mat_length = (width - font_width * 2) / font_width;
for (i, mat) in sink.matches[window.top_index..].iter().enumerate() { for (i, mat) in sink.matches[window.top_index..].iter().enumerate() {
let index = i + window.top_index; let index = i + window.top_index;
@ -281,17 +342,24 @@ extern "C" fn draw_window(plugin: Plugin, window: *const std::ffi::c_void) {
let matched_text = String::from_utf8_lossy(&mat.text); let matched_text = String::from_utf8_lossy(&mat.text);
let text = match mat.line_number { let text = match mat.line_number {
Some(line_number) => format!( Some(line_number) => format!(
"{} - {}:{}:{}: {}\0", "{}:{}:{}: {}",
index, relative_file_path, line_number, mat.column, matched_text relative_file_path, line_number, mat.column, matched_text
), ),
None => format!("{}:{}: {}\0", relative_file_path, mat.column, matched_text), None => format!("{}:{}: {}", relative_file_path, mat.column, matched_text),
}; };
let text = if text.len() > max_mat_length as usize {
text.as_str().split_at(max_mat_length as usize).0
} else {
&text
};
let text = format!("{text}\0");
if index == window.selected_match { if index == window.selected_match {
(plugin.draw_rect)( (plugin.draw_rect)(
x + font_width, x + font_width,
y + font_height + ((index - window.top_index) as i32) * font_height, y + font_height + ((index - window.top_index) as i32) * font_height,
(text.len() as i32) * font_width, (text.chars().count() as i32) * font_width,
font_height, font_height,
PaletteColor::Background2, PaletteColor::Background2,
); );
@ -320,11 +388,18 @@ extern "C" fn on_buffer_input(plugin: Plugin, buffer: Buffer) {
if let Some(buffer_info) = plugin.buffer_table.get_buffer_info(buffer) { if let Some(buffer_info) = plugin.buffer_table.get_buffer_info(buffer) {
if let Some(input) = buffer_info.input.try_as_str() { if let Some(input) = buffer_info.input.try_as_str() {
let directory = OsString::from_str(plugin.get_current_directory().as_ref()); let directory = OsString::from_str(plugin.get_current_directory().as_ref());
window.sink = match directory {
Ok(dir) => search(&input, &[dir]).ok(), match directory {
Ok(dir) => {
if let Err(err) = window
.tx
.send(Message::Search((input.to_string(), vec![dir])))
{
eprintln!("failed to grep: {err:?}");
}
}
Err(_) => { Err(_) => {
eprintln!("failed to parse directory"); eprintln!("failed to parse directory");
None
} }
}; };
} }
@ -335,6 +410,8 @@ extern "C" fn on_buffer_input(plugin: Plugin, buffer: Buffer) {
extern "C" fn free_window(plugin: Plugin, window: *const std::ffi::c_void) { extern "C" fn free_window(plugin: Plugin, window: *const std::ffi::c_void) {
let mut window = unsafe { Box::<GrepWindow>::from_raw(window as *mut GrepWindow) }; let mut window = unsafe { Box::<GrepWindow>::from_raw(window as *mut GrepWindow) };
let _ = window.tx.send(Message::Quit);
if let Some(buffer) = window.input_buffer { if let Some(buffer) = window.input_buffer {
plugin.buffer_table.free_virtual_buffer(buffer); plugin.buffer_table.free_virtual_buffer(buffer);
window.input_buffer = None; window.input_buffer = None;

View File

@ -800,7 +800,7 @@ draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, fon
text_y := y + state.source_font_height * j; text_y := y + state.source_font_height * j;
if show_line_numbers { if show_line_numbers {
raylib.DrawTextEx(font, raylib.TextFormat("%d", begin + j + 1), raylib.Vector2 { f32(x), f32(text_y) }, f32(state.source_font_height), 0, theme.get_palette_raylib_color(.Background3)); raylib.DrawTextEx(font, raylib.TextFormat("%d", begin + j + 1), raylib.Vector2 { f32(x), f32(text_y) }, f32(state.source_font_height), 0, theme.get_palette_raylib_color(.Background4));
} }
for i in 0..<buffer.glyph_buffer_width { for i in 0..<buffer.glyph_buffer_width {

View File

@ -354,17 +354,16 @@ main :: proc() {
draw_text = proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor) { draw_text = proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor) {
context = state.ctx; context = state.ctx;
raylib.DrawTextEx( text := string(text);
for codepoint, index in text {
raylib.DrawTextCodepoint(
state.font, state.font,
text, rune(codepoint),
raylib.Vector2 { raylib.Vector2 { x + f32(index * state.source_font_width), y },
x,
y,
},
f32(state.source_font_height), f32(state.source_font_height),
0,
theme.get_palette_raylib_color(color) theme.get_palette_raylib_color(color)
); );
}
}, },
draw_buffer_from_index = proc "c" (buffer_index: int, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) { draw_buffer_from_index = proc "c" (buffer_index: int, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) {
context = state.ctx; context = state.ctx;
@ -729,7 +728,8 @@ main :: proc() {
} }
// Load plugins // Load plugins
filepath.walk(filepath.join({ state.directory, "bin" }), load_plugin, transmute(rawptr)&state); // TODO(pcleavelin): Get directory of binary instead of shells current working directory
filepath.walk(filepath.join({ os.get_current_directory(), "bin" }), load_plugin, transmute(rawptr)&state);
for plugin in state.plugins { for plugin in state.plugins {
if plugin.on_initialize != nil { if plugin.on_initialize != nil {