mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 23:34:49 +01:00
feat(python): new leaner glue, up to date
This commit is contained in:
parent
a85122050b
commit
7e8a46f9b8
7 changed files with 476 additions and 31 deletions
|
@ -32,8 +32,9 @@ pub struct Op(pub(crate) woot::crdt::Op);
|
||||||
///
|
///
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default)]
|
||||||
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
|
||||||
#[cfg_attr(feature = "js", napi_derive::napi(object))]
|
#[cfg_attr(feature = "js", napi_derive::napi(object))]
|
||||||
|
#[cfg_attr(feature = "python", pyo3::pyclass)]
|
||||||
|
#[cfg_attr(feature = "python", pyo3(get_all))]
|
||||||
pub struct TextChange {
|
pub struct TextChange {
|
||||||
/// range start of text change, as char indexes in buffer previous state
|
/// range start of text change, as char indexes in buffer previous state
|
||||||
pub start: u32,
|
pub start: u32,
|
||||||
|
@ -76,11 +77,11 @@ impl TextChange {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn span(&self) -> std::ops::Range<usize> {
|
pub fn span(&self) -> std::ops::Range<usize> {
|
||||||
self.start as usize .. self.end as usize
|
self.start as usize..self.end as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
/// consume the [TextChange], transforming it into a Vec of [Op]
|
/// consume the [TextChange], transforming it into a Vec of [Op]
|
||||||
pub fn transform(self, woot: &Woot) -> WootResult<Vec<Op>> {
|
pub fn transform(&self, woot: &Woot) -> WootResult<Vec<Op>> {
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
if self.is_empty() {
|
if self.is_empty() {
|
||||||
return Ok(out);
|
return Ok(out);
|
||||||
|
@ -180,7 +181,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_works_for_insertions() {
|
fn textchange_apply_works_for_insertions() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
start: 5, end: 5,
|
start: 5,
|
||||||
|
end: 5,
|
||||||
content: " cruel".to_string(),
|
content: " cruel".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("hello world!");
|
let result = change.apply("hello world!");
|
||||||
|
@ -190,7 +192,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_works_for_deletions() {
|
fn textchange_apply_works_for_deletions() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
start: 5, end: 11,
|
start: 5,
|
||||||
|
end: 11,
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("hello cruel world!");
|
let result = change.apply("hello cruel world!");
|
||||||
|
@ -200,7 +203,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_works_for_replacements() {
|
fn textchange_apply_works_for_replacements() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
start: 5, end: 11,
|
start: 5,
|
||||||
|
end: 11,
|
||||||
content: " not very pleasant".to_string(),
|
content: " not very pleasant".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("hello cruel world!");
|
let result = change.apply("hello cruel world!");
|
||||||
|
@ -210,7 +214,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn textchange_apply_never_panics() {
|
fn textchange_apply_never_panics() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
start: 100, end: 110,
|
start: 100,
|
||||||
|
end: 110,
|
||||||
content: "a very long string \n which totally matters".to_string(),
|
content: "a very long string \n which totally matters".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("a short text");
|
let result = change.apply("a short text");
|
||||||
|
@ -229,7 +234,8 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn empty_textchange_doesnt_alter_buffer() {
|
fn empty_textchange_doesnt_alter_buffer() {
|
||||||
let change = super::TextChange {
|
let change = super::TextChange {
|
||||||
start: 42, end: 42,
|
start: 42,
|
||||||
|
end: 42,
|
||||||
content: "".to_string(),
|
content: "".to_string(),
|
||||||
};
|
};
|
||||||
let result = change.apply("some important text");
|
let result = change.apply("some important text");
|
||||||
|
|
|
@ -8,9 +8,12 @@ use dashmap::DashMap;
|
||||||
use tonic::transport::{Channel, Endpoint};
|
use tonic::transport::{Channel, Endpoint};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::workspace::Workspace;
|
||||||
use codemp_proto::auth::auth_client::AuthClient;
|
use codemp_proto::auth::auth_client::AuthClient;
|
||||||
use codemp_proto::auth::{Token, WorkspaceJoinRequest};
|
use codemp_proto::auth::{Token, WorkspaceJoinRequest};
|
||||||
use crate::workspace::Workspace;
|
|
||||||
|
#[cfg(feature = "python")]
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AuthWrap {
|
pub struct AuthWrap {
|
||||||
|
@ -21,9 +24,7 @@ pub struct AuthWrap {
|
||||||
|
|
||||||
impl AuthWrap {
|
impl AuthWrap {
|
||||||
async fn try_new(username: &str, password: &str, host: &str) -> crate::Result<Self> {
|
async fn try_new(username: &str, password: &str, host: &str) -> crate::Result<Self> {
|
||||||
let channel = Endpoint::from_shared(host.to_string())?
|
let channel = Endpoint::from_shared(host.to_string())?.connect().await?;
|
||||||
.connect()
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(AuthWrap {
|
Ok(AuthWrap {
|
||||||
username: username.to_string(),
|
username: username.to_string(),
|
||||||
|
@ -33,16 +34,16 @@ impl AuthWrap {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn login_workspace(&self, ws: &str) -> crate::Result<Token> {
|
async fn login_workspace(&self, ws: &str) -> crate::Result<Token> {
|
||||||
Ok(
|
Ok(self
|
||||||
self.service.clone()
|
.service
|
||||||
|
.clone()
|
||||||
.login(WorkspaceJoinRequest {
|
.login(WorkspaceJoinRequest {
|
||||||
username: self.username.clone(),
|
username: self.username.clone(),
|
||||||
password: self.password.clone(),
|
password: self.password.clone(),
|
||||||
workspace_id: Some(ws.to_string())
|
workspace_id: Some(ws.to_string()),
|
||||||
})
|
})
|
||||||
.await?
|
.await?
|
||||||
.into_inner()
|
.into_inner())
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,6 +54,7 @@ impl AuthWrap {
|
||||||
/// can be used to interact with server
|
/// can be used to interact with server
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[cfg_attr(feature = "js", napi_derive::napi)]
|
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||||
|
#[cfg_attr(feature = "python", pyclass)]
|
||||||
pub struct Client(Arc<ClientInner>);
|
pub struct Client(Arc<ClientInner>);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -68,7 +70,7 @@ impl Client {
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
host: impl AsRef<str>,
|
host: impl AsRef<str>,
|
||||||
username: impl AsRef<str>,
|
username: impl AsRef<str>,
|
||||||
password: impl AsRef<str>
|
password: impl AsRef<str>,
|
||||||
) -> crate::Result<Self> {
|
) -> crate::Result<Self> {
|
||||||
let host = if host.as_ref().starts_with("http") {
|
let host = if host.as_ref().starts_with("http") {
|
||||||
host.as_ref().to_string()
|
host.as_ref().to_string()
|
||||||
|
@ -95,10 +97,13 @@ impl Client {
|
||||||
workspace.as_ref().to_string(),
|
workspace.as_ref().to_string(),
|
||||||
self.0.user_id,
|
self.0.user_id,
|
||||||
&self.0.host,
|
&self.0.host,
|
||||||
token.clone()
|
token.clone(),
|
||||||
).await?;
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
self.0.workspaces.insert(workspace.as_ref().to_string(), ws.clone());
|
self.0
|
||||||
|
.workspaces
|
||||||
|
.insert(workspace.as_ref().to_string(), ws.clone());
|
||||||
|
|
||||||
Ok(ws)
|
Ok(ws)
|
||||||
}
|
}
|
||||||
|
@ -115,7 +120,11 @@ impl Client {
|
||||||
|
|
||||||
/// get name of all active [Workspace]s
|
/// get name of all active [Workspace]s
|
||||||
pub fn active_workspaces(&self) -> Vec<String> {
|
pub fn active_workspaces(&self) -> Vec<String> {
|
||||||
self.0.workspaces.iter().map(|x| x.key().to_string()).collect()
|
self.0
|
||||||
|
.workspaces
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.key().to_string())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// accessor for user id
|
/// accessor for user id
|
||||||
|
|
53
src/ffi/python/client.rs
Normal file
53
src/ffi/python/client.rs
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
use crate::workspace::Workspace;
|
||||||
|
use crate::Client;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::{PyBool, PyList, PyString};
|
||||||
|
|
||||||
|
// #[pyfunction]
|
||||||
|
// pub fn codemp_init<'a>(py: Python<'a>) -> PyResult<Py<Client>> {
|
||||||
|
// Ok(Py::new(py, Client::default())?)
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Client {
|
||||||
|
#[new]
|
||||||
|
fn pyconnect(host: String, username: String, password: String) -> PyResult<Self> {
|
||||||
|
let cli =
|
||||||
|
pyo3_asyncio::tokio::get_runtime().block_on(Client::new(host, username, password));
|
||||||
|
Ok(cli?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "join_workspace")]
|
||||||
|
fn pyjoin_workspace<'a>(&'a self, py: Python<'a>, workspace: String) -> PyResult<&PyAny> {
|
||||||
|
let rc = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
let workspace: Workspace = rc.join_workspace(workspace.as_str()).await?;
|
||||||
|
Python::with_gil(|py| Py::new(py, workspace))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "leave_workspace")]
|
||||||
|
fn pyleave_workspace<'p>(&'p self, py: Python<'p>, id: String) -> &PyBool {
|
||||||
|
PyBool::new(py, self.leave_workspace(id.as_str()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// join a workspace
|
||||||
|
#[pyo3(name = "get_workspace")]
|
||||||
|
fn pyget_workspace(&self, py: Python<'_>, id: String) -> PyResult<Option<Py<Workspace>>> {
|
||||||
|
match self.get_workspace(id.as_str()) {
|
||||||
|
Some(ws) => Ok(Some(Py::new(py, ws)?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "active_workspaces")]
|
||||||
|
fn pyactive_workspaces<'p>(&'p self, py: Python<'p>) -> &PyList {
|
||||||
|
PyList::new(py, self.active_workspaces())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "user_id")]
|
||||||
|
fn pyuser_id<'p>(&'p self, py: Python<'p>) -> &PyString {
|
||||||
|
PyString::new(py, self.user_id().to_string().as_str())
|
||||||
|
}
|
||||||
|
}
|
161
src/ffi/python/controllers.rs
Normal file
161
src/ffi/python/controllers.rs
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
use crate::api::Controller;
|
||||||
|
use crate::api::Cursor;
|
||||||
|
use crate::api::TextChange;
|
||||||
|
use crate::buffer::Controller as BufferController;
|
||||||
|
use crate::cursor::Controller as CursorController;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyType;
|
||||||
|
|
||||||
|
// use super::CodempController;
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl CursorController {
|
||||||
|
#[pyo3(name = "send")]
|
||||||
|
pub fn pysend(&self, path: String, start: (i32, i32), end: (i32, i32)) -> PyResult<()> {
|
||||||
|
let pos = Cursor {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
buffer: path,
|
||||||
|
user: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(self.send(pos)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "try_recv")]
|
||||||
|
pub fn pytry_recv(&self, py: Python<'_>) -> PyResult<Option<Py<Cursor>>> {
|
||||||
|
match self.try_recv()? {
|
||||||
|
Some(cur_event) => Ok(Some(Py::new(py, cur_event)?)),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "recv")]
|
||||||
|
pub fn pyrecv<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> {
|
||||||
|
let rc = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
let cur_event: Cursor = rc.recv().await?;
|
||||||
|
Python::with_gil(|py| Py::new(py, cur_event))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "poll")]
|
||||||
|
pub fn pypoll<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> {
|
||||||
|
let rc = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.poll().await?) })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "stop")]
|
||||||
|
pub fn pystop(&self) -> bool {
|
||||||
|
self.stop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl Cursor {
|
||||||
|
#[getter(start)]
|
||||||
|
fn pystart(&self) -> (i32, i32) {
|
||||||
|
self.start
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter(end)]
|
||||||
|
fn pyend(&self) -> (i32, i32) {
|
||||||
|
self.end
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter(buffer)]
|
||||||
|
fn pybuffer(&self) -> String {
|
||||||
|
self.buffer.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[getter(user)]
|
||||||
|
fn pyuser(&self) -> String {
|
||||||
|
match self.user {
|
||||||
|
Some(user) => user.to_string(),
|
||||||
|
None => "".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl BufferController {
|
||||||
|
#[pyo3(name = "content")]
|
||||||
|
fn pycontent(&self) -> String {
|
||||||
|
self.content().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "send")]
|
||||||
|
fn pysend(&self, start: u32, end: u32, txt: String) -> PyResult<()> {
|
||||||
|
let op = TextChange {
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
content: txt,
|
||||||
|
};
|
||||||
|
Ok(self.send(op)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "try_recv")]
|
||||||
|
fn pytry_recv(&self, py: Python<'_>) -> PyResult<PyObject> {
|
||||||
|
match self.try_recv()? {
|
||||||
|
Some(txt_change) => {
|
||||||
|
let evt = txt_change;
|
||||||
|
Ok(evt.into_py(py))
|
||||||
|
}
|
||||||
|
None => Ok(py.None()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "recv")]
|
||||||
|
fn pyrecv<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> {
|
||||||
|
let rc = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
let txt_change: TextChange = rc.recv().await?;
|
||||||
|
Python::with_gil(|py| Py::new(py, txt_change))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "poll")]
|
||||||
|
fn pypoll<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> {
|
||||||
|
let rc = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.poll().await?) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl TextChange {
|
||||||
|
#[pyo3(name = "is_deletion")]
|
||||||
|
fn pyis_deletion(&self) -> bool {
|
||||||
|
self.is_deletion()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "is_addition")]
|
||||||
|
fn pyis_addition(&self) -> bool {
|
||||||
|
self.is_addition()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "is_empty")]
|
||||||
|
fn pyis_empty(&self) -> bool {
|
||||||
|
self.is_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "apply")]
|
||||||
|
fn pyapply(&self, txt: &str) -> String {
|
||||||
|
self.apply(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[classmethod]
|
||||||
|
#[pyo3(name = "from_diff")]
|
||||||
|
fn pyfrom_diff(_cls: &PyType, before: &str, after: &str) -> TextChange {
|
||||||
|
TextChange::from_diff(before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[classmethod]
|
||||||
|
#[pyo3(name = "index_to_rowcol")]
|
||||||
|
fn pyindex_to_rowcol(_cls: &PyType, txt: &str, index: usize) -> (i32, i32) {
|
||||||
|
TextChange::index_to_rowcol(txt, index).into()
|
||||||
|
}
|
||||||
|
}
|
101
src/ffi/python/mod.rs
Normal file
101
src/ffi/python/mod.rs
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
pub mod client;
|
||||||
|
pub mod controllers;
|
||||||
|
pub mod workspace;
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
api::Cursor, api::TextChange, buffer::Controller as BufferController,
|
||||||
|
cursor::Controller as CursorController, Client, Workspace,
|
||||||
|
};
|
||||||
|
use pyo3::exceptions::{PyConnectionError, PyRuntimeError, PySystemError};
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use tokio::sync::{mpsc, Mutex};
|
||||||
|
|
||||||
|
impl From<crate::Error> for PyErr {
|
||||||
|
fn from(value: crate::Error) -> Self {
|
||||||
|
match value {
|
||||||
|
crate::Error::Transport { status, message } => {
|
||||||
|
PyConnectionError::new_err(format!("Transport error: ({}) {}", status, message))
|
||||||
|
}
|
||||||
|
crate::Error::Channel { send } => {
|
||||||
|
PyConnectionError::new_err(format!("Channel error (send:{})", send))
|
||||||
|
}
|
||||||
|
crate::Error::InvalidState { msg } => {
|
||||||
|
PyRuntimeError::new_err(format!("Invalid state: {}", msg))
|
||||||
|
}
|
||||||
|
crate::Error::Deadlocked => PyRuntimeError::new_err("Deadlock, retry."),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LoggerProducer(mpsc::Sender<String>);
|
||||||
|
|
||||||
|
impl std::io::Write for LoggerProducer {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let _ = self.0.try_send(String::from_utf8_lossy(buf).to_string()); // ignore: logger disconnected or with full buffer
|
||||||
|
Ok(buf.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> std::io::Result<()> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyclass]
|
||||||
|
struct PyLogger(Arc<Mutex<mpsc::Receiver<String>>>);
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyLogger {
|
||||||
|
#[new]
|
||||||
|
fn init_logger(debug: bool) -> PyResult<Self> {
|
||||||
|
let (tx, rx) = mpsc::channel(256);
|
||||||
|
let level = if debug {
|
||||||
|
tracing::Level::DEBUG
|
||||||
|
} else {
|
||||||
|
tracing::Level::INFO
|
||||||
|
};
|
||||||
|
|
||||||
|
let format = tracing_subscriber::fmt::format()
|
||||||
|
.without_time()
|
||||||
|
.with_level(true)
|
||||||
|
.with_target(true)
|
||||||
|
.with_thread_ids(false)
|
||||||
|
.with_thread_names(false)
|
||||||
|
.with_file(false)
|
||||||
|
.with_line_number(false)
|
||||||
|
.with_source_location(false)
|
||||||
|
.compact();
|
||||||
|
|
||||||
|
match tracing_subscriber::fmt()
|
||||||
|
.with_ansi(false)
|
||||||
|
.event_format(format)
|
||||||
|
.with_max_level(level)
|
||||||
|
.with_writer(std::sync::Mutex::new(LoggerProducer(tx)))
|
||||||
|
.try_init()
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(PyLogger(Arc::new(Mutex::new(rx)))),
|
||||||
|
Err(_) => Err(PySystemError::new_err("A logger already exists")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn listen<'p>(&'p self, py: Python<'p>) -> PyResult<&'p PyAny> {
|
||||||
|
let rc = self.0.clone();
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.lock().await.recv().await) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymodule]
|
||||||
|
fn codemp(_py: Python, m: &PyModule) -> PyResult<()> {
|
||||||
|
m.add_class::<Client>()?;
|
||||||
|
m.add_class::<PyLogger>()?;
|
||||||
|
m.add_class::<Workspace>()?;
|
||||||
|
m.add_class::<CursorController>()?;
|
||||||
|
m.add_class::<BufferController>()?;
|
||||||
|
|
||||||
|
m.add_class::<Cursor>()?;
|
||||||
|
m.add_class::<TextChange>()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
116
src/ffi/python/workspace.rs
Normal file
116
src/ffi/python/workspace.rs
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
use crate::buffer::Controller as CodempBufferController;
|
||||||
|
use crate::cursor::Controller as CodempCursorController;
|
||||||
|
use crate::workspace::Workspace as CodempWorkspace;
|
||||||
|
use pyo3::prelude::*;
|
||||||
|
use pyo3::types::PyString;
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl CodempWorkspace {
|
||||||
|
// join a workspace
|
||||||
|
#[pyo3(name = "create")]
|
||||||
|
fn pycreate<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&'p PyAny> {
|
||||||
|
let ws = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
ws.create(path.as_str()).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[pyo3(name = "attach")]
|
||||||
|
fn pyattach<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> {
|
||||||
|
let ws = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
let buffctl: CodempBufferController = ws.attach(path.as_str()).await?;
|
||||||
|
Python::with_gil(|py| Py::new(py, buffctl))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "detach")]
|
||||||
|
fn pydetach(&self, path: String) -> bool {
|
||||||
|
match self.detach(path.as_str()) {
|
||||||
|
crate::workspace::DetachResult::NotAttached => false,
|
||||||
|
crate::workspace::DetachResult::Detaching => true,
|
||||||
|
crate::workspace::DetachResult::AlreadyDetached => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "fetch_buffers")]
|
||||||
|
fn pyfetch_buffers<'p>(&'p self, py: Python<'p>) -> PyResult<&PyAny> {
|
||||||
|
let ws = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
ws.fetch_buffers().await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "fetch_users")]
|
||||||
|
fn pyfetch_users<'p>(&'p self, py: Python<'p>) -> PyResult<&PyAny> {
|
||||||
|
let ws = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
ws.fetch_users().await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "list_buffer_users")]
|
||||||
|
fn pylist_buffer_users<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> {
|
||||||
|
let ws = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
let usrlist: Vec<String> = ws
|
||||||
|
.list_buffer_users(path.as_str())
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| e.id)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Ok(usrlist)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "delete")]
|
||||||
|
fn pydelete<'p>(&'p self, py: Python<'p>, path: String) -> PyResult<&PyAny> {
|
||||||
|
let ws = self.clone();
|
||||||
|
|
||||||
|
pyo3_asyncio::tokio::future_into_py(py, async move {
|
||||||
|
ws.delete(path.as_str()).await?;
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "id")]
|
||||||
|
fn pyid(&self, py: Python<'_>) -> Py<PyString> {
|
||||||
|
PyString::new(py, self.id().as_str()).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "cursor")]
|
||||||
|
fn pycursor(&self, py: Python<'_>) -> PyResult<Py<CodempCursorController>> {
|
||||||
|
Py::new(py, self.cursor())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "buffer_by_name")]
|
||||||
|
fn pybuffer_by_name(
|
||||||
|
&self,
|
||||||
|
py: Python<'_>,
|
||||||
|
path: String,
|
||||||
|
) -> PyResult<Option<Py<CodempBufferController>>> {
|
||||||
|
let Some(bufctl) = self.buffer_by_name(path.as_str()) else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Some(Py::new(py, bufctl)?))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "buffer_list")]
|
||||||
|
fn pybuffer_list(&self) -> Vec<String> {
|
||||||
|
self.buffer_list()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pyo3(name = "filetree")]
|
||||||
|
fn pyfiletree(&self) -> Vec<String> {
|
||||||
|
self.filetree()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,3 @@
|
||||||
use pyo3::types::{PyList, PyTuple};
|
|
||||||
use std::{format, sync::Arc};
|
use std::{format, sync::Arc};
|
||||||
use tokio::sync::{mpsc, Mutex, RwLock};
|
use tokio::sync::{mpsc, Mutex, RwLock};
|
||||||
use tracing;
|
use tracing;
|
||||||
|
@ -7,9 +6,9 @@ use tracing_subscriber;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
|
|
||||||
use pyo3::{
|
use pyo3::{
|
||||||
exceptions::{PyBaseException, PyConnectionError, PyRuntimeError},
|
exceptions::{PyConnectionError, PyRuntimeError},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
types::{PyString, PyType},
|
types::{PyList, PyString, PyTuple, PyType},
|
||||||
};
|
};
|
||||||
|
|
||||||
// ERRORS And LOGGING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
// ERRORS And LOGGING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
@ -25,7 +24,7 @@ impl From<CodempError> for PyErr {
|
||||||
CodempError::InvalidState { msg } => {
|
CodempError::InvalidState { msg } => {
|
||||||
PyRuntimeError::new_err(format!("Invalid state: {}", msg))
|
PyRuntimeError::new_err(format!("Invalid state: {}", msg))
|
||||||
}
|
}
|
||||||
CodempError::Deadlocked => PyRuntimeError::new_err(format!("Deadlock, retry."))
|
CodempError::Deadlocked => PyRuntimeError::new_err(format!("Deadlock, retry.")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in a new issue