Got a bindings library that compiles.

Former-commit-id: b08de0a869b6738131e96dd616ef0b96bfb2d975
This commit is contained in:
Camillo Schenone 2023-08-25 03:05:53 +02:00
parent bfad81f958
commit e387b726c8
3 changed files with 260 additions and 61 deletions

View file

@ -1 +1 @@
8d18661818436ebf65d16f9447601b123aef410e
80ba7b16f0b0647e2f1d8520f3a90c51000bbd2c

View file

@ -14,7 +14,9 @@ PYO3_PYTHON="$(pyenv which python)"
PYTHON_SYS_EXECUTABLE="$PYO3_PYTHON"
CARGO_FEATURES="pyo3/extension-module"
echo "Building with python: $PYO3_PYTHON"
env PYO3_PYTHON="${PYO3_PYTHON}" PYTHON_SYS_EXECUTABLE="$PYO3_PYTHON" cargo build --features "$CARGO_FEATURES"
echo "Copying into: $TARGET_DIR/$FULL_TARGET"
[[ -f "$TARGET_DIR/$FUll_TARGET" ]] && echo "$FILE exists."
ditto "$BUILD_DIR/${FILENAME}.dylib" "$TARGET_DIR/$FULL_TARGET"

View file

@ -38,9 +38,7 @@ impl From<PyCodempError> for PyErr {
#[pyfunction]
fn codemp_init<'a>(py: Python<'a>) -> PyResult<Py<PyClientHandle>> {
let py_instance: PyClientHandle = CodempInstance::default().into();
Python::with_gil(|py| {
Ok(Py::new(py, py_instance)?)
})
}
#[pyclass]
@ -79,12 +77,13 @@ impl PyClientHandle {
let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
let buffctrl = rc.attach(path.as_str())
let buffctrl: PyBufferController = rc.attach(path.as_str())
.await
.map_err(PyCodempError::from)?;
.map_err(PyCodempError::from)?
.into();
Python::with_gil(|py| {
Ok(Py::new(py, PyBufferController(buffctrl))?)
Ok(Py::new(py, buffctrl)?)
})
})
}
@ -93,12 +92,13 @@ impl PyClientHandle {
let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
let curctrl = rc.join(session.as_str())
let curctrl: PyCursorController = rc.join(session.as_str())
.await
.map_err(PyCodempError::from)?;
.map_err(PyCodempError::from)?
.into();
Python::with_gil(|py| {
Ok(Py::new(py, PyCursorController(curctrl))?)
Ok(Py::new(py, curctrl)?)
})
})
}
@ -107,12 +107,13 @@ impl PyClientHandle {
let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
let curctrl = rc.get_cursor()
let curctrl: PyCursorController = rc.get_cursor()
.await
.map_err(PyCodempError::from)?;
.map_err(PyCodempError::from)?
.into();
Python::with_gil(|py| {
Ok(Py::new(py, PyCursorController(curctrl))?)
Ok(Py::new(py, curctrl)?)
})
})
}
@ -121,12 +122,13 @@ impl PyClientHandle {
let rc = self.0.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
let buffctrl = rc.get_buffer(path.as_str())
let buffctrl: PyBufferController = rc.get_buffer(path.as_str())
.await
.map_err(PyCodempError::from)?;
.map_err(PyCodempError::from)?
.into();
Python::with_gil(|py| {
Ok(Py::new(py, PyBufferController(buffctrl))?)
Ok(Py::new(py, buffctrl)?)
})
})
}
@ -154,20 +156,30 @@ impl PyClientHandle {
}
}
impl From::<CodempCursorController> for PyCursorController {
fn from(value: CodempCursorController) -> Self {
PyCursorController(Arc::new(value))
}
}
impl From::<CodempBufferController> for PyBufferController {
fn from(value: CodempBufferController) -> Self {
PyBufferController(Arc::new(value))
}
}
#[pyclass]
struct PyCursorController(Arc<CodempCursorController>);
struct PyCursorController {
handle: Arc<CodempCursorController>,
cb_trigger: Option<tokio::sync::mpsc::UnboundedSender<()>>
}
impl From::<Arc<CodempCursorController>> for PyCursorController {
fn from(value: Arc<CodempCursorController>) -> Self {
PyCursorController {
handle: value,
cb_trigger: None
}
}
}
fn py_cursor_callback_wrapper(cb: PyObject)
-> Box<dyn FnMut(CodempCursorEvent) -> () + Send + Sync + 'static>
{
let closure = move |data: CodempCursorEvent| {
let args: PyCursorEvent = data.into();
Python::with_gil(|py| { let _ = cb.call1(py, (args,)); });
};
Box::new(closure)
}
#[pymethods]
impl PyCursorController {
@ -193,13 +205,39 @@ impl PyCursorController {
// Ok(())
// })
// }
fn drop_callback(&mut self) -> PyResult<()> {
if let Some(channel) = &self.cb_trigger {
channel.send(())
.map_err(CodempError::from)
.map_err(PyCodempError::from)?;
self.cb_trigger = None;
}
Ok(())
}
fn callback<'a>(&'a mut self, py_cb: Py<PyAny>) -> PyResult<()> {
if let Some(_channel) = &self.cb_trigger {
Err(PyCodempError::from(CodempError::InvalidState { msg: "A callback is already running.".into() }).into())
} else {
let rt = pyo3_asyncio::tokio::get_runtime();
// create a channel to stop the callback task running on the tokio runtime.
// and save the sendent inside the python object, so that we can later call it.
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
self.cb_trigger = Some(tx);
self.handle.callback(rt, rx, py_cursor_callback_wrapper(py_cb));
Ok(())
}
}
fn send<'a>(&'a self, py: Python<'a>, path: String, start: (i32, i32), end: (i32, i32)) -> PyResult<&'a PyAny> {
let rc = self.0.clone();
let rc = self.handle.clone();
let pos = CodempCursorPosition {
buffer: path,
start: Some(CodempRowCol { row: start.0, col: start.1 }),
end: Some(CodempRowCol { row: end.0, col: end.1 })
start: Some(start.into()),
end: Some(end.into())
};
pyo3_asyncio::tokio::future_into_py(py, async move {
@ -210,19 +248,63 @@ impl PyCursorController {
}
fn try_recv(&self, py: Python<'_>) -> PyResult<PyObject> {
match self.handle.try_recv().map_err(PyCodempError::from)? {
Some(cur_event) => {
let evt = PyCursorEvent::from(cur_event);
Ok(evt.into_py(py))
},
None => Ok(py.None())
}
}
fn recv<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.0.clone();
let rc = self.handle.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
let cur_event = rc.recv()
let cur_event: PyCursorEvent = rc.recv()
.await
.map_err(PyCodempError::from)?;
Ok(())
.map_err(PyCodempError::from)?
.into();
Python::with_gil(|py| {
Ok(Py::new(py, cur_event)?)
})
})
}
fn poll<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.handle.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
Ok(rc.poll().await.map_err(PyCodempError::from)?)
})
}
}
#[pyclass]
struct PyBufferController(Arc<CodempBufferController>);
struct PyBufferController {
handle: Arc<CodempBufferController>,
cb_trigger: Option<tokio::sync::mpsc::UnboundedSender<()>>
}
impl From::<Arc<CodempBufferController>> for PyBufferController {
fn from(value: Arc<CodempBufferController>) -> Self {
PyBufferController{
handle: value,
cb_trigger: None
}
}
}
fn py_buffer_callback_wrapper(cb: PyObject)
-> Box<dyn FnMut(CodempTextChange) -> () + Send + Sync + 'static>
{
let closure = move |data: CodempTextChange| {
let args: PyTextChange = data.into();
Python::with_gil(|py| { let _ = cb.call1(py, (args,)); });
};
Box::new(closure)
}
#[pymethods]
impl PyBufferController {
@ -250,40 +332,150 @@ impl PyBufferController {
// })
// }
fn drop_callback(&mut self) -> PyResult<()> {
if let Some(channel) = &self.cb_trigger {
channel.send(())
.map_err(CodempError::from)
.map_err(PyCodempError::from)?;
self.cb_trigger = None;
}
Ok(())
}
fn callback<'a>(&'a mut self, py_cb: Py<PyAny>) -> PyResult<()> {
if let Some(_channel) = &self.cb_trigger {
Err(PyCodempError::from(CodempError::InvalidState { msg: "A callback is already running.".into() }).into())
} else {
let rt = pyo3_asyncio::tokio::get_runtime();
// could this be a oneshot channel?
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
self.cb_trigger = Some(tx);
self.handle.callback(rt, rx, py_buffer_callback_wrapper(py_cb));
Ok(())
}
}
fn replace(&self, txt: &str) -> PyResult<()> {
if let Some(op) = self.handle.replace(txt) {
self.handle.send(op).map_err(PyCodempError::from)?;
}
Ok(())
}
fn delta(&self, start: usize, txt: &str, end: usize) -> PyResult<()> {
if let Some(op) = self.handle.delta(start, txt, end){
self.handle.send(op).map_err(PyCodempError::from)?;
}
Ok(())
}
fn insert(&self, txt: &str, pos: u64) -> PyResult<()> {
let op = self.handle.insert(txt, pos);
self.handle.send(op).map_err(PyCodempError::from)?;
Ok(())
}
fn delete(&self, pos: u64, count: u64) -> PyResult<()> {
let op = self.handle.delete(pos, count);
self.handle.send(op).map_err(PyCodempError::from)?;
Ok(())
}
fn cancel(&self, pos: u64, count: u64) -> PyResult<()> {
let op = self.handle.cancel(pos, count);
self.handle.send(op).map_err(PyCodempError::from)?;
Ok(())
}
fn content(&self, py: Python<'_>) -> PyResult<Py<PyString>> {
let cont: Py<PyString> = PyString::new(py, self.0.content().as_str()).into();
let cont: Py<PyString> = PyString::new(py, self.handle.content().as_str()).into();
Ok(cont)
}
fn send<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{
let rc = self.0.clone();
// What to do with this send? does it make sense to implement it at all?
// fn send<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{
// let rc = self.handle.clone();
// pyo3_asyncio::tokio::future_into_py(py, async move {
// Ok(())
// })
// }
fn try_recv(&self, py: Python<'_>) -> PyResult<PyObject> {
match self.handle.try_recv().map_err(PyCodempError::from)? {
Some(txt_change) => {
let evt = PyTextChange::from(txt_change);
Ok(evt.into_py(py))
},
None => Ok(py.None())
}
}
fn recv<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.handle.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
Ok(())
let txt_change: PyTextChange = rc.recv()
.await
.map_err(PyCodempError::from)?
.into();
Python::with_gil(|py| {
Ok(Py::new(py, txt_change)?)
})
})
}
fn poll<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
let rc = self.handle.clone();
pyo3_asyncio::tokio::future_into_py(py, async move {
Ok(rc.poll().await.map_err(PyCodempError::from)?)
})
}
}
// #[pyclass]
// struct PyCursorEvent {
// user: String,
// buffer: Some(String),
// start: (i32, i32),
// end: (i32, i32)
// }
/* ---------- Type Wrappers ----------*/
// All these objects are not meant to be handled rust side.
// Just to be sent to the python heap.
// impl From<CodempCursorEvent> for PyCursorEvent {
// fn from(value: CodempCursorEvent) -> Self {
// PyCursorEvent {
// user: value.user,
// buffer: value.position.buffer,
// start: (value.position.start.row, value.position.start.col),
// end: (value.position.end.row, value.position.end.col)
// }
// }
// }
#[pyclass]
struct PyCursorEvent {
user: String,
buffer: String,
start: (i32, i32),
end: (i32, i32)
}
impl From<CodempCursorEvent> for PyCursorEvent {
fn from(value: CodempCursorEvent) -> Self {
// todo, handle this optional better?
let pos = value.position.unwrap_or_default();
PyCursorEvent {
user: value.user,
buffer: pos.buffer,
start: pos.start.unwrap_or_default().into(),
end: pos.end.unwrap_or_default().into()
}
}
}
// Python module
#[pyclass]
struct PyTextChange {
start_incl: usize,
end_excl: usize,
content: String
}
impl From<CodempTextChange> for PyTextChange {
fn from(value: CodempTextChange) -> Self {
PyTextChange { start_incl: value.span.start, end_excl: value.span.end, content: value.content }
}
}
/* ------ Python module --------*/
#[pymodule]
fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(codemp_init, m)?)?;
@ -291,5 +483,10 @@ fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_class::<PyCursorController>()?;
m.add_class::<PyBufferController>()?;
m.add_class::<PyCursorEvent>()?;
m.add_class::<PyTextChange>()?;
Ok(())
}