mirror of
https://github.com/hexedtech/codemp-nvim.git
synced 2024-11-22 15:34:53 +01:00
feat: added whole content diff to opseq
This commit is contained in:
parent
0f5e4c7f8d
commit
7ae5329ce5
5 changed files with 85 additions and 17 deletions
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -89,6 +89,24 @@ impl CodempClient {
|
|||
}
|
||||
}
|
||||
|
||||
pub async fn replace(&mut self, path: String, txt: String) -> Result<bool, Status> {
|
||||
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(),
|
||||
|
|
|
@ -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<OperationSeq, OTError> {
|
||||
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<OperationSeq, OTError> {
|
||||
|
@ -116,6 +129,12 @@ impl AsyncFactory {
|
|||
rx.await.map_err(|_| OTError)?
|
||||
}
|
||||
|
||||
pub async fn replace(&self, txt: String) -> Result<OperationSeq, OTError> {
|
||||
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<String, OTError> {
|
||||
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)?),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue