Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
877 changes: 514 additions & 363 deletions Cargo.lock

Large diffs are not rendered by default.

22 changes: 11 additions & 11 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [
"cschen <[email protected]>"
]
license = "GPL-3.0-only"
edition = "2021"
edition = "2024"
version = "0.8.5"
exclude = ["dist/*"]

Expand All @@ -25,11 +25,11 @@ thiserror = "2.0"
# crdt
diamond-types = "1.0"
# proto
codemp-proto = "0.7"
uuid = { version = "1.13", features = ["v4"] }
tonic = { version = "0.12", features = ["tls", "tls-roots"] }
codemp-proto = { git = "https://github.com/hexedtech/codemp-proto", rev = "23012c1ddd2c488a923d4c71f1d8a1a7a61f7412", features = ["client"] }
uuid = { version = "1.17", features = ["v4"] }
tonic = { version = "0.14", features = ["tls-native-roots"] }
# api
tokio = { version = "1.43", features = ["macros", "rt-multi-thread", "sync"] }
tokio = { version = "1.47", features = ["macros", "rt-multi-thread", "sync"] }
xxhash-rust = { version = "0.8", features = ["xxh3"] }
# client
tokio-stream = "0.1"
Expand All @@ -43,24 +43,24 @@ jni = { version = "0.21", features = ["invocation"], optional = true }
jni-toolbox = { version = "0.2", optional = true, features = ["uuid"] }

# glue (lua)
mlua = { version = "0.10", features = ["module", "serialize", "error-send"], optional = true }
mlua = { version = "0.11", features = ["module", "serialize", "error-send"], optional = true }

# glue (js)
napi = { version = "2.16", features = ["full"], optional = true }
napi-derive = { version="2.16", optional = true}
napi = { version = "3.1", features = ["full"], optional = true }
napi-derive = { version="3.1", optional = true}

# glue (python)
pyo3 = { version = "0.23", features = ["extension-module", "multiple-pymethods"], optional = true}
pyo3 = { version = "0.25", features = ["extension-module", "multiple-pymethods", "uuid"], optional = true}

# extra
async-trait = { version = "0.1", optional = true }
serde = { version = "1.0", features = ["derive"], optional = true }

[build-dependencies]
# glue (js)
napi-build = { version = "2.1", optional = true }
napi-build = { version = "2.2", optional = true }
# glue (python)
pyo3-build-config = { version = "0.23", optional = true }
pyo3-build-config = { version = "0.25", optional = true }

[features]
default = ["lua-jit", "py-abi3"]
Expand Down
62 changes: 56 additions & 6 deletions dist/lua/annotations.lua
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,39 @@ function UserListPromise:cancel() end
---invoke callback asynchronously as soon as promise is ready
function UserListPromise:and_then(cb) end

---@class (exact) WorkspaceInfoPromise : Promise
local WorkspaceInfoPromise = {}
--- block until promise is ready and return value
--- @return WorkspaceInfo
function WorkspaceInfoPromise:await() end
--- cancel promise execution
function WorkspaceInfoPromise:cancel() end
---@param cb fun(x: WorkspaceInfo) callback to invoke
---invoke callback asynchronously as soon as promise is ready
function WorkspaceInfoPromise:and_then(cb) end

---@class (exact) WorkspaceInfoListPromise : Promise
local WorkspaceInfoListPromise = {}
--- block until promise is ready and return value
--- @return WorkspaceInfo[]
function WorkspaceInfoListPromise:await() end
--- cancel promise execution
function WorkspaceInfoListPromise:cancel() end
---@param cb fun(x: WorkspaceInfo[]) callback to invoke
---invoke callback asynchronously as soon as promise is ready
function WorkspaceInfoListPromise:and_then(cb) end

---@class (exact) BufferNodeListPromise : Promise
local BufferNodeListPromise = {}
--- block until promise is ready and return value
--- @return BufferNode[]
function BufferNodeListPromise:await() end
--- cancel promise execution
function BufferNodeListPromise:cancel() end
---@param cb fun(x: BufferNode[]) callback to invoke
---invoke callback asynchronously as soon as promise is ready
function BufferNodeListPromise:and_then(cb) end

-- [[ END ASYNC STUFF ]]


Expand Down Expand Up @@ -198,7 +231,7 @@ function Client:refresh() end
function Client:attach_workspace(ws) end

---@param ws string workspace id to create
---@return NilPromise
---@return WorkspaceInfoPromise
---@async
---@nodiscard
---create a new workspace with given id
Expand All @@ -223,13 +256,13 @@ function Client:delete_workspace(ws) end
---grant user acccess to workspace
function Client:invite_to_workspace(ws, user) end

