Skip to content

Commit

Permalink
Merge branch 'pyo3_bump' into dev
Browse files Browse the repository at this point in the history
  • Loading branch information
ghyatzo committed Aug 21, 2024
2 parents ed3673b + 76f8058 commit 4004f20
Show file tree
Hide file tree
Showing 12 changed files with 399 additions and 288 deletions.
5 changes: 2 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,7 @@ napi = { version = "2.16", features = ["full"], optional = true }
napi-derive = { version="2.16", optional = true}

# glue (python)
pyo3 = { version = "0.20", features = ["extension-module"], optional = true}
pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"], optional = true }
pyo3 = { version = "0.22", features = ["extension-module", "experimental-async"], optional = true}

[build-dependencies]
# glue (js)
Expand All @@ -55,4 +54,4 @@ rust = [] # used for ci matrix
lua = ["mlua", "tracing-subscriber"]
java = ["lazy_static", "jni", "tracing-subscriber"]
js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"]
python = ["pyo3", "pyo3-asyncio", "tracing-subscriber", "pyo3-build-config"]
python = ["pyo3", "tracing-subscriber", "pyo3-build-config"]
8 changes: 8 additions & 0 deletions dist/py/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,16 @@ TARGET_EXT="$($PYO3_PYTHON -c 'import sysconfig; print(sysconfig.get_config_var(
maturin build -i "$PYO3_PYTHON" --out "$WHEEL_DIR"

CODEMPSUBLIME_DIR="../../../codemp-sublime/bindings/"
CODEMPTEST_DIR="../../../codemp-python-test/"

