diff --git a/client/vscode/src/extension.js b/client/vscode/src/extension.js index 176e42b..9be1764 100644 --- a/client/vscode/src/extension.js +++ b/client/vscode/src/extension.js @@ -3,6 +3,8 @@ const codemp = require("./codemp.node"); var CLIENT = null var CONTROLLER +var CURSOR +var DECORATION = null var OP_CACHE = new Set() async function activate(context) { @@ -71,9 +73,59 @@ async function join() { } } +function _order_tuples(a, b) { + if (a[0] < b[0]) return (a, b) + if (a[0] > b[0]) return (b, a) + if (a[1] < b[1]) return (a, b) + return (b, a) +} + async function _attach(path) { - let doc = vscode.window.activeTextEditor.document; + let editor = vscode.window.activeTextEditor + let doc = editor.document; + + CURSOR = await CLIENT.listen() + CURSOR.callback((usr, path, start, end) => { + try { + if (DECORATION != null) { + DECORATION.dispose() + DECORATION = null + } + const range_start = new vscode.Position(start[0] - 1, start[1]); + const range_end = new vscode.Position(start[0] - 1, start[1] + 1); + const decorationRange = new vscode.Range(range_start, range_end); + DECORATION = vscode.window.createTextEditorDecorationType( + {backgroundColor: 'red', color: 'white'} + ) + editor.setDecorations(DECORATION, [decorationRange]) + } catch (err) { + vscode.window.showErrorMessage("fuck! " + err) + } + }) + vscode.window.onDidChangeTextEditorSelection(async (e) => { + let buf = e.textEditor.document.uri.toString() + let selection = e.selections[0] // TODO there may be more than one cursor!! + let anchor = [selection.anchor.line+1, selection.anchor.character] + let position = [selection.active.line+1, selection.active.character] + // (anchor, position) = _order_tuples(anchor, position) + await CURSOR.send(buf, anchor, position) + }) + CONTROLLER = await CLIENT.attach(path) + CONTROLLER.callback((start, end) => { + // TODO only change affected document range + let content = CONTROLLER.content() + let range = new vscode.Range( + editor.document.positionAt(0), + editor.document.positionAt(editor.document.getText().length) + ) + try { + OP_CACHE.add((range, content)) + editor.edit(editBuilder => editBuilder.replace(range, content)) + } catch (err) { + vscode.window.showErrorMessage("could not set buffer: " + err) + } + }) vscode.workspace.onDidChangeTextDocument(async (e) => { if (e.document != doc) return for (let change of e.contentChanges) { @@ -88,21 +140,6 @@ async function _attach(path) { } } }) - let editor = vscode.window.activeTextEditor - CONTROLLER.set_callback((start, end) => { - // TODO only change affected document range - let content = CONTROLLER.content() - let range = new vscode.Range( - editor.document.positionAt(0), - editor.document.positionAt(editor.document.getText().length) - ) - try { - OP_CACHE.add((range, content)) - editor.edit(editBuilder => editBuilder.replace(range, content)) - } catch (err) { - vscode.window.showErrorMessage("could not set buffer: " + err) - } - }) return CONTROLLER } diff --git a/client/vscode/src/lib.rs b/client/vscode/src/lib.rs index 5f1d126..22c77cb 100644 --- a/client/vscode/src/lib.rs +++ b/client/vscode/src/lib.rs @@ -3,10 +3,10 @@ use std::sync::Arc; use neon::prelude::*; use once_cell::sync::OnceCell; use codemp::{ - cursor::Cursor, client::CodempClient, operation::{OperationController, OperationFactory, OperationProcessor}, + cursor::{CursorControllerHandle, CursorSubscriber}, client::CodempClient, operation::{OperationController, OperationFactory, OperationProcessor}, proto::buffer_client::BufferClient, }; -use codemp::tokio::{runtime::Runtime, sync::{Mutex, broadcast}}; +use codemp::tokio::{runtime::Runtime, sync::Mutex}; fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { static RUNTIME: OnceCell = OnceCell::new(); @@ -17,6 +17,22 @@ fn runtime<'a, C: Context<'a>>(cx: &mut C) -> NeonResult<&'static Runtime> { }) } +fn tuple<'a, C: Context<'a>>(cx: &mut C, a: i32, b: i32) -> NeonResult> { + let obj = cx.empty_array(); + let a_val = cx.number(a); + obj.set(cx, 0, a_val)?; + let b_val = cx.number(b); + obj.set(cx, 1, b_val)?; + Ok(obj) +} + +fn unpack_tuple<'a, C: Context<'a>>(cx: &mut C, arr: Handle<'a, JsArray>) -> NeonResult<(i32, i32)> { + Ok(( + arr.get::(cx, 0)?.value(cx) as i32, + arr.get::(cx, 1)?.value(cx) as i32, + )) +} + struct ClientHandle(Arc>); impl Finalize for ClientHandle {} @@ -33,16 +49,12 @@ fn connect(mut cx: FunctionContext) -> JsResult { 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)?; + let method_create = JsFunction::new(&mut cx, create_client)?; obj.set(&mut cx, "create", method_create)?; - let method_listen = JsFunction::new(&mut cx, listen)?; + let method_listen = JsFunction::new(&mut cx, listen_client)?; obj.set(&mut cx, "listen", method_listen)?; - let method_attach = JsFunction::new(&mut cx, attach)?; + let method_attach = JsFunction::new(&mut cx, attach_client)?; 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) }), } @@ -51,7 +63,7 @@ fn connect(mut cx: FunctionContext) -> JsResult { Ok(promise) } -fn create(mut cx: FunctionContext) -> JsResult { +fn create_client(mut cx: FunctionContext) -> JsResult { let path = cx.argument::(0)?.value(&mut cx); let content = cx.argument::(1).ok().map(|x| x.value(&mut cx)); let this = cx.this(); @@ -71,7 +83,7 @@ fn create(mut cx: FunctionContext) -> JsResult { Ok(promise) } -fn listen(mut cx: FunctionContext) -> JsResult { +fn listen_client(mut cx: FunctionContext) -> JsResult { let this = cx.this(); let boxed : Handle> = this.get(&mut cx, "boxed")?; @@ -82,13 +94,14 @@ fn listen(mut cx: FunctionContext) -> JsResult { 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)))); + let boxed_value = cx.boxed(CursorEventsHandle(controller)); obj.set(&mut cx, "boxed", boxed_value)?; - let poll_method = JsFunction::new(&mut cx, poll_cursor)?; - obj.set(&mut cx, "poll", poll_method)?; + let callback_method = JsFunction::new(&mut cx, callback_cursor)?; + obj.set(&mut cx, "callback", callback_method)?; + let send_method = JsFunction::new(&mut cx, send_cursor)?; + obj.set(&mut cx, "send", send_method)?; Ok(obj) }) }, @@ -99,7 +112,7 @@ fn listen(mut cx: FunctionContext) -> JsResult { Ok(promise) } -fn attach(mut cx: FunctionContext) -> JsResult { +fn attach_client(mut cx: FunctionContext) -> JsResult { let this = cx.this(); let boxed : Handle> = this.get(&mut cx, "boxed")?; let path = cx.argument::(0)?.value(&mut cx); @@ -117,12 +130,10 @@ fn attach(mut cx: FunctionContext) -> JsResult { obj.set(&mut cx, "boxed", boxed_value)?; let apply_method = JsFunction::new(&mut cx, apply_operation)?; obj.set(&mut cx, "apply", apply_method)?; - 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)?; let callback_method = JsFunction::new(&mut cx, callback_operation)?; - obj.set(&mut cx, "set_callback", callback_method)?; + obj.set(&mut cx, "callback", callback_method)?; Ok(obj) }) }, @@ -133,28 +144,6 @@ fn attach(mut cx: FunctionContext) -> JsResult { Ok(promise) } -fn cursor(mut cx: FunctionContext) -> JsResult { - let this = cx.this(); - let boxed : Handle> = this.get(&mut cx, "boxed")?; - - let path = cx.argument::(0)?.value(&mut cx); - let row = cx.argument::(1)?.value(&mut cx) as i64; - let col = cx.argument::(2)?.value(&mut cx) as i64; - - let rc = boxed.0.clone(); - 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::<_, Handle>(e.to_string())), - } - }); - - Ok(promise) -} - struct OperationControllerHandle(Arc); impl Finalize for OperationControllerHandle {} @@ -174,43 +163,13 @@ fn apply_operation(mut cx: FunctionContext) -> JsResult { let op = rc.delta(skip, text.as_str(), tail); match rc.apply(op) { Err(e) => deferred.settle_with(&channel, move |mut cx| cx.throw_error::<_, Handle>(format!("could not apply operation: {}", e))), - Ok(span) => 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(span) => deferred.settle_with(&channel, move |mut cx| tuple(&mut cx, span.start as i32, span.end as i32)), } }); Ok(promise) } -fn poll_operation(mut cx: FunctionContext) -> JsResult { - let this = cx.this(); - let boxed : Handle> = 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 { let this = cx.this(); let boxed : Handle> = this.get(&mut cx, "boxed")?; @@ -239,51 +198,59 @@ fn callback_operation(mut cx: FunctionContext) -> JsResult { Ok(()) }); } + }); + + Ok(cx.undefined()) +} + +struct CursorEventsHandle(CursorControllerHandle); +impl Finalize for CursorEventsHandle {} + +fn callback_cursor(mut cx: FunctionContext) -> JsResult { + let this = cx.this(); + let boxed : Handle> = this.get(&mut cx, "boxed")?; + let callback = Arc::new(cx.argument::(0)?.root(&mut cx)); + + let mut rc = boxed.0.clone(); + let channel = cx.channel(); + + // TODO when garbage collecting OperationController stop this worker + runtime(&mut cx)?.spawn(async move { + while let Some(op) = rc.poll().await { + let cb = callback.clone(); + channel.send(move |mut cx| { + cb.to_inner(&mut cx) + .call_with(&cx) + .arg(cx.string(op.user)) + .arg(cx.string(op.buffer)) + .arg(tuple(&mut cx, op.start.row as i32, op.start.col as i32)?) + .arg(tuple(&mut cx, op.end.row as i32, op.end.col as i32)?) + .apply::(&mut cx)?; + Ok(()) + }); + } }); Ok(cx.undefined()) } - - -struct CursorEventsHandle(Arc>>); -impl Finalize for CursorEventsHandle {} - -fn poll_cursor(mut cx: FunctionContext) -> JsResult { +fn send_cursor(mut cx: FunctionContext) -> JsResult { let this = cx.this(); let boxed : Handle> = this.get(&mut cx, "boxed")?; - let rc = boxed.0.clone(); + let path = cx.argument::(0)?.value(&mut cx); + let start_obj = cx.argument::(1)?; + let start = unpack_tuple(&mut cx, start_obj)?; + let end_obj = cx.argument::(2)?; + let end = unpack_tuple(&mut cx, end_obj)?; + 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::>(e.to_string())), - } + rc.send(&path, start.into(), end.into()).await; + deferred.settle_with(&channel, |mut cx| Ok(cx.undefined())) }); Ok(promise) @@ -291,7 +258,6 @@ fn poll_cursor(mut cx: FunctionContext) -> JsResult { #[neon::main] fn main(mut cx: ModuleContext) -> NeonResult<()> { - cx.export_function("connect", connect)?; Ok(())