mirror of
https://github.com/hexedtech/codemp-sublime.git
synced 2024-12-23 13:14:52 +01:00
Implemented the client handle interface. TODO: controllers
Former-commit-id: 6b273dffce4e28a1e41f31a71f5a4ce78ecfe264
This commit is contained in:
parent
31f296a55c
commit
bfad81f958
2 changed files with 190 additions and 150 deletions
|
@ -9,7 +9,7 @@ name = "codemp_client"
|
|||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", tag = "v0.4.4" }
|
||||
codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", tag = "v0.4.4"}
|
||||
pyo3 = { version = "0.19", features = ["extension-module"] }
|
||||
pyo3-asyncio = { version = "0.19", features = ["tokio-runtime"] }
|
||||
tokio = "1.29.1"
|
||||
|
|
338
src/lib.rs
338
src/lib.rs
|
@ -1,14 +1,12 @@
|
|||
use std::{sync::Arc, error::Error, format};
|
||||
use std::{sync::Arc, format};
|
||||
|
||||
use codemp::prelude::*;
|
||||
use codemp::{prelude::*};
|
||||
use codemp::errors::Error as CodempError;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use pyo3::{
|
||||
prelude::*,
|
||||
exceptions::{PyConnectionError, PyRuntimeError},
|
||||
types::{PyBool, PyString}
|
||||
exceptions::{PyConnectionError, PyRuntimeError, PyBaseException},
|
||||
types::PyString
|
||||
};
|
||||
|
||||
struct PyCodempError(CodempError);
|
||||
|
@ -18,7 +16,7 @@ impl From::<CodempError> for PyCodempError {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::convert::From<PyCodempError> for PyErr {
|
||||
impl From<PyCodempError> for PyErr {
|
||||
fn from(err: PyCodempError) -> PyErr {
|
||||
match err.0 {
|
||||
CodempError::Transport { status, message } => {
|
||||
|
@ -29,227 +27,269 @@ impl std::convert::From<PyCodempError> for PyErr {
|
|||
},
|
||||
CodempError::InvalidState { msg } => {
|
||||
PyRuntimeError::new_err(format!("Invalid state: {}", msg))
|
||||
},
|
||||
CodempError::Filler { message } => {
|
||||
PyBaseException::new_err(format!("Generic error: {}", message))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pyfunction]
|
||||
fn connect<'a>(py: Python<'a>, dest: String) -> PyResult<&'a PyAny> {
|
||||
// construct a python coroutine
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
match CodempClient::new(dest.as_str()).await {
|
||||
Ok(c) => {
|
||||
Python::with_gil(|py|{
|
||||
let cc: PyClientHandle = c.into();
|
||||
let handle = Py::new(py, cc)?;
|
||||
Ok(handle)
|
||||
})
|
||||
},
|
||||
Err(e) => { Err(PyConnectionError::new_err(e.source().unwrap().to_string())) }
|
||||
}
|
||||
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]
|
||||
#[derive(Clone)]
|
||||
struct PyClientHandle(Arc<Mutex<CodempClient>>);
|
||||
struct PyClientHandle(Arc<CodempInstance>);
|
||||
|
||||
impl From::<CodempClient> for PyClientHandle {
|
||||
fn from(value: CodempClient) -> Self {
|
||||
PyClientHandle(Arc::new(Mutex::new(value)))
|
||||
impl From::<CodempInstance> for PyClientHandle {
|
||||
fn from(value: CodempInstance) -> Self {
|
||||
PyClientHandle(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyClientHandle {
|
||||
fn get_id<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> {
|
||||
fn connect<'a>(&'a self, py: Python<'a>, addr: String) ->PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
let binding = rc.lock().await;
|
||||
rc.connect(addr.as_str()).await.map_err(PyCodempError::from)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn create<'a>(&'a self, py: Python<'a>, path: String, content: Option<String>) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
rc.create(path.as_str(), content.as_deref())
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn attach<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
let buffctrl = rc.attach(path.as_str())
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
|
||||
Python::with_gil(|py| {
|
||||
let id: Py<PyString> = PyString::new(py, binding.id()).into();
|
||||
Ok(id)
|
||||
Ok(Py::new(py, PyBufferController(buffctrl))?)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn create<'a>(&self, py: Python<'a>, path: String, content: Option<String>) -> PyResult<&'a PyAny> {
|
||||
fn join<'a>(&'a self, py: Python<'a>, session: String) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
match rc.lock().await.create(path, content).await {
|
||||
Ok(accepted) => {
|
||||
Python::with_gil(|py| {
|
||||
let accepted: Py<PyBool> = PyBool::new(py, accepted).into();
|
||||
Ok(accepted)
|
||||
})
|
||||
},
|
||||
Err(e) => { Err(PyConnectionError::new_err(e.source().unwrap().to_string())) }
|
||||
}
|
||||
|
||||
let curctrl = rc.join(session.as_str())
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
|
||||
Python::with_gil(|py| {
|
||||
Ok(Py::new(py, PyCursorController(curctrl))?)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn get_cursor<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
let curctrl = rc.get_cursor()
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
|
||||
Python::with_gil(|py| {
|
||||
Ok(Py::new(py, PyCursorController(curctrl))?)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn listen<'a>(&self, py: Python<'a>) -> PyResult<&'a PyAny> {
|
||||
fn get_buffer<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
match rc.lock().await.listen().await {
|
||||
Ok(controller) => {
|
||||
Python::with_gil(|py| {
|
||||
let cc: PyControllerHandle = controller.into();
|
||||
let contr = Py::new(py, cc)?;
|
||||
Ok(contr)
|
||||
})
|
||||
},
|
||||
Err(e) => {Err(PyConnectionError::new_err(e.source().unwrap().to_string()))}
|
||||
}
|
||||
let buffctrl = rc.get_buffer(path.as_str())
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
|
||||
Python::with_gil(|py| {
|
||||
Ok(Py::new(py, PyBufferController(buffctrl))?)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn attach<'a>(&self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> {
|
||||
fn leave_workspace<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
let uri = path.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
match rc.lock().await.attach(uri).await {
|
||||
Ok(factory) => {
|
||||
Python::with_gil(|py| {
|
||||
let ff: PyOperationsHandle = factory.into();
|
||||
let fact = Py::new(py, ff)?;
|
||||
Ok(fact)
|
||||
})
|
||||
},
|
||||
Err(e) => {Err(PyConnectionError::new_err(e.source().unwrap().to_string()))}
|
||||
}
|
||||
rc.leave_workspace()
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn disconnect_buffer<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
rc.disconnect_buffer(path.as_str())
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl From::<CursorControllerHandle> for PyControllerHandle {
|
||||
fn from(value: CursorControllerHandle) -> Self {
|
||||
PyControllerHandle(value)
|
||||
impl From::<CodempCursorController> for PyCursorController {
|
||||
fn from(value: CodempCursorController) -> Self {
|
||||
PyCursorController(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
impl From::<OperationControllerHandle> for PyOperationsHandle {
|
||||
fn from(value: OperationControllerHandle) -> Self {
|
||||
PyOperationsHandle(value)
|
||||
impl From::<CodempBufferController> for PyBufferController {
|
||||
fn from(value: CodempBufferController) -> Self {
|
||||
PyBufferController(Arc::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
struct PyControllerHandle(CursorControllerHandle);
|
||||
struct PyCursorController(Arc<CodempCursorController>);
|
||||
|
||||
#[pymethods]
|
||||
impl PyControllerHandle {
|
||||
fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> PyResult<&'a PyAny> {
|
||||
let mut rc = self.0.clone();
|
||||
let cb = coro_py.clone();
|
||||
// We want to start polling the ControlHandle and call the callback every time
|
||||
// we have something.
|
||||
impl PyCursorController {
|
||||
// fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> PyResult<&'a PyAny> {
|
||||
// let mut rc = self.0.clone();
|
||||
// let cb = coro_py.clone();
|
||||
// // We want to start polling the ControlHandle and call the callback every time
|
||||
// // we have something.
|
||||
|
||||
// pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
// while let Some(op) = rc.poll().await {
|
||||
// let start = op.start.unwrap_or(Position { row: 0, col: 0});
|
||||
// let end = op.end.unwrap_or(Position { row: 0, col: 0});
|
||||
|
||||
// let cb_fut = Python::with_gil(|py| -> PyResult<_> {
|
||||
// let args = (op.user, caller_id.clone(), op.buffer, (start.row, start.col), (end.row, end.col));
|
||||
// let coro = cb.call1(py, args)?;
|
||||
// pyo3_asyncio::tokio::into_future(coro.into_ref(py))
|
||||
// })?;
|
||||
|
||||
// cb_fut.await?;
|
||||
// }
|
||||
// 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 pos = CodempCursorPosition {
|
||||
buffer: path,
|
||||
start: Some(CodempRowCol { row: start.0, col: start.1 }),
|
||||
end: Some(CodempRowCol { row: end.0, col: end.1 })
|
||||
};
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
while let Some(op) = rc.poll().await {
|
||||
let start = op.start.unwrap_or(Position { row: 0, col: 0});
|
||||
let end = op.end.unwrap_or(Position { row: 0, col: 0});
|
||||
|
||||
let cb_fut = Python::with_gil(|py| -> PyResult<_> {
|
||||
let args = (op.user, caller_id.clone(), op.buffer, (start.row, start.col), (end.row, end.col));
|
||||
let coro = cb.call1(py, args)?;
|
||||
pyo3_asyncio::tokio::into_future(coro.into_ref(py))
|
||||
})?;
|
||||
|
||||
cb_fut.await?;
|
||||
}
|
||||
rc.send(pos)
|
||||
.map_err(PyCodempError::from)?;
|
||||
Ok(())
|
||||
})
|
||||
} // to call after polling cursor movements.
|
||||
|
||||
fn send<'a>(&self, py: Python<'a>, path: String, start: (i32, i32), end: (i32, i32)) -> PyResult<&'a PyAny> {
|
||||
}
|
||||
|
||||
fn recv<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> {
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
let startpos = Position { row: start.0, col: start.1 };
|
||||
let endpos = Position { row: end.0, col: end.1 };
|
||||
|
||||
rc.send(path.as_str(), startpos, endpos).await;
|
||||
Ok(Python::with_gil(|py| py.None()))
|
||||
})
|
||||
|
||||
} // when we change the cursor ourselves.
|
||||
}
|
||||
|
||||
#[pyclass]
|
||||
struct PyOperationsHandle(OperationControllerHandle);
|
||||
|
||||
#[pymethods]
|
||||
impl PyOperationsHandle {
|
||||
fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> PyResult<&'a PyAny> {
|
||||
let mut rc = self.0.clone();
|
||||
let cb = coro_py.clone();
|
||||
// We want to start polling the ControlHandle and call the callback every time
|
||||
// we have something.
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
while let Some(edit) = rc.poll().await {
|
||||
let start = edit.span.start;
|
||||
let end = edit.span.end;
|
||||
let text = edit.content;
|
||||
|
||||
let cb_fut = Python::with_gil(|py| -> PyResult<_> {
|
||||
let args = (caller_id.clone(), start, end, text);
|
||||
let coro = cb.call1(py, args)?;
|
||||
pyo3_asyncio::tokio::into_future(coro.into_ref(py))
|
||||
})?;
|
||||
|
||||
cb_fut.await?;
|
||||
}
|
||||
let cur_event = rc.recv()
|
||||
.await
|
||||
.map_err(PyCodempError::from)?;
|
||||
Ok(())
|
||||
})
|
||||
} //to call after polling text changes
|
||||
}
|
||||
}
|
||||
#[pyclass]
|
||||
struct PyBufferController(Arc<CodempBufferController>);
|
||||
|
||||
#[pymethods]
|
||||
impl PyBufferController {
|
||||
// fn callback<'a>(&'a self, py: Python<'a>, coro_py: Py<PyAny>, caller_id: Py<PyString>) -> PyResult<&'a PyAny> {
|
||||
// let mut rc = self.0.clone();
|
||||
// let cb = coro_py.clone();
|
||||
// // We want to start polling the ControlHandle and call the callback every time
|
||||
// // we have something.
|
||||
|
||||
// pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
// while let Some(edit) = rc.poll().await {
|
||||
// let start = edit.span.start;
|
||||
// let end = edit.span.end;
|
||||
// let text = edit.content;
|
||||
|
||||
// let cb_fut = Python::with_gil(|py| -> PyResult<_> {
|
||||
// let args = (caller_id.clone(), start, end, text);
|
||||
// let coro = cb.call1(py, args)?;
|
||||
// pyo3_asyncio::tokio::into_future(coro.into_ref(py))
|
||||
// })?;
|
||||
|
||||
// cb_fut.await?;
|
||||
// }
|
||||
// Ok(())
|
||||
// })
|
||||
// }
|
||||
|
||||
fn content(&self, py: Python<'_>) -> PyResult<Py<PyString>> {
|
||||
let cont: Py<PyString> = PyString::new(py, self.0.content().as_str()).into();
|
||||
Ok(cont)
|
||||
}
|
||||
|
||||
fn apply<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{
|
||||
fn send<'a>(&self, py: Python<'a>, skip: usize, text: String, tail: usize) -> PyResult<&'a PyAny>{
|
||||
let rc = self.0.clone();
|
||||
|
||||
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||
|
||||
match rc.delta(skip, text.as_str(), tail) {
|
||||
Some(op) => { rc.apply(op).await; Ok(()) },
|
||||
None => Err(PyConnectionError::new_err("delta failed"))
|
||||
}
|
||||
// if let Some(op) = rc.delta(skip, text.as_str(), tail) {
|
||||
// rc.apply(op).await;
|
||||
// Python::with_gil(|py| {
|
||||
// let accepted: Py<PyBool> = PyBool::new(py, true).into();
|
||||
// Ok(accepted)
|
||||
// })
|
||||
// } else {
|
||||
// Python::with_gil(|py| {
|
||||
// let accepted: Py<PyBool> = PyBool::new(py, false).into();
|
||||
// Ok(accepted)
|
||||
// })
|
||||
// }
|
||||
Ok(())
|
||||
})
|
||||
} //after making text mofications.
|
||||
}
|
||||
}
|
||||
|
||||
// #[pyclass]
|
||||
// struct PyCursorEvent {
|
||||
// user: String,
|
||||
// buffer: Some(String),
|
||||
// start: (i32, i32),
|
||||
// end: (i32, i32)
|
||||
// }
|
||||
|
||||
// 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)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
// Python module
|
||||
#[pymodule]
|
||||
fn codemp_client(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||
m.add_function(wrap_pyfunction!(connect, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(codemp_init, m)?)?;
|
||||
m.add_class::<PyClientHandle>()?;
|
||||
m.add_class::<PyControllerHandle>()?;
|
||||
m.add_class::<PyOperationsHandle>()?;
|
||||
m.add_class::<PyCursorController>()?;
|
||||
m.add_class::<PyBufferController>()?;
|
||||
|
||||
Ok(())
|
||||
}
|
Loading…
Reference in a new issue