diff --git a/Cargo.lock b/Cargo.lock index 777cfca..cbb8786 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -703,6 +703,12 @@ version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "itertools" version = "0.13.0" @@ -1269,6 +1275,7 @@ checksum = "3d922163ba1f79c04bc49073ba7b32fd5a8d3b76a87c955921234b8e77333c51" dependencies = [ "cfg-if", "indoc", + "inventory", "libc", "memoffset", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index f867898..5c3f4d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -51,7 +51,7 @@ napi = { version = "2.16", features = ["full"], optional = true } napi-derive = { version="2.16", optional = true} # glue (python) -pyo3 = { version = "0.22", features = ["extension-module"], optional = true} +pyo3 = { version = "0.22", features = ["extension-module", "multiple-pymethods"], optional = true} # extra async-trait = { version = "0.1", optional = true } diff --git a/dist/java/src/mp/code/data/TextChange.java b/dist/java/src/mp/code/data/TextChange.java index 3c0a0f9..bcd72c2 100644 --- a/dist/java/src/mp/code/data/TextChange.java +++ b/dist/java/src/mp/code/data/TextChange.java @@ -13,7 +13,6 @@ import java.util.OptionalLong; @ToString @EqualsAndHashCode @RequiredArgsConstructor -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") public class TextChange { /** * The starting position of the change. diff --git a/dist/py/src/codemp/codemp.pyi b/dist/py/src/codemp/codemp.pyi index 9d32002..e4cfa2e 100644 --- a/dist/py/src/codemp/codemp.pyi +++ b/dist/py/src/codemp/codemp.pyi @@ -24,7 +24,7 @@ class Config: port: Optional[int] tls: Optional[bool] - def __new__(cls, *, username: str, password: str, **kwargs) -> Config: ... + def __new__(cls, username: str, password: str, **kwargs) -> Config: ... def init() -> Driver: ... def set_logger(logger_cb: Callable[[str], None], debug: bool) -> bool: ... diff --git a/src/api/user.rs b/src/api/user.rs index 2b610f9..47d133c 100644 --- a/src/api/user.rs +++ b/src/api/user.rs @@ -6,11 +6,13 @@ use uuid::Uuid; /// Represents a service user #[derive(Debug, Clone)] +#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] pub struct User { /// User unique identifier, should never change. pub id: Uuid, /// User name, can change but should be unique. + #[pyo3(get, set)] pub name: String, } diff --git a/src/ffi/mod.rs b/src/ffi/mod.rs index 0de8500..5c5346d 100644 --- a/src/ffi/mod.rs +++ b/src/ffi/mod.rs @@ -80,10 +80,9 @@ //! import codemp //! //! # connect first, api.code.mp is managed by hexed.technology -//! config = codemp.get_default_config() -//! config.username = "mail@example.net" -//! config.password = "dont-use-this-password" -//! client = codemp.connect(config).wait() +//! client = codemp.connect( +//! codemp.Config('mail@example.net', 'dont-use-this-password') +//! ).wait() //! //! # create and join a workspace //! client.create_workspace("some-workspace").wait() @@ -94,14 +93,16 @@ //! buffer = workspace.attach_buffer("/my/file.txt").wait() //! //! # write `hello!` at the beginning of this buffer -//! buffer.send( -//! 0, 0, "hello!" -//! ).wait() +//! buffer.send(codemp.TextChange( +//! start=0, end=0, +//! content="hello!" +//! )).wait() //! //! # wait for cursor movements //! while true: //! event = workspace.cursor().recv().wait() //! print(f"user {event.user} moved on buffer {event.buffer}") +//! //! ``` //! //! ## Lua diff --git a/src/ffi/python/mod.rs b/src/ffi/python/mod.rs index bbeb91b..75e7695 100644 --- a/src/ffi/python/mod.rs +++ b/src/ffi/python/mod.rs @@ -3,14 +3,14 @@ pub mod controllers; pub mod workspace; use crate::{ - api::{Config, Cursor, TextChange}, + api::{BufferUpdate, Config, Cursor, Selection, TextChange, User}, buffer::Controller as BufferController, cursor::Controller as CursorController, Client, Workspace, }; -use pyo3::prelude::*; use pyo3::{ + prelude::*, exceptions::{PyConnectionError, PyRuntimeError, PySystemError}, types::PyDict, }; @@ -153,19 +153,28 @@ fn init() -> PyResult { Ok(Driver(Some(rt_stop_tx))) } -#[pyfunction] -fn get_default_config() -> crate::api::Config { - let mut conf = crate::api::Config::new("".to_string(), "".to_string()); - conf.host = Some(conf.host().to_string()); - conf.port = Some(conf.port()); - conf.tls = Some(false); - conf +#[pymethods] +impl User { + #[getter] + fn get_id(&self) -> pyo3::PyResult { + Ok(self.id.to_string()) + } + + #[setter] + fn set_id(&mut self, value: String) -> pyo3::PyResult<()> { + self.id = value.parse().map_err(|x: ::Err| pyo3::exceptions::PyRuntimeError::new_err(x.to_string()))?; + Ok(()) + } + + fn __str__(&self) -> String { + format!("{self:?}") + } } #[pymethods] impl Config { #[new] - #[pyo3(signature = (*, username, password, **kwds))] + #[pyo3(signature = (username, password, **kwds))] pub fn pynew( username: String, password: String, @@ -176,7 +185,7 @@ impl Config { let port = kwgs.get_item("port")?.and_then(|e| e.extract().ok()); let tls = kwgs.get_item("tls")?.and_then(|e| e.extract().ok()); - Ok(Config { + Ok(Self { username, password, host, @@ -184,9 +193,109 @@ impl Config { tls, }) } else { - Ok(Config::new(username, password)) + Ok(Self::new(username, password)) } } + + fn __str__(&self) -> String { + format!("{self:?}") + } +} + +#[pymethods] +impl Cursor { + fn __str__(&self) -> String { + format!("{self:?}") + } +} + +#[pymethods] +impl Selection { + #[new] + #[pyo3(signature = (**kwds))] + pub fn py_new(kwds: Option<&Bound<'_, PyDict>>) -> PyResult { + if let Some(kwds) = kwds { + let start_row = if let Some(e) = kwds.get_item("start_row")? { + e.extract()? + } else { + 0 + }; + + let start_col = if let Some(e) = kwds.get_item("start_col")? { + e.extract()? + } else { + 0 + }; + + let end_row = if let Some(e) = kwds.get_item("end_row")? { + e.extract()? + } else { + 0 + }; + + let end_col = if let Some(e) = kwds.get_item("end_col")? { + e.extract()? + } else { + 0 + }; + + let buffer = if let Some(e) = kwds.get_item("buffer")? { + e.extract()? + } else { + String::default() + }; + + Ok(Self { start_row, start_col, end_row, end_col, buffer }) + } else { + Ok(Self::default()) + } + } + + fn __str__(&self) -> String { + format!("{self:?}") + } +} + +#[pymethods] +impl BufferUpdate { + fn __str__(&self) -> String { + format!("{self:?}") + } +} + +#[pymethods] +impl TextChange { + #[new] + #[pyo3(signature = (**kwds))] + pub fn py_new(kwds: Option<&Bound<'_, PyDict>>) -> PyResult { + if let Some(kwds) = kwds { + let start = if let Some(e) = kwds.get_item("start")? { + e.extract()? + } else { + 0 + }; + + let end = if let Some(e) = kwds.get_item("end")? { + e.extract()? + } else { + 0 + }; + + let content = if let Some(e) = kwds.get_item("content")? { + e.extract()? + } else { + String::default() + }; + + Ok(Self { start, end, content }) + } else { + Ok(Self::default()) + } + } + + fn __str__(&self) -> String { + format!("{self:?}") + } } #[pyfunction] @@ -254,27 +363,24 @@ impl From for PyErr { } } -impl IntoPy for crate::api::User { - fn into_py(self, py: Python<'_>) -> PyObject { - self.id.to_string().into_py(py) - } -} - #[pymodule] fn codemp(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(version, m)?)?; m.add_function(wrap_pyfunction!(init, m)?)?; - m.add_function(wrap_pyfunction!(get_default_config, m)?)?; m.add_function(wrap_pyfunction!(connect, m)?)?; m.add_function(wrap_pyfunction!(set_logger, m)?)?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; + m.add_class::()?; m.add_class::()?; + m.add_class::()?; + m.add_class::()?; m.add_class::()?; m.add_class::()?;