mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-21 23:04:49 +01:00
feat: initial rust bindings for vscode client
This commit is contained in:
parent
975a082262
commit
d79886e613
6 changed files with 317 additions and 1 deletions
|
@ -8,3 +8,6 @@ indent_size = 4
|
||||||
|
|
||||||
[*.rs]
|
[*.rs]
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
[*.js]
|
||||||
|
indent_size = 2
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = ["client/nvim", "server"]
|
members = ["client/nvim", "client/vscode", "server"]
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "codemp"
|
name = "codemp"
|
||||||
|
|
22
client/vscode/Cargo.toml
Normal file
22
client/vscode/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "codemp-vscode"
|
||||||
|
version = "0.0.1"
|
||||||
|
description = "VSCode extension for CodeMP"
|
||||||
|
edition = "2021"
|
||||||
|
exclude = ["index.node"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
codemp = { path = "../.." }
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = "0.3"
|
||||||
|
uuid = { version = "1.3.1", features = ["v4"] }
|
||||||
|
once_cell = "1"
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
rmpv = "1"
|
||||||
|
clap = { version = "4.2.1", features = ["derive"] }
|
||||||
|
async-trait = "0.1.68"
|
||||||
|
neon = { version = "0.10.1", default-features = false, features = ["channel-api", "napi-6", "promise-api"] }
|
28
client/vscode/package.json
Normal file
28
client/vscode/package.json
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "codemp-vscode",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "VSCode extension for CodeMP",
|
||||||
|
"main": "index.node",
|
||||||
|
"engines": {
|
||||||
|
"vscode": "^1.32.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "cargo-cp-artifact -nc index.node -- cargo build --message-format=json-render-diagnostics",
|
||||||
|
"install": "npm run build",
|
||||||
|
"test": "cargo test"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"cargo-cp-artifact": "^0.1"
|
||||||
|
},
|
||||||
|
"contributes": {
|
||||||
|
"commands": [
|
||||||
|
{
|
||||||
|
"command": "example.helloWorld",
|
||||||
|
"title": "Hello World"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"activationEvents": [
|
||||||
|
"onCommand:example.helloWorld"
|
||||||
|
]
|
||||||
|
}
|
19
client/vscode/src/extension.js
Normal file
19
client/vscode/src/extension.js
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
const vscode = require("vscode");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
activate,
|
||||||
|
deactivate,
|
||||||
|
};
|
||||||
|
|
||||||
|
function activate(context) {
|
||||||
|
// This must match the command property in the package.json
|
||||||
|
const commandID = "codemp.connect";
|
||||||
|
let disposable = vscode.commands.registerCommand(commandID, sayHello);
|
||||||
|
context.subscriptions.push(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
vscode.window.showInformationMessage("Connecting to CodeMP!");
|
||||||
|
}
|
||||||
|
|
||||||
|
function deactivate() {}
|
244
client/vscode/src/lib.rs
Normal file
244
client/vscode/src/lib.rs
Normal file
|
@ -0,0 +1,244 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use neon::prelude::*;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
use codemp::{cursor::Cursor, client::CodempClient, tokio::{runtime::Runtime, sync::{Mutex, broadcast}}, proto::buffer_client::BufferClient, operation::{OperationController, OperationFactory}};
|
||||||
|
|
||||||
|
fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> {
|
||||||
|
static RUNTIME: OnceCell<Runtime> = OnceCell::new();
|
||||||
|
|
||||||
|
RUNTIME.get_or_try_init(|| {
|
||||||
|
Runtime::new()
|
||||||
|
.or_else(|err| cx.throw_error(err.to_string()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ClientHandle(Arc<Mutex<CodempClient>>);
|
||||||
|
impl Finalize for ClientHandle {}
|
||||||
|
|
||||||
|
fn connect(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let host = cx.argument::<JsString>(0).ok().map(|x| x.value(&mut cx));
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
match BufferClient::connect(host.unwrap_or("".into())).await {
|
||||||
|
Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<String, neon::handle::Handle<JsString>>(format!("{}", e))),
|
||||||
|
Ok(c) => deferred.settle_with(&channel, |mut cx| {
|
||||||
|
let obj = cx.empty_object();
|
||||||
|
let boxed_value = cx.boxed(ClientHandle(Arc::new(Mutex::new(c.into()))));
|
||||||
|
obj.set(&mut cx, "boxed", boxed_value)?;
|
||||||
|
let method_create = JsFunction::new(&mut cx, create)?;
|
||||||
|
obj.set(&mut cx, "create", method_create)?;
|
||||||
|
let method_listen = JsFunction::new(&mut cx, listen)?;
|
||||||
|
obj.set(&mut cx, "listen", method_listen)?;
|
||||||
|
let method_attach = JsFunction::new(&mut cx, attach)?;
|
||||||
|
obj.set(&mut cx, "attach", method_attach)?;
|
||||||
|
let method_cursor = JsFunction::new(&mut cx, cursor)?;
|
||||||
|
obj.set(&mut cx, "cursor", method_cursor)?;
|
||||||
|
let test = cx.null();
|
||||||
|
obj.set(&mut cx, "test", test)?;
|
||||||
|
Ok(obj)
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let path = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||||
|
let content = cx.argument::<JsString>(1).ok().map(|x| x.value(&mut cx));
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<ClientHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
match rc.lock().await.create(path, content).await {
|
||||||
|
Ok(accepted) => deferred.settle_with(&channel, move |mut cx| Ok(cx.boolean(accepted))),
|
||||||
|
Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<String, neon::handle::Handle<JsString>>(e.to_string())),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn listen(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<ClientHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
match rc.lock().await.listen().await {
|
||||||
|
Ok(controller) => {
|
||||||
|
let sub = controller.sub();
|
||||||
|
deferred.settle_with(&channel, move |mut cx| {
|
||||||
|
let obj = cx.empty_object();
|
||||||
|
let boxed_value = cx.boxed(CursorEventsHandle(Arc::new(Mutex::new(sub))));
|
||||||
|
obj.set(&mut cx, "boxed", boxed_value)?;
|
||||||
|
let poll_method = JsFunction::new(&mut cx, poll_cursor)?;
|
||||||
|
obj.set(&mut cx, "poll", poll_method)?;
|
||||||
|
Ok(obj)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<String, neon::handle::Handle<JsString>>(e.to_string())),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn attach(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<ClientHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
let path = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
match rc.lock().await.attach(path).await {
|
||||||
|
Ok(controller) => {
|
||||||
|
deferred.settle_with(&channel, move |mut cx| {
|
||||||
|
let obj = cx.empty_object();
|
||||||
|
let boxed_value = cx.boxed(OperationControllerHandle(controller));
|
||||||
|
obj.set(&mut cx, "boxed", boxed_value)?;
|
||||||
|
let poll_method = JsFunction::new(&mut cx, poll_operation)?;
|
||||||
|
obj.set(&mut cx, "poll", poll_method)?;
|
||||||
|
let content_method = JsFunction::new(&mut cx, content_operation)?;
|
||||||
|
obj.set(&mut cx, "content", content_method)?;
|
||||||
|
Ok(obj)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<String, neon::handle::Handle<JsString>>(e.to_string())),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cursor(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<ClientHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
let path = cx.argument::<JsString>(0)?.value(&mut cx);
|
||||||
|
let row = cx.argument::<JsNumber>(0)?.value(&mut cx) as i64;
|
||||||
|
let col = cx.argument::<JsNumber>(0)?.value(&mut cx) as i64;
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
match rc.lock().await.cursor(path, row, col).await {
|
||||||
|
Ok(accepted) => deferred.settle_with(&channel, move |mut cx| Ok(cx.boolean(accepted))),
|
||||||
|
Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<String, neon::handle::Handle<JsString>>(e.to_string())),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct OperationControllerHandle(Arc<OperationController>);
|
||||||
|
impl Finalize for OperationControllerHandle {}
|
||||||
|
|
||||||
|
fn poll_operation(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<OperationControllerHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
let span = rc.wait().await;
|
||||||
|
deferred.settle_with(&channel, move |mut cx| {
|
||||||
|
let obj = cx.empty_array();
|
||||||
|
let start_value = cx.number(span.start as u32);
|
||||||
|
obj.set(&mut cx, 0, start_value)?;
|
||||||
|
let end_value = cx.number(span.end as u32);
|
||||||
|
obj.set(&mut cx, 1, end_value)?;
|
||||||
|
Ok(obj)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn content_operation(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<OperationControllerHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
let content = rc.content();
|
||||||
|
deferred.settle_with(&channel, move |mut cx| {
|
||||||
|
Ok(cx.string(content))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
struct CursorEventsHandle(Arc<Mutex<broadcast::Receiver<(String, Cursor)>>>);
|
||||||
|
impl Finalize for CursorEventsHandle {}
|
||||||
|
|
||||||
|
fn poll_cursor(mut cx: FunctionContext) -> JsResult<JsPromise> {
|
||||||
|
let this = cx.this();
|
||||||
|
let boxed : Handle<JsBox<CursorEventsHandle>> = this.get(&mut cx, "boxed")?;
|
||||||
|
let rc = boxed.0.clone();
|
||||||
|
|
||||||
|
let (deferred, promise) = cx.promise();
|
||||||
|
let channel = cx.channel();
|
||||||
|
|
||||||
|
runtime(&mut cx)?.spawn(async move {
|
||||||
|
match rc.lock().await.recv().await {
|
||||||
|
Ok((name, cursor)) => {
|
||||||
|
deferred.settle_with(&channel, move |mut cx| {
|
||||||
|
let obj = cx.empty_object();
|
||||||
|
let name_value = cx.string(name);
|
||||||
|
obj.set(&mut cx, "user", name_value)?;
|
||||||
|
let buffer_value = cx.string(cursor.buffer);
|
||||||
|
obj.set(&mut cx, "buffer", buffer_value)?;
|
||||||
|
let start_value = cx.empty_array();
|
||||||
|
let start_value_row = cx.number(cursor.start.row as i32);
|
||||||
|
start_value.set(&mut cx, 0, start_value_row)?;
|
||||||
|
let start_value_col = cx.number(cursor.start.col as i32);
|
||||||
|
start_value.set(&mut cx, 0, start_value_col)?;
|
||||||
|
obj.set(&mut cx, "start", start_value)?;
|
||||||
|
let end_value = cx.empty_array();
|
||||||
|
let end_value_row = cx.number(cursor.end.row as i32);
|
||||||
|
end_value.set(&mut cx, 0, end_value_row)?;
|
||||||
|
let end_value_col = cx.number(cursor.end.col as i32);
|
||||||
|
end_value.set(&mut cx, 0, end_value_col)?;
|
||||||
|
obj.set(&mut cx, "end", end_value)?;
|
||||||
|
Ok(obj)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<String, neon::handle::Handle<JsString>>(e.to_string())),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Ok(promise)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[neon::main]
|
||||||
|
fn main(mut cx: ModuleContext) -> NeonResult<()> {
|
||||||
|
|
||||||
|
cx.export_function("connect", connect)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
Loading…
Reference in a new issue