diff --git a/.gitignore b/.gitignore index 617d777..b435ef7 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ examples/mimeout* build .ccls-cache docs/playground/mime.* +target +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6335362 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mime-rs" +description = "A text processing framework, inspired by Emacs lisp and keyboard macros." +version = "0.3.0" +edition = "2021" +repository = "https://github.com/shsms/mime" +license = "GPL-3.0" +keywords = ["scripting", "text-processing"] +categories = ["text-processing"] +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +name = "mime" +path = "mime-rs/lib.rs" + +[dependencies] +cxx = "1.0" + +[build-dependencies] +cxx-build = "1.0" diff --git a/README.org b/README.org index 45f9981..0d8c645 100644 --- a/README.org +++ b/README.org @@ -10,7 +10,7 @@ but in a scripting environment, without an editor. This enables very sophisticated transformations that are easy to do through tools like Emacs Keyboard Macros, but hard to do in code. -- *Documentation*: https://mime.dev +- *Documentation*: https://shsms.github.io/mime ** Dependencies For providing its functionalities, Mime depends on these amazing diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..7931f88 --- /dev/null +++ b/build.rs @@ -0,0 +1,11 @@ +fn main() { + println!("cargo:rerun-if-changed=src/ffi.rs"); + + cxx_build::bridge("mime-rs/ffi.rs") + .includes(["c++/vendor/immer", "c++/include"]) + .file("c++/src/lib/mime.cc") + .warnings(false) + .flag_if_supported("-std=c++17") + .opt_level(3) + .compile("mime"); +} diff --git a/mime-rs/ffi.rs b/mime-rs/ffi.rs new file mode 100644 index 0000000..c69ca61 --- /dev/null +++ b/mime-rs/ffi.rs @@ -0,0 +1,69 @@ +#[cxx::bridge(namespace = "mime")] +pub(crate) mod cpp { + + unsafe extern "C++" { + include!("mime/mime.hh"); + + type text; + type buffer; + + fn new_buffer() -> UniquePtr; + fn open_buffer(name: &CxxString) -> UniquePtr; + + fn empty(self: &buffer) -> bool; + fn size(self: &buffer) -> usize; + fn narrowed(self: &buffer) -> bool; + + fn save_as(self: &buffer, name: &CxxString); + + fn set_mark(self: Pin<&mut buffer>); + fn get_mark(self: &buffer) -> i64; + + fn get_contents_box(self: &buffer) -> UniquePtr; + + fn find(self: Pin<&mut buffer>, t: &CxxString) -> i64; + // fn find_text(self: Pin<&mut buffer>, t: &text) -> i64; + + fn rfind(self: Pin<&mut buffer>, t: &CxxString) -> i64; + // fn rfind_text(self: Pin<&mut buffer>, t: &text) -> i64; + + fn replace(self: Pin<&mut buffer>, from: &CxxString, to: &CxxString, n: usize) -> i32; + // fn replace_all(self: Pin<&mut buffer>, from: &CxxString, to: &CxxString) -> i32; + + fn copy_box(self: &buffer) -> UniquePtr; + fn cut_box(self: Pin<&mut buffer>) -> UniquePtr; + + fn paste(self: Pin<&mut buffer>, t: &CxxString); + fn paste_text(self: Pin<&mut buffer>, t: &text); + + fn erase_region(self: Pin<&mut buffer>); + fn clear(self: Pin<&mut buffer>); + + fn del_backward(self: Pin<&mut buffer>, n: usize) -> usize; + fn del_forward(self: Pin<&mut buffer>, n: usize) -> usize; + + fn new_cursor(self: Pin<&mut buffer>) -> usize; + fn use_cursor(self: Pin<&mut buffer>, c: usize); + fn get_pos(self: &buffer) -> usize; + fn goto_pos(self: Pin<&mut buffer>, pos: i64) -> bool; + + fn forward(self: Pin<&mut buffer>, n: usize) -> usize; + fn backward(self: Pin<&mut buffer>, n: usize) -> usize; + fn next_line(self: Pin<&mut buffer>, n: usize) -> usize; + fn prev_line(self: Pin<&mut buffer>, n: usize) -> usize; + + fn start_of_buffer(self: Pin<&mut buffer>); + fn end_of_buffer(self: Pin<&mut buffer>); + fn start_of_line(self: Pin<&mut buffer>); + fn end_of_line(self: Pin<&mut buffer>); + + fn start_of_block(self: Pin<&mut buffer>) -> bool; + fn end_of_block(self: Pin<&mut buffer>) -> bool; + + fn narrow_to_block(self: Pin<&mut buffer>) -> bool; + fn narrow_to_region(self: Pin<&mut buffer>) -> bool; + fn widen(self: Pin<&mut buffer>); + + fn text_to_string(t: &text) -> UniquePtr; + } +} diff --git a/mime-rs/lib.rs b/mime-rs/lib.rs new file mode 100644 index 0000000..99ca6d0 --- /dev/null +++ b/mime-rs/lib.rs @@ -0,0 +1,256 @@ +mod ffi; + +use cxx::{let_cxx_string, UniquePtr}; +use ffi::cpp; +use std::{cell::RefCell, fmt::Display, rc::Rc}; + +pub enum Text { + Text(UniquePtr), + Str(String), +} + +impl Display for Text { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Text::Text(t) => f.write_str(&cpp::text_to_string(t).to_string()), + Text::Str(t) => f.write_str(&t), + } + } +} + +impl From for Text { + fn from(value: String) -> Self { + Self::Str(value) + } +} + +impl From<&str> for Text { + fn from(value: &str) -> Self { + Self::Str(value.to_string()) + } +} + +pub struct Window { + buffer: Rc>>, + cursor: usize, +} + +impl Clone for Window { + /// Returns a new window for the same buffer, effectively adding a new + /// cursor to the existing buffer. + fn clone(&self) -> Self { + let buffer = self.buffer.clone(); + let cursor = buffer.borrow_mut().as_mut().unwrap().new_cursor(); + Self { buffer, cursor } + } +} + +impl Window { + pub fn new() -> Self { + let buffer = Rc::new(RefCell::new(cpp::new_buffer())); + let cursor = buffer.borrow_mut().as_mut().unwrap().new_cursor(); + Self { buffer, cursor } + } + + pub fn open(filename: &str) -> Self { + let_cxx_string!(filename = filename); + let buffer = Rc::new(RefCell::new(cpp::open_buffer(&filename))); + let cursor = buffer.borrow_mut().as_mut().unwrap().new_cursor(); + Self { buffer, cursor } + } + + pub fn empty(&self) -> bool { + self.buffer.borrow().empty() + } + + pub fn size(&self) -> usize { + self.buffer.borrow().size() + } + + pub fn narrowed(&self) -> bool { + self.update_cursor(); + self.buffer.borrow().narrowed() + } + + pub fn save_as(&self, filename: &str) { + let_cxx_string!(filename = filename); + self.buffer.borrow().save_as(&filename); + } + + pub fn set_mark(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().set_mark(); + } + + // TODO: add bookmark interface + pub fn get_mark(&self) -> Option { + self.update_cursor(); + let pos = self.buffer.borrow().get_mark(); + (pos >= 0).then(|| pos) + } + + pub fn get_contents(&self) -> Text { + Text::Text(self.buffer.borrow().get_contents_box()) + } + + pub fn find(&self, text: &str) -> Option { + self.update_cursor(); + let_cxx_string!(text = text); + let pos = self.buffer.borrow_mut().as_mut().unwrap().find(&text); + (pos >= 0).then(|| pos) + } + + pub fn rfind(&self, text: &str) -> Option { + self.update_cursor(); + let_cxx_string!(text = text); + let pos = self.buffer.borrow_mut().as_mut().unwrap().rfind(&text); + (pos >= 0).then(|| pos) + } + + pub fn replace(&self, from: &str, to: &str, n: usize) -> i32 { + self.update_cursor(); + let_cxx_string!(from = from); + let_cxx_string!(to = to); + self.buffer + .borrow_mut() + .as_mut() + .unwrap() + .replace(&from, &to, n) + } + + pub fn copy(&self) -> Text { + self.update_cursor(); + Text::Text(self.buffer.borrow().copy_box()) + } + + pub fn cut(&self) -> Text { + self.update_cursor(); + Text::Text(self.buffer.borrow_mut().as_mut().unwrap().cut_box()) + } + + pub fn paste>(&self, text: T) { + self.update_cursor(); + match text.into() { + Text::Text(ref text) => self.buffer.borrow_mut().as_mut().unwrap().paste_text(text), + Text::Str(ref text) => { + let_cxx_string!(text = text); + self.buffer.borrow_mut().as_mut().unwrap().paste(&text) + } + } + } + + pub fn erase_region(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().erase_region() + } + + pub fn clear(&self) { + self.buffer.borrow_mut().as_mut().unwrap().clear() + } + + pub fn get_pos(&self) -> usize { + self.update_cursor(); + self.buffer.borrow().get_pos() + } + + pub fn goto_pos(&self, pos: i64) -> bool { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().goto_pos(pos) + } + + pub fn del_backward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().del_backward(n) + } + + pub fn del_forward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().del_forward(n) + } + + pub fn backward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().backward(n) + } + + pub fn forward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().forward(n) + } + + pub fn prev_line(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().prev_line(n) + } + + pub fn next_line(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().next_line(n) + } + + pub fn start_of_buffer(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().start_of_buffer() + } + + pub fn end_of_buffer(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().end_of_buffer() + } + + pub fn start_of_line(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().start_of_line() + } + + pub fn end_of_line(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().end_of_line() + } + + pub fn start_of_block(&self) -> bool { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().start_of_block() + } + + pub fn end_of_block(&self) -> bool { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().end_of_block() + } + + pub fn narrow_to_block(&self) -> bool { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().narrow_to_block() + } + + pub fn narrow_to_region(&self) -> bool { + self.update_cursor(); + self.buffer + .borrow_mut() + .as_mut() + .unwrap() + .narrow_to_region() + } + + pub fn widen(&self) { + self.update_cursor(); + self.buffer.borrow_mut().as_mut().unwrap().widen() + } +} + +impl Default for Window { + fn default() -> Self { + Self::new() + } +} + +// Private functions +impl Window { + fn update_cursor(&self) { + self.buffer + .borrow_mut() + .as_mut() + .unwrap() + .use_cursor(self.cursor); + } +}