From 703e3e0b688f165fd82077eb914707e6ae2e81ab Mon Sep 17 00:00:00 2001 From: cschen Date: Wed, 7 Aug 2024 00:17:31 +0200 Subject: [PATCH] feat: removed all wheel building from the repo, moved it to the codemp repo directly feat: updated the plugin to latest glue. feat: updated the bundled wheel Former-commit-id: 59efd17b2225c700a6144572a1d44c873d1da268 --- Cargo.toml | 22 - ...-cp38-macosx_11_0_arm64.whl.REMOVED.git-id | 1 - ...-cp38-macosx_11_0_arm64.whl.REMOVED.git-id | 1 + build.rs | 5 - plugin.py | 16 +- src/TaskManager.py | 6 +- src/client.py | 44 +- src/lib.rs | 555 ------------------ src/wrappers.py | 101 ---- 9 files changed, 39 insertions(+), 712 deletions(-) delete mode 100644 Cargo.toml delete mode 100644 bindings/Codemp_Sublime-0.6.1-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id create mode 100644 bindings/codemp-0.6.2-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id delete mode 100644 build.rs delete mode 100644 src/lib.rs delete mode 100644 src/wrappers.py diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 4f00020..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "Codemp-Sublime" -version = "0.6.1" -edition = "2021" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "codemp" -crate-type = ["cdylib"] - -[dependencies] -codemp = { git = "ssh://git@github.com/hexedtech/codemp.git", branch="glue"} -codemp-proto = { git = "ssh://git@github.com/hexedtech/codemp-proto.git", tag = "v0.6.1" } -pyo3 = { version = "0.20", features = ["extension-module"] } -pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"] } -serde = { version = "1.0.196", features = ["derive"] } -tokio = "1.29.1" -tracing = "0.1.40" -tracing-subscriber = "0.3.18" - -[build-dependencies] -pyo3-build-config = "0.20" diff --git a/bindings/Codemp_Sublime-0.6.1-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id b/bindings/Codemp_Sublime-0.6.1-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id deleted file mode 100644 index 2116b0f..0000000 --- a/bindings/Codemp_Sublime-0.6.1-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id +++ /dev/null @@ -1 +0,0 @@ -38750f240dab1caf464d1f46f00f09a1042fa194 \ No newline at end of file diff --git a/bindings/codemp-0.6.2-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id b/bindings/codemp-0.6.2-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id new file mode 100644 index 0000000..95ad782 --- /dev/null +++ b/bindings/codemp-0.6.2-cp38-cp38-macosx_11_0_arm64.whl.REMOVED.git-id @@ -0,0 +1 @@ +8d0c5dec04835cf686c98fd9666f20b981450d6e \ No newline at end of file diff --git a/build.rs b/build.rs deleted file mode 100644 index 642e934..0000000 --- a/build.rs +++ /dev/null @@ -1,5 +0,0 @@ -use pyo3_build_config; - -fn main() { - pyo3_build_config::add_extension_module_link_args(); -} \ No newline at end of file diff --git a/plugin.py b/plugin.py index 9af73e4..13c780e 100644 --- a/plugin.py +++ b/plugin.py @@ -1,3 +1,5 @@ +# pyright: reportIncompatibleMethodOverride=false + import sublime import sublime_plugin @@ -12,7 +14,6 @@ from .src.utils import status_log from .src.utils import safe_listener_detach from .src.utils import safe_listener_attach from .src import globals as g -from .bindings.codemp import init_logger TEXT_LISTENER = None @@ -27,7 +28,7 @@ def plugin_loaded(): # pass in the exit_handler coroutine that will be called upon relasing the event loop. tm.acquire(disconnect_client) - logger = CodempLogger(init_logger(False)) + logger = CodempLogger() tm.dispatch(logger.log(), "codemp-logger") TEXT_LISTENER = CodempClientTextChangeListener() @@ -105,18 +106,18 @@ class CodempClientViewEventListener(sublime_plugin.ViewEventListener): g.ACTIVE_CODEMP_VIEW = self.view.id() # print("view {} activated".format(self.view.id())) global TEXT_LISTENER - safe_listener_attach(TEXT_LISTENER, self.view.buffer()) + safe_listener_attach(TEXT_LISTENER, self.view.buffer()) # pyright: ignore def on_deactivated(self): g.ACTIVE_CODEMP_VIEW = None # print("view {} deactivated".format(self.view.id())) global TEXT_LISTENER - safe_listener_detach(TEXT_LISTENER) + safe_listener_detach(TEXT_LISTENER) # pyright: ignore def on_pre_close(self): global TEXT_LISTENER if self.view.id() == g.ACTIVE_CODEMP_VIEW: - safe_listener_detach(TEXT_LISTENER) + safe_listener_detach(TEXT_LISTENER) # pyright: ignore ws = client.get_workspace(self.view) if ws is None: @@ -197,7 +198,7 @@ class CodempJoinCommand(sublime_plugin.WindowCommand): # Join Workspace Command ############################################################################# class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand): - def run(self, workspace_id): + def run(self, workspace_id): # pyright: ignore tm.dispatch(client.join_workspace(workspace_id)) def input_description(self): @@ -211,7 +212,7 @@ class CodempJoinWorkspaceCommand(sublime_plugin.WindowCommand): # Join Buffer Command ############################################################################# class CodempJoinBufferCommand(sublime_plugin.WindowCommand): - def run(self, buffer_id): + def run(self, buffer_id): # pyright: ignore if client.active_workspace is None: sublime.error_message( "You haven't joined any worksapce yet. \ @@ -265,6 +266,7 @@ class ListBufferId(sublime_plugin.ListInputHandler): return "buffer_id" def list_items(self): + assert client.active_workspace is not None return client.active_workspace.handle.filetree() def next_input(self, args): diff --git a/src/TaskManager.py b/src/TaskManager.py index a33ebd5..a5289bf 100644 --- a/src/TaskManager.py +++ b/src/TaskManager.py @@ -40,15 +40,15 @@ class TaskManager: return _store - def get_task(self, name) -> Optional: + def get_task(self, name) -> Optional[asyncio.Task]: return next((t for t in self.tasks if t.get_name() == name), None) - def get_task_idx(self, name) -> Optional: + def get_task_idx(self, name) -> Optional[int]: return next( (i for (i, t) in enumerate(self.tasks) if t.get_name() == name), None ) - def pop_task(self, name) -> Optional: + def pop_task(self, name) -> Optional[asyncio.Task]: idx = self.get_task_idx(name) if id is not None: return self.tasks.pop(idx) diff --git a/src/client.py b/src/client.py index 18fc39d..5b131b3 100644 --- a/src/client.py +++ b/src/client.py @@ -8,18 +8,21 @@ import tempfile import os import shutil +from codemp import ( + init_logger, + codemp_init, + CodempBufferController, + CodempWorkspace, + Client, +) from ..src import globals as g from ..src.TaskManager import tm -from ..src.wrappers import BufferController, Workspace, Client from ..src.utils import status_log, rowcol_to_region class CodempLogger: - def __init__(self, handle): - self.handle = handle - - async def message(self): - return await self.handle.message() + def __init__(self, debug: bool = False): + self.handle = init_logger(debug) async def log(self): status_log("spinning up the logger...") @@ -43,7 +46,7 @@ class VirtualBuffer: self, workspace: VirtualWorkspace, remote_id: str, - buffctl: BufferController, + buffctl: CodempBufferController, ): self.view = sublime.active_window().new_file() self.codemp_id = remote_id @@ -106,7 +109,7 @@ class VirtualBuffer: "end": text_change.end_excl, "content": text_change.content, "change_id": self.view.change_id(), - }, + }, # pyright: ignore ) except asyncio.CancelledError: @@ -126,7 +129,9 @@ class VirtualBuffer: region.begin(), region.end(), change.str ) ) - self.buffctl.send(region.begin(), region.end()+len(change.str)-1, change.str) + self.buffctl.send( + region.begin(), region.end() + len(change.str) - 1, change.str + ) def send_cursor(self, vws: VirtualWorkspace): # TODO: only the last placed cursor/selection. @@ -141,7 +146,7 @@ class VirtualBuffer: # A virtual workspace is a bridge class that aims to translate # events that happen to the codemp workspaces into sublime actions class VirtualWorkspace: - def __init__(self, workspace_id: str, handle: Workspace): + def __init__(self, workspace_id: str, handle: CodempWorkspace): self.id = workspace_id self.sublime_window = sublime.active_window() self.handle = handle @@ -149,8 +154,8 @@ class VirtualWorkspace: self.isactive = False # mapping remote ids -> local ids - self.id_map: dict[str, str] = {} - self.active_buffers: dict[str, VirtualBuffer] = {} # local_id -> VBuff + self.id_map: dict[str, int] = {} + self.active_buffers: dict[int, VirtualBuffer] = {} # local_id -> VBuff # initialise the virtual filesystem tmpdir = tempfile.mkdtemp(prefix="codemp_") @@ -158,7 +163,7 @@ class VirtualWorkspace: self.rootdir = tmpdir # and add a new "project folder" - proj_data = self.sublime_window.project_data() + proj_data: dict = self.sublime_window.project_data() # pyright: ignore if proj_data is None: proj_data = {"folders": []} @@ -167,7 +172,7 @@ class VirtualWorkspace: ) self.sublime_window.set_project_data(proj_data) - s: dict = self.sublime_window.settings() + s: dict = self.sublime_window.settings() # pyright: ignore if s.get(g.CODEMP_WINDOW_TAG, False): s[g.CODEMP_WINDOW_WORKSPACES].append(self.id) else: @@ -184,7 +189,7 @@ class VirtualWorkspace: self.active_buffers = {} # drop all buffers, let them be garbace collected (hopefully) - d = self.sublime_window.project_data() + d: dict = self.sublime_window.project_data() # pyright: ignore newf = list( filter( lambda f: f.get("name", "") != f"{g.WORKSPACE_FOLDER_PREFIX}{self.id}", @@ -216,7 +221,7 @@ class VirtualWorkspace: self.id_map[remote_id] = vbuff.view.buffer_id() self.active_buffers[vbuff.view.buffer_id()] = vbuff - def get_by_local(self, local_id: str) -> Optional[VirtualBuffer]: + def get_by_local(self, local_id: int) -> Optional[VirtualBuffer]: return self.active_buffers.get(local_id) def get_by_remote(self, remote_id: str) -> Optional[VirtualBuffer]: @@ -290,7 +295,7 @@ class VirtualWorkspace: class VirtualClient: def __init__(self): - self.handle: Client = Client() + self.handle: Client = codemp_init() self.workspaces: dict[str, VirtualWorkspace] = {} self.active_workspace: Optional[VirtualWorkspace] = None @@ -341,7 +346,10 @@ class VirtualClient: status_log(f"Connected to '{server_host}' with user id: {id}") async def join_workspace( - self, workspace_id: str, user=f"user-{random.random()}", password="***REMOVED***" + self, + workspace_id: str, + user=f"user-{random.random()}", + password="***REMOVED***", ) -> Optional[VirtualWorkspace]: try: status_log(f"Logging into workspace: '{workspace_id}' with user: {user}") diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2349a43..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,555 +0,0 @@ -use pyo3::types::PyList; -use std::{format, sync::Arc}; -use tokio::sync::{mpsc, Mutex, RwLock}; -use tracing; -use tracing_subscriber; - -use ::codemp::errors::Error as CodempError; -use ::codemp::prelude::*; -use codemp_proto::{ - common::Identity, - cursor::{CursorEvent, CursorPosition}, - files::BufferNode, -}; - -use pyo3::{ - exceptions::{PyBaseException, PyConnectionError, PyRuntimeError}, - prelude::*, - types::{PyString, PyType}, -}; - -// ERRORS And LOGGING ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -struct PyCodempError(CodempError); -impl From for PyCodempError { - fn from(err: CodempError) -> Self { - PyCodempError(err) - } -} - -impl From for PyErr { - fn from(err: PyCodempError) -> PyErr { - match err.0 { - CodempError::Transport { status, message } => { - PyConnectionError::new_err(format!("Transport error: ({}) {}", status, message)) - } - CodempError::Channel { send } => { - PyConnectionError::new_err(format!("Channel error (send:{})", send)) - } - CodempError::InvalidState { msg } => { - PyRuntimeError::new_err(format!("Invalid state: {}", msg)) - } - CodempError::Deadlocked => PyRuntimeError::new_err(format!("Deadlock, retry.")), - CodempError::Filler { message } => { - PyBaseException::new_err(format!("Generic error: {}", message)) - } - } - } -} - -#[derive(Debug, Clone)] -struct LoggerProducer(mpsc::Sender); - -impl std::io::Write for LoggerProducer { - fn write(&mut self, buf: &[u8]) -> std::io::Result { - // TODO this is a LOSSY logger!! - 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>>); - -#[pymethods] -impl PyLogger { - fn message<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { - let rc = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { Ok(rc.lock().await.recv().await) }) - } -} -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -// Workflow: -// We first spin up an empty handler, that can connect to a server, and create a client. -// We then use the client, to login into a workspace, and join it, obtaining a workspace object -// We will then use that workspace object to interact with the buffers in that workspace. -// In steps: -// 1. Get Object that can initiate a connection -// 2. Connect to a server -// 3. Login to a workspace -// 4. Join a workspace/get an already joined workspace -// 5. Create a new buffer/attach to an existing one - -#[pyfunction] -fn codemp_init<'a>(py: Python<'a>) -> PyResult> { - Ok(Py::new(py, PyClient::default())?) -} - -#[pyfunction] -fn init_logger(py: Python<'_>, debug: Option) -> PyResult> { - let (tx, rx) = mpsc::channel(256); - let level = if debug.unwrap_or(false) { - 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(); - - let _ = tracing_subscriber::fmt() - .with_ansi(false) - .event_format(format) - .with_max_level(level) - .with_writer(std::sync::Mutex::new(LoggerProducer(tx))) - .try_init(); - - Ok(Py::new(py, PyLogger(Arc::new(Mutex::new(rx))))?) -} - -#[pyclass] -struct PyClient(Arc>>); - -impl Default for PyClient { - fn default() -> Self { - PyClient(Arc::new(RwLock::new(None))) - } -} - -impl From for PyClient { - fn from(value: CodempClient) -> Self { - PyClient(RwLock::new(Some(value)).into()) - } -} - -#[pymethods] -impl PyClient { - fn connect<'a>(&'a self, py: Python<'a>, dest: String) -> PyResult<&'a PyAny> { - let cli = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let client: CodempClient = CodempClient::new(dest.as_str()) - .await - .map_err(PyCodempError::from)?; - - let _ = cli.write().await.insert(client); - - Ok(()) - }) - } - - fn login<'a>( - &'a self, - py: Python<'a>, - user: String, - password: String, - workspace_id: Option, - ) -> PyResult<&'a PyAny> { - let rc = self.0.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - let cli = rc.read().await; - if cli.is_none() { - return Err(PyConnectionError::new_err("Connect to a server first.")); - }; - - cli.as_ref() - .unwrap() - .login(user, password, workspace_id) - .await - .map_err(PyCodempError::from)?; - - Ok(()) - }) - } - - fn join_workspace<'a>(&'a self, py: Python<'a>, workspace: String) -> PyResult<&'a PyAny> { - let rc = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let mut cli = rc.write().await; - if cli.is_none() { - return Err(PyConnectionError::new_err("Connect to a server first.")); - }; - - let workspace: PyWorkspace = cli - .as_mut() - .unwrap() - .join_workspace(workspace.as_str()) - .await - .map_err(PyCodempError::from)? - .into(); - - Python::with_gil(|py| Ok(Py::new(py, workspace)?)) - }) - } - - // join a workspace - fn get_workspace<'a>(&'a self, py: Python<'a>, id: String) -> PyResult<&'a PyAny> { - let rc = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let cli = rc.read().await; - if cli.is_none() { - return Err(PyConnectionError::new_err("Connect to a server first.")); - }; - - let Some(ws) = cli.as_ref().unwrap().get_workspace(id.as_str()) else { - return Ok(None); - }; - - Python::with_gil(|py| Ok(Some(Py::new(py, PyWorkspace(ws))?))) - }) - } - - fn user_id<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { - let rc = self.0.clone(); - pyo3_asyncio::tokio::future_into_py(py, async move { - let cli = rc.read().await; - if cli.is_none() { - return Err(PyConnectionError::new_err("Connect to a server first.")); - }; - let id = cli.as_ref().unwrap().user_id().to_string(); - - Python::with_gil(|py| { - let id: Py = PyString::new(py, id.as_str()).into_py(py); - Ok(id) - }) - }) - } -} - -#[pyclass] -struct PyWorkspace(Arc); - -impl From> for PyWorkspace { - fn from(value: Arc) -> Self { - PyWorkspace(value) - } -} - -#[pymethods] -impl PyWorkspace { - // join a workspace - fn create<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { - let ws = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.create(path.as_str()) - .await - .map_err(PyCodempError::from)?; - Ok(()) - }) - } - - fn attach<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { - let ws = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let buffctl: PyBufferController = ws - .attach(path.as_str()) - .await - .map_err(PyCodempError::from)? - .into(); - Python::with_gil(|py| Ok(Py::new(py, buffctl)?)) - }) - } - - fn fetch_buffers<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { - let ws = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.fetch_buffers().await.map_err(PyCodempError::from)?; - Ok(()) - }) - } - - fn fetch_users<'a>(&'a self, py: Python<'a>) -> PyResult<&'a PyAny> { - let ws = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.fetch_users().await.map_err(PyCodempError::from)?; - Ok(()) - }) - } - - fn list_buffer_users<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { - let ws = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let usrlist: Vec = ws - .list_buffer_users(path.as_str()) - .await - .map_err(PyCodempError::from)? - .into_iter() - .map(PyId::from) - .collect(); - - Ok(usrlist) - }) - } - - fn delete<'a>(&'a self, py: Python<'a>, path: String) -> PyResult<&'a PyAny> { - let ws = self.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - ws.delete(path.as_str()) - .await - .map_err(PyCodempError::from)?; - Ok(()) - }) - } - - fn id(&self, py: Python<'_>) -> Py { - PyString::new(py, self.0.id().as_str()).into() - } - - fn cursor(&self, py: Python<'_>) -> PyResult> { - Ok(Py::new(py, PyCursorController::from(self.0.cursor()))?) - } - - fn buffer_by_name( - &self, - py: Python<'_>, - path: String, - ) -> PyResult>> { - let Some(bufctl) = self.0.buffer_by_name(path.as_str()) else { - return Ok(None); - }; - - Ok(Some(Py::new(py, PyBufferController::from(bufctl))?)) - } - - fn filetree(&self, py: Python<'_>) -> Py { - PyList::new(py, self.0.filetree()).into_py(py) - } -} - -/* ########################################################################### */ - -#[pyclass] -struct PyCursorController(Arc); - -impl From> for PyCursorController { - fn from(value: Arc) -> Self { - PyCursorController(value) - } -} - -#[pymethods] -impl PyCursorController { - fn send<'a>(&'a self, path: String, start: (i32, i32), end: (i32, i32)) -> PyResult<()> { - let pos = CursorPosition { - buffer: BufferNode { path }, - start: start.into(), - end: end.into(), - }; - - Ok(self.0.send(pos).map_err(PyCodempError::from)?) - } - - fn try_recv(&self, py: Python<'_>) -> PyResult { - match self.0.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(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - let cur_event: PyCursorEvent = rc.recv().await.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.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - Ok(rc.poll().await.map_err(PyCodempError::from)?) - }) - } -} - -#[pyclass] -struct PyBufferController(Arc); - -impl From> for PyBufferController { - fn from(value: Arc) -> Self { - PyBufferController(value) - } -} - -#[pymethods] -impl PyBufferController { - fn content<'a>(&self, py: Python<'a>) -> &'a PyString { - PyString::new(py, self.0.content().as_str()) - } - - fn send(&self, start: usize, end: usize, txt: String) -> PyResult<()> { - let op = CodempTextChange { - span: start..end, - content: txt.into(), - }; - Ok(self.0.send(op).map_err(PyCodempError::from)?) - } - - fn try_recv(&self, py: Python<'_>) -> PyResult { - match self.0.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.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - 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.0.clone(); - - pyo3_asyncio::tokio::future_into_py(py, async move { - Ok(rc.poll().await.map_err(PyCodempError::from)?) - }) - } -} - -/* ---------- Type Wrappers ----------*/ -// All these objects are not meant to be handled rust side. -// Just to be sent to the python heap. - -#[pyclass] -struct PyId { - #[pyo3(get, set)] - id: String, -} - -impl From for PyId { - fn from(value: Identity) -> Self { - PyId { id: value.id } - } -} - -#[pyclass] -struct PyCursorEvent { - #[pyo3(get, set)] - user: String, - - #[pyo3(get, set)] - buffer: String, - - #[pyo3(get, set)] - start: (i32, i32), - - #[pyo3(get, set)] - end: (i32, i32), -} - -impl From for PyCursorEvent { - fn from(value: CursorEvent) -> Self { - // todo, handle this optional better? - let pos = value.position; - PyCursorEvent { - user: value.user.id, - buffer: pos.buffer.path, - start: pos.start.into(), - end: pos.end.into(), - } - } -} - -#[pyclass] -struct PyTextChange(CodempTextChange); - -impl From for PyTextChange { - fn from(value: CodempTextChange) -> Self { - PyTextChange(value) - } -} - -#[pymethods] -impl PyTextChange { - #[getter] - fn start_incl(&self) -> PyResult { - Ok(self.0.span.start) - } - - #[getter] - fn end_excl(&self) -> PyResult { - Ok(self.0.span.end) - } - - #[getter] - fn content(&self) -> PyResult { - Ok(self.0.content.clone()) - } - - fn is_deletion(&self) -> bool { - self.0.is_deletion() - } - - fn is_addition(&self) -> bool { - self.0.is_addition() - } - - fn is_empty(&self) -> bool { - self.0.is_empty() - } - - fn apply(&self, txt: &str) -> String { - self.0.apply(txt) - } - - #[classmethod] - fn from_diff(_cls: &PyType, before: &str, after: &str) -> PyTextChange { - PyTextChange(CodempTextChange::from_diff(before, after)) - } - - #[classmethod] - fn index_to_rowcol(_cls: &PyType, txt: &str, index: usize) -> (i32, i32) { - CodempTextChange::index_to_rowcol(txt, index).into() - } -} - -/* ------ Python module --------*/ -#[pymodule] -fn codemp(_py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(codemp_init, m)?)?; - m.add_function(wrap_pyfunction!(init_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::()?; - - Ok(()) -} diff --git a/src/wrappers.py b/src/wrappers.py deleted file mode 100644 index 0e54447..0000000 --- a/src/wrappers.py +++ /dev/null @@ -1,101 +0,0 @@ -from __future__ import annotations -from typing import Optional -from ..bindings.codemp import codemp_init, PyCursorEvent, PyTextChange, PyId - -###################################################################################### -# These are helper wrappers, that wrap the coroutines returned from the -# pyo3 bindings into usable awaitable functions. -# These should not be directly extended but rather use the higher -# level "virtual" counterparts above. - -# All methods, without an explicit 'noexcept' are to be treated as failable -# and can throw an error - - -class CursorController: - def __init__(self, handle) -> None: # noexcept - self.handle = handle - - def send(self, path: str, start: tuple[int, int], end: tuple[int, int]) -> None: - self.handle.send(path, start, end) - - def try_recv(self) -> Optional[PyCursorEvent]: - return self.handle.try_recv() - - async def recv(self) -> PyCursorEvent: - return await self.handle.recv() - - async def poll(self) -> None: - return await self.handle.poll() - - -class BufferController: - def __init__(self, handle) -> None: # noexcept - self.handle = handle - - def send(self, start: int, end: int, txt: str) -> None: - self.handle.send(start, end, txt) - - def try_recv(self) -> Optional[PyTextChange]: - return self.handle.try_recv() - - async def recv(self) -> PyTextChange: - return await self.handle.recv() - - async def poll(self) -> None: - return await self.handle.poll() - - -class Workspace: - def __init__(self, handle) -> None: # noexcept - self.handle = handle - - async def create(self, path: str) -> None: - await self.handle.create(path) - - async def attach(self, path: str) -> BufferController: - return BufferController(await self.handle.attach(path)) - - async def fetch_buffers(self) -> None: - await self.handle.fetch_buffers() - - async def fetch_users(self) -> None: - await self.handle.fetch_users() - - async def list_buffer_users(self, path: str) -> list[PyId]: - return await self.handle.list_buffer_users(path) - - async def delete(self, path) -> None: - await self.handle.delete(path) - - def id(self) -> str: # noexcept - return self.handle.id() - - def cursor(self) -> CursorController: - return CursorController(self.handle.cursor()) - - def buffer_by_name(self, path) -> BufferController: - return BufferController(self.handle.buffer_by_name(path)) - - def filetree(self) -> list[str]: # noexcept - return self.handle.filetree() - - -class Client: - def __init__(self) -> None: - self.handle = codemp_init() - - async def connect(self, server_host: str) -> None: - await self.handle.connect(server_host) - - async def login(self, user: str, password: str, workspace: Optional[str]) -> None: - await self.handle.login(user, password, workspace) - - async def join_workspace(self, workspace: str) -> Workspace: - return Workspace(await self.handle.join_workspace(workspace)) - - async def get_workspace(self, id: str) -> Optional[Workspace]: - return Workspace(await self.handle.get_workspace(id)) - - async def user_id(self) -> str: - return await self.handle.user_id()