---@return StringArrayPromise
---@return WorkspaceInfoListPromise
---@async
---@nodiscard
---fetch and list owned workspaces
function Client:fetch_owned_workspaces() end

---@return StringArrayPromise
---@return WorkspaceInfoListPromise
---@async
---@nodiscard
---fetch and list joined workspaces
Expand All @@ -243,10 +276,18 @@ function Client:get_workspace(ws) end


---@class User
---represents a service user and contains all its relevant info
---@field id string user uuid
---@field name string user display name


---@class WorkspaceInfo
---represents informations about a workspace, without having an handle to it
---@field id string
---@field name string
---@field owner User



---@class (exact) Workspace
---a joined codemp workspace
Expand All @@ -265,11 +306,12 @@ function Workspace:active_buffers() end
function Workspace:cursor() end

---@param path string relative path ("name") of new buffer
---@param ephemeral boolean wether this buffer is ephemeral (auto deletes)
---@return NilPromise
---@async
---@nodiscard
---create a new empty buffer
function Workspace:create_buffer(path) end
function Workspace:create_buffer(path, ephemeral) end

---@param path string relative path ("name") of buffer to delete
---@return NilPromise
Expand Down Expand Up @@ -304,11 +346,12 @@ function Workspace:search_buffers(filter) end
---return all names of users currently in this workspace
function Workspace:user_list() end

---@return NilPromise
---@param filter string filter buffers we want to fetch relative to this path
---@return BufferNodeListPromise
---@async
---@nodiscard
---force refresh buffer list from workspace
function Workspace:fetch_buffers(path) end
function Workspace:list_buffers(filter) end

---@return NilPromise
---@async
Expand Down Expand Up @@ -356,6 +399,13 @@ function Workspace:callback(cb) end



---@class BufferNode
---@field id string
---@field name string
---@field owner User



---@class (exact) BufferController
---handle to a remote buffer, for async send/recv operations
local BufferController = {}
Expand Down
17 changes: 9 additions & 8 deletions dist/py/src/codemp/codemp.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import Optional, Callable
from uuid import uuid4, UUID

def version() -> str: ...

Expand Down Expand Up @@ -49,15 +50,15 @@ class Client:
Handle to the actual client that manages the session. It manages the connection
to a server and joining/creating new workspaces
"""
def attach_workspace(self, workspace: str) -> Promise[Workspace]: ...
def attach_workspace(self, workspace: UUID) -> Promise[Workspace]: ...
def create_workspace(self, workspace: str) -> Promise[None]: ...
def delete_workspace(self, workspace: str) -> Promise[None]: ...
def invite_to_workspace(self, workspace: str, username: str) -> Promise[None]: ...
def fetch_owned_workspaces(self) -> Promise[list[str]]: ...
def fetch_joined_workspaces(self) -> Promise[list[str]]: ...
def leave_workspace(self, workspace: str) -> bool: ...
def get_workspace(self, id: str) -> Workspace: ...
def active_workspaces(self) -> list[str]: ...
def leave_workspace(self, workspace: UUID) -> bool: ...
def get_workspace(self, id: UUID) -> Workspace: ...
def active_workspaces(self) -> list[UUID]: ...
def current_user(self) -> User: ...
def refresh(self) -> Promise[None]: ...

Expand Down Expand Up @@ -93,12 +94,12 @@ class Workspace:
Handle to a workspace inside codemp. It manages buffers.
A cursor is tied to the single workspace.
"""
def create_buffer(self, path: str) -> Promise[None]: ...
def create_buffer(self, path: str, ephemeral: str) -> Promise[None]: ...
def attach_buffer(self, path: str) -> Promise[BufferController]: ...
def detach_buffer(self, path: str) -> bool: ...
def fetch_buffers(self) -> Promise[list[str]]: ...
def fetch_users(self) -> Promise[list[User]]: ...
def fetch_buffer_users(self, path: str) -> Promise[list[User]]: ...
def list_buffers(self, filter: str) -> Promise[list[str]]: ...
def list_users(self) -> Promise[list[User]]: ...
def list_buffer_users(self, path: str) -> Promise[list[User]]: ...
def delete_buffer(self, path: str) -> Promise[None]: ...
def id(self) -> str: ...
def cursor(self) -> CursorController: ...
Expand Down
22 changes: 22 additions & 0 deletions src/api/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//! # Buffer
//! TODO TODO TODO

