From 7ae5329ce5c7d5a2d23c189dc688e13e88308cda Mon Sep 17 00:00:00 2001 From: ftbsc Date: Wed, 12 Apr 2023 16:58:28 +0200 Subject: [PATCH] feat: added whole content diff to opseq --- Cargo.toml | 1 + src/client/nvim/codemp.lua | 35 +++++++++++++++++++++++------------ src/client/nvim/main.rs | 17 +++++++++++++++++ src/lib/client.rs | 18 ++++++++++++++++++ src/lib/opfactory.rs | 31 ++++++++++++++++++++++++++----- 5 files changed, 85 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cda7b1f..79148e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,6 +41,7 @@ tracing-subscriber = { version = "0.3", optional = true } rmpv = { version = "1", optional = true } clap = { version = "4.2.1", features = ["derive"], optional = true } nvim-rs = { version = "0.5", features = ["use_tokio"], optional = true } +similar = { version = "2.2", features = ["inline"] } [build-dependencies] tonic-build = "0.9" diff --git a/src/client/nvim/codemp.lua b/src/client/nvim/codemp.lua index ecc236e..c265c19 100644 --- a/src/client/nvim/codemp.lua +++ b/src/client/nvim/codemp.lua @@ -1,14 +1,16 @@ local BINARY = vim.g.codemp_binary or "./codemp-client-nvim" local M = {} -M.jobid = nil -M.create = function(path, content) return vim.rpcrequest(M.jobid, "create", path, content) end -M.insert = function(path, txt, pos) return vim.rpcrequest(M.jobid, "insert", path, txt, pos) end -M.cursor = function(path, row, col) return vim.rpcrequest(M.jobid, "cursor", path, row, col) end -M.delete = function(path, pos, count) return vim.rpcrequest(M.jobid, "delete", path, pos, count) end -M.attach = function(path) return vim.rpcrequest(M.jobid, "attach", path) end -M.listen = function(path) return vim.rpcrequest(M.jobid, "listen", path) end -M.detach = function(path) return vim.rpcrequest(M.jobid, "detach", path) end + +M.jobid = nil +M.create = function(path, content) return vim.rpcrequest(M.jobid, "create", path, content) end +M.insert = function(path, txt, pos) return vim.rpcrequest(M.jobid, "insert", path, txt, pos) end +M.delete = function(path, pos, count) return vim.rpcrequest(M.jobid, "delete", path, pos, count) end +M.replace = function(path, txt) return vim.rpcrequest(M.jobid, "replace", path, txt) end +M.cursor = function(path, row, col) return vim.rpcrequest(M.jobid, "cursor", path, row, col) end +M.attach = function(path) return vim.rpcrequest(M.jobid, "attach", path) end +M.listen = function(path) return vim.rpcrequest(M.jobid, "listen", path) end +M.detach = function(path) return vim.rpcrequest(M.jobid, "detach", path) end local function cursor_offset() local cursor = vim.api.nvim_win_get_cursor(0) @@ -18,10 +20,14 @@ end local codemp_autocmds = vim.api.nvim_create_augroup("CodempAuGroup", { clear = true }) local function hook_callbacks(path, buffer) + local prev_changedtick = vim.b.changedtick vim.api.nvim_create_autocmd( { "InsertCharPre" }, { - callback = function(_) M.insert(path, vim.v.char, cursor_offset()) end, + callback = function(_) + M.insert(path, vim.v.char, cursor_offset()) + prev_changedtick = vim.b.changedtick + 1 + end, buffer = buffer, group = codemp_autocmds, } @@ -29,7 +35,12 @@ local function hook_callbacks(path, buffer) vim.api.nvim_create_autocmd( { "CursorMoved", "CursorMovedI" }, { - callback = function(_) + callback = function(args) + if vim.b.changedtick ~= prev_changedtick then + prev_changedtick = vim.b.changedtick + local lines = vim.api.nvim_buf_get_lines(args.buf, 0, -1, false) + M.replace(path, vim.fn.join(lines, "\n")) + end local cursor = vim.api.nvim_win_get_cursor(0) M.cursor(path, cursor[1], cursor[2]) end, @@ -56,9 +67,9 @@ vim.api.nvim_create_user_command('Connect', return end local bin_args = { BINARY } - if #args.args > 0 then + if #args.fargs > 0 then table.insert(bin_args, "--host") - table.insert(bin_args, args.args[1]) + table.insert(bin_args, args.fargs[1]) end if args.bang then table.insert(bin_args, "--debug") diff --git a/src/client/nvim/main.rs b/src/client/nvim/main.rs index 9a2a880..4743fe5 100644 --- a/src/client/nvim/main.rs +++ b/src/client/nvim/main.rs @@ -100,6 +100,23 @@ impl Handler for NeovimHandler { } }, + "replace" => { + if args.len() < 2 { + return Err(Value::from("not enough arguments")); + } + let path = default_empty_str(&args, 0); + let txt = default_empty_str(&args, 1); + + let mut c = self.client.clone(); + match c.replace(path, txt).await { + Ok(res) => match res { + true => Ok(Value::Nil), + false => Err(Value::from("rejected")), + }, + Err(e) => Err(Value::from(format!("could not send replace: {}", e))), + } + }, + "attach" => { if args.len() < 1 { return Err(Value::from("no path given")); diff --git a/src/lib/client.rs b/src/lib/client.rs index f2ecb1d..ade4aca 100644 --- a/src/lib/client.rs +++ b/src/lib/client.rs @@ -89,6 +89,24 @@ impl CodempClient { } } + pub async fn replace(&mut self, path: String, txt: String) -> Result { + let factory = self.get_factory(&path)?; + match factory.replace(txt).await { + Err(e) => Err(Status::internal(format!("invalid operation: {}", e))), + Ok(op) => { + let req = OperationRequest { + path, + hash: "".into(), + user: self.id.to_string(), + opseq: serde_json::to_string(&op) + .map_err(|_| Status::invalid_argument("could not serialize opseq"))?, + }; + let res = self.client.edit(req).await?.into_inner(); + Ok(res.accepted) + }, + } + } + pub async fn cursor(&mut self, path: String, row: i64, col: i64) -> Result<(), Status> { let req = CursorMov { path, user: self.id.to_string(), diff --git a/src/lib/opfactory.rs b/src/lib/opfactory.rs index 1dd85a1..659511d 100644 --- a/src/lib/opfactory.rs +++ b/src/lib/opfactory.rs @@ -1,4 +1,5 @@ use operational_transform::{OperationSeq, OTError}; +use similar::TextDiff; use tokio::sync::{mpsc, watch, oneshot}; use tracing::error; @@ -21,13 +22,25 @@ impl OperationFactory { self.content == txt } - pub fn replace(&mut self, txt: &str) -> OperationSeq { - let out = OperationSeq::default(); - if self.content == txt { - return out; // nothing to do + pub fn replace(&mut self, txt: &str) -> Result { + let mut out = OperationSeq::default(); + if self.content == txt { // TODO throw and error rather than wasting everyone's resources + out.retain(txt.len() as u64); + return Ok(out); // nothing to do } - todo!() + let diff = TextDiff::from_chars(self.content.as_str(), txt); + + for change in diff.iter_all_changes() { + match change.tag() { + similar::ChangeTag::Equal => out.retain(1), + similar::ChangeTag::Delete => out.delete(1), + similar::ChangeTag::Insert => out.insert(change.value()), + } + } + + self.content = out.apply(&self.content)?; + Ok(out) } pub fn insert(&mut self, txt: &str, pos: u64) -> Result { @@ -116,6 +129,12 @@ impl AsyncFactory { rx.await.map_err(|_| OTError)? } + pub async fn replace(&self, txt: String) -> Result { + let (tx, rx) = oneshot::channel(); + self.ops.send(OpMsg::Exec(OpWrapper::Replace(txt), tx)).await.map_err(|_| OTError)?; + rx.await.map_err(|_| OTError)? + } + pub async fn process(&self, opseq: OperationSeq) -> Result { let (tx, rx) = oneshot::channel(); self.ops.send(OpMsg::Process(opseq, tx)).await.map_err(|_| OTError)?; @@ -135,6 +154,7 @@ enum OpWrapper { Insert(String, u64), Delete(u64, u64), Cancel(u64, u64), + Replace(String), } struct AsyncFactoryWorker { @@ -176,6 +196,7 @@ impl AsyncFactoryWorker { OpWrapper::Insert(txt, pos) => Ok(self.factory.insert(&txt, pos)?), OpWrapper::Delete(pos, count) => Ok(self.factory.delete(pos, count)?), OpWrapper::Cancel(pos, count) => Ok(self.factory.cancel(pos, count)?), + OpWrapper::Replace(txt) => Ok(self.factory.replace(&txt)?), } } }