mirror of
https://github.com/hexedtech/codemp.git
synced 2024-11-22 07:14:50 +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 }
|
rmpv = { version = "1", optional = true }
|
||||||
clap = { version = "4.2.1", features = ["derive"], optional = true }
|
clap = { version = "4.2.1", features = ["derive"], optional = true }
|
||||||
nvim-rs = { version = "0.5", features = ["use_tokio"], optional = true }
|
nvim-rs = { version = "0.5", features = ["use_tokio"], optional = true }
|
||||||
|
similar = { version = "2.2", features = ["inline"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = "0.9"
|
tonic-build = "0.9"
|
||||||
|
|
|
@ -1,14 +1,16 @@
|
||||||
local BINARY = vim.g.codemp_binary or "./codemp-client-nvim"
|
local BINARY = vim.g.codemp_binary or "./codemp-client-nvim"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
M.jobid = nil
|
|
||||||
M.create = function(path, content) return vim.rpcrequest(M.jobid, "create", path, content) end
|
M.jobid = nil
|
||||||
M.insert = function(path, txt, pos) return vim.rpcrequest(M.jobid, "insert", path, txt, pos) end
|
M.create = function(path, content) return vim.rpcrequest(M.jobid, "create", path, content) end
|
||||||
M.cursor = function(path, row, col) return vim.rpcrequest(M.jobid, "cursor", path, row, col) 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.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.replace = function(path, txt) return vim.rpcrequest(M.jobid, "replace", path, txt) end
|
||||||
M.listen = function(path) return vim.rpcrequest(M.jobid, "listen", path) end
|
M.cursor = function(path, row, col) return vim.rpcrequest(M.jobid, "cursor", path, row, col) end
|
||||||
M.detach = function(path) return vim.rpcrequest(M.jobid, "detach", path) 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 function cursor_offset()
|
||||||
local cursor = vim.api.nvim_win_get_cursor(0)
|
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 codemp_autocmds = vim.api.nvim_create_augroup("CodempAuGroup", { clear = true })
|
||||||
|
|
||||||
local function hook_callbacks(path, buffer)
|
local function hook_callbacks(path, buffer)
|
||||||
|
local prev_changedtick = vim.b.changedtick
|
||||||
vim.api.nvim_create_autocmd(
|
vim.api.nvim_create_autocmd(
|
||||||
{ "InsertCharPre" },
|
{ "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,
|
buffer = buffer,
|
||||||
group = codemp_autocmds,
|
group = codemp_autocmds,
|
||||||
}
|
}
|
||||||
|
@ -29,7 +35,12 @@ local function hook_callbacks(path, buffer)
|
||||||
vim.api.nvim_create_autocmd(
|
vim.api.nvim_create_autocmd(
|
||||||
{ "CursorMoved", "CursorMovedI" },
|
{ "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)
|
local cursor = vim.api.nvim_win_get_cursor(0)
|
||||||
M.cursor(path, cursor[1], cursor[2])
|
M.cursor(path, cursor[1], cursor[2])
|
||||||
end,
|
end,
|
||||||
|
@ -56,9 +67,9 @@ vim.api.nvim_create_user_command('Connect',
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local bin_args = { BINARY }
|
local bin_args = { BINARY }
|
||||||
if #args.args > 0 then
|
if #args.fargs > 0 then
|
||||||
table.insert(bin_args, "--host")
|
table.insert(bin_args, "--host")
|
||||||
table.insert(bin_args, args.args[1])
|
table.insert(bin_args, args.fargs[1])
|
||||||
end
|
end
|
||||||
if args.bang then
|
if args.bang then
|
||||||
table.insert(bin_args, "--debug")
|
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" => {
|
"attach" => {
|
||||||
if args.len() < 1 {
|
if args.len() < 1 {
|
||||||
return Err(Value::from("no path given"));
|
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> {
|
pub async fn cursor(&mut self, path: String, row: i64, col: i64) -> Result<(), Status> {
|
||||||
let req = CursorMov {
|
let req = CursorMov {
|
||||||
path, user: self.id.to_string(),
|
path, user: self.id.to_string(),
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use operational_transform::{OperationSeq, OTError};
|
use operational_transform::{OperationSeq, OTError};
|
||||||
|
use similar::TextDiff;
|
||||||
use tokio::sync::{mpsc, watch, oneshot};
|
use tokio::sync::{mpsc, watch, oneshot};
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
|
|
||||||
|
@ -21,13 +22,25 @@ impl OperationFactory {
|
||||||
self.content == txt
|
self.content == txt
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn replace(&mut self, txt: &str) -> OperationSeq {
|
pub fn replace(&mut self, txt: &str) -> Result<OperationSeq, OTError> {
|
||||||
let out = OperationSeq::default();
|
let mut out = OperationSeq::default();
|
||||||
if self.content == txt {
|
if self.content == txt { // TODO throw and error rather than wasting everyone's resources
|
||||||
return out; // nothing to do
|
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> {
|
pub fn insert(&mut self, txt: &str, pos: u64) -> Result<OperationSeq, OTError> {
|
||||||
|
@ -116,6 +129,12 @@ impl AsyncFactory {
|
||||||
rx.await.map_err(|_| OTError)?
|
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> {
|
pub async fn process(&self, opseq: OperationSeq) -> Result<String, OTError> {
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
self.ops.send(OpMsg::Process(opseq, tx)).await.map_err(|_| OTError)?;
|
self.ops.send(OpMsg::Process(opseq, tx)).await.map_err(|_| OTError)?;
|
||||||
|
@ -135,6 +154,7 @@ enum OpWrapper {
|
||||||
Insert(String, u64),
|
Insert(String, u64),
|
||||||
Delete(u64, u64),
|
Delete(u64, u64),
|
||||||
Cancel(u64, u64),
|
Cancel(u64, u64),
|
||||||
|
Replace(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AsyncFactoryWorker {
|
struct AsyncFactoryWorker {
|
||||||
|
@ -176,6 +196,7 @@ impl AsyncFactoryWorker {
|
||||||
OpWrapper::Insert(txt, pos) => Ok(self.factory.insert(&txt, pos)?),
|
OpWrapper::Insert(txt, pos) => Ok(self.factory.insert(&txt, pos)?),
|
||||||
OpWrapper::Delete(pos, count) => Ok(self.factory.delete(pos, count)?),
|
OpWrapper::Delete(pos, count) => Ok(self.factory.delete(pos, count)?),
|
||||||
OpWrapper::Cancel(pos, count) => Ok(self.factory.cancel(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