/// Represents a service buffer
#[derive(Debug, Clone)]
#[cfg_attr(feature = "py", pyo3::pyclass)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct BufferNode {
/// Buffer path, sort of like a UNIX path.
pub path: String,
/// Wether this buffer gets auto-deleted once all users left
pub ephemeral: bool,
}

impl From<codemp_proto::files::BufferNode> for BufferNode {
fn from(value: codemp_proto::files::BufferNode) -> Self {
Self {
path: value.path,
ephemeral: value.ephemeral,
}
}
}
11 changes: 8 additions & 3 deletions src/api/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,9 @@ impl Config {
impl std::fmt::Debug for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
write!(f,
r#"""Config {{
write!(
f,
r#"""Config {{
username: {},
password: ********,
host: {:#?},
Expand All @@ -79,7 +80,11 @@ r#"""Config {{
self.username, self.host, self.port, self.tls
)
} else {
write!(f, "Config {{ username: {}, password: ********, host: {:?}, port: {:?}, tls: {:?} }}", self.username, self.host, self.port, self.tls)
write!(
f,
"Config {{ username: {}, password: ********, host: {:?}, port: {:?}, tls: {:?} }}",
self.username, self.host, self.port, self.tls
)
}
}
}
20 changes: 16 additions & 4 deletions src/api/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,25 @@ use pyo3::prelude::*;
#[cfg_attr(feature = "py", pyclass(get_all))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
// #[cfg_attr(feature = "py", pyo3(crate = "reexported::pyo3"))]
pub struct Cursor {
pub struct CursorEvent {
/// User who sent the cursor.
pub user: String,
/// Cursor position data
pub cursor: Cursor,
}


/// An event that occurred about a user's cursor.
#[derive(Clone, Debug, Default)]
#[cfg_attr(feature = "js", napi_derive::napi(object))]
#[cfg_attr(feature = "py", pyclass(get_all))]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
// #[cfg_attr(feature = "py", pyo3(crate = "reexported::pyo3"))]
pub struct Cursor {
/// Path of buffer this cursor is on
pub buffer: String,
/// The updated cursor selection.
pub sel: Selection,
pub sel: Vec<Selection>,
}

/// A cursor selection span.
Expand All @@ -32,6 +46,4 @@ pub struct Selection {
pub end_row: i32,
/// Cursor position final column in buffer.
pub end_col: i32,
/// Path of buffer this cursor is on.
pub buffer: String,
}
8 changes: 8 additions & 0 deletions src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,23 @@ pub mod config;
/// representation for an user's cursor
pub mod cursor;

/// representation of remote buffers
pub mod buffer;

/// live events in workspaces
pub mod event;

/// data structure for remote users
pub mod user;

/// data structure for workspaces
pub mod workspace;

pub use buffer::BufferNode;
pub use change::{BufferUpdate, TextChange};
pub use config::Config;
pub use controller::{AsyncReceiver, AsyncSender, Controller};
pub use cursor::{Cursor, Selection};
pub use event::Event;
pub use user::User;
pub use workspace::WorkspaceInfo;
31 changes: 31 additions & 0 deletions src/api/workspace.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! # Workspace
//! A workspace is a working environment containing many buffers, owned by one user.
//! Many users can be invited and join a workspace, accessing its buffer list and being able to
//! attach to its buffers (depending on permissions) to send changes. Workspaces are namespaced to
//! users, meaning two workspaces with the same name can exist, but one user can own only one
//! workspace with a given name.

use uuid::Uuid;

/// Represents a service workspace
#[derive(Debug, Clone)]
#[cfg_attr(feature = "py", pyo3::pyclass)]
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
pub struct WorkspaceInfo {
/// Workspace unique identifier, should never change.
pub id: Uuid,
/// Workspace name, cannot change and is unique per owner.
pub name: String,
/// Workspace owning user
pub owner: super::User,
}

impl From<codemp_proto::common::WorkspaceInfo> for WorkspaceInfo {
fn from(value: codemp_proto::common::WorkspaceInfo) -> Self {
Self {
id: Uuid::from(value.id),
name: value.name,
owner: super::User::from(value.owner),
}
}
}
2 changes: 1 addition & 1 deletion src/buffer/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use std::sync::Arc;
use diamond_types::LocalVersion;
use tokio::sync::{mpsc, oneshot, watch};

use crate::api::controller::{AsyncReceiver, AsyncSender, Controller, ControllerCallback};
use crate::api::BufferUpdate;
use crate::api::TextChange;
use crate::api::controller::{AsyncReceiver, AsyncSender, Controller, ControllerCallback};
use crate::errors::ControllerResult;
use crate::ext::IgnorableError;

Expand Down
Loading
Loading