mirror of
https://github.com/hexedtech/codemp.git
synced 2024-12-22 21:04:53 +01:00
Merge pull request #12 from hexedtech/fix-ci-python
New Ci for Windows and no-abi build option for python
This commit is contained in:
commit
d7c4ef3891
13 changed files with 152 additions and 84 deletions
11
.github/workflows/python.yml
vendored
11
.github/workflows/python.yml
vendored
|
@ -89,6 +89,11 @@ jobs:
|
|||
runs-on: ${{ matrix.platform.runner }}
|
||||
strategy:
|
||||
matrix:
|
||||
versions:
|
||||
- python: 3.8
|
||||
features: py-noabi
|
||||
- python: 3.x
|
||||
features: py
|
||||
platform:
|
||||
- runner: windows-latest
|
||||
target: x64
|
||||
|
@ -101,7 +106,7 @@ jobs:
|
|||
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: 3.x
|
||||
python-version: ${{ matrix.versions.python }}
|
||||
architecture: ${{ matrix.platform.target }}
|
||||
- name: Build wheels
|
||||
uses: PyO3/maturin-action@v1
|
||||
|
@ -109,12 +114,12 @@ jobs:
|
|||
working-directory: dist/py
|
||||
target: ${{ matrix.platform.target }}
|
||||
container: 'off'
|
||||
args: --release --out ./build
|
||||
args: --release --out ./build --features ${{ matrix.versions.features }}
|
||||
sccache: 'true'
|
||||
- name: Upload wheels
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: codemp-py-windows-${{ matrix.platform.target }}
|
||||
name: codemp-${{ matrix.versions.features }}-windows-${{ matrix.platform.target }}
|
||||
path: dist/py/build
|
||||
|
||||
macos:
|
||||
|
|
|
@ -50,7 +50,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", "abi3-py38"], optional = true}
|
||||
pyo3 = { version = "0.22", features = ["extension-module"], optional = true}
|
||||
|
||||
# extra
|
||||
async-trait = { version = "0.1", optional = true }
|
||||
|
@ -71,7 +71,8 @@ serialize = ["dep:serde", "uuid/serde"]
|
|||
rust = [] # used for ci matrix
|
||||
java = ["lazy_static", "jni", "tracing-subscriber"]
|
||||
js = ["napi-build", "tracing-subscriber", "napi", "napi-derive"]
|
||||
py = ["pyo3", "tracing-subscriber", "pyo3-build-config"]
|
||||
py-noabi = ["pyo3", "tracing-subscriber", "pyo3-build-config"]
|
||||
py = ["py-noabi", "pyo3/abi3-py38"]
|
||||
lua = ["mlua-codemp-patch", "tracing-subscriber", "lazy_static", "serialize"]
|
||||
lua54 =["lua", "mlua-codemp-patch/lua54"]
|
||||
luajit = ["lua", "mlua-codemp-patch/luajit"]
|
||||
|
|
4
build.rs
4
build.rs
|
@ -1,7 +1,7 @@
|
|||
#[cfg(feature = "js")]
|
||||
extern crate napi_build;
|
||||
|
||||
#[cfg(feature = "py")]
|
||||
#[cfg(any(feature = "py", feature = "py-noabi"))]
|
||||
extern crate pyo3_build_config;
|
||||
|
||||
/// The main method of the buildscript, required by some glue modules.
|
||||
|
@ -11,7 +11,7 @@ fn main() {
|
|||
napi_build::setup();
|
||||
}
|
||||
|
||||
#[cfg(feature = "py")]
|
||||
#[cfg(any(feature = "py", feature = "py-noabi"))]
|
||||
{
|
||||
pyo3_build_config::add_extension_module_link_args();
|
||||
}
|
||||
|
|
6
dist/py/pyproject.toml
vendored
6
dist/py/pyproject.toml
vendored
|
@ -1,8 +1,8 @@
|
|||
[project]
|
||||
name = "codemp"
|
||||
version = "0.7.0"
|
||||
version = "0.7.1"
|
||||
description = "code multiplexer"
|
||||
requires-python = ">= 3.8"
|
||||
requires-python = ">=3.8"
|
||||
license = "GPL-3.0-only"
|
||||
keywords = ["codemp", "cooperative", "rust", "python"]
|
||||
authors = [
|
||||
|
@ -28,7 +28,7 @@ requires = ["maturin>=1.0,<2.0"]
|
|||
build-backend = "maturin"
|
||||
|
||||
[tool.maturin]
|
||||
features = ["py", "pyo3/extension-module"]
|
||||
features = ["py"]
|
||||
manifest-path = "../../Cargo.toml"
|
||||
python-source = "src"
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
//! A high-level representation of a change within a given buffer.
|
||||
|
||||
/// An editor-friendly representation of a text change in a given buffer.
|
||||
///
|
||||
///
|
||||
/// It's expressed with a range of characters and a string of content that should replace them,
|
||||
/// allowing representation of any combination of deletions, insertions or replacements.
|
||||
///
|
||||
|
@ -22,7 +22,7 @@
|
|||
///
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi(object))]
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass(get_all))]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass(get_all))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct TextChange {
|
||||
/// Range start of text change, as char indexes in buffer previous state.
|
||||
|
@ -43,7 +43,7 @@ impl TextChange {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "py", pyo3::pymethods)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pymethods)]
|
||||
impl TextChange {
|
||||
/// Returns true if this [`TextChange`] deletes existing text.
|
||||
///
|
||||
|
|
|
@ -10,7 +10,10 @@
|
|||
/// http{tls?'s':''}://{host}:{port}
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi(object))]
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass(get_all, set_all))]
|
||||
#[cfg_attr(
|
||||
any(feature = "py", feature = "py-noabi"),
|
||||
pyo3::pyclass(get_all, set_all)
|
||||
)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct Config {
|
||||
/// user identifier used to register, possibly your email
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
//! ### Cursor
|
||||
//! Represents the position of a remote user's cursor.
|
||||
|
||||
#[cfg(feature = "py")]
|
||||
#[cfg(any(feature = "py", feature = "py-noabi"))]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// User cursor position in a buffer
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyclass)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
// #[cfg_attr(feature = "py", pyo3(crate = "reexported::pyo3"))]
|
||||
pub struct Cursor {
|
||||
|
|
|
@ -4,7 +4,7 @@ use codemp_proto::workspace::workspace_event::Event as WorkspaceEventInner;
|
|||
|
||||
/// Event in a [crate::Workspace].
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub enum Event {
|
||||
/// Fired when the file tree changes.
|
||||
|
|
|
@ -18,7 +18,7 @@ use super::worker::DeltaRequest;
|
|||
/// Each buffer controller internally tracks the last acknowledged state, remaining always in sync
|
||||
/// with the server while allowing to procedurally receive changes while still sending new ones.
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||
pub struct BufferController(pub(crate) Arc<BufferControllerInner>);
|
||||
|
||||
|
|
|
@ -4,15 +4,25 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use dashmap::DashMap;
|
||||
use tonic::{service::interceptor::InterceptedService, transport::{Channel, Endpoint}};
|
||||
|
||||
use crate::{api::User, errors::{ConnectionResult, RemoteResult}, ext::InternallyMutable, network, workspace::Workspace};
|
||||
use codemp_proto::{
|
||||
auth::{auth_client::AuthClient, LoginRequest},
|
||||
common::{Empty, Token}, session::{session_client::SessionClient, InviteRequest, WorkspaceRequest},
|
||||
use tonic::{
|
||||
service::interceptor::InterceptedService,
|
||||
transport::{Channel, Endpoint},
|
||||
};
|
||||
|
||||
#[cfg(feature = "py")]
|
||||
use crate::{
|
||||
api::User,
|
||||
errors::{ConnectionResult, RemoteResult},
|
||||
ext::InternallyMutable,
|
||||
network,
|
||||
workspace::Workspace,
|
||||
};
|
||||
use codemp_proto::{
|
||||
auth::{auth_client::AuthClient, LoginRequest},
|
||||
common::{Empty, Token},
|
||||
session::{session_client::SessionClient, InviteRequest, WorkspaceRequest},
|
||||
};
|
||||
|
||||
#[cfg(any(feature = "py", feature = "py-noabi"))]
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// A `codemp` client handle.
|
||||
|
@ -22,7 +32,7 @@ use pyo3::prelude::*;
|
|||
/// A new [`Client`] can be obtained with [`Client::connect`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||
#[cfg_attr(feature = "py", pyclass)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyclass)]
|
||||
pub struct Client(Arc<ClientInner>);
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -42,30 +52,37 @@ impl Client {
|
|||
let channel = Endpoint::from_shared(config.endpoint())?.connect().await?;
|
||||
let mut auth = AuthClient::new(channel.clone());
|
||||
|
||||
let resp = auth.login(LoginRequest {
|
||||
username: config.username.clone(),
|
||||
password: config.password.clone(),
|
||||
})
|
||||
let resp = auth
|
||||
.login(LoginRequest {
|
||||
username: config.username.clone(),
|
||||
password: config.password.clone(),
|
||||
})
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
let claims = InternallyMutable::new(resp.token);
|
||||
|
||||
// TODO move this one into network.rs
|
||||
let session = SessionClient::with_interceptor(
|
||||
channel, network::SessionInterceptor(claims.channel())
|
||||
);
|
||||
let session =
|
||||
SessionClient::with_interceptor(channel, network::SessionInterceptor(claims.channel()));
|
||||
|
||||
Ok(Client(Arc::new(ClientInner {
|
||||
user: resp.user.into(),
|
||||
workspaces: DashMap::default(),
|
||||
claims, auth, session, config
|
||||
claims,
|
||||
auth,
|
||||
session,
|
||||
config,
|
||||
})))
|
||||
}
|
||||
|
||||
/// Refresh session token.
|
||||
pub async fn refresh(&self) -> RemoteResult<()> {
|
||||
let new_token = self.0.auth.clone().refresh(self.0.claims.get())
|
||||
let new_token = self
|
||||
.0
|
||||
.auth
|
||||
.clone()
|
||||
.refresh(self.0.claims.get())
|
||||
.await?
|
||||
.into_inner();
|
||||
self.0.claims.set(new_token);
|
||||
|
@ -74,25 +91,36 @@ impl Client {
|
|||
|
||||
/// Attempt to create a new workspace with given name.
|
||||
pub async fn create_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> {
|
||||
self.0.session
|
||||
self.0
|
||||
.session
|
||||
.clone()
|
||||
.create_workspace(WorkspaceRequest { workspace: name.as_ref().to_string() })
|
||||
.create_workspace(WorkspaceRequest {
|
||||
workspace: name.as_ref().to_string(),
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Delete an existing workspace if possible.
|
||||
pub async fn delete_workspace(&self, name: impl AsRef<str>) -> RemoteResult<()> {
|
||||
self.0.session
|
||||
self.0
|
||||
.session
|
||||
.clone()
|
||||
.delete_workspace(WorkspaceRequest { workspace: name.as_ref().to_string() })
|
||||
.delete_workspace(WorkspaceRequest {
|
||||
workspace: name.as_ref().to_string(),
|
||||
})
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Invite user with given username to the given workspace, if possible.
|
||||
pub async fn invite_to_workspace(&self, workspace_name: impl AsRef<str>, user_name: impl AsRef<str>) -> RemoteResult<()> {
|
||||
self.0.session
|
||||
pub async fn invite_to_workspace(
|
||||
&self,
|
||||
workspace_name: impl AsRef<str>,
|
||||
user_name: impl AsRef<str>,
|
||||
) -> RemoteResult<()> {
|
||||
self.0
|
||||
.session
|
||||
.clone()
|
||||
.invite_to_workspace(InviteRequest {
|
||||
workspace: workspace_name.as_ref().to_string(),
|
||||
|
@ -104,7 +132,9 @@ impl Client {
|
|||
|
||||
/// List all available workspaces, also filtering between those owned and those invited to.
|
||||
pub async fn list_workspaces(&self, owned: bool, invited: bool) -> RemoteResult<Vec<String>> {
|
||||
let mut workspaces = self.0.session
|
||||
let mut workspaces = self
|
||||
.0
|
||||
.session
|
||||
.clone()
|
||||
.list_workspaces(Empty {})
|
||||
.await?
|
||||
|
@ -112,17 +142,25 @@ impl Client {
|
|||
|
||||
let mut out = Vec::new();
|
||||
|
||||
if owned { out.append(&mut workspaces.owned) }
|
||||
if invited { out.append(&mut workspaces.invited) }
|
||||
if owned {
|
||||
out.append(&mut workspaces.owned)
|
||||
}
|
||||
if invited {
|
||||
out.append(&mut workspaces.invited)
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Join and return a [`Workspace`].
|
||||
pub async fn join_workspace(&self, workspace: impl AsRef<str>) -> ConnectionResult<Workspace> {
|
||||
let token = self.0.session
|
||||
let token = self
|
||||
.0
|
||||
.session
|
||||
.clone()
|
||||
.access_workspace(WorkspaceRequest { workspace: workspace.as_ref().to_string() })
|
||||
.access_workspace(WorkspaceRequest {
|
||||
workspace: workspace.as_ref().to_string(),
|
||||
})
|
||||
.await?
|
||||
.into_inner();
|
||||
|
||||
|
|
|
@ -5,14 +5,20 @@ use std::sync::Arc;
|
|||
|
||||
use tokio::sync::{mpsc, oneshot, watch};
|
||||
|
||||
use crate::{api::{controller::ControllerCallback, Controller, Cursor}, errors::ControllerResult};
|
||||
use codemp_proto::{cursor::{CursorPosition, RowCol}, files::BufferNode};
|
||||
use crate::{
|
||||
api::{controller::ControllerCallback, Controller, Cursor},
|
||||
errors::ControllerResult,
|
||||
};
|
||||
use codemp_proto::{
|
||||
cursor::{CursorPosition, RowCol},
|
||||
files::BufferNode,
|
||||
};
|
||||
|
||||
/// A [Controller] for asynchronously sending and receiving [Cursor] event.
|
||||
///
|
||||
/// An unique [CursorController] exists for each active [crate::Workspace].
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi_derive::napi)]
|
||||
pub struct CursorController(pub(crate) Arc<CursorControllerInner>);
|
||||
|
||||
|
@ -31,11 +37,23 @@ impl Controller<Cursor> for CursorController {
|
|||
if cursor.start > cursor.end {
|
||||
std::mem::swap(&mut cursor.start, &mut cursor.end);
|
||||
}
|
||||
Ok(self.0.op.send(CursorPosition {
|
||||
buffer: BufferNode { path: cursor.buffer },
|
||||
start: RowCol { row: cursor.start.0, col: cursor.start.1 },
|
||||
end: RowCol { row: cursor.end.0, col: cursor.end.1 },
|
||||
}).await?)
|
||||
Ok(self
|
||||
.0
|
||||
.op
|
||||
.send(CursorPosition {
|
||||
buffer: BufferNode {
|
||||
path: cursor.buffer,
|
||||
},
|
||||
start: RowCol {
|
||||
row: cursor.start.0,
|
||||
col: cursor.start.1,
|
||||
},
|
||||
end: RowCol {
|
||||
row: cursor.end.0,
|
||||
col: cursor.end.1,
|
||||
},
|
||||
})
|
||||
.await?)
|
||||
}
|
||||
|
||||
async fn try_recv(&self) -> ControllerResult<Option<Cursor>> {
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
//! # Foreign Function Interface
|
||||
//! `codemp` aims to be available as a library from as many programming languages as possible.
|
||||
//! To achieve this, we rely on Foreign Function Interface.
|
||||
//!
|
||||
//!
|
||||
//! ## JavaScript
|
||||
//! Our JavaScript glue is built with [`napi`](https://napi.rs).
|
||||
//!
|
||||
//! All async operations are handled on a separate tokio runtime, automatically managed by `napi`.
|
||||
//! Callbacks are safely scheduled to be called on the main loop thread.
|
||||
//!
|
||||
//!
|
||||
//! ## Python
|
||||
//! Our Python glue is built with [`PyO3`](https://pyo3.rs).
|
||||
//!
|
||||
//! All async operations return a `Promise`, which can we `.wait()`-ed to block and get the return
|
||||
//! value. The `Future` itself is run on a `tokio` runtime in a dedicated thread, which must be
|
||||
//! stared with `codemp.init()` before doing any async operations.
|
||||
//!
|
||||
//!
|
||||
//! ## Lua
|
||||
//! Our Lua glue is built with [`mlua`](https://github.com/mlua-rs/mlua).
|
||||
//!
|
||||
|
@ -27,21 +27,21 @@
|
|||
//! Note as Lua uses filename to locate entrypoint symbol, so shared object can't just have any name.
|
||||
//! Accepted filenames are `libcodemp.___`, `codemp.___`, `codemp_native.___`, `codemp_lua.___` (extension depends on your platform: `so` on linux, `dll` on windows, `dylib` on macos).
|
||||
//! Type hints are provided in `dist/lua/annotations.lua`, just include them in your language server: `---@module 'annotations'`.
|
||||
//!
|
||||
//!
|
||||
//! `codemp` is available as a rock on [LuaRocks](https://luarocks.org/modules/alemi/codemp),
|
||||
//! however LuaRocks compiles from source and will require having `cargo` installed.
|
||||
//! We provide pre-built binaries at [codemp.dev/releases/lua](https://codemp.dev/releases/lua/).
|
||||
//! **Please do not rely on this link, as our built binaries will likely move somewhere else soon!**.
|
||||
//!
|
||||
//!
|
||||
//! ## Java
|
||||
//! Our Java glue is built with [`jni`](https://github.com/jni-rs/jni-rs).
|
||||
//!
|
||||
//! Memory management is entirely delegated to the JVM's garbage collector.
|
||||
//! A more elegant solution than `Object.finalize()`, who is deprecated in newer Java versions, may be coming eventually.
|
||||
//!
|
||||
//!
|
||||
//! Exceptions coming from the native side have generally been made checked to imitate Rust's philosophy with `Result`.
|
||||
//! `JNIException`s are however unchecked: there is nothing you can do to recover from them, as they usually represent a severe error in the glue code. If they arise, it's probably a bug.
|
||||
//!
|
||||
//!
|
||||
|
||||
/// java bindings, built with [jni]
|
||||
#[cfg(feature = "java")]
|
||||
|
@ -56,5 +56,5 @@ pub mod lua;
|
|||
pub mod js;
|
||||
|
||||
/// python bindings, built with [pyo3]
|
||||
#[cfg(feature = "py")]
|
||||
#[cfg(any(feature = "py", feature = "py-noabi"))]
|
||||
pub mod python;
|
||||
|
|
|
@ -9,7 +9,7 @@ use crate::{
|
|||
cursor::{self, worker::CursorWorker},
|
||||
errors::{ConnectionResult, ControllerResult, RemoteResult},
|
||||
ext::InternallyMutable,
|
||||
network::Services
|
||||
network::Services,
|
||||
};
|
||||
|
||||
use codemp_proto::{
|
||||
|
@ -33,7 +33,7 @@ use uuid::Uuid;
|
|||
use napi_derive::napi;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass)]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass)]
|
||||
#[cfg_attr(feature = "js", napi)]
|
||||
pub struct Workspace(Arc<WorkspaceInner>);
|
||||
|
||||
|
@ -59,7 +59,8 @@ impl Workspace {
|
|||
claims: tokio::sync::watch::Receiver<codemp_proto::common::Token>, // TODO ughh receiving this
|
||||
) -> ConnectionResult<Self> {
|
||||
let workspace_claim = InternallyMutable::new(token);
|
||||
let services = Services::try_new(&config.endpoint(), claims, workspace_claim.channel()).await?;
|
||||
let services =
|
||||
Services::try_new(&config.endpoint(), claims, workspace_claim.channel()).await?;
|
||||
let ws_stream = services.ws().attach(Empty {}).await?.into_inner();
|
||||
|
||||
let (tx, rx) = mpsc::channel(128);
|
||||
|
@ -126,12 +127,12 @@ impl Workspace {
|
|||
|
||||
let (tx, rx) = mpsc::channel(256);
|
||||
let mut req = tonic::Request::new(tokio_stream::wrappers::ReceiverStream::new(rx));
|
||||
req.metadata_mut()
|
||||
.insert(
|
||||
"buffer",
|
||||
tonic::metadata::MetadataValue::try_from(credentials.token)
|
||||
.map_err(|e| tonic::Status::internal(format!("failed representing token to string: {e}")))?,
|
||||
);
|
||||
req.metadata_mut().insert(
|
||||
"buffer",
|
||||
tonic::metadata::MetadataValue::try_from(credentials.token).map_err(|e| {
|
||||
tonic::Status::internal(format!("failed representing token to string: {e}"))
|
||||
})?,
|
||||
);
|
||||
let stream = self.0.services.buf().attach(req).await?.into_inner();
|
||||
|
||||
let worker = BufferWorker::new(self.0.user.id, path);
|
||||
|
@ -282,14 +283,18 @@ impl Workspace {
|
|||
/// A filter may be applied, and it may be strict (equality check) or not (starts_with check).
|
||||
// #[cfg_attr(feature = "js", napi)] // https://github.com/napi-rs/napi-rs/issues/1120
|
||||
pub fn filetree(&self, filter: Option<&str>, strict: bool) -> Vec<String> {
|
||||
self.0.filetree.iter()
|
||||
.filter(|f| filter.map_or(true, |flt| {
|
||||
if strict {
|
||||
f.as_str() == flt
|
||||
} else {
|
||||
f.starts_with(flt)
|
||||
}
|
||||
}))
|
||||
self.0
|
||||
.filetree
|
||||
.iter()
|
||||
.filter(|f| {
|
||||
filter.map_or(true, |flt| {
|
||||
if strict {
|
||||
f.as_str() == flt
|
||||
} else {
|
||||
f.starts_with(flt)
|
||||
}
|
||||
})
|
||||
})
|
||||
.map(|f| f.clone())
|
||||
.collect()
|
||||
}
|
||||
|
@ -315,9 +320,7 @@ impl Workspace {
|
|||
match ev {
|
||||
// user
|
||||
WorkspaceEventInner::Join(UserJoin { user }) => {
|
||||
inner
|
||||
.users
|
||||
.insert(user.id.uuid(), user.into());
|
||||
inner.users.insert(user.id.uuid(), user.into());
|
||||
}
|
||||
WorkspaceEventInner::Leave(UserLeave { user }) => {
|
||||
inner.users.remove(&user.id.uuid());
|
||||
|
@ -364,8 +367,8 @@ impl Drop for WorkspaceInner {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "py", pyo3::pyclass(eq, eq_int))]
|
||||
#[cfg_attr(feature = "py", derive(PartialEq))]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), pyo3::pyclass(eq, eq_int))]
|
||||
#[cfg_attr(any(feature = "py", feature = "py-noabi"), derive(PartialEq))]
|
||||
pub enum DetachResult {
|
||||
NotAttached,
|
||||
Detaching,
|
||||
|
|
Loading…
Reference in a new issue