wheels=($WHEEL_DIR/*.whl)
for wheel in $wheels; do
echo "moving $wheel to $CODEMPSUBLIME_DIR"
cp $wheel "$CODEMPSUBLIME_DIR"
cp $wheel "$CODEMPTEST_DIR"
done

cd "$CODEMPSUBLIME_DIR"
source .venv/bin/activate
pip install $wheel --force-reinstall

101 changes: 73 additions & 28 deletions dist/py/codemp.pyi
Original file line number Diff line number Diff line change
@@ -1,65 +1,110 @@
from typing import Tuple
from typing import Tuple, Optional, Callable

class PyLogger:
def __init__(self, debug) -> None: ...
async def listen(self) -> str | None: ...
class Driver:
"""
this is akin to a big red button with a white "STOP" on top of it.
it is used to stop the runtime.
"""
def stop(self) -> None: ...


def init(logger_cb: Callable, debug: bool) -> Driver: ...

class Promise[T]:
"""
This is a class akin to a future, which wraps a join handle from a spawned
task on the rust side. you may call .pyawait() on this promise to block
until we have a result, or return immediately if we already have one.
This only goes one way rust -> python.
It can either be used directly or you can wrap it inside a future python side.
"""
def wait(self) -> T: ...
def is_done(self) -> bool: ...

class TextChange:
"""
Editor agnostic representation of a text change, it translate between internal
codemp text operations and editor operations
"""
start: int
end: int
content: str

def is_deletion(self) -> bool: ...
def is_addition(self) -> bool: ...
def is_delete(self) -> bool: ...
def is_insert(self) -> bool: ...
def is_empty(self) -> bool: ...
def apply(self, txt: str) -> str: ...
def from_diff(self, before: str, after: str) -> TextChange: ...
def index_to_rowcol(self, txt: str, index: int) -> Tuple[int, int]: ...


class BufferController:
def content(self) -> str: ...
def send(self, start: int, end: int, txt: str) -> None: ...
async def try_recv(self) -> TextChange | None: ...
async def recv(self) -> TextChange: ...
async def poll(self) -> None: ...
"""
Handle to the controller for a specific buffer, which manages the back and forth
of operations to and from other peers.
"""
def content(self) -> Promise[str]: ...
def send(self,
start: int,
end: int,
txt: str) -> Promise[None]: ...
def try_recv(self) -> Optional[TextChange]: ...
def recv(self) -> Promise[TextChange]: ...
def poll(self) -> Promise[None]: ...
def stop(self) -> bool: ...



class Cursor:
"""
An Editor agnostic cursor position representation
"""
start: Tuple[int, int]
end: Tuple[int, int]
buffer: str
user: str # can be an empty string
user: Optional[str] # can be an empty string


class CursorController:
def send(self, path: str, start: Tuple[int, int], end: Tuple[int, int]) -> None: ...
def try_recv(self) -> Cursor | None: ...
async def recv(self) -> Cursor: ...
async def poll(self) -> None: ...
"""
Handle to the controller for a workspace, which manages the back and forth of
cursor movements to and from other peers
"""
def send(self,
path: str,
start: Tuple[int, int],
end: Tuple[int, int]) -> Promise[None]: ...
def try_recv(self) -> Optional[Cursor]: ...
def recv(self) -> Promise[Cursor]: ...
def poll(self) -> Promise[None]: ...
def stop(self) -> bool: ...


class Workspace:
async def create(self, path: str) -> None: ...
async def attach(self, path: str) -> BufferController: ...
"""
Handle to a workspace inside codemp. It manages buffers.
A cursor is tied to the single workspace.
"""
def create(self, path: str) -> Promise[None]: ...
def attach(self, path: str) -> Promise[BufferController]: ...
def detach(self, path: str) -> bool: ...
async def fetch_buffers(self) -> None: ...
async def fetch_users(self) -> None: ...
async def list_buffer_users(self, path: str) -> list[str]: ...
async def delete(self, path: str) -> None: ...
def fetch_buffers(self) -> Promise[None]: ...
def fetch_users(self) -> Promise[None]: ...
def list_buffer_users(self, path: str) -> Promise[list[str]]: ...
def delete(self, path: str) -> Promise[None]: ...
def id(self) -> str: ...
def cursor(self) -> CursorController: ...
def buffer_by_name(self, path: str) -> BufferController | None: ...
def buffer_by_name(self, path: str) -> Optional[BufferController]: ...
def buffer_list(self) -> list[str]: ...
def filetree(self) -> list[str]: ...
def filetree(self, filter: Optional[str]) -> list[str]: ...


class Client:
def __init__(self, host: str, username: str, password: str) -> None: ...
async def join_workspace(self, workspace: str) -> Workspace: ...
"""
Handle to the actual client that manages the session. It manages the connection
to a server and joining/creating new workspaces
"""
def __new__(cls, host: str, username: str, password: str) -> None: ...
def join_workspace(self, workspace: str) -> Promise[Workspace]: ...
def leave_workspace(self, workspace: str) -> bool: ...
def get_workspace(self, id: str) -> Workspace: ...
def active_workspaces(self) -> list[str]: ...
Expand Down
22 changes: 12 additions & 10 deletions src/api/change.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@
///
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
#[cfg_attr(feature = "python", pyo3::pyclass)]
#[cfg_attr(feature = "python", pyo3(get_all))]
#[cfg_attr(feature = "python", pyo3::pyclass(get_all))]
pub struct TextChange {
/// range start of text change, as char indexes in buffer previous state
pub start: u32,
Expand All @@ -37,7 +36,10 @@ impl TextChange {
pub fn span(&self) -> std::ops::Range<usize> {
self.start as usize..self.end as usize
}
}

#[cfg_attr(feature = "python", pyo3::pymethods)]
impl TextChange {
/// returns true if this TextChange deletes existing text
pub fn is_delete(&self) -> bool {
self.start < self.end
Expand All @@ -52,12 +54,12 @@ impl TextChange {
pub fn is_empty(&self) -> bool {
!self.is_delete() && !self.is_insert()
}

/// applies this text change to given text, returning a new string
pub fn apply(&self, txt: &str) -> String {
let pre_index = std::cmp::min(self.span().start, txt.len());
let pre_index = std::cmp::min(self.start as usize, txt.len());
let pre = txt.get(..pre_index).unwrap_or("").to_string();
let post = txt.get(self.span().end..).unwrap_or("").to_string();
let post = txt.get(self.end as usize..).unwrap_or("").to_string();
format!("{}{}{}", pre, self.content, post)
}
}
Expand All @@ -70,7 +72,7 @@ mod tests {
start: 5,
end: 5,
content: " cruel".to_string(),
hash: None
hash: None,
};
let result = change.apply("hello world!");
assert_eq!(result, "hello cruel world!");
Expand All @@ -82,7 +84,7 @@ mod tests {
start: 5,
end: 11,
content: "".to_string(),
hash: None
hash: None,
};
let result = change.apply("hello cruel world!");
assert_eq!(result, "hello world!");
Expand All @@ -94,7 +96,7 @@ mod tests {
start: 5,
end: 11,
content: " not very pleasant".to_string(),
hash: None
hash: None,
};
let result = change.apply("hello cruel world!");
assert_eq!(result, "hello not very pleasant world!");
Expand All @@ -106,7 +108,7 @@ mod tests {
start: 100,
end: 110,
content: "a very long string \n which totally matters".to_string(),
hash: None
hash: None,
};
let result = change.apply("a short text");
assert_eq!(
Expand All @@ -121,7 +123,7 @@ mod tests {
start: 42,
end: 42,
content: "".to_string(),
hash: None
hash: None,
};
let result = change.apply("some important text");
assert_eq!(result, "some important text");
Expand Down
1 change: 1 addition & 0 deletions src/api/event.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use codemp_proto::workspace::workspace_event::Event as WorkspaceEventInner;

#[derive(Debug, Clone)]
#[cfg_attr(feature = "python", pyo3::pyclass)]
pub enum Event {
FileTreeUpdated(String),
UserJoin(String),
Expand Down
6 changes: 4 additions & 2 deletions src/buffer/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
use std::sync::Arc;

use diamond_types::LocalVersion;
use tokio::sync::{oneshot, mpsc, watch};
use tokio::sync::{mpsc, oneshot, watch};
use tonic::async_trait;

use crate::api::controller::ControllerCallback;
Expand Down Expand Up @@ -40,7 +40,9 @@ impl BufferController {
let (tx, rx) = oneshot::channel();
self.0.content_request.send(tx).await?;
let content = rx.await?;
self.0.last_update.set(self.0.latest_version.borrow().clone());
self.0
.last_update
.set(self.0.latest_version.borrow().clone());
Ok(content)
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/buffer/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ impl ControllerWorker<TextChange> for BufferWorker {
let last_ver = oplog.local_version();

if change.is_delete() {
branch.delete_without_content(&mut oplog, 1, change.span());
branch.delete_without_content(&mut oplog, 1, change.start as usize..change.end as usize);
}

if change.is_insert() {
Expand Down
48 changes: 28 additions & 20 deletions src/ffi/python/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,39 @@ use crate::workspace::Workspace;
use crate::Client;
use pyo3::prelude::*;

// #[pyfunction]
// pub fn codemp_init<'a>(py: Python<'a>) -> PyResult<Py<Client>> {
// Ok(Py::new(py, Client::default())?)
// }
use super::tokio;

#[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?)
fn __new__(host: String, username: String, password: String) -> crate::Result<Self> {
tokio().block_on(Client::new(host, username, password))
}

#[pyo3(name = "join_workspace")]
fn pyjoin_workspace<'a>(&'a self, py: Python<'a>, workspace: String) -> PyResult<&PyAny> {
let rc = self.clone();
// #[pyo3(name = "join_workspace")]
// async fn pyjoin_workspace(&self, workspace: String) -> JoinHandle<crate::Result<Workspace>> {
// tracing::info!("attempting to join the workspace {}", workspace);

// let this = self.clone();
// async {
// tokio()
// .spawn(async move { this.join_workspace(workspace).await })
// .await
// }
// }

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 = "join_workspace")]
fn pyjoin_workspace(&self, py: Python<'_>, workspace: String) -> PyResult<super::Promise> {
tracing::info!("attempting to join the workspace {}", workspace);
let this = self.clone();
crate::a_sync_allow_threads!(py, this.join_workspace(workspace).await)
// let this = self.clone();
// Ok(super::Promise(Some(tokio().spawn(async move {
// Ok(this
// .join_workspace(workspace)
// .await
// .map(|f| Python::with_gil(|py| f.into_py(py)))?)
// }))))
}

#[pyo3(name = "leave_workspace")]
Expand All @@ -33,11 +44,8 @@ impl Client {

// 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),
}
fn pyget_workspace(&self, id: String) -> Option<Workspace> {
self.get_workspace(id.as_str())
}

#[pyo3(name = "active_workspaces")]
Expand Down
Loading

0 comments on commit 4004f20

Please sign in to comment.