codemp/src/lib.rs

217 lines
6.9 KiB
Rust
Raw Normal View History

2023-09-05 01:30:07 +02:00
//! # MultiPlayer Code Editing lib
//!
//! ![just a nice pic](https://alemi.dev/img/about-slice-1.png)
//!
2023-11-17 05:58:12 +01:00
//! > the core library of the codemp project, driving all editor plugins
//!
//! ## structure
2023-09-05 01:31:57 +02:00
//! The main entrypoint is the [Instance] object, that maintains a connection and can
2023-09-10 03:43:46 +02:00
//! be used to join workspaces or attach to buffers. It contains the underlying [client::Client] and
2023-09-05 01:31:57 +02:00
//! stores active controllers.
//!
2023-09-10 03:43:46 +02:00
//! Some actions will return structs implementing the [api::Controller] trait. These can be polled
//! for new stream events ([api::Controller::poll]/[api::Controller::recv]), which will be returned in order.
//! Blocking and callback variants are also implemented. The [api::Controller] can also be used to send new
//! events to the server ([api::Controller::send]).
2023-08-20 00:46:55 +02:00
//!
2023-11-17 05:47:40 +01:00
//! Each operation on a buffer is represented as an [woot::crdt::Op]. The underlying buffer is a
//! [WOOT CRDT](https://inria.hal.science/file/index/docid/71240/filename/RR-5580.pdf),
//! but to use this library it's only sufficient to know that all WOOT buffers that have received
//! the same operations converge to the same state, and that operations might not get integrated
//! immediately but instead deferred until compatible.
//!
//! ## features
2024-02-09 00:39:07 +01:00
//! * `woot` : include the underlying CRDT library and re-exports it (default enabled)
//! * `api` : include traits for core interfaces under [api] (default enabled)
//! * `proto` : include GRCP protocol definitions under [proto] (default enabled)
//! * `client` : include the local [client] implementation (default enabled)
//!
//! ## examples
2024-02-09 01:04:24 +01:00
//! the [client::Client] itself is the core structure implementing all methods, plugins should
//! attempt to keep a single instance at any time
//!
2024-02-09 01:04:24 +01:00
//! working sessions are [workspace::Workspace] and while managing multiple ones is in theory
//! possible, it's not really working right now due to how authentication is managed
//!
2024-02-09 01:04:24 +01:00
//! ### async
//! ```rust,no_run
2023-11-17 05:47:40 +01:00
//! use codemp::api::{Controller, TextChange};
2024-02-09 01:04:24 +01:00
//! # use codemp::client::Client;
//!
//! # async fn async_example() -> codemp::Result<()> {
2024-02-09 01:04:24 +01:00
//! // creating a client session will immediately attempt to connect
//! let mut session = Client::new("http://alemi.dev:50053").await?;
//!
//! // login first, obtaining a new token granting access to 'some_workspace'
//! session.login(
//! "username".to_string(),
//! "password".to_string(),
//! Some("some_workspace".to_string())
//! ).await?;
2023-08-20 00:46:55 +02:00
//!
2024-02-09 01:04:24 +01:00
//! // join a remote workspace, obtaining a workspace handle
//! let workspace = session.join_workspace("some_workspace").await?;
//!
//! workspace.cursor().send( // move cursor
//! codemp::proto::cursor::CursorPosition {
//! buffer: "test.txt".into(),
2024-02-09 01:04:24 +01:00
//! start: codemp::proto::cursor::RowCol { row: 0, col: 0 },
//! end: codemp::proto::cursor::RowCol { row: 0, col: 1 },
2023-08-20 00:46:55 +02:00
//! }
//! )?;
2024-02-09 01:04:24 +01:00
//! let op = workspace.cursor().recv().await?; // receive event from server
//! println!("received cursor event: {:?}", op);
2023-08-20 00:46:55 +02:00
//!
//! // attach to a new buffer and execute operations on it
2024-02-09 01:04:24 +01:00
//! workspace.create("test.txt").await?; // create new buffer
//! let buffer = workspace.attach("test.txt").await?; // attach to it
2023-11-17 05:47:40 +01:00
//! let local_change = TextChange { span: 0..0, content: "hello!".into() };
//! buffer.send(local_change)?; // insert some text
2024-02-09 01:04:24 +01:00
//! let remote_change = buffer.recv().await?; // await remote change
//! #
//! # Ok(())
//! # }
2023-08-20 00:46:55 +02:00
//! ```
//!
//! ### sync
2024-02-09 01:04:24 +01:00
//! if async is not viable, a solution might be keeping a global tokio runtime and blocking on it:
//!
//! ```rust,no_run
2024-02-09 01:04:24 +01:00
//! # use codemp::client::Client;
//! # use std::sync::Arc;
//! # use codemp::api::Controller;
//! #
//! # fn sync_example() -> codemp::Result<()> {
2024-02-09 01:04:24 +01:00
//! let rt = tokio::runtime::Runtime::new().unwrap();
//! let mut session = rt.block_on( // using block_on allows calling async code
//! Client::new("http://alemi.dev:50051")
//! )?;
//!
//! rt.block_on(session.login(
//! "username".to_string(),
//! "password".to_string(),
//! Some("some_workspace".to_string())
//! ))?;
//!
//! let workspace = rt.block_on(session.join_workspace("some_workspace"))?;
//!
//! // attach to buffer and blockingly receive events
2024-02-09 01:04:24 +01:00
//! let buffer = rt.block_on(workspace.attach("test.txt"))?; // attach to buffer, must already exist
//! while let Ok(op) = rt.block_on(buffer.recv()) { // must pass runtime
//! println!("received buffer event: {:?}", op);
//! }
//! #
//! # Ok(())
//! # }
//! ```
//!
//! ## references
//!
//! ![another cool pic coz why not](https://alemi.dev/img/about-slice-2.png)
//!
//! check [codemp-vscode](https://github.com/codewithotherpeopleandchangenamelater/codemp-vscode)
//! or [codemp-nvim](https://github.com/codewithotherpeopleandchangenamelater/codemp-nvim)
//! or [codemp-server](https://github.com/codewithotherpeopleandchangenamelater/codemp-server) for
//! reference implementations.
//!
//! keep track of feature completedness with the
//! [feature comparison matrix](https://github.com/orgs/codewithotherpeopleandchangenamelater/projects/3)
//!
#![doc(html_no_source)]
/// public traits exposed to clients
#[cfg(feature = "api")]
pub mod api;
/// cursor related types and controller
#[cfg(feature = "client")]
pub mod cursor;
/// buffer operations, factory, controller and types
#[cfg(feature = "client")]
pub mod buffer;
/// crate error types and helpers
pub mod errors;
/// underlying client session manager
#[cfg(feature = "client")]
pub mod client;
/// workspace operations
#[cfg(feature = "client")]
pub mod workspace;
/// all-in-one imports : `use codemp::prelude::*;`
pub mod prelude;
/// underlying OperationalTransform library used, re-exported
#[cfg(feature = "woot")]
pub use woot;
/// protocol types and services auto-generated by grpc
2024-02-09 00:39:07 +01:00
#[cfg(feature = "proto")]
2023-08-16 17:08:31 +02:00
#[allow(non_snake_case)]
pub mod proto {
pub mod common {
tonic::include_proto!("common");
impl From<uuid::Uuid> for Identity {
fn from(id: uuid::Uuid) -> Self {
Identity { id: id.to_string() }
}
}
impl From<&uuid::Uuid> for Identity {
fn from(id: &uuid::Uuid) -> Self {
Identity { id: id.to_string() }
}
}
impl From<Identity> for uuid::Uuid {
fn from(value: Identity) -> Self {
uuid::Uuid::parse_str(&value.id).expect("invalid uuid in identity")
}
}
impl From<&Identity> for uuid::Uuid {
fn from(value: &Identity) -> Self {
uuid::Uuid::parse_str(&value.id).expect("invalid uuid in identity")
}
}
}
pub mod files {
tonic::include_proto!("files");
impl From<String> for BufferNode {
fn from(value: String) -> Self {
BufferNode { path: value }
}
}
2024-02-09 00:59:04 +01:00
impl From<&str> for BufferNode {
fn from(value: &str) -> Self {
BufferNode { path: value.to_string() }
}
}
impl From<BufferNode> for String {
fn from(value: BufferNode) -> Self {
value.path
}
}
}
pub mod buffer { tonic::include_proto!("buffer"); }
pub mod cursor { tonic::include_proto!("cursor"); }
2024-01-01 23:34:59 +01:00
pub mod workspace { tonic::include_proto!("workspace"); }
pub mod auth { tonic::include_proto!("auth"); }
}
pub use errors::Error;
pub use errors::Result;