ayo, grep is now a plugin (still a lot of jank in the plugin API tho)

plugins
Patrick Cleavelin 2024-01-15 19:39:43 -06:00
parent efdd02a423
commit 322c524158
16 changed files with 1031 additions and 552 deletions

2
.gitignore vendored
View File

@ -1,2 +1,2 @@
bin/ bin/
lib-rg/target **/target

View File

@ -1,6 +1,6 @@
all: editor all: editor
editor: src/*.odin rg odin_highlighter buffer_search editor: src/*.odin grep odin_highlighter buffer_search
odin build src/ -out:bin/editor -lld odin build src/ -out:bin/editor -lld
buffer_search: buffer_search:
@ -8,5 +8,6 @@ buffer_search:
odin_highlighter: odin_highlighter:
odin build plugins/highlighter/src/ -build-mode:dll -no-entry-point -out:bin/highlighter odin build plugins/highlighter/src/ -build-mode:dll -no-entry-point -out:bin/highlighter
rg: grep:
cargo b --manifest-path=lib-rg/Cargo.toml cargo b --manifest-path=plugins/grep/Cargo.toml
cp plugins/grep/target/debug/libgrep_plugin.dylib bin/

View File

@ -1,220 +0,0 @@
use std::{
error::Error,
ffi::{CStr, OsString},
os::raw::c_char,
str::FromStr,
};
use grep::{
regex::RegexMatcher,
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
};
use walkdir::WalkDir;
#[derive(Debug)]
pub enum SimpleSinkError {
StandardError,
NoLine,
BadString,
}
impl SinkError for SimpleSinkError {
fn error_message<T: std::fmt::Display>(message: T) -> Self {
eprintln!("{message}");
Self::StandardError
}
}
#[repr(C)]
pub struct UnsafeMatch {
text_str: *mut u8,
text_len: usize,
text_cap: usize,
path_str: *mut u8,
path_len: usize,
path_cap: usize,
line_number: u64,
column: u64,
}
impl From<Match> for UnsafeMatch {
fn from(value: Match) -> Self {
let mut text_boxed = Box::new(value.text);
let text_str = text_boxed.as_mut_ptr();
let text_len = text_boxed.len();
let text_cap = text_boxed.capacity();
Box::leak(text_boxed);
let mut path_boxed = Box::new(value.path);
let path_str = path_boxed.as_mut_ptr();
let path_len = path_boxed.len();
let path_cap = path_boxed.capacity();
Box::leak(path_boxed);
Self {
text_str,
text_len,
text_cap,
path_str,
path_len,
path_cap,
line_number: value.line_number.unwrap_or_default(),
column: value.column,
}
}
}
struct Match {
text: Vec<u8>,
path: String,
line_number: Option<u64>,
column: u64,
}
impl Match {
fn from_sink_match_with_path(
value: &grep::searcher::SinkMatch<'_>,
path: Option<String>,
) -> Result<Self, SimpleSinkError> {
let line = value
.lines()
.next()
.ok_or(SimpleSinkError::NoLine)?
.to_vec();
let column = value.bytes_range_in_buffer().len() as u64;
Ok(Self {
text: line,
path: path.unwrap_or_default(),
line_number: value.line_number(),
column,
})
}
}
#[derive(Default)]
struct SimpleSink {
current_path: Option<String>,
matches: Vec<Match>,
}
impl Sink for SimpleSink {
type Error = SimpleSinkError;
fn matched(
&mut self,
_searcher: &grep::searcher::Searcher,
mat: &grep::searcher::SinkMatch<'_>,
) -> Result<bool, Self::Error> {
self.matches.push(Match::from_sink_match_with_path(
mat,
self.current_path.clone(),
)?);
Ok(true)
}
}
#[repr(C)]
pub struct UnsafeMatchArray {
matches: *mut UnsafeMatch,
len: usize,
capacity: usize,
}
impl Default for UnsafeMatchArray {
fn default() -> Self {
Self {
matches: std::ptr::null_mut(),
len: 0,
capacity: 0,
}
}
}
impl From<SimpleSink> for UnsafeMatchArray {
fn from(value: SimpleSink) -> Self {
let matches: Vec<UnsafeMatch> = value.matches.into_iter().map(Into::into).collect();
let mut boxed_vec = Box::new(matches);
let ptr = boxed_vec.as_mut_ptr();
let len = boxed_vec.len();
let capacity = boxed_vec.capacity();
Box::leak(boxed_vec);
Self {
matches: ptr,
len,
capacity,
}
}
}
/// # Safety
/// Who knows what'll happen if you don't pass valid strings
#[no_mangle]
pub unsafe extern "C" fn rg_search(
pattern: *const c_char,
path: *const c_char,
) -> UnsafeMatchArray {
let pattern = CStr::from_ptr(pattern);
let path = CStr::from_ptr(path);
if let (Ok(path), Ok(pattern)) = (path.to_str(), pattern.to_str()) {
if let Ok(path) = OsString::from_str(path) {
return match search(pattern, &[path]) {
Ok(sink) => sink.into(),
Err(err) => {
eprintln!("rg search failed: {}", err);
Default::default()
}
};
}
}
Default::default()
}
/// # Safety
/// Who knows what'll happen if you don't pass back the same vec
#[no_mangle]
pub unsafe extern "C" fn drop_match_array(match_array: UnsafeMatchArray) {
let matches = Vec::from_raw_parts(match_array.matches, match_array.len, match_array.capacity);
for mat in matches {
let _ = String::from_raw_parts(mat.text_str, mat.text_len, mat.text_cap);
let _ = String::from_raw_parts(mat.path_str, mat.path_len, mat.path_cap);
}
}
fn search(pattern: &str, paths: &[OsString]) -> Result<SimpleSink, Box<dyn Error>> {
let matcher = RegexMatcher::new_line_matcher(pattern)?;
let mut searcher = SearcherBuilder::new()
.binary_detection(BinaryDetection::quit(b'\x00'))
.line_number(true)
.build();
let mut sink = SimpleSink::default();
for path in paths {
for result in WalkDir::new(path) {
let dent = match result {
Ok(dent) => dent,
Err(err) => {
eprintln!("{}", err);
continue;
}
};
if !dent.file_type().is_file() {
continue;
}
sink.current_path = Some(dent.path().to_string_lossy().into());
let result = searcher.search_path(&matcher, dent.path(), &mut sink);
if let Err(err) = result {
eprintln!("{}: {:?}", dent.path().display(), err);
}
}
}
Ok(sink)
}

7
plugin-rs-bindings/Cargo.lock generated Normal file
View File

@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "plugin_rs_bindings"
version = "0.1.0"

View File

@ -0,0 +1,8 @@
[package]
name = "plugin_rs_bindings"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]

View File

@ -0,0 +1,509 @@
use std::{
borrow::Cow,
ffi::{c_char, c_void, CStr},
path::Path,
};
#[macro_export]
macro_rules! Closure {
(($($arg: ident: $type: ty),+) => $body: expr) => {
{
extern "C" fn f($($arg: $type),+) {
$body
}
f
}
};
(($($arg: ident: $type: ty),+) -> $return_type: ty => $body: expr) => {
{
extern "C" fn f($($arg: $type),+) -> $return_type {
$body
}
f
}
};
}
#[repr(C)]
#[derive(Clone, Copy)]
pub struct InputMap {
internal: *const std::ffi::c_void,
}
#[repr(C)]
#[derive(Debug)]
pub struct BufferIndex {
pub slice_index: isize,
pub content_index: isize,
}
#[repr(C)]
#[derive(Debug)]
pub struct Cursor {
pub col: isize,
pub line: isize,
pub index: BufferIndex,
}
#[repr(C)]
#[derive(Debug)]
struct InternalBufferIter {
cursor: Cursor,
buffer: *const c_void,
hit_end: bool,
}
#[repr(C)]
pub struct IterateResult {
pub char: u8,
pub should_continue: bool,
}
#[repr(C)]
#[derive(Debug)]
pub struct BufferInput {
bytes: *const u8,
length: isize,
}
impl BufferInput {
pub fn try_as_str(&self) -> Option<Cow<'_, str>> {
if self.bytes.is_null() {
None
} else {
let slice = unsafe { std::slice::from_raw_parts(self.bytes, self.length as usize) };
Some(String::from_utf8_lossy(slice))
}
}
}
#[repr(C)]
#[derive(Debug)]
pub struct BufferInfo {
pub buffer: Buffer,
pub file_path: *const i8,
pub input: BufferInput,
pub cursor: Cursor,
pub glyph_buffer_width: isize,
pub glyph_buffer_height: isize,
pub top_line: isize,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Buffer {
internal: *const c_void,
}
impl Buffer {
pub fn null() -> Buffer {
Buffer {
internal: std::ptr::null(),
}
}
}
#[repr(C)]
pub struct BufferVTable {
pub get_num_buffers: extern "C" fn() -> isize,
get_buffer_info: extern "C" fn(buffer: Buffer) -> BufferInfo,
pub get_buffer_info_from_index: extern "C" fn(buffer_index: isize) -> BufferInfo,
pub color_char_at: extern "C" fn(
buffer: *const c_void,
start_cursor: Cursor,
end_cursor: Cursor,
palette_index: i32,
),
pub set_current_buffer: extern "C" fn(buffer_index: isize),
open_buffer: extern "C" fn(path: *const u8, line: isize, col: isize),
open_virtual_buffer: extern "C" fn() -> *const c_void,
free_virtual_buffer: extern "C" fn(buffer: Buffer),
}
impl BufferVTable {
pub fn get_buffer_info(&self, buffer: Buffer) -> Option<BufferInfo> {
if buffer.internal.is_null() {
None
} else {
Some((self.get_buffer_info)(buffer))
}
}
pub fn open_buffer(&self, path: impl AsRef<Path>, line: i32, col: i32) {
let c_str = path.as_ref().to_string_lossy().as_ptr();
(self.open_buffer)(c_str, line as isize, col as isize);
}
pub fn open_virtual_buffer(&self) -> Buffer {
Buffer {
internal: (self.open_virtual_buffer)(),
}
}
pub fn free_virtual_buffer(&self, buffer: Buffer) {
(self.free_virtual_buffer)(buffer);
}
}
#[repr(C)]
pub struct IteratorVTable {
get_current_buffer_iterator: extern "C" fn() -> InternalBufferIter,
get_buffer_iterator: extern "C" fn(buffer: *const c_void) -> InternalBufferIter,
get_char_at_iter: extern "C" fn(it: *const InternalBufferIter) -> u8,
get_buffer_list_iter: extern "C" fn(prev_buffer: *const isize) -> isize,
iterate_buffer: extern "C" fn(it: *mut InternalBufferIter) -> IterateResult,
iterate_buffer_reverse: extern "C" fn(it: *mut InternalBufferIter) -> IterateResult,
iterate_buffer_until: extern "C" fn(it: *mut InternalBufferIter, until_proc: *const c_void),
iterate_buffer_until_reverse:
extern "C" fn(it: *mut InternalBufferIter, until_proc: *const c_void),
iterate_buffer_peek: extern "C" fn(it: *mut InternalBufferIter) -> IterateResult,
pub until_line_break: *const c_void,
pub until_single_quote: *const c_void,
pub until_double_quote: *const c_void,
pub until_end_of_word: *const c_void,
}
type OnColorBufferProc = extern "C" fn(plugin: Plugin, buffer: *const c_void);
type OnHookProc = extern "C" fn(plugin: Plugin, buffer: Buffer);
type InputGroupProc = extern "C" fn(plugin: Plugin, input_map: InputMap);
type InputActionProc = extern "C" fn(plugin: Plugin);
type WindowDrawProc = extern "C" fn(plugin: Plugin, window: *const c_void);
type WindowFreeProc = extern "C" fn(plugin: Plugin, window: *const c_void);
type WindowGetBufferProc = extern "C" fn(plugin: Plugin, window: *const c_void) -> Buffer;
#[repr(C)]
pub struct Plugin {
state: *const c_void,
pub iter_table: IteratorVTable,
pub buffer_table: BufferVTable,
pub register_hook: extern "C" fn(hook: Hook, on_hook: OnHookProc),
pub register_highlighter:
extern "C" fn(extension: *const c_char, on_color_buffer: OnColorBufferProc),
pub register_input_group:
extern "C" fn(input_map: InputMap, key: Key, register_group: InputGroupProc),
pub register_input: extern "C" fn(
input_map: InputMap,
key: Key,
input_action: InputActionProc,
description: *const u8,
),
pub create_window: extern "C" fn(
user_data: *const c_void,
register_group: InputGroupProc,
draw_proc: WindowDrawProc,
free_window_proc: WindowFreeProc,
get_buffer_proc: *const (),
) -> *const c_void,
get_window: extern "C" fn() -> *const c_void,
pub request_window_close: extern "C" fn(),
pub get_screen_width: extern "C" fn() -> isize,
pub get_screen_height: extern "C" fn() -> isize,
pub get_font_width: extern "C" fn() -> isize,
pub get_font_height: extern "C" fn() -> isize,
get_current_directory: extern "C" fn() -> *const c_char,
pub enter_insert_mode: extern "C" fn(),
pub draw_rect: extern "C" fn(x: i32, y: i32, width: i32, height: i32, color: PaletteColor),
pub draw_text: extern "C" fn(text: *const c_char, x: f32, y: f32, color: PaletteColor),
pub draw_buffer_from_index: extern "C" fn(
buffer_index: isize,
x: isize,
y: isize,
glyph_buffer_width: isize,
glyph_buffer_height: isize,
show_line_numbers: bool,
),
pub draw_buffer: extern "C" fn(
buffer: Buffer,
x: isize,
y: isize,
glyph_buffer_width: isize,
glyph_buffer_height: isize,
show_line_numbers: bool,
),
}
pub struct BufferIter {
iter: InternalBufferIter,
iter_table: IteratorVTable,
}
impl BufferIter {
pub fn new(plugin: Plugin, buffer: Buffer) -> Self {
let buffer_info = (plugin.buffer_table.get_buffer_info)(buffer);
Self {
iter: InternalBufferIter {
cursor: buffer_info.cursor,
buffer: buffer.internal,
hit_end: false,
},
iter_table: plugin.iter_table,
}
}
}
impl Iterator for BufferIter {
type Item = char;
fn next(&mut self) -> Option<Self::Item> {
let iter_ptr = (&mut self.iter) as *mut InternalBufferIter;
let result = (self.iter_table.iterate_buffer)(iter_ptr);
if result.should_continue {
Some(result.char as char)
} else {
None
}
}
}
pub struct BufferListIter {
index: isize,
next_fn: extern "C" fn(prev_buffer: *const isize) -> isize,
}
impl From<&Plugin> for BufferListIter {
fn from(value: &Plugin) -> Self {
BufferListIter {
index: 0,
next_fn: value.iter_table.get_buffer_list_iter,
}
}
}
impl Iterator for BufferListIter {
type Item = isize;
fn next(&mut self) -> Option<Self::Item> {
if self.index == -1 {
return None;
}
Some((self.next_fn)(&mut self.index))
}
}
impl Plugin {
pub fn get_current_directory(&self) -> Cow<str> {
unsafe {
let c_str = CStr::from_ptr((self.get_current_directory)());
c_str.to_string_lossy()
}
}
/// # Safety
/// If `W` is not the same type as given in `self.create_window`, it will result in undefined
/// behavior. `W` can also be a different type if another plugin has created a window.
pub unsafe fn get_window<'a, W>(&self) -> Option<&'a mut W> {
let window_ptr = (self.get_window)() as *mut W;
if window_ptr.is_null() {
None
} else {
let window = Box::from_raw(window_ptr);
Some(Box::leak(window))
}
}
pub fn create_window<W>(
&self,
window: W,
register_group: InputGroupProc,
draw_proc: WindowDrawProc,
free_window_proc: WindowFreeProc,
get_buffer_proc: Option<WindowGetBufferProc>,
) {
let boxed = Box::new(window);
(self.create_window)(
Box::into_raw(boxed) as *const std::ffi::c_void,
register_group,
draw_proc,
free_window_proc,
if let Some(proc) = get_buffer_proc {
proc as *const ()
} else {
std::ptr::null()
},
);
}
pub fn register_hook(&self, hook: Hook, on_hook: OnHookProc) {
(self.register_hook)(hook, on_hook)
}
pub fn register_input_group(
&self,
input_map: Option<InputMap>,
key: Key,
register_group: InputGroupProc,
) {
let input_map = match input_map {
Some(input_map) => input_map,
None => InputMap {
internal: std::ptr::null(),
},
};
(self.register_input_group)(input_map, key, register_group);
}
}
#[repr(i32)]
pub enum Hook {
BufferInput,
}
#[repr(i32)]
pub enum Key {
KeyNull = 0, // Key: NULL, used for no key pressed
// Alphanumeric keys
Apostrophe = 39, // key: '
Comma = 44, // Key: ,
Minus = 45, // Key: -
Period = 46, // Key: .
Slash = 47, // Key: /
Zero = 48, // Key: 0
One = 49, // Key: 1
Two = 50, // Key: 2
Three = 51, // Key: 3
Four = 52, // Key: 4
Five = 53, // Key: 5
Six = 54, // Key: 6
Seven = 55, // Key: 7
Eight = 56, // Key: 8
Nine = 57, // Key: 9
Semicolon = 59, // Key: ;
Equal = 61, // Key: =
A = 65, // Key: A | a
B = 66, // Key: B | b
C = 67, // Key: C | c
D = 68, // Key: D | d
E = 69, // Key: E | e
F = 70, // Key: F | f
G = 71, // Key: G | g
H = 72, // Key: H | h
I = 73, // Key: I | i
J = 74, // Key: J | j
K = 75, // Key: K | k
L = 76, // Key: L | l
M = 77, // Key: M | m
N = 78, // Key: N | n
O = 79, // Key: O | o
P = 80, // Key: P | p
Q = 81, // Key: Q | q
R = 82, // Key: R | r
S = 83, // Key: S | s
T = 84, // Key: T | t
U = 85, // Key: U | u
V = 86, // Key: V | v
W = 87, // Key: W | w
X = 88, // Key: X | x
Y = 89, // Key: Y | y
Z = 90, // Key: Z | z
LeftBracket = 91, // Key: [
Backslash = 92, // Key: '\'
RightBracket = 93, // Key: ]
Grave = 96, // Key: `
// Function keys
Space = 32, // Key: Space
Escape = 256, // Key: Esc
Enter = 257, // Key: Enter
Tab = 258, // Key: Tab
Backspace = 259, // Key: Backspace
Insert = 260, // Key: Ins
Delete = 261, // Key: Del
Right = 262, // Key: Cursor right
Left = 263, // Key: Cursor left
Down = 264, // Key: Cursor down
Up = 265, // Key: Cursor up
PageUp = 266, // Key: Page up
PageDown = 267, // Key: Page down
Home = 268, // Key: Home
End = 269, // Key: End
CapsLock = 280, // Key: Caps lock
ScrollLock = 281, // Key: Scroll down
NumLock = 282, // Key: Num lock
PrintScreen = 283, // Key: Print screen
Pause = 284, // Key: Pause
F1 = 290, // Key: F1
F2 = 291, // Key: F2
F3 = 292, // Key: F3
F4 = 293, // Key: F4
F5 = 294, // Key: F5
F6 = 295, // Key: F6
F7 = 296, // Key: F7
F8 = 297, // Key: F8
F9 = 298, // Key: F9
F10 = 299, // Key: F10
F11 = 300, // Key: F11
F12 = 301, // Key: F12
LeftShift = 340, // Key: Shift left
LeftControl = 341, // Key: Control left
LeftAlt = 342, // Key: Alt left
LeftSuper = 343, // Key: Super left
RightShift = 344, // Key: Shift right
RightControl = 345, // Key: Control right
RightAlt = 346, // Key: Alt right
RightSuper = 347, // Key: Super right
KbMenu = 348, // Key: KB menu
// Keypad keys
Kp0 = 320, // Key: Keypad 0
Kp1 = 321, // Key: Keypad 1
Kp2 = 322, // Key: Keypad 2
Kp3 = 323, // Key: Keypad 3
Kp4 = 324, // Key: Keypad 4
Kp5 = 325, // Key: Keypad 5
Kp6 = 326, // Key: Keypad 6
Kp7 = 327, // Key: Keypad 7
Kp8 = 328, // Key: Keypad 8
Kp9 = 329, // Key: Keypad 9
KpDecimal = 330, // Key: Keypad .
KpDivide = 331, // Key: Keypad /
KpMultiply = 332, // Key: Keypad *
KpSubtract = 333, // Key: Keypad -
KpAdd = 334, // Key: Keypad +
KpEnter = 335, // Key: Keypad Enter
KpEqual = 336, // Key: Keypad =
// Android key buttons
Back = 4, // Key: Android back button
VolumeUp = 24, // Key: Android volume up button
VolumeDown = 25, // Key: Android volume down button
}
#[repr(i32)]
pub enum PaletteColor {
Background,
Foreground,
Background1,
Background2,
Background3,
Background4,
Foreground1,
Foreground2,
Foreground3,
Foreground4,
Red,
Green,
Yellow,
Blue,
Purple,
Aqua,
Gray,
BrightRed,
BrightGreen,
BrightYellow,
BrightBlue,
BrightPurple,
BrightAqua,
BrightGray,
}

View File

@ -75,7 +75,7 @@ open_buffer_window :: proc "c" (plugin: Plugin) {
plugin.request_window_close(); plugin.request_window_close();
}, "switch to buffer") }, "switch to buffer")
}, draw_buffer_window, free_buffer_window); }, draw_buffer_window, free_buffer_window, nil);
} }
free_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) { free_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) {
@ -149,7 +149,7 @@ draw_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) {
text_width := len(text) * source_font_width; text_width := len(text) * source_font_width;
if index == win.selected_index { if index == win.selected_index {
plugin.draw_buffer( plugin.draw_buffer_from_index(
index, index,
int(win_rec.x + win_margin.x + win_rec.width / 2), int(win_rec.x + win_margin.x + win_rec.width / 2),
int(win_rec.y + win_margin.y), int(win_rec.y + win_margin.y),

View File

@ -138,6 +138,16 @@ dependencies = [
"memmap2", "memmap2",
] ]
[[package]]
name = "grep_plugin"
version = "0.1.0"
dependencies = [
"grep",
"plugin_rs_bindings",
"termcolor",
"walkdir",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.10" version = "1.0.10"
@ -171,6 +181,10 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "plugin_rs_bindings"
version = "0.1.0"
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.74" version = "1.0.74"
@ -206,15 +220,6 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
[[package]]
name = "rg"
version = "0.1.0"
dependencies = [
"grep",
"termcolor",
"walkdir",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.16" version = "1.0.16"

View File

@ -1,5 +1,5 @@
[package] [package]
name = "rg" name = "grep_plugin"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
publish = false publish = false
@ -11,3 +11,4 @@ crate-type = ["cdylib"]
grep = "0.3.1" grep = "0.3.1"
termcolor = "1.4.0" termcolor = "1.4.0"
walkdir = "2.4.0" walkdir = "2.4.0"
plugin_rs_bindings = { path = "../../plugin-rs-bindings" }

342
plugins/grep/src/lib.rs Normal file
View File

@ -0,0 +1,342 @@
use std::{error::Error, ffi::OsString, path::Path, str::FromStr};
use grep::{
regex::RegexMatcher,
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
};
use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, PaletteColor, Plugin};
use walkdir::WalkDir;
#[derive(Debug)]
pub enum SimpleSinkError {
StandardError,
NoLine,
BadString,
}
impl SinkError for SimpleSinkError {
fn error_message<T: std::fmt::Display>(message: T) -> Self {
eprintln!("{message}");
Self::StandardError
}
}
#[derive(Debug)]
struct Match {
text: Vec<u8>,
path: String,
line_number: Option<u64>,
column: u64,
}
impl Match {
fn from_sink_match_with_path(
value: &grep::searcher::SinkMatch<'_>,
path: Option<String>,
) -> Result<Self, SimpleSinkError> {
let line = value
.lines()
.next()
.ok_or(SimpleSinkError::NoLine)?
.to_vec();
let column = value.bytes_range_in_buffer().len() as u64;
Ok(Self {
text: line,
path: path.unwrap_or_default(),
line_number: value.line_number(),
column,
})
}
}
#[derive(Default, Debug)]
struct SimpleSink {
current_path: Option<String>,
matches: Vec<Match>,
}
impl Sink for SimpleSink {
type Error = SimpleSinkError;
fn matched(
&mut self,
_searcher: &grep::searcher::Searcher,
mat: &grep::searcher::SinkMatch<'_>,
) -> Result<bool, Self::Error> {
self.matches.push(Match::from_sink_match_with_path(
mat,
self.current_path.clone(),
)?);
Ok(true)
}
}
fn search(pattern: &str, paths: &[OsString]) -> Result<SimpleSink, Box<dyn Error>> {
let matcher = RegexMatcher::new_line_matcher(pattern)?;
let mut searcher = SearcherBuilder::new()
.binary_detection(BinaryDetection::quit(b'\x00'))
.line_number(true)
.build();
let mut sink = SimpleSink::default();
for path in paths {
for result in WalkDir::new(path) {
let dent = match result {
Ok(dent) => dent,
Err(err) => {
eprintln!("{}", err);
continue;
}
};
if !dent.file_type().is_file() {
continue;
}
sink.current_path = Some(dent.path().to_string_lossy().into());
let result = searcher.search_path(&matcher, dent.path(), &mut sink);
if let Err(err) = result {
eprintln!("{}: {:?}", dent.path().display(), err);
}
}
}
Ok(sink)
}
#[derive(Default)]
struct GrepWindow {
sink: Option<SimpleSink>,
selected_match: usize,
top_index: usize,
input_buffer: Option<Buffer>,
}
#[no_mangle]
pub extern "C" fn OnInitialize(plugin: Plugin) {
println!("Grep Plugin Initialized");
plugin.register_hook(Hook::BufferInput, on_buffer_input);
plugin.register_input_group(
None,
Key::Space,
Closure!((plugin: Plugin, input_map: InputMap) => {
(plugin.register_input)(
input_map,
Key::R,
Closure!((plugin: Plugin) => {
let window = GrepWindow {
selected_match: 0,
top_index: 0,
input_buffer: Some(plugin.buffer_table.open_virtual_buffer()),
sink: None,
};
plugin.create_window(window, Closure!((plugin: Plugin, input_map: InputMap) => {
(plugin.enter_insert_mode)();
(plugin.register_input)(input_map, Key::I, Closure!((plugin: Plugin) => {
(plugin.enter_insert_mode)()
}), "\0".as_ptr());
(plugin.register_input)(input_map, Key::Enter, Closure!((plugin: Plugin) => {
if let Some(window) = unsafe { plugin.get_window::<GrepWindow>() } {
match &window.sink {
Some(sink) => if window.selected_match < sink.matches.len() {
let mat = unsafe { &sink.matches.get_unchecked(window.selected_match) };
plugin.buffer_table.open_buffer(&mat.path, (mat.line_number.unwrap_or(1)-1) as i32, 0);
(plugin.request_window_close)();
},
None => {},
}
}
}), "move selection up\0".as_ptr());
(plugin.register_input)(input_map, Key::K, Closure!((plugin: Plugin) => {
if let Some(window) = unsafe { plugin.get_window::<GrepWindow>() } {
if window.selected_match > 0 {
window.selected_match -= 1;
if window.selected_match < window.top_index {
window.top_index = window.selected_match;
}
} else {
window.selected_match = match &window.sink {
Some(sink) => sink.matches.len()-1,
None => 0,
};
window.top_index = window.selected_match;
}
}
}), "move selection up\0".as_ptr());
(plugin.register_input)(input_map, Key::J, Closure!((plugin: Plugin) => {
if let Some(window) = unsafe { plugin.get_window::<GrepWindow>() } {
let screen_height = (plugin.get_screen_height)() as i32;
let font_height = (plugin.get_font_height)() as i32;
let height = screen_height - screen_height / 4;
let max_mats_to_draw = (height - font_height * 2) / (font_height) - 1;
let match_count = match &window.sink {
Some(sink) => sink.matches.len(),
None => 0,
};
let index_threshold = std::cmp::max(max_mats_to_draw-4, 0) as usize;
if window.selected_match < match_count-1 {
window.selected_match += 1;
if window.selected_match - window.top_index > index_threshold {
window.top_index += 1;
}
} else {
window.selected_match = 0;
window.top_index = 0;
}
}
}), "move selection down\0".as_ptr());
}), draw_window, free_window, Some(Closure!((_plugin: Plugin, window: *const std::ffi::c_void) -> Buffer => {
let window = Box::leak(unsafe { Box::<GrepWindow>::from_raw(window as *mut GrepWindow) });
if let Some(buffer) = window.input_buffer {
return buffer;
} else {
return Buffer::null();
}
})));
}),
"Open Grep Window\0".as_ptr(),
);
}),
);
}
#[no_mangle]
pub extern "C" fn OnExit(_plugin: Plugin) {
println!("Grep Plugin Exiting");
}
extern "C" fn draw_window(plugin: Plugin, window: *const std::ffi::c_void) {
let window = Box::leak(unsafe { Box::<GrepWindow>::from_raw(window as *mut GrepWindow) });
let screen_width = (plugin.get_screen_width)() as i32;
let screen_height = (plugin.get_screen_height)() as i32;
let font_width = (plugin.get_font_width)() as i32;
let font_height = (plugin.get_font_height)() as i32;
let x = screen_width / 8;
let y = screen_height / 8;
let width = screen_width - screen_width / 4;
let height = screen_height - screen_height / 4;
let buffer_prev_width = (width - font_width * 2) / 2;
let glyph_buffer_width = buffer_prev_width / font_width - 1;
let glyph_buffer_height = 1;
let dir = plugin.get_current_directory();
let directory = Path::new(dir.as_ref());
(plugin.draw_rect)(x, y, width, height, PaletteColor::Background4);
if let Some(buffer) = window.input_buffer {
(plugin.draw_rect)(
x + font_width,
y + height - font_height * 2,
buffer_prev_width,
font_height,
PaletteColor::Background2,
);
(plugin.draw_buffer)(
buffer,
(x + font_width) as isize,
(y + height - font_height * 2) as isize,
(glyph_buffer_width) as isize,
(glyph_buffer_height) as isize,
false,
);
}
if let Some(sink) = &window.sink {
if !sink.matches.is_empty() {
let num_mats_to_draw = std::cmp::min(
(sink.matches.len() - window.top_index) as i32,
(height - font_height * 2) / (font_height) - 1,
);
for (i, mat) in sink.matches[window.top_index..].iter().enumerate() {
let index = i + window.top_index;
if i as i32 >= num_mats_to_draw {
break;
}
let path = Path::new(&mat.path);
let relative_file_path = path
.strip_prefix(directory)
.unwrap_or(path)
.to_str()
.unwrap_or("");
let matched_text = String::from_utf8_lossy(&mat.text);
let text = match mat.line_number {
Some(line_number) => format!(
"{} - {}:{}:{}: {}\0",
index, relative_file_path, line_number, mat.column, matched_text
),
None => format!("{}:{}: {}\0", relative_file_path, mat.column, matched_text),
};
if index == window.selected_match {
(plugin.draw_rect)(
x + font_width,
y + font_height + ((index - window.top_index) as i32) * font_height,
(text.len() as i32) * font_width,
font_height,
PaletteColor::Background2,
);
}
(plugin.draw_text)(
text.as_ptr() as *const i8,
(x + font_width) as f32,
(y + font_height + ((index - window.top_index) as i32) * font_height) as f32,
PaletteColor::Foreground2,
);
}
}
}
}
extern "C" fn on_buffer_input(plugin: Plugin, buffer: Buffer) {
// NOTE(pcleavelin): this is super jank, because another plugin could have a window open when
// this gets called, however its fine here because we aren't manipulating any data, and a check
// is made between the buffer pointers which will only be correct if its our window.
if let Some(window) = unsafe { plugin.get_window::<GrepWindow>() } {
if window.input_buffer == Some(buffer) {
window.selected_match = 0;
window.top_index = 0;
if let Some(buffer_info) = plugin.buffer_table.get_buffer_info(buffer) {
if let Some(input) = buffer_info.input.try_as_str() {
let directory = OsString::from_str(plugin.get_current_directory().as_ref());
window.sink = match directory {
Ok(dir) => search(&input, &[dir]).ok(),
Err(_) => {
eprintln!("failed to parse directory");
None
}
};
}
}
}
}
}
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) };
if let Some(buffer) = window.input_buffer {
plugin.buffer_table.free_virtual_buffer(buffer);
window.input_buffer = None;
}
}

View File

@ -34,13 +34,13 @@ OnDraw :: proc "c" (plugin: Plugin) {
iterate_buffer :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { iterate_buffer :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) {
result := iter_funcs.iterate_buffer(it); result := iter_funcs.iterate_buffer(it);
return result.char, it.cursor.index, result.should_stop; return result.char, it.cursor.index, result.should_continue;
} }
iterate_buffer_reverse :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { iterate_buffer_reverse :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) {
result := iter_funcs.iterate_buffer_reverse(it); result := iter_funcs.iterate_buffer_reverse(it);
return result.char, it.cursor.index, result.should_stop; return result.char, it.cursor.index, result.should_continue;
} }
iterate_buffer_until :: proc(plugin: Plugin, it: ^BufferIter, until_proc: rawptr) { iterate_buffer_until :: proc(plugin: Plugin, it: ^BufferIter, until_proc: rawptr) {
@ -50,7 +50,7 @@ iterate_buffer_until :: proc(plugin: Plugin, it: ^BufferIter, until_proc: rawptr
iterate_buffer_peek :: proc(plugin: Plugin, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) { iterate_buffer_peek :: proc(plugin: Plugin, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) {
result := plugin.iter.iterate_buffer_peek(it); result := plugin.iter.iterate_buffer_peek(it);
return result.char, it.cursor.index, result.should_stop; return result.char, it.cursor.index, result.should_continue;
} }
is_odin_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) { is_odin_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) {

View File

@ -11,15 +11,12 @@ Mode :: enum {
Insert, Insert,
} }
WindowDrawProc :: proc "c" (plugin: plugin.Plugin, user_data: rawptr);
WindowFreeProc :: proc "c" (plugin: plugin.Plugin, user_data: rawptr);
WindowGetBufferProc :: proc(win: ^Window) -> ^FileBuffer;
Window :: struct { Window :: struct {
input_map: InputMap, input_map: InputMap,
draw: WindowDrawProc, draw: plugin.WindowDrawProc,
free_user_data: WindowFreeProc, free_user_data: plugin.WindowFreeProc,
get_buffer: WindowGetBufferProc, get_buffer: plugin.WindowGetBufferProc,
// TODO: create hook for when mode changes happen // TODO: create hook for when mode changes happen
@ -69,7 +66,16 @@ State :: struct {
plugins: [dynamic]plugin.Interface, plugins: [dynamic]plugin.Interface,
plugin_vtable: plugin.Plugin, plugin_vtable: plugin.Plugin,
highlighters: map[string]plugin.OnColorBufferProc highlighters: map[string]plugin.OnColorBufferProc,
hooks: map[plugin.Hook][dynamic]plugin.OnHookProc,
}
add_hook :: proc(state: ^State, hook: plugin.Hook, hook_proc: plugin.OnHookProc) {
if _, exists := state.hooks[hook]; !exists {
state.hooks[hook] = make([dynamic]plugin.OnHookProc);
}
runtime.append(&state.hooks[hook], hook_proc);
} }
PluginEditorAction :: proc "c" (plugin: plugin.Plugin); PluginEditorAction :: proc "c" (plugin: plugin.Plugin);

View File

@ -630,9 +630,13 @@ next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int {
return index; return index;
} }
into_buffer_info:: proc(state: ^State, buffer: ^FileBuffer) -> plugin.BufferInfo { into_buffer_info :: proc(state: ^State, buffer: ^FileBuffer) -> plugin.BufferInfo {
return plugin.BufferInfo { return plugin.BufferInfo {
buffer = buffer, buffer = buffer,
input = plugin.BufferInput {
bytes = raw_data(buffer.input_buffer),
length = len(buffer.input_buffer),
},
cursor = plugin.Cursor { cursor = plugin.Cursor {
col = buffer.cursor.col, col = buffer.cursor.col,
line = buffer.cursor.line, line = buffer.cursor.line,

View File

@ -62,6 +62,10 @@ do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) {
for key > 0 { for key > 0 {
if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 { if key >= 32 && key <= 125 && len(buffer.input_buffer) < 1024-1 {
append(&buffer.input_buffer, u8(key)); append(&buffer.input_buffer, u8(key));
for hook_proc in state.hooks[plugin.Hook.BufferInput] {
hook_proc(state.plugin_vtable, buffer);
}
} }
key = raylib.GetCharPressed(); key = raylib.GetCharPressed();
@ -81,6 +85,10 @@ do_insert_mode :: proc(state: ^State, buffer: ^FileBuffer) {
if raylib.IsKeyPressed(.BACKSPACE) { if raylib.IsKeyPressed(.BACKSPACE) {
core.delete_content(buffer, 1); core.delete_content(buffer, 1);
for hook_proc in state.hooks[plugin.Hook.BufferInput] {
hook_proc(state.plugin_vtable, buffer);
}
} }
} }
@ -94,15 +102,6 @@ switch_to_buffer :: proc(state: ^State, item: ^ui.MenuBarItem) {
} }
register_default_leader_actions :: proc(input_map: ^core.InputMap) { register_default_leader_actions :: proc(input_map: ^core.InputMap) {
// core.register_key_action(input_map, .B, proc(state: ^State) {
// state.window = ui.create_buffer_list_window();
// state.current_input_map = &state.window.input_map;
// }, "show list of open buffers");
core.register_key_action(input_map, .R, proc(state: ^State) {
state.window = ui.create_grep_window();
state.current_input_map = &state.window.input_map;
state.mode = .Insert;
}, "live grep");
core.register_key_action(input_map, .Q, proc(state: ^State) { core.register_key_action(input_map, .Q, proc(state: ^State) {
state.current_input_map = &state.input_map; state.current_input_map = &state.input_map;
}, "close this help"); }, "close this help");
@ -223,9 +222,15 @@ main :: proc() {
directory = os.get_current_directory(), directory = os.get_current_directory(),
plugins = make([dynamic]plugin.Interface), plugins = make([dynamic]plugin.Interface),
highlighters = make(map[string]plugin.OnColorBufferProc), highlighters = make(map[string]plugin.OnColorBufferProc),
hooks = make(map[plugin.Hook][dynamic]plugin.OnHookProc),
}; };
state.plugin_vtable = plugin.Plugin { state.plugin_vtable = plugin.Plugin {
state = cast(rawptr)&state, state = cast(rawptr)&state,
register_hook = proc "c" (hook: plugin.Hook, on_hook: plugin.OnHookProc) {
context = state.ctx;
core.add_hook(&state, hook, on_hook);
},
register_highlighter = proc "c" (extension: cstring, on_color_buffer: plugin.OnColorBufferProc) { register_highlighter = proc "c" (extension: cstring, on_color_buffer: plugin.OnColorBufferProc) {
context = state.ctx; context = state.ctx;
@ -290,12 +295,13 @@ main :: proc() {
core.register_key_action(to_be_edited_map, key, input_action, description); core.register_key_action(to_be_edited_map, key, input_action, description);
} }
}, },
create_window = proc "c" (user_data: rawptr, register_group: plugin.InputGroupProc, draw_proc: plugin.WindowDrawProc, free_window_proc: plugin.WindowFreeProc) -> rawptr { create_window = proc "c" (user_data: rawptr, register_group: plugin.InputGroupProc, draw_proc: plugin.WindowDrawProc, free_window_proc: plugin.WindowFreeProc, get_buffer_proc: plugin.WindowGetBufferProc) -> rawptr {
context = state.ctx; context = state.ctx;
window := new(core.Window); window := new(core.Window);
window^ = core.Window { window^ = core.Window {
input_map = core.new_input_map(), input_map = core.new_input_map(),
draw = draw_proc, draw = draw_proc,
get_buffer = get_buffer_proc,
free_user_data = free_window_proc, free_user_data = free_window_proc,
user_data = user_data, user_data = user_data,
@ -337,6 +343,9 @@ main :: proc() {
return strings.clone_to_cstring(state.directory, context.temp_allocator); return strings.clone_to_cstring(state.directory, context.temp_allocator);
}, },
enter_insert_mode = proc "c" () {
state.mode = .Insert;
},
draw_rect = proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor) { draw_rect = proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor) {
context = state.ctx; context = state.ctx;
@ -357,7 +366,7 @@ main :: proc() {
theme.get_palette_raylib_color(color) theme.get_palette_raylib_color(color)
); );
}, },
draw_buffer = 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;
state.buffers[buffer_index].glyph_buffer_width = glyph_buffer_width; state.buffers[buffer_index].glyph_buffer_width = glyph_buffer_width;
state.buffers[buffer_index].glyph_buffer_height = glyph_buffer_height; state.buffers[buffer_index].glyph_buffer_height = glyph_buffer_height;
@ -370,6 +379,21 @@ main :: proc() {
state.font, state.font,
show_line_numbers); show_line_numbers);
}, },
draw_buffer = proc "c" (buffer: rawptr, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool) {
context = state.ctx;
buffer := transmute(^core.FileBuffer)buffer;
buffer.glyph_buffer_width = glyph_buffer_width;
buffer.glyph_buffer_height = glyph_buffer_height;
core.draw_file_buffer(
&state,
buffer,
x,
y,
state.font,
show_line_numbers);
},
iter = plugin.Iterator { iter = plugin.Iterator {
get_current_buffer_iterator = proc "c" () -> plugin.BufferIter { get_current_buffer_iterator = proc "c" () -> plugin.BufferIter {
context = state.ctx; context = state.ctx;
@ -467,7 +491,7 @@ main :: proc() {
return plugin.IterateResult { return plugin.IterateResult {
char = char, char = char,
should_stop = cond, should_continue = cond,
}; };
}, },
iterate_buffer_reverse = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult { iterate_buffer_reverse = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
@ -504,7 +528,7 @@ main :: proc() {
return plugin.IterateResult { return plugin.IterateResult {
char = char, char = char,
should_stop = cond, should_continue = cond,
}; };
}, },
iterate_buffer_until = proc "c" (it: ^plugin.BufferIter, until_proc: rawptr) { iterate_buffer_until = proc "c" (it: ^plugin.BufferIter, until_proc: rawptr) {
@ -573,7 +597,7 @@ main :: proc() {
return plugin.IterateResult { return plugin.IterateResult {
char = char, char = char,
should_stop = cond, should_continue = cond,
}; };
}, },
until_line_break = transmute(rawptr)core.until_line_break, until_line_break = transmute(rawptr)core.until_line_break,
@ -624,7 +648,62 @@ main :: proc() {
}, },
set_current_buffer = proc "c" (buffer_index: int) { set_current_buffer = proc "c" (buffer_index: int) {
state.current_buffer = buffer_index; state.current_buffer = buffer_index;
} },
open_buffer = proc "c" (path: cstring, line: int, col: int) {
context = state.ctx;
path := string(path);
should_create_buffer := true;
for buffer, index in state.buffers {
if strings.compare(buffer.file_path, path) == 0 {
state.current_buffer = index;
should_create_buffer = false;
break;
}
}
buffer: ^core.FileBuffer = nil;
err := core.no_error();
if should_create_buffer {
new_buffer, err := core.new_file_buffer(context.allocator, strings.clone(path));
if err.type != .None {
fmt.println("Failed to open/create file buffer:", err);
} else {
runtime.append(&state.buffers, new_buffer);
state.current_buffer = len(state.buffers)-1;
buffer = &state.buffers[state.current_buffer];
}
} else {
buffer = &state.buffers[state.current_buffer];
}
if buffer != nil {
buffer.cursor.line = line;
buffer.cursor.col = col;
buffer.glyph_buffer_height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1;
buffer.glyph_buffer_width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width));
core.update_file_buffer_index_from_cursor(buffer);
}
},
open_virtual_buffer = proc "c" () -> rawptr {
context = state.ctx;
buffer := new(FileBuffer);
buffer^ = core.new_virtual_file_buffer(context.allocator);
return buffer;
},
free_virtual_buffer = proc "c" (buffer: rawptr) {
context = state.ctx;
if buffer != nil {
buffer := cast(^core.FileBuffer)buffer;
core.free_file_buffer(buffer);
free(buffer);
}
},
} }
}; };
state.current_input_map = &state.input_map; state.current_input_map = &state.input_map;
@ -824,13 +903,15 @@ main :: proc() {
switch state.mode { switch state.mode {
case .Normal: case .Normal:
if state.window != nil && state.window.get_buffer != nil { if state.window != nil && state.window.get_buffer != nil {
do_normal_mode(&state, state.window->get_buffer()); buffer := transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data));
do_normal_mode(&state, buffer);
} else { } else {
do_normal_mode(&state, buffer); do_normal_mode(&state, buffer);
} }
case .Insert: case .Insert:
if state.window != nil && state.window.get_buffer != nil { if state.window != nil && state.window.get_buffer != nil {
do_insert_mode(&state, state.window->get_buffer()); buffer := transmute(^core.FileBuffer)(state.window.get_buffer(state.plugin_vtable, state.window.user_data));
do_insert_mode(&state, buffer);
} else { } else {
do_insert_mode(&state, buffer); do_insert_mode(&state, buffer);
} }

View File

@ -35,12 +35,18 @@ BufferIter :: struct {
IterateResult :: struct { IterateResult :: struct {
char: u8, char: u8,
should_stop: bool, should_continue: bool,
}
BufferInput :: struct {
bytes: [^]u8,
length: int,
} }
BufferInfo :: struct { BufferInfo :: struct {
buffer: rawptr, buffer: rawptr,
file_path: cstring, file_path: cstring,
input: BufferInput,
cursor: Cursor, cursor: Cursor,
@ -55,6 +61,10 @@ Buffer :: struct {
get_buffer_info_from_index: proc "c" (buffer_index: int) -> BufferInfo, get_buffer_info_from_index: proc "c" (buffer_index: int) -> BufferInfo,
color_char_at: proc "c" (buffer: rawptr, start_cursor: Cursor, end_cursor: Cursor, palette_index: i32), color_char_at: proc "c" (buffer: rawptr, start_cursor: Cursor, end_cursor: Cursor, palette_index: i32),
set_current_buffer: proc "c" (buffer_index: int), set_current_buffer: proc "c" (buffer_index: int),
open_buffer: proc "c" (path: cstring, line: int, col: int),
open_virtual_buffer: proc "c" () -> rawptr,
free_virtual_buffer: proc "c" (buffer: rawptr),
} }
Iterator :: struct { Iterator :: struct {
@ -78,20 +88,24 @@ Iterator :: struct {
OnColorBufferProc :: proc "c" (plugin: Plugin, buffer: rawptr); OnColorBufferProc :: proc "c" (plugin: Plugin, buffer: rawptr);
InputGroupProc :: proc "c" (plugin: Plugin, input_map: rawptr); InputGroupProc :: proc "c" (plugin: Plugin, input_map: rawptr);
InputActionProc :: proc "c" (plugin: Plugin); InputActionProc :: proc "c" (plugin: Plugin);
OnHookProc :: proc "c" (plugin: Plugin, buffer: rawptr);
WindowInputProc :: proc "c" (plugin: Plugin, window: rawptr); WindowInputProc :: proc "c" (plugin: Plugin, window: rawptr);
WindowDrawProc :: proc "c" (plugin: Plugin, window: rawptr); WindowDrawProc :: proc "c" (plugin: Plugin, window: rawptr);
WindowGetBufferProc :: proc(plugin: Plugin, window: rawptr) -> rawptr;
WindowFreeProc :: proc "c" (plugin: Plugin, window: rawptr); WindowFreeProc :: proc "c" (plugin: Plugin, window: rawptr);
Plugin :: struct { Plugin :: struct {
state: rawptr, state: rawptr,
iter: Iterator, iter: Iterator,
buffer: Buffer, buffer: Buffer,
register_hook: proc "c" (hook: Hook, on_hook: OnHookProc),
register_highlighter: proc "c" (extension: cstring, on_color_buffer: OnColorBufferProc), register_highlighter: proc "c" (extension: cstring, on_color_buffer: OnColorBufferProc),
register_input_group: proc "c" (input_map: rawptr, key: Key, register_group: InputGroupProc), register_input_group: proc "c" (input_map: rawptr, key: Key, register_group: InputGroupProc),
register_input: proc "c" (input_map: rawptr, key: Key, input_action: InputActionProc, description: cstring), register_input: proc "c" (input_map: rawptr, key: Key, input_action: InputActionProc, description: cstring),
create_window: proc "c" (user_data: rawptr, register_group: InputGroupProc, draw_proc: WindowDrawProc, free_window_proc: WindowFreeProc) -> rawptr, create_window: proc "c" (user_data: rawptr, register_group: InputGroupProc, draw_proc: WindowDrawProc, free_window_proc: WindowFreeProc, get_buffer_proc: WindowGetBufferProc) -> rawptr,
get_window: proc "c" () -> rawptr, get_window: proc "c" () -> rawptr,
request_window_close: proc "c" (), request_window_close: proc "c" (),
@ -100,10 +114,16 @@ Plugin :: struct {
get_font_width: proc "c" () -> int, get_font_width: proc "c" () -> int,
get_font_height: proc "c" () -> int, get_font_height: proc "c" () -> int,
get_current_directory: proc "c" () -> cstring, get_current_directory: proc "c" () -> cstring,
enter_insert_mode: proc "c" (),
draw_rect: proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor), draw_rect: proc "c" (x: i32, y: i32, width: i32, height: i32, color: theme.PaletteColor),
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),
draw_buffer: 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),
draw_buffer: proc "c" (buffer: rawptr, x: int, y: int, glyph_buffer_width: int, glyph_buffer_height: int, show_line_numbers: bool),
}
Hook :: enum {
BufferInput = 0,
} }
Key :: enum { Key :: enum {

View File

@ -1,285 +0,0 @@
package ui;
@(extra_linker_flags="-L./lib-rg/target/debug/")
foreign import rg "system:rg"
ExternMatch :: struct {
text_ptr: [^]u8,
text_len: int,
text_cap: int,
path_ptr: [^]u8,
path_len: int,
path_cap: int,
line: u64,
col: u64
}
ExternMatchArray :: struct {
matches: [^]ExternMatch,
len: uint,
capacity: uint,
}
foreign rg {
rg_search :: proc (pattern: cstring, path: cstring) -> ExternMatchArray ---
drop_match_array :: proc(match_array: ExternMatchArray) ---
}
import "core:os"
import "core:path/filepath"
import "core:math"
import "core:fmt"
import "core:runtime"
import "core:strings"
import "vendor:raylib"
import "../core"
import "../theme"
GrepMatch :: struct {
text: string,
path: string,
line: int,
col: int,
}
transmute_extern_matches :: proc(extern_matches: ExternMatchArray, dest: ^[dynamic]GrepMatch) {
if extern_matches.matches != nil {
for i in 0..<extern_matches.len {
match := &extern_matches.matches[i];
path: string = "";
if match.path_ptr != nil && match.path_len > 0 {
path, _ = filepath.abs(strings.string_from_ptr(match.path_ptr, match.path_len));
}
text: string = "";
if match.text_ptr != nil && match.text_len > 0 {
text = strings.string_from_ptr(match.text_ptr, match.text_len);
}
cloned := GrepMatch {
text = text,
path = path,
line = int(match.line),
col = int(match.col)
};
append(dest, cloned);
}
}
}
GrepWindow :: struct {
using window: core.Window,
input_buffer: core.FileBuffer,
selected_match: int,
extern_matches: ExternMatchArray,
matches: [dynamic]GrepMatch,
}
create_grep_window :: proc() -> ^GrepWindow {
input_map := core.new_input_map();
core.register_key_action(&input_map, .ENTER, proc(state: ^core.State) {
win := cast(^GrepWindow)(state.window);
if win.matches != nil && len(win.matches) > 0 {
should_create_buffer := true;
for buffer, index in state.buffers {
if strings.compare(buffer.file_path, win.matches[win.selected_match].path) == 0 {
state.current_buffer = index;
should_create_buffer = false;
break;
}
}
buffer: ^core.FileBuffer = nil;
err := core.no_error();
if should_create_buffer {
new_buffer, err := core.new_file_buffer(context.allocator, strings.clone(win.matches[win.selected_match].path));
if err.type != .None {
fmt.println("Failed to open/create file buffer:", err);
} else {
runtime.append(&state.buffers, new_buffer);
state.current_buffer = len(state.buffers)-1;
buffer = &state.buffers[state.current_buffer];
}
} else {
buffer = &state.buffers[state.current_buffer];
}
if buffer != nil {
buffer.cursor.line = win.matches[win.selected_match].line-1;
buffer.cursor.col = 0;
buffer.glyph_buffer_height = math.min(256, int((state.screen_height - state.source_font_height*2) / state.source_font_height)) + 1;
buffer.glyph_buffer_width = math.min(256, int((state.screen_width - state.source_font_width) / state.source_font_width));
core.update_file_buffer_index_from_cursor(buffer);
core.request_window_close(state);
}
}
}, "jump to location");
core.register_key_action(&input_map, .I, proc(state: ^core.State) {
state.mode = .Insert;
}, "enter insert mode");
core.register_key_action(&input_map, .T, proc(state: ^core.State) {
win := cast(^GrepWindow)(state.window);
grep_files(win, state);
}, "example search");
core.register_key_action(&input_map, .K, proc(state: ^core.State) {
win := cast(^GrepWindow)(state.window);
if win.selected_match > 0 {
win.selected_match -= 1;
} else {
win.selected_match = len(win.matches)-1;
}
}, "move selection up");
core.register_key_action(&input_map, .J, proc(state: ^core.State) {
win := cast(^GrepWindow)(state.window);
if win.selected_match >= len(win.matches)-1 {
win.selected_match = 0;
} else {
win.selected_match += 1;
}
}, "move selection down");
grep_window := new(GrepWindow);
grep_window^ = GrepWindow {
window = core.Window {
input_map = input_map,
//draw = draw_grep_window,
get_buffer = grep_window_get_buffer,
// free_user_data = free_grep_window,
},
input_buffer = core.new_virtual_file_buffer(context.allocator),
matches = make([dynamic]GrepMatch),
};
return grep_window;
}
free_grep_window :: proc(win: ^core.Window, state: ^core.State) {
win := cast(^GrepWindow)(win);
if win.extern_matches.matches != nil {
drop_match_array(win.extern_matches);
win.extern_matches.matches = nil;
win.extern_matches.len = 0;
win.extern_matches.capacity = 0;
}
delete(win.matches);
core.free_file_buffer(&win.input_buffer);
}
grep_window_get_buffer :: proc(win: ^core.Window) -> ^core.FileBuffer {
win := cast(^GrepWindow)(win);
return &win.input_buffer;
}
@private
grep_files :: proc(win: ^core.Window, state: ^core.State) {
win := cast(^GrepWindow)(win);
if win.extern_matches.matches != nil {
drop_match_array(win.extern_matches);
win.extern_matches.matches = nil;
win.extern_matches.len = 0;
win.extern_matches.capacity = 0;
}
if win.matches != nil {
clear_dynamic_array(&win.matches);
} else {
win.matches = make([dynamic]GrepMatch);
}
builder := strings.builder_make();
it := core.new_file_buffer_iter(&win.input_buffer);
for character in core.iterate_file_buffer(&it) {
if character == '\n' { break; }
strings.write_rune(&builder, rune(character));
}
pattern := strings.clone_to_cstring(strings.to_string(builder));
win.extern_matches = rg_search(pattern, strings.clone_to_cstring(state.directory));
transmute_extern_matches(win.extern_matches, &win.matches);
}
draw_grep_window :: proc(win: ^core.Window, state: ^core.State) {
win := cast(^GrepWindow)(win);
win_rec := raylib.Rectangle {
x = f32(state.screen_width/8),
y = f32(state.screen_height/8),
width = f32(state.screen_width - state.screen_width/4),
height = f32(state.screen_height - state.screen_height/4),
};
raylib.DrawRectangleRec(
win_rec,
theme.get_palette_raylib_color(.Background4));
win_margin := raylib.Vector2 { f32(state.source_font_width), f32(state.source_font_height) };
buffer_prev_width := (win_rec.width - win_margin.x*2) / 2;
buffer_prev_height := win_rec.height - win_margin.y*2;
glyph_buffer_width := int(buffer_prev_width) / state.source_font_width - 1;
glyph_buffer_height := 1;
raylib.DrawRectangle(
i32(win_rec.x + win_margin.x),
i32(win_rec.y + win_rec.height - win_margin.y * 2),
i32(buffer_prev_width),
i32(state.source_font_height),
theme.get_palette_raylib_color(.Background2));
win.input_buffer.glyph_buffer_height = glyph_buffer_height;
win.input_buffer.glyph_buffer_width = glyph_buffer_width;
core.draw_file_buffer(
state,
&win.input_buffer,
int(win_rec.x + win_margin.x),
int(win_rec.y + win_rec.height - win_margin.y * 2),
state.font,
show_line_numbers = false);
for match, index in win.matches {
relative_file_path, _ := filepath.rel(state.directory, match.path)
text := raylib.TextFormat("%s:%d:%d: %s", relative_file_path, match.line, match.col, match.text);
text_width := raylib.MeasureTextEx(state.font, text, f32(state.source_font_height), 0);
if index == win.selected_match {
raylib.DrawRectangle(
i32(win_rec.x + win_margin.x),
i32(win_rec.y + win_margin.y) + i32(index * state.source_font_height),
i32(text_width.x),
i32(state.source_font_height),
theme.get_palette_raylib_color(.Background2));
}
raylib.DrawTextEx(
state.font,
text,
raylib.Vector2 { win_rec.x + win_margin.x, win_rec.y + win_margin.y + f32(index * state.source_font_height) },
f32(state.source_font_height),
0,
theme.get_palette_raylib_color(.Foreground2));
}
}