mirror of
https://github.com/hexedtech/codemp-intellij.git
synced 2024-11-21 14:44:49 +01:00
feat: updated glue to support workspaces
This commit is contained in:
parent
ca7da92b59
commit
004e54e3da
3 changed files with 133 additions and 67 deletions
|
@ -4,11 +4,14 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", tag = "v0.5.1", features = ["global", "sync"] }
|
codemp = { git = "ssh://git@github.com/codewithotherpeopleandchangenamelater/codemp.git", branch = "workspace", features = ["global", "sync"] }
|
||||||
jni = { version = "0.21.1", features = ["invocation"] }
|
jni = { version = "0.21.1", features = ["invocation"] }
|
||||||
jni-sys = "0.3.0"
|
jni-sys = "0.3.0"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
rifgen = { git = "https://github.com/Kofituo/rifgen.git", rev = "d27d9785b2febcf5527f1deb6a846be5d583f7d7"}
|
rifgen = { git = "https://github.com/Kofituo/rifgen.git", rev = "d27d9785b2febcf5527f1deb6a846be5d583f7d7"}
|
||||||
|
tokio = "1.35.1"
|
||||||
|
uuid = { version = "1.4.1", features = ["v4"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
flapigen = "0.6.0"
|
flapigen = "0.6.0"
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
foreign_typemap!( //thanks @tasn on GitHub for the idea
|
foreign_typemap!( //thanks @tasn on GitHub for the idea
|
||||||
($p:r_type) <T> CodempResult<T> => swig_i_type!(T) {
|
($p:r_type) <T> CodempResult<T> => swig_i_type!(T) {
|
||||||
$out = match $p {
|
$out = match $p {
|
||||||
Ok(x) => {
|
Ok(x) => {
|
||||||
swig_from_rust_to_i_type!(T, x, ret)
|
swig_from_rust_to_i_type!(T, x, ret)
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
let (msg, exception_class) = match err {
|
let (msg, exception_class) = match err {
|
||||||
CodempError::Filler { message } => (
|
CodempError::Filler { message } => (
|
||||||
message,
|
message,
|
||||||
|
@ -32,8 +32,8 @@ foreign_typemap!( //thanks @tasn on GitHub for the idea
|
||||||
};
|
};
|
||||||
jni_throw(env, exception_class, &msg);
|
jni_throw(env, exception_class, &msg);
|
||||||
return <swig_i_type!(T)>::jni_invalid_value();
|
return <swig_i_type!(T)>::jni_invalid_value();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
($p:f_type, unique_prefix="/*err*/") => "/*err*/swig_f_type!(T)" "swig_foreign_from_i_type!(T, $p)";
|
($p:f_type, unique_prefix="/*err*/") => "/*err*/swig_f_type!(T)" "swig_foreign_from_i_type!(T, $p)";
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,83 +1,144 @@
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use codemp::prelude::*;
|
use codemp::prelude::*;
|
||||||
use codemp::tools;
|
use codemp::tools;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
use rifgen::rifgen_attr::{generate_access_methods, generate_interface, generate_interface_doc};
|
use rifgen::rifgen_attr::{generate_access_methods, generate_interface, generate_interface_doc};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub mod glue { //rifgen generated code
|
pub mod glue { //rifgen generated code
|
||||||
include!(concat!(env!("OUT_DIR"), "/glue.rs"));
|
include!(concat!(env!("OUT_DIR"), "/glue.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
/// the tokio runtime, since we can't easily have Java and Rust async work together
|
||||||
|
static ref RT: tokio::runtime::Runtime = tokio::runtime::Runtime::new()
|
||||||
|
.expect("could not start tokio runtime");
|
||||||
|
}
|
||||||
|
|
||||||
#[generate_interface_doc]
|
#[generate_interface_doc]
|
||||||
struct CodeMPHandler;
|
/// the handler class that represent an instance of a CodeMP client
|
||||||
|
struct CodeMPHandler {
|
||||||
|
client: CodempClient
|
||||||
|
}
|
||||||
|
|
||||||
impl CodeMPHandler {
|
impl CodeMPHandler {
|
||||||
#[generate_interface(constructor)]
|
#[generate_interface(constructor)]
|
||||||
fn new() -> CodeMPHandler {
|
/// constructor required by flapigen, DO NOT CALL THIS
|
||||||
CodeMPHandler {}
|
fn new(address: &str) -> CodeMPHandler {
|
||||||
|
CodeMPHandler {
|
||||||
|
client: RT.block_on(CodempClient::new(address)).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn connect(addr: String) -> CodempResult<()> {
|
/// create a new workspcae
|
||||||
CODEMP_INSTANCE.connect(&addr)
|
fn create_workspace(&mut self, workspace_id: &str) -> CodempResult<WorkspaceHandler> {
|
||||||
|
RT.block_on(self.client.create_workspace(workspace_id))
|
||||||
|
.map(|workspace| WorkspaceHandler { workspace })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn join(session: String) -> CodempResult<CursorHandler> {
|
/// join a workspace by name
|
||||||
CODEMP_INSTANCE.join(&session).map(|controller| CursorHandler { cursor: controller })
|
fn join_workspace(&mut self, workspace_id: &str) -> CodempResult<WorkspaceHandler> {
|
||||||
|
RT.block_on(self.client.join_workspace(workspace_id))
|
||||||
|
.map(|workspace| WorkspaceHandler { workspace })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn create(path: String) -> CodempResult<()> {
|
/// leave a workspace
|
||||||
CODEMP_INSTANCE.create(&path, None)
|
fn leave_workspace(&mut self, workspace_id: &str) -> CodempResult<()> {
|
||||||
|
RT.block_on(self.client.leave_workspace(workspace_id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[generate_interface_doc]
|
||||||
|
/// wraps a [codemp::workspace::Workspace] to be handled by Java
|
||||||
|
struct WorkspaceHandler {
|
||||||
|
workspace: Arc<RwLock<CodempWorkspace>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WorkspaceHandler {
|
||||||
|
#[generate_interface(constructor)]
|
||||||
|
/// constructor required by flapigen, DO NOT CALL THIS
|
||||||
|
fn new() -> WorkspaceHandler {
|
||||||
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn create_with_content(path: String, content: String) -> CodempResult<()> {
|
/// create a new buffer in current workspace
|
||||||
CODEMP_INSTANCE.create(&path, Some(&content))
|
fn create_buffer(&mut self, path: &str) -> CodempResult<Option<BufferHandler>> {
|
||||||
|
RT.block_on(RT.block_on(self.workspace.write()).create(path))?;
|
||||||
|
Ok(self.get_buffer(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn attach(path: String) -> CodempResult<BufferHandler> {
|
/// attach to a buffer and get a [crate::BufferHandler] for it
|
||||||
CODEMP_INSTANCE.attach(&path).map(|b| BufferHandler { buffer: b })
|
fn attach_buffer(&mut self, path: &str) -> CodempResult<BufferHandler> {
|
||||||
|
RT.block_on(RT.block_on(self.workspace.write()).attach(path))
|
||||||
|
.map(|buffer| BufferHandler { buffer })
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn detach(path: String) -> CodempResult<bool> {
|
/// updates the local list of the workspace's buffers
|
||||||
CODEMP_INSTANCE.disconnect_buffer(&path)
|
fn fetch_buffers(&mut self) -> CodempResult<()> {
|
||||||
|
RT.block_on(RT.block_on(self.workspace.write()).fetch_buffers())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn get_cursor() -> CodempResult<CursorHandler> {
|
/// updates the local list of the workspace's users
|
||||||
CODEMP_INSTANCE.get_cursor().map(|c| CursorHandler { cursor: c })
|
fn fetch_users(&mut self) -> CodempResult<()> {
|
||||||
|
RT.block_on(RT.block_on(self.workspace.write()).fetch_users())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn get_buffer(path: String) -> CodempResult<BufferHandler> {
|
/// gets a list of all users in a buffer
|
||||||
CODEMP_INSTANCE.get_buffer(&path).map(|b| BufferHandler { buffer: b })
|
fn list_buffer_users(&mut self, path: &str) -> CodempResult<StringVec> {
|
||||||
|
let mut res = StringVec::new();
|
||||||
|
RT.block_on(RT.block_on(self.workspace.write())
|
||||||
|
.list_buffer_users(path))?
|
||||||
|
.iter().for_each(|u| res.push(Uuid::from(u.clone()).to_string()));
|
||||||
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn leave_workspace() -> CodempResult<()> {
|
/// delete a buffer
|
||||||
CODEMP_INSTANCE.leave_workspace()
|
fn delete_buffer(&mut self, path: &str) -> CodempResult<()> {
|
||||||
|
RT.block_on(RT.block_on(self.workspace.write()).delete(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn disconnect_buffer(path: String) -> CodempResult<bool> {
|
/// detach from a buffer
|
||||||
CODEMP_INSTANCE.disconnect_buffer(&path)
|
fn detach_buffer(&mut self, path: &str) -> bool {
|
||||||
|
RT.block_on(self.workspace.write()).detach(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn select_buffer(mut buffer_ids: StringVec, timeout: i64) -> CodempResult<Option<BufferHandler>> {
|
/// get a [crate::CursorHandler] for the workspace's cursor
|
||||||
|
fn get_cursor(&mut self) -> CursorHandler {
|
||||||
|
CursorHandler { cursor: RT.block_on(self.workspace.read()).cursor().clone() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[generate_interface]
|
||||||
|
/// get a [crate::BufferHandler] for one of the workspace's buffers
|
||||||
|
fn get_buffer(&mut self, path: &str) -> Option<BufferHandler> {
|
||||||
|
RT.block_on(self.workspace.read()).buffer_by_name(path)
|
||||||
|
.map(|b| BufferHandler { buffer: b })
|
||||||
|
}
|
||||||
|
|
||||||
|
#[generate_interface]
|
||||||
|
/// polls a list of buffers, returning the first ready one
|
||||||
|
fn select_buffer(&mut self, mut buffer_ids: StringVec, timeout: i64) -> CodempResult<Option<BufferHandler>> {
|
||||||
let mut buffers = Vec::new();
|
let mut buffers = Vec::new();
|
||||||
for id in buffer_ids.v.iter_mut() {
|
for id in buffer_ids.v.iter_mut() {
|
||||||
match CODEMP_INSTANCE.get_buffer(id.as_str()) {
|
match self.get_buffer(id.as_str()) {
|
||||||
Ok(buf) => buffers.push(buf),
|
Some(buf) => buffers.push(buf.buffer),
|
||||||
Err(_) => continue
|
None => continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let result = CODEMP_INSTANCE.rt().block_on(tools::select_buffer(
|
let result = RT.block_on(tools::select_buffer(
|
||||||
buffers.as_slice(),
|
buffers.as_slice(),
|
||||||
Some(Duration::from_millis(timeout as u64))
|
Some(Duration::from_millis(timeout as u64))
|
||||||
));
|
));
|
||||||
|
@ -92,6 +153,7 @@ impl CodeMPHandler {
|
||||||
|
|
||||||
#[generate_interface_doc]
|
#[generate_interface_doc]
|
||||||
#[generate_access_methods]
|
#[generate_access_methods]
|
||||||
|
/// wraps a [codemp::proto::cursor::CursorEvent] to be handled by Java
|
||||||
struct CursorEventWrapper {
|
struct CursorEventWrapper {
|
||||||
user: String,
|
user: String,
|
||||||
buffer: String,
|
buffer: String,
|
||||||
|
@ -103,44 +165,49 @@ struct CursorEventWrapper {
|
||||||
|
|
||||||
|
|
||||||
#[generate_interface_doc]
|
#[generate_interface_doc]
|
||||||
|
/// a handler providing Java access to [codemp::cursor::Controller] methods
|
||||||
struct CursorHandler {
|
struct CursorHandler {
|
||||||
#[allow(unused)]
|
pub cursor: Arc<CodempCursorController>
|
||||||
cursor: Arc<CodempCursorController>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CursorHandler {
|
impl CursorHandler {
|
||||||
#[generate_interface(constructor)]
|
#[generate_interface(constructor)]
|
||||||
|
/// constructor required by flapigen, DO NOT CALL THIS
|
||||||
fn new() -> CursorHandler {
|
fn new() -> CursorHandler {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
|
/// get next cursor event from current workspace, or block until one is available
|
||||||
fn recv(&self) -> CodempResult<CursorEventWrapper> {
|
fn recv(&self) -> CodempResult<CursorEventWrapper> {
|
||||||
match self.cursor.blocking_recv(CODEMP_INSTANCE.rt()) {
|
match RT.block_on(self.cursor.recv()) {
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
Ok(event) => Ok(CursorEventWrapper {
|
Ok(event) => Ok(CursorEventWrapper {
|
||||||
user: event.user,
|
user: Uuid::from(event.user).to_string(),
|
||||||
buffer: event.position.as_ref().unwrap().buffer.clone(),
|
buffer: event.position.buffer.clone(),
|
||||||
start_row: event.position.as_ref().unwrap().start().row,
|
start_row: event.position.start.row,
|
||||||
start_col: event.position.as_ref().unwrap().start().col,
|
start_col: event.position.start.col,
|
||||||
end_row: event.position.as_ref().unwrap().end().row,
|
end_row: event.position.end.row,
|
||||||
end_col: event.position.as_ref().unwrap().end().col
|
end_col: event.position.end.col
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
|
/// broadcast a cursor event
|
||||||
|
/// will automatically fix start and end if they are accidentally inverted
|
||||||
fn send(&self, buffer: String, start_row: i32, start_col: i32, end_row: i32, end_col: i32) -> CodempResult<()> {
|
fn send(&self, buffer: String, start_row: i32, start_col: i32, end_row: i32, end_col: i32) -> CodempResult<()> {
|
||||||
self.cursor.send(CodempCursorPosition {
|
self.cursor.send(CodempCursorPosition {
|
||||||
buffer,
|
buffer,
|
||||||
start: CodempRowCol::wrap(start_row, start_col),
|
start: CodempRowCol::from((start_row, start_col)),
|
||||||
end: CodempRowCol::wrap(end_row, end_col)
|
end: CodempRowCol::from((end_row, end_col))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface_doc]
|
#[generate_interface_doc]
|
||||||
#[generate_access_methods]
|
#[generate_access_methods]
|
||||||
|
/// wraps a [codemp::api::change::TextChange] to make it accessible from Java
|
||||||
struct TextChangeWrapper {
|
struct TextChangeWrapper {
|
||||||
start: usize,
|
start: usize,
|
||||||
end: usize, //not inclusive
|
end: usize, //not inclusive
|
||||||
|
@ -148,28 +215,32 @@ struct TextChangeWrapper {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface_doc]
|
#[generate_interface_doc]
|
||||||
|
/// a handler providing Java access to [codemp::buffer::Controller] methods
|
||||||
struct BufferHandler {
|
struct BufferHandler {
|
||||||
#[allow(unused)]
|
pub buffer: Arc<CodempBufferController>
|
||||||
buffer: Arc<CodempBufferController>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferHandler {
|
impl BufferHandler {
|
||||||
#[generate_interface(constructor)]
|
#[generate_interface(constructor)]
|
||||||
|
/// constructor required by flapigen, DO NOT CALL THIS
|
||||||
fn new() -> BufferHandler {
|
fn new() -> BufferHandler {
|
||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
|
/// get the name of the buffer
|
||||||
fn get_name(&self) -> String {
|
fn get_name(&self) -> String {
|
||||||
self.buffer.name().to_string()
|
self.buffer.name().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
|
/// get the contents of the buffer
|
||||||
fn get_content(&self) -> String {
|
fn get_content(&self) -> String {
|
||||||
self.buffer.content()
|
self.buffer.content()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
|
/// if a text change is available on the buffer, return it immediately
|
||||||
fn try_recv(&self) -> CodempResult<Option<TextChangeWrapper>> {
|
fn try_recv(&self) -> CodempResult<Option<TextChangeWrapper>> {
|
||||||
match self.buffer.try_recv() {
|
match self.buffer.try_recv() {
|
||||||
Err(err) => Err(err),
|
Err(err) => Err(err),
|
||||||
|
@ -183,36 +254,28 @@ impl BufferHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
fn recv(&self) -> CodempResult<TextChangeWrapper> {
|
/// broadcast a text change on the buffer
|
||||||
match self.buffer.blocking_recv(CODEMP_INSTANCE.rt()) {
|
|
||||||
Err(err) => Err(err),
|
|
||||||
Ok(change) => Ok(TextChangeWrapper {
|
|
||||||
start: change.span.start,
|
|
||||||
end: change.span.end,
|
|
||||||
content: change.content.clone()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[generate_interface]
|
|
||||||
fn send(&self, start_offset: usize, end_offset: usize, content: String) -> CodempResult<()> {
|
fn send(&self, start_offset: usize, end_offset: usize, content: String) -> CodempResult<()> {
|
||||||
self.buffer.send(CodempTextChange { span: start_offset..end_offset, content })
|
self.buffer.send(CodempTextChange { span: start_offset..end_offset, content })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface_doc]
|
#[generate_interface_doc]
|
||||||
|
/// a convenience struct allowing Java access to a Rust vector
|
||||||
struct StringVec { //jni moment
|
struct StringVec { //jni moment
|
||||||
v: Vec<String>
|
v: Vec<String>
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StringVec {
|
impl StringVec {
|
||||||
#[generate_interface(constructor)]
|
#[generate_interface(constructor)]
|
||||||
|
/// initializes an empty vector
|
||||||
fn new() -> StringVec {
|
fn new() -> StringVec {
|
||||||
Self { v: Vec::new() }
|
Self { v: Vec::new() }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[generate_interface]
|
#[generate_interface]
|
||||||
|
/// pushes a new value onto the vector
|
||||||
fn push(&mut self, s: String) {
|
fn push(&mut self, s: String) {
|
||||||
self.v.push(s);
|
self.v.push(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue