merge plugins into master
parent
44d3e498a1
commit
13240b4f3a
|
@ -1,2 +1,2 @@
|
||||||
bin/
|
bin/
|
||||||
lib-rg/target
|
**/target
|
||||||
|
|
12
Makefile
12
Makefile
|
@ -1,7 +1,13 @@
|
||||||
all: editor
|
all: editor
|
||||||
|
|
||||||
editor: src/*.odin rg
|
editor: src/*.odin grep odin_highlighter buffer_search
|
||||||
odin build src/ -out:bin/editor -lld
|
odin build src/ -out:bin/editor -lld
|
||||||
|
|
||||||
rg:
|
buffer_search:
|
||||||
cargo b --manifest-path=lib-rg/Cargo.toml
|
odin build plugins/buffer_search/ -build-mode:dll -no-entry-point -out:bin/buffer_search
|
||||||
|
odin_highlighter:
|
||||||
|
odin build plugins/highlighter/src/ -build-mode:dll -no-entry-point -out:bin/highlighter
|
||||||
|
|
||||||
|
grep:
|
||||||
|
cargo b --manifest-path=plugins/grep/Cargo.toml
|
||||||
|
cp plugins/grep/target/debug/libgrep_plugin.dylib bin/
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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"
|
|
@ -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]
|
|
@ -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,
|
||||||
|
}
|
|
@ -0,0 +1,177 @@
|
||||||
|
// A simple window to view/search open buffers
|
||||||
|
package buffer_search;
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
|
import "core:fmt"
|
||||||
|
import "core:path/filepath"
|
||||||
|
import "vendor:raylib"
|
||||||
|
|
||||||
|
import p "../../src/plugin"
|
||||||
|
import "../../src/theme"
|
||||||
|
|
||||||
|
Plugin :: p.Plugin;
|
||||||
|
Iterator :: p.Iterator;
|
||||||
|
BufferIter :: p.BufferIter;
|
||||||
|
BufferIndex :: p.BufferIndex;
|
||||||
|
Key :: p.Key;
|
||||||
|
|
||||||
|
BufferListWindow :: struct {
|
||||||
|
selected_index: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
@export
|
||||||
|
OnInitialize :: proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
fmt.println("builtin buffer search plugin initialized!");
|
||||||
|
|
||||||
|
plugin.register_input_group(nil, .SPACE, proc "c" (plugin: Plugin, input_map: rawptr) {
|
||||||
|
plugin.register_input(input_map, .B, open_buffer_window, "show list of open buffers");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@export
|
||||||
|
OnExit :: proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
}
|
||||||
|
|
||||||
|
open_buffer_window :: proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
|
||||||
|
window := new(BufferListWindow);
|
||||||
|
window^ = BufferListWindow {};
|
||||||
|
|
||||||
|
plugin.create_window(window, proc "c" (plugin: Plugin, input_map: rawptr) {
|
||||||
|
plugin.register_input(input_map, .K, proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
|
||||||
|
win := cast(^BufferListWindow)plugin.get_window();
|
||||||
|
if win != nil {
|
||||||
|
if win.selected_index > 0 {
|
||||||
|
win.selected_index -= 1;
|
||||||
|
} else {
|
||||||
|
win.selected_index = plugin.buffer.get_num_buffers()-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "move selection up");
|
||||||
|
plugin.register_input(input_map, .J, proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
|
||||||
|
win := cast(^BufferListWindow)plugin.get_window();
|
||||||
|
if win != nil {
|
||||||
|
if win.selected_index < plugin.buffer.get_num_buffers()-1 {
|
||||||
|
win.selected_index += 1;
|
||||||
|
} else {
|
||||||
|
win.selected_index = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, "move selection down");
|
||||||
|
plugin.register_input(input_map, .ENTER, proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
|
||||||
|
win := cast(^BufferListWindow)plugin.get_window();
|
||||||
|
if win != nil {
|
||||||
|
plugin.buffer.set_current_buffer(win.selected_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.request_window_close();
|
||||||
|
}, "switch to buffer")
|
||||||
|
}, draw_buffer_window, free_buffer_window, nil);
|
||||||
|
}
|
||||||
|
|
||||||
|
free_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
win := cast(^BufferListWindow)plugin.get_window();
|
||||||
|
if win == nil {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(win);
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer_list_iter :: proc(plugin: Plugin, buffer_index: ^int) -> (int, int, bool) {
|
||||||
|
if buffer_index^ == -1 {
|
||||||
|
return 0, 0, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
index := plugin.iter.get_buffer_list_iter(buffer_index);
|
||||||
|
return index, 0, true;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw_buffer_window :: proc "c" (plugin: Plugin, win: rawptr) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
win := cast(^BufferListWindow)win;
|
||||||
|
if win == nil {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
screen_width := plugin.get_screen_width();
|
||||||
|
screen_height := plugin.get_screen_height();
|
||||||
|
source_font_width := plugin.get_font_width();
|
||||||
|
source_font_height := plugin.get_font_height();
|
||||||
|
|
||||||
|
win_rec := raylib.Rectangle {
|
||||||
|
x = f32(screen_width/8),
|
||||||
|
y = f32(screen_height/8),
|
||||||
|
width = f32(screen_width - screen_width/4),
|
||||||
|
height = f32(screen_height - screen_height/4),
|
||||||
|
};
|
||||||
|
plugin.draw_rect(
|
||||||
|
i32(win_rec.x),
|
||||||
|
i32(win_rec.y),
|
||||||
|
i32(win_rec.width),
|
||||||
|
i32(win_rec.height),
|
||||||
|
.Background4
|
||||||
|
);
|
||||||
|
|
||||||
|
win_margin := raylib.Vector2 { f32(source_font_width), f32(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) / source_font_width - 1;
|
||||||
|
glyph_buffer_height := int(buffer_prev_height) / source_font_height;
|
||||||
|
|
||||||
|
directory := string(plugin.get_current_directory());
|
||||||
|
|
||||||
|
plugin.draw_rect(
|
||||||
|
i32(win_rec.x + win_rec.width / 2),
|
||||||
|
i32(win_rec.y + win_margin.y),
|
||||||
|
i32(buffer_prev_width),
|
||||||
|
i32(buffer_prev_height),
|
||||||
|
.Background2,
|
||||||
|
);
|
||||||
|
|
||||||
|
_buffer_index := 0;
|
||||||
|
for index in buffer_list_iter(plugin, &_buffer_index) {
|
||||||
|
buffer := plugin.buffer.get_buffer_info_from_index(index);
|
||||||
|
relative_file_path, _ := filepath.rel(directory, string(buffer.file_path), context.temp_allocator)
|
||||||
|
text := fmt.ctprintf("%s:%d", relative_file_path, buffer.cursor.line+1);
|
||||||
|
text_width := len(text) * source_font_width;
|
||||||
|
|
||||||
|
if index == win.selected_index {
|
||||||
|
plugin.draw_buffer_from_index(
|
||||||
|
index,
|
||||||
|
int(win_rec.x + win_margin.x + win_rec.width / 2),
|
||||||
|
int(win_rec.y + win_margin.y),
|
||||||
|
glyph_buffer_width,
|
||||||
|
glyph_buffer_height,
|
||||||
|
false);
|
||||||
|
|
||||||
|
plugin.draw_rect(
|
||||||
|
i32(win_rec.x + win_margin.x),
|
||||||
|
i32(win_rec.y + win_margin.y) + i32(index * source_font_height),
|
||||||
|
i32(text_width),
|
||||||
|
i32(source_font_height),
|
||||||
|
.Background2,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.draw_text(
|
||||||
|
text,
|
||||||
|
win_rec.x + win_margin.x, win_rec.y + win_margin.y + f32(index * source_font_height),
|
||||||
|
.Foreground2
|
||||||
|
);
|
||||||
|
|
||||||
|
runtime.free_all(context.temp_allocator);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
@ -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" }
|
|
@ -0,0 +1,419 @@
|
||||||
|
use std::{
|
||||||
|
error::Error,
|
||||||
|
ffi::OsString,
|
||||||
|
path::Path,
|
||||||
|
str::FromStr,
|
||||||
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use grep::{
|
||||||
|
regex::{RegexMatcher, RegexMatcherBuilder},
|
||||||
|
searcher::{BinaryDetection, SearcherBuilder, Sink, SinkError},
|
||||||
|
};
|
||||||
|
use plugin_rs_bindings::{Buffer, Closure, Hook, InputMap, Key, PaletteColor, Plugin};
|
||||||
|
use std::sync::mpsc::channel;
|
||||||
|
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 = RegexMatcherBuilder::new()
|
||||||
|
.case_smart(true)
|
||||||
|
.fixed_strings(true)
|
||||||
|
.build(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).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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Message {
|
||||||
|
Search((String, Vec<OsString>)),
|
||||||
|
Quit,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct GrepWindow {
|
||||||
|
sink: Option<SimpleSink>,
|
||||||
|
selected_match: usize,
|
||||||
|
top_index: usize,
|
||||||
|
input_buffer: Option<Buffer>,
|
||||||
|
|
||||||
|
tx: Sender<Message>,
|
||||||
|
rx: Receiver<SimpleSink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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_tx, thread_rx) = channel();
|
||||||
|
let (thread_tx, window_rx) = channel();
|
||||||
|
create_search_thread(thread_tx, thread_rx);
|
||||||
|
|
||||||
|
let window = GrepWindow {
|
||||||
|
selected_match: 0,
|
||||||
|
top_index: 0,
|
||||||
|
input_buffer: Some(plugin.buffer_table.open_virtual_buffer()),
|
||||||
|
sink: None,
|
||||||
|
tx: window_tx,
|
||||||
|
rx: window_rx,
|
||||||
|
};
|
||||||
|
|
||||||
|
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(),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
|
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);
|
||||||
|
(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 {
|
||||||
|
(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 Ok(sink) = window.rx.try_recv() {
|
||||||
|
window.sink = Some(sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
let max_mat_length = (width - font_width * 2) / font_width;
|
||||||
|
|
||||||
|
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!(
|
||||||
|
"{}:{}:{}: {}",
|
||||||
|
relative_file_path, line_number, 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 {
|
||||||
|
(plugin.draw_rect)(
|
||||||
|
x + font_width,
|
||||||
|
y + font_height + ((index - window.top_index) as i32) * font_height,
|
||||||
|
(text.chars().count() 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());
|
||||||
|
|
||||||
|
match directory {
|
||||||
|
Ok(dir) => {
|
||||||
|
if let Err(err) = window
|
||||||
|
.tx
|
||||||
|
.send(Message::Search((input.to_string(), vec![dir])))
|
||||||
|
{
|
||||||
|
eprintln!("failed to grep: {err:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
eprintln!("failed to parse directory");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _ = window.tx.send(Message::Quit);
|
||||||
|
|
||||||
|
if let Some(buffer) = window.input_buffer {
|
||||||
|
plugin.buffer_table.free_virtual_buffer(buffer);
|
||||||
|
window.input_buffer = None;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,420 @@
|
||||||
|
// The default syntax highlighter plugin for Odin & Rust
|
||||||
|
package highlighter;
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
|
import "core:fmt"
|
||||||
|
|
||||||
|
import p "../../../src/plugin"
|
||||||
|
|
||||||
|
Plugin :: p.Plugin;
|
||||||
|
Iterator :: p.Iterator;
|
||||||
|
BufferIter :: p.BufferIter;
|
||||||
|
BufferIndex :: p.BufferIndex;
|
||||||
|
|
||||||
|
@export
|
||||||
|
OnInitialize :: proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
fmt.println("builtin highlighter plugin initialized!");
|
||||||
|
|
||||||
|
plugin.register_highlighter(".odin", color_buffer_odin);
|
||||||
|
plugin.register_highlighter(".rs", color_buffer_rust);
|
||||||
|
}
|
||||||
|
|
||||||
|
@export
|
||||||
|
OnExit :: proc "c" () {
|
||||||
|
context = runtime.default_context();
|
||||||
|
fmt.println("Goodbye from the Odin Highlighter Plugin!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@export
|
||||||
|
OnDraw :: proc "c" (plugin: Plugin) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate_buffer :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) {
|
||||||
|
result := iter_funcs.iterate_buffer(it);
|
||||||
|
|
||||||
|
return result.char, it.cursor.index, result.should_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate_buffer_reverse :: proc(iter_funcs: Iterator, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) {
|
||||||
|
result := iter_funcs.iterate_buffer_reverse(it);
|
||||||
|
|
||||||
|
return result.char, it.cursor.index, result.should_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate_buffer_until :: proc(plugin: Plugin, it: ^BufferIter, until_proc: rawptr) {
|
||||||
|
plugin.iter.iterate_buffer_until(it, until_proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
iterate_buffer_peek :: proc(plugin: Plugin, it: ^BufferIter) -> (character: u8, idx: BufferIndex, cond: bool) {
|
||||||
|
result := plugin.iter.iterate_buffer_peek(it);
|
||||||
|
|
||||||
|
return result.char, it.cursor.index, result.should_continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_odin_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) {
|
||||||
|
keywords := []string {
|
||||||
|
"using",
|
||||||
|
"transmute",
|
||||||
|
"cast",
|
||||||
|
"distinct",
|
||||||
|
"opaque",
|
||||||
|
"where",
|
||||||
|
"struct",
|
||||||
|
"enum",
|
||||||
|
"union",
|
||||||
|
"bit_field",
|
||||||
|
"bit_set",
|
||||||
|
"if",
|
||||||
|
"when",
|
||||||
|
"else",
|
||||||
|
"do",
|
||||||
|
"for",
|
||||||
|
"switch",
|
||||||
|
"case",
|
||||||
|
"continue",
|
||||||
|
"break",
|
||||||
|
"size_of",
|
||||||
|
"offset_of",
|
||||||
|
"type_info_of",
|
||||||
|
"typeid_of",
|
||||||
|
"type_of",
|
||||||
|
"align_of",
|
||||||
|
"or_return",
|
||||||
|
"or_else",
|
||||||
|
"inline",
|
||||||
|
"no_inline",
|
||||||
|
"string",
|
||||||
|
"cstring",
|
||||||
|
"bool",
|
||||||
|
"b8",
|
||||||
|
"b16",
|
||||||
|
"b32",
|
||||||
|
"b64",
|
||||||
|
"rune",
|
||||||
|
"any",
|
||||||
|
"rawptr",
|
||||||
|
"f16",
|
||||||
|
"f32",
|
||||||
|
"f64",
|
||||||
|
"f16le",
|
||||||
|
"f16be",
|
||||||
|
"f32le",
|
||||||
|
"f32be",
|
||||||
|
"f64le",
|
||||||
|
"f64be",
|
||||||
|
"u8",
|
||||||
|
"u16",
|
||||||
|
"u32",
|
||||||
|
"u64",
|
||||||
|
"u128",
|
||||||
|
"u16le",
|
||||||
|
"u32le",
|
||||||
|
"u64le",
|
||||||
|
"u128le",
|
||||||
|
"u16be",
|
||||||
|
"u32be",
|
||||||
|
"u64be",
|
||||||
|
"u128be",
|
||||||
|
"uint",
|
||||||
|
"uintptr",
|
||||||
|
"i8",
|
||||||
|
"i16",
|
||||||
|
"i32",
|
||||||
|
"i64",
|
||||||
|
"i128",
|
||||||
|
"i16le",
|
||||||
|
"i32le",
|
||||||
|
"i64le",
|
||||||
|
"i128le",
|
||||||
|
"i16be",
|
||||||
|
"i32be",
|
||||||
|
"i64be",
|
||||||
|
"i128be",
|
||||||
|
"int",
|
||||||
|
"complex",
|
||||||
|
"complex32",
|
||||||
|
"complex64",
|
||||||
|
"complex128",
|
||||||
|
"quaternion",
|
||||||
|
"quaternion64",
|
||||||
|
"quaternion128",
|
||||||
|
"quaternion256",
|
||||||
|
"matrix",
|
||||||
|
"typeid",
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
"nil",
|
||||||
|
"dynamic",
|
||||||
|
"map",
|
||||||
|
"proc",
|
||||||
|
"in",
|
||||||
|
"notin",
|
||||||
|
"not_in",
|
||||||
|
"import",
|
||||||
|
"export",
|
||||||
|
"foreign",
|
||||||
|
"const",
|
||||||
|
"package",
|
||||||
|
"return",
|
||||||
|
"defer",
|
||||||
|
};
|
||||||
|
|
||||||
|
for keyword in keywords {
|
||||||
|
it := start;
|
||||||
|
keyword_index := 0;
|
||||||
|
|
||||||
|
for character in iterate_buffer(plugin.iter, &it) {
|
||||||
|
if character != keyword[keyword_index] {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword_index += 1;
|
||||||
|
if keyword_index >= len(keyword)-1 && it == end {
|
||||||
|
if plugin.iter.get_char_at_iter(&it) == keyword[keyword_index] {
|
||||||
|
matches = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if keyword_index >= len(keyword)-1 {
|
||||||
|
break;
|
||||||
|
} else if it == end {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is_rust_keyword :: proc(plugin: Plugin, start: BufferIter, end: BufferIter) -> (matches: bool) {
|
||||||
|
keywords := []string {
|
||||||
|
"as",
|
||||||
|
"break",
|
||||||
|
"const",
|
||||||
|
"continue",
|
||||||
|
"crate",
|
||||||
|
"else",
|
||||||
|
"enum",
|
||||||
|
"extern",
|
||||||
|
"false",
|
||||||
|
"fn",
|
||||||
|
"for",
|
||||||
|
"if",
|
||||||
|
"impl",
|
||||||
|
"in",
|
||||||
|
"let",
|
||||||
|
"loop",
|
||||||
|
"match",
|
||||||
|
"mod",
|
||||||
|
"move",
|
||||||
|
"mut",
|
||||||
|
"pub",
|
||||||
|
"ref",
|
||||||
|
"return",
|
||||||
|
"self",
|
||||||
|
"Self",
|
||||||
|
"static",
|
||||||
|
"struct",
|
||||||
|
"super",
|
||||||
|
"trait",
|
||||||
|
"true",
|
||||||
|
"type",
|
||||||
|
"unsafe",
|
||||||
|
"use",
|
||||||
|
"where",
|
||||||
|
"while",
|
||||||
|
"u8",
|
||||||
|
"i8",
|
||||||
|
"u16",
|
||||||
|
"i16",
|
||||||
|
"u32",
|
||||||
|
"i32",
|
||||||
|
"u64",
|
||||||
|
"i64",
|
||||||
|
"bool",
|
||||||
|
"usize",
|
||||||
|
"isize",
|
||||||
|
"str",
|
||||||
|
"String",
|
||||||
|
"Option",
|
||||||
|
"Result",
|
||||||
|
};
|
||||||
|
|
||||||
|
for keyword in keywords {
|
||||||
|
it := start;
|
||||||
|
keyword_index := 0;
|
||||||
|
|
||||||
|
for character in iterate_buffer(plugin.iter, &it) {
|
||||||
|
if character != keyword[keyword_index] {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyword_index += 1;
|
||||||
|
if keyword_index >= len(keyword)-1 && it == end {
|
||||||
|
if plugin.iter.get_char_at_iter(&it) == keyword[keyword_index] {
|
||||||
|
matches = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
} else if keyword_index >= len(keyword)-1 {
|
||||||
|
break;
|
||||||
|
} else if it == end {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if matches {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
color_buffer_odin :: proc "c" (plugin: Plugin, buffer: rawptr) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
|
||||||
|
buffer := plugin.buffer.get_buffer_info(buffer);
|
||||||
|
|
||||||
|
start_it := plugin.iter.get_buffer_iterator(buffer.buffer);
|
||||||
|
it := plugin.iter.get_buffer_iterator(buffer.buffer);
|
||||||
|
|
||||||
|
for character in iterate_buffer(plugin.iter, &it) {
|
||||||
|
if it.cursor.line > buffer.glyph_buffer_height && (it.cursor.line - buffer.top_line) > buffer.glyph_buffer_height {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if character == '/' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
|
||||||
|
character, _, succ := iterate_buffer(plugin.iter, &it);
|
||||||
|
if !succ { break; }
|
||||||
|
|
||||||
|
if character == '/' {
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_line_break);
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 9);
|
||||||
|
} else if character == '*' {
|
||||||
|
// TODO: block comments
|
||||||
|
}
|
||||||
|
} else if character == '\'' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
|
||||||
|
// jump into the quoted text
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_single_quote);
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12);
|
||||||
|
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
} else if character == '"' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
|
||||||
|
// jump into the quoted text
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_double_quote);
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12);
|
||||||
|
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
} else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
it = start_it;
|
||||||
|
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_end_of_word);
|
||||||
|
|
||||||
|
if is_odin_keyword(plugin, start_it, it) {
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 13);
|
||||||
|
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
} else if character, _, cond := iterate_buffer_peek(plugin, &it); cond {
|
||||||
|
if character == '(' {
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 11);
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
color_buffer_rust :: proc "c" (plugin: Plugin, buffer: rawptr) {
|
||||||
|
context = runtime.default_context();
|
||||||
|
|
||||||
|
buffer := plugin.buffer.get_buffer_info(buffer);
|
||||||
|
|
||||||
|
start_it := plugin.iter.get_buffer_iterator(buffer.buffer);
|
||||||
|
it := plugin.iter.get_buffer_iterator(buffer.buffer);
|
||||||
|
|
||||||
|
for character in iterate_buffer(plugin.iter, &it) {
|
||||||
|
if it.cursor.line > buffer.glyph_buffer_height && (it.cursor.line - buffer.top_line) > buffer.glyph_buffer_height {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if character == '/' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
|
||||||
|
character, _, succ := iterate_buffer(plugin.iter, &it);
|
||||||
|
if !succ { break; }
|
||||||
|
|
||||||
|
if character == '/' {
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_line_break);
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 9);
|
||||||
|
} else if character == '*' {
|
||||||
|
// TODO: block comments
|
||||||
|
}
|
||||||
|
} else if character == '\'' && false {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
|
||||||
|
// jump into the quoted text
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_single_quote);
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12);
|
||||||
|
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
} else if character == '"' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
|
||||||
|
// jump into the quoted text
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_double_quote);
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 12);
|
||||||
|
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
} else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' {
|
||||||
|
start_it = it;
|
||||||
|
// need to go back one character because `it` is on the next character
|
||||||
|
iterate_buffer_reverse(plugin.iter, &start_it);
|
||||||
|
it = start_it;
|
||||||
|
|
||||||
|
iterate_buffer_until(plugin, &it, plugin.iter.until_end_of_word);
|
||||||
|
|
||||||
|
if is_rust_keyword(plugin, start_it, it) {
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 13);
|
||||||
|
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
} else if character, _, cond := iterate_buffer_peek(plugin, &it); cond {
|
||||||
|
if character == '(' || character == '<' || character == '!' {
|
||||||
|
plugin.buffer.color_char_at(it.buffer, start_it.cursor, it.cursor, 11);
|
||||||
|
iterate_buffer(plugin.iter, &it);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +1,26 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
|
import "core:runtime"
|
||||||
import "core:fmt"
|
import "core:fmt"
|
||||||
import "vendor:raylib"
|
import "vendor:raylib"
|
||||||
|
|
||||||
|
import "../plugin"
|
||||||
|
|
||||||
Mode :: enum {
|
Mode :: enum {
|
||||||
Normal,
|
Normal,
|
||||||
Insert,
|
Insert,
|
||||||
}
|
}
|
||||||
|
|
||||||
WindowDrawProc :: proc(win: ^Window, state: ^State);
|
|
||||||
WindowFreeProc :: proc(win: ^Window, state: ^State);
|
|
||||||
WindowGetBufferProc :: proc(win: ^Window) -> ^FileBuffer;
|
|
||||||
Window :: struct {
|
Window :: struct {
|
||||||
input_map: InputMap,
|
input_map: InputMap,
|
||||||
draw: WindowDrawProc,
|
draw: plugin.WindowDrawProc,
|
||||||
free: 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
|
||||||
|
|
||||||
|
user_data: rawptr,
|
||||||
}
|
}
|
||||||
request_window_close :: proc(state: ^State) {
|
request_window_close :: proc(state: ^State) {
|
||||||
state.should_close_window = true;
|
state.should_close_window = true;
|
||||||
|
@ -26,8 +28,8 @@ request_window_close :: proc(state: ^State) {
|
||||||
|
|
||||||
close_window_and_free :: proc(state: ^State) {
|
close_window_and_free :: proc(state: ^State) {
|
||||||
if state.window != nil {
|
if state.window != nil {
|
||||||
if state.window.free != nil {
|
if state.window.free_user_data != nil {
|
||||||
state.window->free(state);
|
state.window.free_user_data(state.plugin_vtable, state.window.user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
delete_input_map(&state.window.input_map);
|
delete_input_map(&state.window.input_map);
|
||||||
|
@ -39,6 +41,8 @@ close_window_and_free :: proc(state: ^State) {
|
||||||
}
|
}
|
||||||
|
|
||||||
State :: struct {
|
State :: struct {
|
||||||
|
ctx: runtime.Context,
|
||||||
|
|
||||||
mode: Mode,
|
mode: Mode,
|
||||||
should_close: bool,
|
should_close: bool,
|
||||||
screen_height: int,
|
screen_height: int,
|
||||||
|
@ -59,10 +63,24 @@ State :: struct {
|
||||||
|
|
||||||
input_map: InputMap,
|
input_map: InputMap,
|
||||||
current_input_map: ^InputMap,
|
current_input_map: ^InputMap,
|
||||||
|
|
||||||
|
plugins: [dynamic]plugin.Interface,
|
||||||
|
plugin_vtable: plugin.Plugin,
|
||||||
|
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);
|
||||||
EditorAction :: proc(state: ^State);
|
EditorAction :: proc(state: ^State);
|
||||||
InputGroup :: union {EditorAction, InputMap}
|
InputGroup :: union {PluginEditorAction, EditorAction, InputMap}
|
||||||
Action :: struct {
|
Action :: struct {
|
||||||
action: InputGroup,
|
action: InputGroup,
|
||||||
description: string,
|
description: string,
|
||||||
|
@ -88,6 +106,18 @@ delete_input_map :: proc(input_map: ^InputMap) {
|
||||||
// NOTE(pcleavelin): might be a bug in the compiler where it can't coerce
|
// NOTE(pcleavelin): might be a bug in the compiler where it can't coerce
|
||||||
// `EditorAction` to `InputGroup` when given as a proc parameter, that is why there
|
// `EditorAction` to `InputGroup` when given as a proc parameter, that is why there
|
||||||
// are two functions
|
// are two functions
|
||||||
|
register_plugin_key_action_single :: proc(input_map: ^InputMap, key: raylib.KeyboardKey, action: PluginEditorAction, description: string = "") {
|
||||||
|
if ok := key in input_map.key_actions; ok {
|
||||||
|
// TODO: log that key is already registered
|
||||||
|
fmt.eprintln("plugin key already registered with single action", key);
|
||||||
|
}
|
||||||
|
|
||||||
|
input_map.key_actions[key] = Action {
|
||||||
|
action = action,
|
||||||
|
description = description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
register_key_action_single :: proc(input_map: ^InputMap, key: raylib.KeyboardKey, action: EditorAction, description: string = "") {
|
register_key_action_single :: proc(input_map: ^InputMap, key: raylib.KeyboardKey, action: EditorAction, description: string = "") {
|
||||||
if ok := key in input_map.key_actions; ok {
|
if ok := key in input_map.key_actions; ok {
|
||||||
// TODO: log that key is already registered
|
// TODO: log that key is already registered
|
||||||
|
@ -136,5 +166,5 @@ register_ctrl_key_action_group :: proc(input_map: ^InputMap, key: raylib.Keyboar
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
register_key_action :: proc{register_key_action_single, register_key_action_group};
|
register_key_action :: proc{register_plugin_key_action_single, register_key_action_single, register_key_action_group};
|
||||||
register_ctrl_key_action :: proc{register_ctrl_key_action_single, register_ctrl_key_action_group};
|
register_ctrl_key_action :: proc{register_ctrl_key_action_single, register_ctrl_key_action_group};
|
||||||
|
|
|
@ -5,6 +5,7 @@ import "core:runtime"
|
||||||
ErrorType :: enum {
|
ErrorType :: enum {
|
||||||
None,
|
None,
|
||||||
FileIOError,
|
FileIOError,
|
||||||
|
PluginLoadError,
|
||||||
}
|
}
|
||||||
|
|
||||||
Error :: struct {
|
Error :: struct {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import "core:strings"
|
||||||
import "vendor:raylib"
|
import "vendor:raylib"
|
||||||
|
|
||||||
import "../theme"
|
import "../theme"
|
||||||
|
import "../plugin"
|
||||||
|
|
||||||
ScrollDir :: enum {
|
ScrollDir :: enum {
|
||||||
Up,
|
Up,
|
||||||
|
@ -48,6 +49,8 @@ FileBuffer :: struct {
|
||||||
|
|
||||||
directory: string,
|
directory: string,
|
||||||
file_path: string,
|
file_path: string,
|
||||||
|
extension: string,
|
||||||
|
|
||||||
top_line: int,
|
top_line: int,
|
||||||
cursor: Cursor,
|
cursor: Cursor,
|
||||||
|
|
||||||
|
@ -584,6 +587,8 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
|
||||||
dir = filepath.dir(fi.fullpath);
|
dir = filepath.dir(fi.fullpath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension := filepath.ext(fi.fullpath);
|
||||||
|
|
||||||
if original_content, success := os.read_entire_file_from_handle(fd); success {
|
if original_content, success := os.read_entire_file_from_handle(fd); success {
|
||||||
width := 256;
|
width := 256;
|
||||||
height := 256;
|
height := 256;
|
||||||
|
@ -592,6 +597,7 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
|
||||||
allocator = allocator,
|
allocator = allocator,
|
||||||
directory = dir,
|
directory = dir,
|
||||||
file_path = fi.fullpath,
|
file_path = fi.fullpath,
|
||||||
|
extension = extension,
|
||||||
|
|
||||||
original_content = slice.clone_to_dynamic(original_content),
|
original_content = slice.clone_to_dynamic(original_content),
|
||||||
added_content = make([dynamic]u8, 0, 1024*1024),
|
added_content = make([dynamic]u8, 0, 1024*1024),
|
||||||
|
@ -612,6 +618,44 @@ new_file_buffer :: proc(allocator: mem.Allocator, file_path: string, base_dir: s
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
next_buffer :: proc(state: ^State, prev_buffer: ^int) -> int {
|
||||||
|
index := prev_buffer^;
|
||||||
|
|
||||||
|
if prev_buffer^ >= len(state.buffers)-1 {
|
||||||
|
prev_buffer^ = -1;
|
||||||
|
} else {
|
||||||
|
prev_buffer^ += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
into_buffer_info :: proc(state: ^State, buffer: ^FileBuffer) -> plugin.BufferInfo {
|
||||||
|
return plugin.BufferInfo {
|
||||||
|
buffer = buffer,
|
||||||
|
input = plugin.BufferInput {
|
||||||
|
bytes = raw_data(buffer.input_buffer),
|
||||||
|
length = len(buffer.input_buffer),
|
||||||
|
},
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = buffer.cursor.col,
|
||||||
|
line = buffer.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = buffer.cursor.index.slice_index,
|
||||||
|
content_index = buffer.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
file_path = strings.clone_to_cstring(buffer.file_path, context.temp_allocator),
|
||||||
|
glyph_buffer_width = buffer.glyph_buffer_width,
|
||||||
|
glyph_buffer_height = buffer.glyph_buffer_height,
|
||||||
|
top_line = buffer.top_line,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
into_buffer_info_from_index :: proc(state: ^State, buffer_index: int) -> plugin.BufferInfo {
|
||||||
|
buffer := &state.buffers[buffer_index];
|
||||||
|
return into_buffer_info(state, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
free_file_buffer :: proc(buffer: ^FileBuffer) {
|
free_file_buffer :: proc(buffer: ^FileBuffer) {
|
||||||
delete(buffer.original_content);
|
delete(buffer.original_content);
|
||||||
delete(buffer.added_content);
|
delete(buffer.added_content);
|
||||||
|
@ -620,145 +664,6 @@ free_file_buffer :: proc(buffer: ^FileBuffer) {
|
||||||
delete(buffer.input_buffer);
|
delete(buffer.input_buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
is_keyword :: proc(start: FileBufferIter, end: FileBufferIter) -> (matches: bool) {
|
|
||||||
keywords := []string {
|
|
||||||
"using",
|
|
||||||
"transmute",
|
|
||||||
"cast",
|
|
||||||
"distinct",
|
|
||||||
"opaque",
|
|
||||||
"where",
|
|
||||||
"struct",
|
|
||||||
"enum",
|
|
||||||
"union",
|
|
||||||
"bit_field",
|
|
||||||
"bit_set",
|
|
||||||
"if",
|
|
||||||
"when",
|
|
||||||
"else",
|
|
||||||
"do",
|
|
||||||
"for",
|
|
||||||
"switch",
|
|
||||||
"case",
|
|
||||||
"continue",
|
|
||||||
"break",
|
|
||||||
"size_of",
|
|
||||||
"offset_of",
|
|
||||||
"type_info_of",
|
|
||||||
"typeid_of",
|
|
||||||
"type_of",
|
|
||||||
"align_of",
|
|
||||||
"or_return",
|
|
||||||
"or_else",
|
|
||||||
"inline",
|
|
||||||
"no_inline",
|
|
||||||
"string",
|
|
||||||
"cstring",
|
|
||||||
"bool",
|
|
||||||
"b8",
|
|
||||||
"b16",
|
|
||||||
"b32",
|
|
||||||
"b64",
|
|
||||||
"rune",
|
|
||||||
"any",
|
|
||||||
"rawptr",
|
|
||||||
"f16",
|
|
||||||
"f32",
|
|
||||||
"f64",
|
|
||||||
"f16le",
|
|
||||||
"f16be",
|
|
||||||
"f32le",
|
|
||||||
"f32be",
|
|
||||||
"f64le",
|
|
||||||
"f64be",
|
|
||||||
"u8",
|
|
||||||
"u16",
|
|
||||||
"u32",
|
|
||||||
"u64",
|
|
||||||
"u128",
|
|
||||||
"u16le",
|
|
||||||
"u32le",
|
|
||||||
"u64le",
|
|
||||||
"u128le",
|
|
||||||
"u16be",
|
|
||||||
"u32be",
|
|
||||||
"u64be",
|
|
||||||
"u128be",
|
|
||||||
"uint",
|
|
||||||
"uintptr",
|
|
||||||
"i8",
|
|
||||||
"i16",
|
|
||||||
"i32",
|
|
||||||
"i64",
|
|
||||||
"i128",
|
|
||||||
"i16le",
|
|
||||||
"i32le",
|
|
||||||
"i64le",
|
|
||||||
"i128le",
|
|
||||||
"i16be",
|
|
||||||
"i32be",
|
|
||||||
"i64be",
|
|
||||||
"i128be",
|
|
||||||
"int",
|
|
||||||
"complex",
|
|
||||||
"complex32",
|
|
||||||
"complex64",
|
|
||||||
"complex128",
|
|
||||||
"quaternion",
|
|
||||||
"quaternion64",
|
|
||||||
"quaternion128",
|
|
||||||
"quaternion256",
|
|
||||||
"matrix",
|
|
||||||
"typeid",
|
|
||||||
"true",
|
|
||||||
"false",
|
|
||||||
"nil",
|
|
||||||
"dynamic",
|
|
||||||
"map",
|
|
||||||
"proc",
|
|
||||||
"in",
|
|
||||||
"notin",
|
|
||||||
"not_in",
|
|
||||||
"import",
|
|
||||||
"export",
|
|
||||||
"foreign",
|
|
||||||
"const",
|
|
||||||
"package",
|
|
||||||
"return",
|
|
||||||
"defer",
|
|
||||||
};
|
|
||||||
|
|
||||||
for keyword in keywords {
|
|
||||||
it := start;
|
|
||||||
keyword_index := 0;
|
|
||||||
|
|
||||||
for character in iterate_file_buffer(&it) {
|
|
||||||
if character != keyword[keyword_index] {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
keyword_index += 1;
|
|
||||||
if keyword_index >= len(keyword)-1 && it == end {
|
|
||||||
if get_character_at_iter(it) == keyword[keyword_index] {
|
|
||||||
matches = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
} else if keyword_index >= len(keyword)-1 {
|
|
||||||
break;
|
|
||||||
} else if it == end {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matches {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: theme.PaletteColor) {
|
color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette_index: theme.PaletteColor) {
|
||||||
start, end := start, end;
|
start, end := start, end;
|
||||||
|
|
||||||
|
@ -794,73 +699,6 @@ color_character :: proc(buffer: ^FileBuffer, start: Cursor, end: Cursor, palette
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
color_buffer :: proc(buffer: ^FileBuffer) {
|
|
||||||
start_it := new_file_buffer_iter(buffer);
|
|
||||||
it := new_file_buffer_iter(buffer);
|
|
||||||
|
|
||||||
for character in iterate_file_buffer(&it) {
|
|
||||||
if it.cursor.line > it.buffer.glyph_buffer_height && (it.cursor.line - it.buffer.top_line) > it.buffer.glyph_buffer_height {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if character == '/' {
|
|
||||||
start_it = it;
|
|
||||||
// need to go back one character because `it` is on the next character
|
|
||||||
iterate_file_buffer_reverse(&start_it);
|
|
||||||
|
|
||||||
character, _, succ := iterate_file_buffer(&it);
|
|
||||||
if !succ { break; }
|
|
||||||
|
|
||||||
if character == '/' {
|
|
||||||
iterate_file_buffer_until(&it, until_line_break);
|
|
||||||
color_character(buffer, start_it.cursor, it.cursor, .Foreground4);
|
|
||||||
} else if character == '*' {
|
|
||||||
// TODO: block comments
|
|
||||||
}
|
|
||||||
} else if character == '\'' {
|
|
||||||
start_it = it;
|
|
||||||
// need to go back one character because `it` is on the next character
|
|
||||||
iterate_file_buffer_reverse(&start_it);
|
|
||||||
|
|
||||||
// jump into the quoted text
|
|
||||||
iterate_file_buffer_until(&it, until_single_quote);
|
|
||||||
color_character(buffer, start_it.cursor, it.cursor, .Yellow);
|
|
||||||
|
|
||||||
iterate_file_buffer(&it);
|
|
||||||
} else if character == '"' {
|
|
||||||
start_it = it;
|
|
||||||
// need to go back one character because `it` is on the next character
|
|
||||||
iterate_file_buffer_reverse(&start_it);
|
|
||||||
|
|
||||||
// jump into the quoted text
|
|
||||||
iterate_file_buffer_until(&it, until_double_quote);
|
|
||||||
color_character(buffer, start_it.cursor, it.cursor, .Yellow);
|
|
||||||
|
|
||||||
iterate_file_buffer(&it);
|
|
||||||
} else if (character >= 'a' && character <= 'z') || (character >= 'A' && character <= 'Z') || character == '_' {
|
|
||||||
start_it = it;
|
|
||||||
// need to go back one character because `it` is on the next character
|
|
||||||
iterate_file_buffer_reverse(&start_it);
|
|
||||||
it = start_it;
|
|
||||||
|
|
||||||
iterate_file_buffer_until(&it, until_end_of_word);
|
|
||||||
|
|
||||||
// TODO: color keywords
|
|
||||||
if is_keyword(start_it, it) {
|
|
||||||
color_character(buffer, start_it.cursor, it.cursor, .Blue);
|
|
||||||
} else if character, _, cond := iterate_peek(&it, iterate_file_buffer); cond {
|
|
||||||
if character == '(' {
|
|
||||||
color_character(buffer, start_it.cursor, it.cursor, .Green);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
iterate_file_buffer(&it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
update_glyph_buffer :: proc(buffer: ^FileBuffer) {
|
update_glyph_buffer :: proc(buffer: ^FileBuffer) {
|
||||||
for &glyph in buffer.glyph_buffer {
|
for &glyph in buffer.glyph_buffer {
|
||||||
glyph = Glyph{};
|
glyph = Glyph{};
|
||||||
|
@ -916,7 +754,9 @@ update_glyph_buffer :: proc(buffer: ^FileBuffer) {
|
||||||
|
|
||||||
draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, font: raylib.Font, show_line_numbers: bool = true) {
|
draw_file_buffer :: proc(state: ^State, buffer: ^FileBuffer, x: int, y: int, font: raylib.Font, show_line_numbers: bool = true) {
|
||||||
update_glyph_buffer(buffer);
|
update_glyph_buffer(buffer);
|
||||||
color_buffer(buffer);
|
if highlighter, exists := state.highlighters[buffer.extension]; exists {
|
||||||
|
highlighter(state.plugin_vtable, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
padding := 0;
|
padding := 0;
|
||||||
if show_line_numbers {
|
if show_line_numbers {
|
||||||
|
@ -960,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 {
|
||||||
|
|
567
src/main.odin
567
src/main.odin
|
@ -13,10 +13,13 @@ import "vendor:raylib"
|
||||||
import "core"
|
import "core"
|
||||||
import "theme"
|
import "theme"
|
||||||
import "ui"
|
import "ui"
|
||||||
|
import "plugin"
|
||||||
|
|
||||||
State :: core.State;
|
State :: core.State;
|
||||||
FileBuffer :: core.FileBuffer;
|
FileBuffer :: core.FileBuffer;
|
||||||
|
|
||||||
|
state := core.State {};
|
||||||
|
|
||||||
// TODO: use buffer list in state
|
// TODO: use buffer list in state
|
||||||
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||||
if state.current_input_map != nil {
|
if state.current_input_map != nil {
|
||||||
|
@ -26,6 +29,8 @@ do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||||
for key, action in &state.current_input_map.ctrl_key_actions {
|
for key, action in &state.current_input_map.ctrl_key_actions {
|
||||||
if raylib.IsKeyPressed(key) {
|
if raylib.IsKeyPressed(key) {
|
||||||
switch value in action.action {
|
switch value in action.action {
|
||||||
|
case core.PluginEditorAction:
|
||||||
|
value(state.plugin_vtable);
|
||||||
case core.EditorAction:
|
case core.EditorAction:
|
||||||
value(state);
|
value(state);
|
||||||
case core.InputMap:
|
case core.InputMap:
|
||||||
|
@ -37,6 +42,8 @@ do_normal_mode :: proc(state: ^State, buffer: ^FileBuffer) {
|
||||||
for key, action in state.current_input_map.key_actions {
|
for key, action in state.current_input_map.key_actions {
|
||||||
if raylib.IsKeyPressed(key) {
|
if raylib.IsKeyPressed(key) {
|
||||||
switch value in action.action {
|
switch value in action.action {
|
||||||
|
case core.PluginEditorAction:
|
||||||
|
value(state.plugin_vtable);
|
||||||
case core.EditorAction:
|
case core.EditorAction:
|
||||||
value(state);
|
value(state);
|
||||||
case core.InputMap:
|
case core.InputMap:
|
||||||
|
@ -55,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();
|
||||||
|
@ -74,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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,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");
|
||||||
|
@ -185,14 +191,519 @@ register_default_input_actions :: proc(input_map: ^core.InputMap) {
|
||||||
register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputMap));
|
register_default_go_actions(&(&input_map.key_actions[.G]).action.(core.InputMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
load_plugin :: proc(info: os.File_Info, in_err: os.Errno, state: rawptr) -> (err: os.Errno, skip_dir: bool) {
|
||||||
|
state := cast(^State)state;
|
||||||
|
|
||||||
|
relative_file_path, rel_error := filepath.rel(state.directory, info.fullpath);
|
||||||
|
extension := filepath.ext(info.fullpath);
|
||||||
|
|
||||||
|
if extension == ".dylib" || extension == ".dll" || extension == ".so" {
|
||||||
|
if loaded_plugin, succ := plugin.try_load_plugin(info.fullpath); succ {
|
||||||
|
append(&state.plugins, loaded_plugin);
|
||||||
|
|
||||||
|
if rel_error == .None {
|
||||||
|
fmt.println("Loaded", relative_file_path);
|
||||||
|
} else {
|
||||||
|
fmt.println("Loaded", info.fullpath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return in_err, skip_dir;
|
||||||
|
}
|
||||||
|
|
||||||
main :: proc() {
|
main :: proc() {
|
||||||
state := State {
|
state = State {
|
||||||
|
ctx = context,
|
||||||
source_font_width = 8,
|
source_font_width = 8,
|
||||||
source_font_height = 16,
|
source_font_height = 16,
|
||||||
input_map = core.new_input_map(),
|
input_map = core.new_input_map(),
|
||||||
window = nil,
|
window = nil,
|
||||||
|
|
||||||
directory = os.get_current_directory(),
|
directory = os.get_current_directory(),
|
||||||
|
plugins = make([dynamic]plugin.Interface),
|
||||||
|
highlighters = make(map[string]plugin.OnColorBufferProc),
|
||||||
|
hooks = make(map[plugin.Hook][dynamic]plugin.OnHookProc),
|
||||||
|
};
|
||||||
|
state.plugin_vtable = plugin.Plugin {
|
||||||
|
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) {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
extension := strings.clone(string(extension));
|
||||||
|
|
||||||
|
if _, exists := state.highlighters[extension]; exists {
|
||||||
|
fmt.eprintln("Highlighter already registered for", extension, "files");
|
||||||
|
} else {
|
||||||
|
state.highlighters[extension] = on_color_buffer;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
register_input_group = proc "c" (input_map: rawptr, key: plugin.Key, register_group: plugin.InputGroupProc) {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
to_be_edited_map: ^core.InputMap = nil;
|
||||||
|
key := raylib.KeyboardKey(int(key));
|
||||||
|
|
||||||
|
if input_map != nil {
|
||||||
|
to_be_edited_map = transmute(^core.InputMap)input_map;
|
||||||
|
} else {
|
||||||
|
to_be_edited_map = state.current_input_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
if action, exists := to_be_edited_map.key_actions[key]; exists {
|
||||||
|
switch value in action.action {
|
||||||
|
case core.PluginEditorAction:
|
||||||
|
fmt.eprintln("Plugin attempted to register input group on existing key action (added from Plugin)");
|
||||||
|
case core.EditorAction:
|
||||||
|
fmt.eprintln("Plugin attempted to register input group on existing key action");
|
||||||
|
case core.InputMap:
|
||||||
|
input_map := &(&to_be_edited_map.key_actions[key]).action.(core.InputMap);
|
||||||
|
register_group(state.plugin_vtable, transmute(rawptr)input_map);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
core.register_key_action(to_be_edited_map, key, core.new_input_map(), "PLUGIN INPUT GROUP");
|
||||||
|
register_group(state.plugin_vtable, &(&to_be_edited_map.key_actions[key]).action.(core.InputMap));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
register_input = proc "c" (input_map: rawptr, key: plugin.Key, input_action: plugin.InputActionProc, description: cstring) {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
to_be_edited_map: ^core.InputMap = nil;
|
||||||
|
key := raylib.KeyboardKey(int(key));
|
||||||
|
description := strings.clone(string(description));
|
||||||
|
|
||||||
|
if input_map != nil {
|
||||||
|
to_be_edited_map = transmute(^core.InputMap)input_map;
|
||||||
|
} else {
|
||||||
|
to_be_edited_map = state.current_input_map;
|
||||||
|
}
|
||||||
|
|
||||||
|
if action, exists := to_be_edited_map.key_actions[key]; exists {
|
||||||
|
switch value in action.action {
|
||||||
|
case core.PluginEditorAction:
|
||||||
|
fmt.eprintln("Plugin attempted to register key action on existing key action (added from Plugin)");
|
||||||
|
case core.EditorAction:
|
||||||
|
fmt.eprintln("Plugin attempted to register input key action on existing key action");
|
||||||
|
case core.InputMap:
|
||||||
|
fmt.eprintln("Plugin attempted to register input key action on existing input group");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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, get_buffer_proc: plugin.WindowGetBufferProc) -> rawptr {
|
||||||
|
context = state.ctx;
|
||||||
|
window := new(core.Window);
|
||||||
|
window^ = core.Window {
|
||||||
|
input_map = core.new_input_map(),
|
||||||
|
draw = draw_proc,
|
||||||
|
get_buffer = get_buffer_proc,
|
||||||
|
free_user_data = free_window_proc,
|
||||||
|
|
||||||
|
user_data = user_data,
|
||||||
|
};
|
||||||
|
|
||||||
|
register_group(state.plugin_vtable, transmute(rawptr)&window.input_map);
|
||||||
|
|
||||||
|
state.window = window;
|
||||||
|
state.current_input_map = &window.input_map;
|
||||||
|
|
||||||
|
return window;
|
||||||
|
},
|
||||||
|
get_window = proc "c" () -> rawptr {
|
||||||
|
if state.window != nil {
|
||||||
|
return state.window.user_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
},
|
||||||
|
request_window_close = proc "c" () {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
core.request_window_close(&state);
|
||||||
|
},
|
||||||
|
get_screen_width = proc "c" () -> int {
|
||||||
|
return state.screen_width;
|
||||||
|
},
|
||||||
|
get_screen_height = proc "c" () -> int {
|
||||||
|
return state.screen_height;
|
||||||
|
},
|
||||||
|
get_font_width = proc "c" () -> int {
|
||||||
|
return state.source_font_width;
|
||||||
|
},
|
||||||
|
get_font_height = proc "c" () -> int {
|
||||||
|
return state.source_font_height;
|
||||||
|
},
|
||||||
|
get_current_directory = proc "c" () -> cstring {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
raylib.DrawRectangle(x, y, width, height, theme.get_palette_raylib_color(color));
|
||||||
|
},
|
||||||
|
draw_text = proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor) {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
text := string(text);
|
||||||
|
for codepoint, index in text {
|
||||||
|
raylib.DrawTextCodepoint(
|
||||||
|
state.font,
|
||||||
|
rune(codepoint),
|
||||||
|
raylib.Vector2 { x + f32(index * state.source_font_width), y },
|
||||||
|
f32(state.source_font_height),
|
||||||
|
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) {
|
||||||
|
context = state.ctx;
|
||||||
|
state.buffers[buffer_index].glyph_buffer_width = glyph_buffer_width;
|
||||||
|
state.buffers[buffer_index].glyph_buffer_height = glyph_buffer_height;
|
||||||
|
|
||||||
|
core.draw_file_buffer(
|
||||||
|
&state,
|
||||||
|
&state.buffers[buffer_index],
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
state.font,
|
||||||
|
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 {
|
||||||
|
get_current_buffer_iterator = proc "c" () -> plugin.BufferIter {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
it := core.new_file_buffer_iter(&state.buffers[state.current_buffer]);
|
||||||
|
|
||||||
|
// TODO: make this into a function
|
||||||
|
return plugin.BufferIter {
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(rawptr)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get_buffer_iterator = proc "c" (buffer: rawptr) -> plugin.BufferIter {
|
||||||
|
buffer := cast(^core.FileBuffer)buffer;
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
it := core.new_file_buffer_iter(buffer);
|
||||||
|
|
||||||
|
// TODO: make this into a function
|
||||||
|
return plugin.BufferIter {
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(rawptr)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get_char_at_iter = proc "c" (it: ^plugin.BufferIter) -> u8 {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
internal_it := core.FileBufferIter {
|
||||||
|
cursor = core.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(^core.FileBuffer)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
|
||||||
|
return core.get_character_at_iter(internal_it);
|
||||||
|
},
|
||||||
|
get_buffer_list_iter = proc "c" (prev_buffer: ^int) -> int {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
return core.next_buffer(&state, prev_buffer);
|
||||||
|
},
|
||||||
|
iterate_buffer = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
// TODO: make this into a function
|
||||||
|
internal_it := core.FileBufferIter {
|
||||||
|
cursor = core.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(^core.FileBuffer)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
|
||||||
|
char, _, cond := core.iterate_file_buffer(&internal_it);
|
||||||
|
|
||||||
|
it^ = plugin.BufferIter {
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = internal_it.cursor.col,
|
||||||
|
line = internal_it.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = internal_it.cursor.index.slice_index,
|
||||||
|
content_index = internal_it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(rawptr)internal_it.buffer,
|
||||||
|
hit_end = internal_it.hit_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin.IterateResult {
|
||||||
|
char = char,
|
||||||
|
should_continue = cond,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
iterate_buffer_reverse = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
// TODO: make this into a function
|
||||||
|
internal_it := core.FileBufferIter {
|
||||||
|
cursor = core.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(^core.FileBuffer)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
|
||||||
|
char, _, cond := core.iterate_file_buffer_reverse(&internal_it);
|
||||||
|
|
||||||
|
it^ = plugin.BufferIter {
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = internal_it.cursor.col,
|
||||||
|
line = internal_it.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = internal_it.cursor.index.slice_index,
|
||||||
|
content_index = internal_it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(rawptr)internal_it.buffer,
|
||||||
|
hit_end = internal_it.hit_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin.IterateResult {
|
||||||
|
char = char,
|
||||||
|
should_continue = cond,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
iterate_buffer_until = proc "c" (it: ^plugin.BufferIter, until_proc: rawptr) {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
// TODO: make this into a function
|
||||||
|
internal_it := core.FileBufferIter {
|
||||||
|
cursor = core.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(^core.FileBuffer)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
|
||||||
|
core.iterate_file_buffer_until(&internal_it, transmute(core.UntilProc)until_proc);
|
||||||
|
|
||||||
|
it^ = plugin.BufferIter {
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = internal_it.cursor.col,
|
||||||
|
line = internal_it.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = internal_it.cursor.index.slice_index,
|
||||||
|
content_index = internal_it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(rawptr)internal_it.buffer,
|
||||||
|
hit_end = internal_it.hit_end,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
iterate_buffer_peek = proc "c" (it: ^plugin.BufferIter) -> plugin.IterateResult {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
// TODO: make this into a function
|
||||||
|
internal_it := core.FileBufferIter {
|
||||||
|
cursor = core.Cursor {
|
||||||
|
col = it.cursor.col,
|
||||||
|
line = it.cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = it.cursor.index.slice_index,
|
||||||
|
content_index = it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(^core.FileBuffer)it.buffer,
|
||||||
|
hit_end = it.hit_end,
|
||||||
|
}
|
||||||
|
|
||||||
|
char, _, cond := core.iterate_peek(&internal_it, core.iterate_file_buffer);
|
||||||
|
|
||||||
|
it^ = plugin.BufferIter {
|
||||||
|
cursor = plugin.Cursor {
|
||||||
|
col = internal_it.cursor.col,
|
||||||
|
line = internal_it.cursor.line,
|
||||||
|
index = plugin.BufferIndex {
|
||||||
|
slice_index = internal_it.cursor.index.slice_index,
|
||||||
|
content_index = internal_it.cursor.index.content_index,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer = cast(rawptr)internal_it.buffer,
|
||||||
|
hit_end = internal_it.hit_end,
|
||||||
|
};
|
||||||
|
|
||||||
|
return plugin.IterateResult {
|
||||||
|
char = char,
|
||||||
|
should_continue = cond,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
until_line_break = transmute(rawptr)core.until_line_break,
|
||||||
|
until_single_quote = transmute(rawptr)core.until_single_quote,
|
||||||
|
until_double_quote = transmute(rawptr)core.until_double_quote,
|
||||||
|
until_end_of_word = transmute(rawptr)core.until_end_of_word,
|
||||||
|
},
|
||||||
|
buffer = plugin.Buffer {
|
||||||
|
get_num_buffers = proc "c" () -> int {
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
return len(state.buffers);
|
||||||
|
},
|
||||||
|
get_buffer_info = proc "c" (buffer: rawptr) -> plugin.BufferInfo {
|
||||||
|
context = state.ctx;
|
||||||
|
buffer := cast(^core.FileBuffer)buffer;
|
||||||
|
|
||||||
|
return core.into_buffer_info(&state, buffer);
|
||||||
|
},
|
||||||
|
get_buffer_info_from_index = proc "c" (buffer_index: int) -> plugin.BufferInfo {
|
||||||
|
context = state.ctx;
|
||||||
|
buffer := &state.buffers[buffer_index];
|
||||||
|
|
||||||
|
return core.into_buffer_info(&state, buffer);
|
||||||
|
},
|
||||||
|
color_char_at = proc "c" (buffer: rawptr, start_cursor: plugin.Cursor, end_cursor: plugin.Cursor, palette_index: i32) {
|
||||||
|
buffer := cast(^core.FileBuffer)buffer;
|
||||||
|
context = state.ctx;
|
||||||
|
|
||||||
|
start_cursor := core.Cursor {
|
||||||
|
col = start_cursor.col,
|
||||||
|
line = start_cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = start_cursor.index.slice_index,
|
||||||
|
content_index = start_cursor.index.content_index,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
end_cursor := core.Cursor {
|
||||||
|
col = end_cursor.col,
|
||||||
|
line = end_cursor.line,
|
||||||
|
index = core.FileBufferIndex {
|
||||||
|
slice_index = end_cursor.index.slice_index,
|
||||||
|
content_index = end_cursor.index.content_index,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
core.color_character(buffer, start_cursor, end_cursor, cast(theme.PaletteColor)palette_index);
|
||||||
|
},
|
||||||
|
set_current_buffer = proc "c" (buffer_index: int) {
|
||||||
|
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;
|
||||||
register_default_input_actions(&state.input_map);
|
register_default_input_actions(&state.input_map);
|
||||||
|
@ -216,6 +727,16 @@ main :: proc() {
|
||||||
runtime.append(&buffer_items, item);
|
runtime.append(&buffer_items, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load plugins
|
||||||
|
// 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 {
|
||||||
|
if plugin.on_initialize != nil {
|
||||||
|
plugin.on_initialize(state.plugin_vtable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
raylib.InitWindow(640, 480, "odin_editor - [back to basics]");
|
raylib.InitWindow(640, 480, "odin_editor - [back to basics]");
|
||||||
raylib.SetWindowState({ .WINDOW_RESIZABLE, .VSYNC_HINT });
|
raylib.SetWindowState({ .WINDOW_RESIZABLE, .VSYNC_HINT });
|
||||||
raylib.SetTargetFPS(60);
|
raylib.SetTargetFPS(60);
|
||||||
|
@ -248,6 +769,14 @@ main :: proc() {
|
||||||
defer raylib.EndDrawing();
|
defer raylib.EndDrawing();
|
||||||
|
|
||||||
raylib.ClearBackground(theme.get_palette_raylib_color(.Background));
|
raylib.ClearBackground(theme.get_palette_raylib_color(.Background));
|
||||||
|
|
||||||
|
// TODO: be more granular in /what/ is being draw by the plugin
|
||||||
|
for plugin in state.plugins {
|
||||||
|
if plugin.on_initialize != nil {
|
||||||
|
//plugin.on_draw(plugin.plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
core.draw_file_buffer(&state, buffer, 32, state.source_font_height, state.font);
|
core.draw_file_buffer(&state, buffer, 32, state.source_font_height, state.font);
|
||||||
ui.draw_menu_bar(&state, &menu_bar_state, 0, 0, i32(state.screen_width), i32(state.screen_height), state.source_font_height);
|
ui.draw_menu_bar(&state, &menu_bar_state, 0, 0, i32(state.screen_width), i32(state.screen_height), state.source_font_height);
|
||||||
|
|
||||||
|
@ -320,7 +849,7 @@ main :: proc() {
|
||||||
theme.get_palette_raylib_color(.Background1));
|
theme.get_palette_raylib_color(.Background1));
|
||||||
|
|
||||||
if state.window != nil && state.window.draw != nil {
|
if state.window != nil && state.window.draw != nil {
|
||||||
state.window->draw(&state);
|
state.window.draw(state.plugin_vtable, state.window.user_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if state.current_input_map != &state.input_map {
|
if state.current_input_map != &state.input_map {
|
||||||
|
@ -374,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);
|
||||||
}
|
}
|
||||||
|
@ -392,5 +923,13 @@ main :: proc() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ui.test_menu_bar(&state, &menu_bar_state, 0,0, mouse_pos, raylib.IsMouseButtonReleased(.LEFT), state.source_font_height);
|
ui.test_menu_bar(&state, &menu_bar_state, 0,0, mouse_pos, raylib.IsMouseButtonReleased(.LEFT), state.source_font_height);
|
||||||
|
|
||||||
|
runtime.free_all(context.temp_allocator);
|
||||||
|
}
|
||||||
|
|
||||||
|
for plugin in state.plugins {
|
||||||
|
if plugin.on_exit != nil {
|
||||||
|
plugin.on_exit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,275 @@
|
||||||
|
package plugin;
|
||||||
|
|
||||||
|
import "core:intrinsics"
|
||||||
|
import "core:dynlib"
|
||||||
|
import "core:fmt"
|
||||||
|
import "vendor:raylib"
|
||||||
|
|
||||||
|
import "../theme"
|
||||||
|
|
||||||
|
OnInitializeProc :: proc "c" (plugin: Plugin);
|
||||||
|
OnExitProc :: proc "c" (/* probably needs some state eventually */);
|
||||||
|
OnDrawProc :: proc "c" (plugin: Plugin);
|
||||||
|
Interface :: struct {
|
||||||
|
on_initialize: OnInitializeProc,
|
||||||
|
on_exit: OnExitProc,
|
||||||
|
on_draw: OnDrawProc,
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferIndex :: struct {
|
||||||
|
slice_index: int,
|
||||||
|
content_index: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Cursor :: struct {
|
||||||
|
col: int,
|
||||||
|
line: int,
|
||||||
|
index: BufferIndex,
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferIter :: struct {
|
||||||
|
cursor: Cursor,
|
||||||
|
buffer: rawptr,
|
||||||
|
hit_end: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
IterateResult :: struct {
|
||||||
|
char: u8,
|
||||||
|
should_continue: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferInput :: struct {
|
||||||
|
bytes: [^]u8,
|
||||||
|
length: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferInfo :: struct {
|
||||||
|
buffer: rawptr,
|
||||||
|
file_path: cstring,
|
||||||
|
input: BufferInput,
|
||||||
|
|
||||||
|
cursor: Cursor,
|
||||||
|
|
||||||
|
glyph_buffer_width: int,
|
||||||
|
glyph_buffer_height: int,
|
||||||
|
top_line: int,
|
||||||
|
}
|
||||||
|
|
||||||
|
Buffer :: struct {
|
||||||
|
get_num_buffers: proc "c" () -> int,
|
||||||
|
get_buffer_info: proc "c" (buffer: rawptr) -> 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),
|
||||||
|
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 {
|
||||||
|
get_current_buffer_iterator: proc "c" () -> BufferIter,
|
||||||
|
get_buffer_iterator: proc "c" (buffer: rawptr) -> BufferIter,
|
||||||
|
get_char_at_iter: proc "c" (it: ^BufferIter) -> u8,
|
||||||
|
get_buffer_list_iter: proc "c" (prev_buffer: ^int) -> int,
|
||||||
|
|
||||||
|
iterate_buffer: proc "c" (it: ^BufferIter) -> IterateResult,
|
||||||
|
iterate_buffer_reverse: proc "c" (it: ^BufferIter) -> IterateResult,
|
||||||
|
iterate_buffer_until: proc "c" (it: ^BufferIter, until_proc: rawptr),
|
||||||
|
iterate_buffer_until_reverse: proc "c" (it: ^BufferIter, until_proc: rawptr),
|
||||||
|
iterate_buffer_peek: proc "c" (it: ^BufferIter) -> IterateResult,
|
||||||
|
|
||||||
|
until_line_break: rawptr,
|
||||||
|
until_single_quote: rawptr,
|
||||||
|
until_double_quote: rawptr,
|
||||||
|
until_end_of_word: rawptr,
|
||||||
|
}
|
||||||
|
|
||||||
|
OnColorBufferProc :: proc "c" (plugin: Plugin, buffer: rawptr);
|
||||||
|
InputGroupProc :: proc "c" (plugin: Plugin, input_map: rawptr);
|
||||||
|
InputActionProc :: proc "c" (plugin: Plugin);
|
||||||
|
OnHookProc :: proc "c" (plugin: Plugin, buffer: rawptr);
|
||||||
|
|
||||||
|
WindowInputProc :: 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);
|
||||||
|
Plugin :: struct {
|
||||||
|
state: rawptr,
|
||||||
|
iter: Iterator,
|
||||||
|
buffer: Buffer,
|
||||||
|
|
||||||
|
register_hook: proc "c" (hook: Hook, on_hook: OnHookProc),
|
||||||
|
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: 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, get_buffer_proc: WindowGetBufferProc) -> rawptr,
|
||||||
|
get_window: proc "c" () -> rawptr,
|
||||||
|
|
||||||
|
request_window_close: proc "c" (),
|
||||||
|
get_screen_width: proc "c" () -> int,
|
||||||
|
get_screen_height: proc "c" () -> int,
|
||||||
|
get_font_width: proc "c" () -> int,
|
||||||
|
get_font_height: proc "c" () -> int,
|
||||||
|
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_text: proc "c" (text: cstring, x: f32, y: f32, color: theme.PaletteColor),
|
||||||
|
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_NULL = 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
|
||||||
|
LEFT_BRACKET = 91, // Key: [
|
||||||
|
BACKSLASH = 92, // Key: '\'
|
||||||
|
RIGHT_BRACKET = 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
|
||||||
|
PAGE_UP = 266, // Key: Page up
|
||||||
|
PAGE_DOWN = 267, // Key: Page down
|
||||||
|
HOME = 268, // Key: Home
|
||||||
|
END = 269, // Key: End
|
||||||
|
CAPS_LOCK = 280, // Key: Caps lock
|
||||||
|
SCROLL_LOCK = 281, // Key: Scroll down
|
||||||
|
NUM_LOCK = 282, // Key: Num lock
|
||||||
|
PRINT_SCREEN = 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
|
||||||
|
LEFT_SHIFT = 340, // Key: Shift left
|
||||||
|
LEFT_CONTROL = 341, // Key: Control left
|
||||||
|
LEFT_ALT = 342, // Key: Alt left
|
||||||
|
LEFT_SUPER = 343, // Key: Super left
|
||||||
|
RIGHT_SHIFT = 344, // Key: Shift right
|
||||||
|
RIGHT_CONTROL = 345, // Key: Control right
|
||||||
|
RIGHT_ALT = 346, // Key: Alt right
|
||||||
|
RIGHT_SUPER = 347, // Key: Super right
|
||||||
|
KB_MENU = 348, // Key: KB menu
|
||||||
|
// Keypad keys
|
||||||
|
KP_0 = 320, // Key: Keypad 0
|
||||||
|
KP_1 = 321, // Key: Keypad 1
|
||||||
|
KP_2 = 322, // Key: Keypad 2
|
||||||
|
KP_3 = 323, // Key: Keypad 3
|
||||||
|
KP_4 = 324, // Key: Keypad 4
|
||||||
|
KP_5 = 325, // Key: Keypad 5
|
||||||
|
KP_6 = 326, // Key: Keypad 6
|
||||||
|
KP_7 = 327, // Key: Keypad 7
|
||||||
|
KP_8 = 328, // Key: Keypad 8
|
||||||
|
KP_9 = 329, // Key: Keypad 9
|
||||||
|
KP_DECIMAL = 330, // Key: Keypad .
|
||||||
|
KP_DIVIDE = 331, // Key: Keypad /
|
||||||
|
KP_MULTIPLY = 332, // Key: Keypad *
|
||||||
|
KP_SUBTRACT = 333, // Key: Keypad -
|
||||||
|
KP_ADD = 334, // Key: Keypad +
|
||||||
|
KP_ENTER = 335, // Key: Keypad Enter
|
||||||
|
KP_EQUAL = 336, // Key: Keypad =
|
||||||
|
// Android key buttons
|
||||||
|
BACK = 4, // Key: Android back button
|
||||||
|
MENU = 82, // Key: Android menu button
|
||||||
|
VOLUME_UP = 24, // Key: Android volume up button
|
||||||
|
VOLUME_DOWN = 25, // Key: Android volume down button
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
load_proc_address :: proc(lib_path: string, library: dynlib.Library, symbol: string, $ProcType: typeid) -> ProcType
|
||||||
|
where intrinsics.type_is_proc(ProcType)
|
||||||
|
{
|
||||||
|
if address, found := dynlib.symbol_address(library, symbol); found {
|
||||||
|
return transmute(ProcType)address;
|
||||||
|
} else {
|
||||||
|
fmt.println("Could not find symbol", symbol, "in library", lib_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil;
|
||||||
|
}
|
||||||
|
|
||||||
|
try_load_plugin :: proc(lib_path: string) -> (plugin: Interface, success: bool) {
|
||||||
|
library, ok := dynlib.load_library(lib_path)
|
||||||
|
if !ok {
|
||||||
|
return {}, false;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface := Interface {
|
||||||
|
on_initialize = load_proc_address(lib_path, library, "OnInitialize", OnInitializeProc),
|
||||||
|
on_exit = load_proc_address(lib_path, library, "OnExit", OnExitProc),
|
||||||
|
on_draw = load_proc_address(lib_path, library, "OnDraw", OnDrawProc),
|
||||||
|
};
|
||||||
|
|
||||||
|
if interface.on_initialize == nil do return interface, false;
|
||||||
|
if interface.on_exit == nil do return interface, false;
|
||||||
|
|
||||||
|
return interface, true
|
||||||
|
}
|
|
@ -1,122 +0,0 @@
|
||||||
package ui;
|
|
||||||
|
|
||||||
import "core:math"
|
|
||||||
import "core:path/filepath"
|
|
||||||
import "vendor:raylib"
|
|
||||||
|
|
||||||
import "../core"
|
|
||||||
import "../theme"
|
|
||||||
|
|
||||||
BufferListWindow :: struct {
|
|
||||||
using window: core.Window,
|
|
||||||
|
|
||||||
selected_buffer: int,
|
|
||||||
}
|
|
||||||
|
|
||||||
create_buffer_list_window :: proc() -> ^BufferListWindow {
|
|
||||||
input_map := core.new_input_map();
|
|
||||||
|
|
||||||
core.register_key_action(&input_map, .K, proc(state: ^core.State) {
|
|
||||||
win := cast(^BufferListWindow)(state.window);
|
|
||||||
|
|
||||||
if win.selected_buffer > 0 {
|
|
||||||
win.selected_buffer -= 1;
|
|
||||||
} else {
|
|
||||||
win.selected_buffer = len(state.buffers)-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
}, "move selection up");
|
|
||||||
core.register_key_action(&input_map, .J, proc(state: ^core.State) {
|
|
||||||
win := cast(^BufferListWindow)(state.window);
|
|
||||||
|
|
||||||
if win.selected_buffer >= len(state.buffers)-1 {
|
|
||||||
win.selected_buffer = 0;
|
|
||||||
} else {
|
|
||||||
win.selected_buffer += 1;
|
|
||||||
}
|
|
||||||
}, "move selection down");
|
|
||||||
core.register_key_action(&input_map, .ENTER, proc(state: ^core.State) {
|
|
||||||
win := cast(^BufferListWindow)(state.window);
|
|
||||||
|
|
||||||
state.current_buffer = win.selected_buffer;
|
|
||||||
|
|
||||||
core.request_window_close(state);
|
|
||||||
}, "switch to file");
|
|
||||||
|
|
||||||
core.register_key_action(&input_map, .Q, proc(state: ^core.State) {
|
|
||||||
core.request_window_close(state);
|
|
||||||
}, "close window");
|
|
||||||
|
|
||||||
list_window := new(BufferListWindow);
|
|
||||||
list_window^ = BufferListWindow {
|
|
||||||
window = core.Window {
|
|
||||||
input_map = input_map,
|
|
||||||
draw = draw_buffer_list_window,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
return list_window;
|
|
||||||
}
|
|
||||||
|
|
||||||
draw_buffer_list_window :: proc(win: ^core.Window, state: ^core.State) {
|
|
||||||
win := cast(^BufferListWindow)(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 := int(buffer_prev_height) / state.source_font_height;
|
|
||||||
|
|
||||||
raylib.DrawRectangle(
|
|
||||||
i32(win_rec.x + win_rec.width / 2),
|
|
||||||
i32(win_rec.y + win_margin.y),
|
|
||||||
i32(buffer_prev_width),
|
|
||||||
i32(buffer_prev_height),
|
|
||||||
theme.get_palette_raylib_color(.Background2));
|
|
||||||
|
|
||||||
for _, index in state.buffers {
|
|
||||||
buffer := &state.buffers[index];
|
|
||||||
relative_file_path, _ := filepath.rel(state.directory, buffer.file_path)
|
|
||||||
text := raylib.TextFormat("%s:%d", relative_file_path, buffer.cursor.line+1);
|
|
||||||
text_width := raylib.MeasureTextEx(state.font, text, f32(state.source_font_height), 0);
|
|
||||||
|
|
||||||
if index == win.selected_buffer {
|
|
||||||
buffer.glyph_buffer_height = glyph_buffer_height;
|
|
||||||
buffer.glyph_buffer_width = glyph_buffer_width;
|
|
||||||
core.draw_file_buffer(
|
|
||||||
state,
|
|
||||||
buffer,
|
|
||||||
int(win_rec.x + win_margin.x + win_rec.width / 2),
|
|
||||||
int(win_rec.y + win_margin.y),
|
|
||||||
state.font,
|
|
||||||
show_line_numbers = false);
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 = 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));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue