diff --git a/build.rs b/build.rs index 7931f88..da5444f 100644 --- a/build.rs +++ b/build.rs @@ -1,5 +1,5 @@ fn main() { - println!("cargo:rerun-if-changed=src/ffi.rs"); + println!("cargo:rerun-if-changed=mime-rs/ffi.rs"); cxx_build::bridge("mime-rs/ffi.rs") .includes(["c++/vendor/immer", "c++/include"]) diff --git a/mime-py/.github/workflows/CI.yml b/mime-py/.github/workflows/CI.yml new file mode 100644 index 0000000..74a463b --- /dev/null +++ b/mime-py/.github/workflows/CI.yml @@ -0,0 +1,41 @@ +name: CI + +on: + push: + branches: + - main + pull_request: + workflow_dispatch: + +jobs: + linux: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: PyO3/maturin-action@v1 + with: + manylinux: auto + command: build + args: --release --sdist -o dist --find-interpreter + - name: Upload wheels + uses: actions/upload-artifact@v3 + with: + name: wheels + path: dist + + release: + name: Release + runs-on: ubuntu-latest + if: "startsWith(github.ref, 'refs/tags/')" + needs: linux + steps: + - uses: actions/download-artifact@v3 + with: + name: wheels + - name: Publish to PyPI + uses: PyO3/maturin-action@v1 + env: + MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + with: + command: upload + args: --skip-existing * diff --git a/mime-py/.gitignore b/mime-py/.gitignore new file mode 100644 index 0000000..af3ca5e --- /dev/null +++ b/mime-py/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version \ No newline at end of file diff --git a/mime-py/Cargo.toml b/mime-py/Cargo.toml new file mode 100644 index 0000000..37619d4 --- /dev/null +++ b/mime-py/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "mime-py" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "mime" +crate-type = ["cdylib"] + +[dependencies] +pyo3 = { version = "0.18.2", features = ["extension-module"] } +mime-rs = { path = "../" } \ No newline at end of file diff --git a/mime-py/mime-test.py b/mime-py/mime-test.py new file mode 100644 index 0000000..96a8c11 --- /dev/null +++ b/mime-py/mime-test.py @@ -0,0 +1,23 @@ +import mime + + +def run() -> None: + doc_view = mime.open("main.go") + nav_view = doc_view.clone() + + while nav_view.find("func "): + nav_view.set_mark() + if not nav_view.find("("): + raise RuntimeError("Can't find an open paranthesis after func keyword") + + nav_view.backward(1) + fnname = nav_view.copy() + + doc_view.paste(f"// Python: {fnname};\n") + + doc_view.paste("\n") + print(doc_view.get_contents()) + doc_view.save_as("mainout.go") + + +run() diff --git a/mime-py/pyproject.toml b/mime-py/pyproject.toml new file mode 100644 index 0000000..4038680 --- /dev/null +++ b/mime-py/pyproject.toml @@ -0,0 +1,14 @@ +[build-system] +requires = ["maturin>=0.14,<0.15"] +build-backend = "maturin" + +[project] +name = "mime-py" +requires-python = ">=3.7" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] + + diff --git a/mime-py/src/lib.rs b/mime-py/src/lib.rs new file mode 100644 index 0000000..05cd6c6 --- /dev/null +++ b/mime-py/src/lib.rs @@ -0,0 +1,188 @@ +use pyo3::prelude::*; + +#[pyclass] +struct Window(::mime::Window); + +#[pyclass] +#[derive(Clone)] +struct Text(::mime::Text); + +#[pymethods] +impl Text { + fn __str__(&self) -> String { + self.0.to_string() + } +} + +#[derive(FromPyObject)] +enum StringOrText { + String(String), + Text(Text), +} + +#[pymethods] +impl Window { + #[staticmethod] + pub fn new() -> Self { + Self(::mime::Window::new()) + } + + #[staticmethod] + pub fn open(filename: &str) -> Self { + Self(::mime::Window::open(filename)) + } + + pub fn clone(&self) -> Self { + Window(self.0.clone()) + } + + pub fn empty(&self) -> bool { + self.0.empty() + } + + pub fn size(&self) -> usize { + self.0.size() + } + + pub fn narrowed(&self) -> bool { + self.0.narrowed() + } + + pub fn save_as(&self, filename: &str) { + self.0.save_as(filename) + } + + pub fn set_mark(&self) { + self.0.set_mark() + } + + pub fn get_mark(&self) -> Option { + self.0.get_mark() + } + + pub fn get_contents(&self) -> Text { + Text(self.0.get_contents().into()) + } + + pub fn find(&self, text: &str) -> Option { + self.0.find(text) + } + + pub fn rfind(&self, text: &str) -> Option { + self.0.rfind(text) + } + + pub fn replace(&self, from: &str, to: &str, n: usize) -> i32 { + self.0.replace(from, to, n) + } + + pub fn copy(&self) -> Text { + Text(self.0.copy().into()) + } + + pub fn cut(&self) -> Text { + Text(self.0.cut().into()) + } + + pub fn paste(&self, text: StringOrText) { + match text { + StringOrText::String(ref string) => self.0.paste(string.as_str()), + StringOrText::Text(text) => self.0.paste(text.0), + } + // self.0.paste(t) + } + + pub fn erase_region(&self) { + self.0.erase_region() + } + + pub fn clear(&self) { + self.0.clear() + } + + pub fn get_pos(&self) -> usize { + self.0.get_pos() + } + + pub fn goto_pos(&self, pos: i64) -> bool { + self.0.goto_pos(pos) + } + + pub fn del_backward(&self, n: usize) -> usize { + self.0.del_backward(n) + } + + pub fn del_forward(&self, n: usize) -> usize { + self.0.del_forward(n) + } + + pub fn backward(&self, n: usize) -> usize { + self.0.backward(n) + } + + pub fn forward(&self, n: usize) -> usize { + self.0.forward(n) + } + + pub fn prev_line(&self, n: usize) -> usize { + self.0.prev_line(n) + } + + pub fn next_line(&self, n: usize) -> usize { + self.0.next_line(n) + } + + pub fn start_of_buffer(&self) { + self.0.start_of_buffer() + } + + pub fn end_of_buffer(&self) { + self.0.end_of_buffer() + } + + pub fn start_of_line(&self) { + self.0.start_of_line() + } + + pub fn end_of_line(&self) { + self.0.end_of_line() + } + + pub fn start_of_block(&self) -> bool { + self.0.start_of_block() + } + + pub fn end_of_block(&self) -> bool { + self.0.end_of_block() + } + + pub fn narrow_to_block(&self) -> bool { + self.0.narrow_to_block() + } + + pub fn narrow_to_region(&self) -> bool { + self.0.narrow_to_region() + } + + pub fn widen(&self) { + self.0.widen() + } +} + +#[pyfunction] +fn new() -> Window { + Window::new() +} + +#[pyfunction] +fn open(name: &str) -> Window { + Window::open(name) +} + +/// A Python module implemented in Rust. +#[pymodule] +fn mime(_py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(new, m)?)?; + m.add_function(wrap_pyfunction!(open, m)?)?; + Ok(()) +} diff --git a/mime-rs/lib.rs b/mime-rs/lib.rs index 99ca6d0..d852c7d 100644 --- a/mime-rs/lib.rs +++ b/mime-rs/lib.rs @@ -1,256 +1,6 @@ mod ffi; +mod text; +mod window; -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); - } -} +pub use text::Text; +pub use window::Window; diff --git a/mime-rs/text.rs b/mime-rs/text.rs new file mode 100644 index 0000000..76f943f --- /dev/null +++ b/mime-rs/text.rs @@ -0,0 +1,55 @@ +use std::fmt::Display; +use std::sync::Arc; + +use cxx::UniquePtr; + +use crate::ffi::cpp; + +unsafe impl Send for cpp::text {} +unsafe impl Sync for cpp::text {} +unsafe impl Send for _TextImpl {} +unsafe impl Sync for _TextImpl {} + +#[derive(Clone)] +pub struct Text(pub(crate) Arc<_TextImpl>); + +impl From> for Text { + fn from(value: UniquePtr) -> Self { + Text(Arc::new(_TextImpl::Text(value))) + } +} + +impl From for Text { + fn from(value: String) -> Self { + Text(Arc::new(_TextImpl::Str(value))) + } +} + +impl From<&str> for Text { + fn from(value: &str) -> Self { + Text(Arc::new(_TextImpl::Str(value.to_string()))) + } +} + +pub(crate) enum _TextImpl { + Text(UniquePtr), + Str(String), +} + +impl Display for Text { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &*self.0 { + _TextImpl::Text(t) => f.write_str(&cpp::text_to_string(&t).to_string()), + _TextImpl::Str(t) => f.write_str(&t), + } + } +} + +impl Text { + pub fn to_string(&self) -> String { + match &*self.0 { + _TextImpl::Text(t) => cpp::text_to_string(&t).to_string(), + _TextImpl::Str(t) => t.to_owned(), + } + } +} diff --git a/mime-rs/window.rs b/mime-rs/window.rs new file mode 100644 index 0000000..3e83455 --- /dev/null +++ b/mime-rs/window.rs @@ -0,0 +1,277 @@ +use crate::text::_TextImpl; +use crate::{ffi::cpp, Text}; + +use cxx::{let_cxx_string, UniquePtr}; +use std::sync::{Arc, Mutex}; + +unsafe impl Send for cpp::buffer {} + +pub struct Window { + buffer: Arc>>, + 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.lock().unwrap().as_mut().unwrap().new_cursor(); + Self { buffer, cursor } + } +} + +impl Window { + pub fn new() -> Self { + let buffer = Arc::new(Mutex::new(cpp::new_buffer())); + let cursor = buffer.lock().unwrap().as_mut().unwrap().new_cursor(); + Self { buffer, cursor } + } + + pub fn open(filename: &str) -> Self { + let_cxx_string!(filename = filename); + let buffer = Arc::new(Mutex::new(cpp::open_buffer(&filename))); + let cursor = buffer.lock().unwrap().as_mut().unwrap().new_cursor(); + Self { buffer, cursor } + } + + pub fn empty(&self) -> bool { + self.buffer.lock().unwrap().empty() + } + + pub fn size(&self) -> usize { + self.buffer.lock().unwrap().size() + } + + pub fn narrowed(&self) -> bool { + self.update_cursor(); + self.buffer.lock().unwrap().narrowed() + } + + pub fn save_as(&self, filename: &str) { + let_cxx_string!(filename = filename); + self.buffer.lock().unwrap().save_as(&filename); + } + + pub fn set_mark(&self) { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().set_mark(); + } + + // TODO: add bookmark interface + pub fn get_mark(&self) -> Option { + self.update_cursor(); + let pos = self.buffer.lock().unwrap().get_mark(); + (pos >= 0).then(|| pos) + } + + pub fn get_contents(&self) -> Text { + self.buffer.lock().unwrap().get_contents_box().into() + } + + pub fn find(&self, text: &str) -> Option { + self.update_cursor(); + let_cxx_string!(text = text); + let pos = self.buffer.lock().unwrap().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.lock().unwrap().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 + .lock() + .unwrap() + .as_mut() + .unwrap() + .replace(&from, &to, n) + } + + pub fn copy(&self) -> Text { + self.update_cursor(); + self.buffer.lock().unwrap().copy_box().into() + } + + pub fn cut(&self) -> Text { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .cut_box() + .into() + } + + pub fn paste>(&self, text: T) { + self.update_cursor(); + match &*text.into().0 { + _TextImpl::Text(ref text) => self + .buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .paste_text(text), + _TextImpl::Str(ref text) => { + let_cxx_string!(text = text); + self.buffer.lock().unwrap().as_mut().unwrap().paste(&text) + } + } + } + + pub fn erase_region(&self) { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().erase_region() + } + + pub fn clear(&self) { + self.buffer.lock().unwrap().as_mut().unwrap().clear() + } + + pub fn get_pos(&self) -> usize { + self.update_cursor(); + self.buffer.lock().unwrap().get_pos() + } + + pub fn goto_pos(&self, pos: i64) -> bool { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().goto_pos(pos) + } + + pub fn del_backward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .del_backward(n) + } + + pub fn del_forward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().del_forward(n) + } + + pub fn backward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().backward(n) + } + + pub fn forward(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().forward(n) + } + + pub fn prev_line(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().prev_line(n) + } + + pub fn next_line(&self, n: usize) -> usize { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().next_line(n) + } + + pub fn start_of_buffer(&self) { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .start_of_buffer() + } + + pub fn end_of_buffer(&self) { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .end_of_buffer() + } + + pub fn start_of_line(&self) { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .start_of_line() + } + + pub fn end_of_line(&self) { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().end_of_line() + } + + pub fn start_of_block(&self) -> bool { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .start_of_block() + } + + pub fn end_of_block(&self) -> bool { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().end_of_block() + } + + pub fn narrow_to_block(&self) -> bool { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .narrow_to_block() + } + + pub fn narrow_to_region(&self) -> bool { + self.update_cursor(); + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .narrow_to_region() + } + + pub fn widen(&self) { + self.update_cursor(); + self.buffer.lock().unwrap().as_mut().unwrap().widen() + } +} + +impl Default for Window { + fn default() -> Self { + Self::new() + } +} + +// Private functions +impl Window { + fn update_cursor(&self) { + self.buffer + .lock() + .unwrap() + .as_mut() + .unwrap() + .use_cursor(self.cursor); + } +}