mirror of
https://github.com/hexedtech/codemp-nvim.git
synced 2024-12-23 22:04:52 +01:00
feat: more capillar but broken text changes
requires more help from codemp lib
This commit is contained in:
parent
773d76e7f8
commit
ec873ee8b4
2 changed files with 121 additions and 72 deletions
117
src/codemp.lua
117
src/codemp.lua
|
@ -1,22 +1,52 @@
|
|||
local codemp = require("libcodemp_nvim")
|
||||
|
||||
local function register_async_waker(target, cb)
|
||||
local async = vim.loop.new_async(cb)
|
||||
vim.loop.new_thread(function(_async, _target)
|
||||
local _codemp = require("libcodemp_nvim")
|
||||
local _cntrl
|
||||
if _target ~= nil then
|
||||
_cntrl = _codemp.get_buffer(_target)
|
||||
else
|
||||
_cntrl = _codemp.get_cursor()
|
||||
end
|
||||
local codemp_changed_tick = nil -- TODO this doesn't work when events are coalesced
|
||||
|
||||
local function register_controller_handler(target, controller, handler)
|
||||
local async = vim.loop.new_async(function()
|
||||
while true do
|
||||
_cntrl:poll()
|
||||
local event = controller:try_recv()
|
||||
if event == nil then break end
|
||||
vim.schedule(function() handler(event) end)
|
||||
end
|
||||
end)
|
||||
-- TODO controller can't be passed to the uvloop new_thread: when sent to the new
|
||||
-- Lua runtime it "loses" its methods defined with mlua, making the userdata object
|
||||
-- completely useless. We can circumvent this by requiring codemp again in the new
|
||||
-- thread and requesting a new reference to the same controller from che global instance
|
||||
-- NOTE variables prefixed with underscore live in another Lua runtime
|
||||
vim.loop.new_thread({}, function(_async, _target)
|
||||
local _codemp = require("libcodemp_nvim")
|
||||
local _controller = _target ~= nil and _codemp.get_buffer(_target) or _codemp.get_cursor()
|
||||
while true do
|
||||
_controller:poll()
|
||||
_async:send()
|
||||
end
|
||||
end, async, target)
|
||||
end
|
||||
|
||||
-- local function byte2rowcol(buf, x)
|
||||
-- local row
|
||||
-- local row_start
|
||||
-- vim.api.nvim_buf_call(buf, function ()
|
||||
-- row = vim.fn.byte2line(x)
|
||||
-- row_start = vim.fn.line2byte(row)
|
||||
-- end)
|
||||
-- local col = x - row_start
|
||||
-- return { row, col }
|
||||
-- end
|
||||
|
||||
local function split_without_trim(str, sep)
|
||||
local res = vim.fn.split(str, sep)
|
||||
if str:sub(1,1) == "\n" then
|
||||
table.insert(res, 1, '')
|
||||
end
|
||||
if str:sub(-1) == "\n" then
|
||||
table.insert(res, '')
|
||||
end
|
||||
return res
|
||||
end
|
||||
|
||||
local function order_tuples(x) -- TODO send help...
|
||||
if x[1][1] < x[2][1] then
|
||||
return { { x[1][1], x[1][2] }, { x[2][1], x[2][2] } }
|
||||
|
@ -31,10 +61,20 @@ end
|
|||
|
||||
local function cursor_position()
|
||||
local mode = vim.api.nvim_get_mode().mode
|
||||
if mode == "v" or mode == "V" then
|
||||
if mode == "v" then
|
||||
local _, ls, cs = unpack(vim.fn.getpos('v'))
|
||||
local _, le, ce = unpack(vim.fn.getpos('.'))
|
||||
return order_tuples({ { ls-1, cs-1 }, { le-1, ce } })
|
||||
elseif mode == "V" then
|
||||
local _, ls, _ = unpack(vim.fn.getpos('v'))
|
||||
local _, le, _ = unpack(vim.fn.getpos('.'))
|
||||
if le > ls then
|
||||
local ce = vim.fn.strlen(vim.fn.getline(le))
|
||||
return { { ls-1, 0 }, { le-1, ce } }
|
||||
else
|
||||
local ce = vim.fn.strlen(vim.fn.getline(ls))
|
||||
return { { le-1, 0 }, { ls-1, ce } }
|
||||
end
|
||||
else
|
||||
local win = vim.api.nvim_get_current_win()
|
||||
local cur = vim.api.nvim_win_get_cursor(win)
|
||||
|
@ -51,7 +91,7 @@ local function buffer_get_content(buf)
|
|||
end
|
||||
|
||||
local function buffer_set_content(buf, content)
|
||||
local lines = vim.fn.split(content, "\n")
|
||||
local lines = split_without_trim(content, "\n")
|
||||
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||
end
|
||||
|
||||
|
@ -86,7 +126,7 @@ vim.api.nvim_create_user_command(
|
|||
local ns = vim.api.nvim_create_namespace("codemp-cursors")
|
||||
|
||||
-- hook serverbound callbacks
|
||||
vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, {
|
||||
vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI", "ModeChanged"}, {
|
||||
group = vim.api.nvim_create_augroup("codemp-workspace-" .. args.args, { clear = true }),
|
||||
callback = function (_)
|
||||
local cur = cursor_position()
|
||||
|
@ -95,15 +135,9 @@ vim.api.nvim_create_user_command(
|
|||
})
|
||||
|
||||
-- hook clientbound callbacks
|
||||
register_async_waker(nil, function()
|
||||
while true do
|
||||
local event = controller:try_recv()
|
||||
if event == nil then break end
|
||||
vim.schedule(function()
|
||||
vim.api.nvim_buf_clear_namespace(buffer, ns, 0, -1)
|
||||
multiline_highlight(buffer, ns, "ErrorMsg", event.start, event.finish)
|
||||
end)
|
||||
end
|
||||
register_controller_handler(nil, controller, function(event)
|
||||
vim.api.nvim_buf_clear_namespace(buffer, ns, 0, -1)
|
||||
multiline_highlight(buffer, ns, "ErrorMsg", event.start, event.finish)
|
||||
end)
|
||||
|
||||
print(" ++ joined workspace " .. args.args)
|
||||
|
@ -130,24 +164,42 @@ vim.api.nvim_create_user_command(
|
|||
"Attach",
|
||||
function (args)
|
||||
local controller = codemp.attach(args.args)
|
||||
|
||||
local buffer = vim.api.nvim_get_current_buf()
|
||||
|
||||
buffer_set_content(buffer, controller.content)
|
||||
|
||||
-- hook serverbound callbacks
|
||||
vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, {
|
||||
group = vim.api.nvim_create_augroup("codemp-buffer-" .. args.args, { clear = true }),
|
||||
buffer = buffer,
|
||||
callback = function (_)
|
||||
controller:replace(buffer_get_content(buffer))
|
||||
vim.api.nvim_buf_attach(buffer, false, {
|
||||
on_lines = function (_, buf, tick, firstline, lastline, new_lastline, old_byte_size)
|
||||
if tick == codemp_changed_tick then return end
|
||||
print(string.format(">[%s] %s:%s|%s (%s)", tick, firstline, lastline, new_lastline, old_byte_size))
|
||||
local start_index = firstline == 0 and 0 or vim.fn.line2byte(firstline + 1) - 1
|
||||
local text = table.concat(
|
||||
vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, true),
|
||||
"\n"
|
||||
)
|
||||
if lastline ~= new_lastline then
|
||||
text = text .. "\n"
|
||||
end
|
||||
print(string.format(">delta [%d,%s,%d]", start_index, text, start_index + old_byte_size - 1))
|
||||
controller:delta(start_index, text, start_index + old_byte_size - 1)
|
||||
end
|
||||
})
|
||||
|
||||
-- hook clientbound callbacks
|
||||
register_async_waker(args.args, function()
|
||||
vim.schedule(function()
|
||||
buffer_set_content(buffer, controller.content)
|
||||
end)
|
||||
register_controller_handler(args.args, controller, function(event)
|
||||
codemp_changed_tick = vim.api.nvim_buf_get_changedtick(buffer) + 1
|
||||
local start = controller:byte2rowcol(event.start)
|
||||
local finish = controller:byte2rowcol(event.finish)
|
||||
print(string.format(
|
||||
"buf_set_text(%s,%s, %s,%s, '%s')",
|
||||
start.row, start.col, finish.row, finish.col, vim.inspect(split_without_trim(event.content, "\n"))
|
||||
))
|
||||
vim.api.nvim_buf_set_text(
|
||||
buffer, start.row, start.col, finish.row, finish.col,
|
||||
split_without_trim(event.content, "\n")
|
||||
)
|
||||
end)
|
||||
|
||||
print(" ++ joined workspace " .. args.args)
|
||||
|
@ -160,5 +212,6 @@ return {
|
|||
utils = {
|
||||
buffer = buffer_get_content,
|
||||
cursor = cursor_position,
|
||||
split = split_without_trim,
|
||||
}
|
||||
}
|
||||
|
|
76
src/lib.rs
76
src/lib.rs
|
@ -13,6 +13,16 @@ impl From::<LuaCodempError> for LuaError {
|
|||
}
|
||||
}
|
||||
|
||||
fn byte_to_rowcol(text: &str, index: usize) -> CodempRowCol {
|
||||
let lines_before = text[..index].split('\n').count() - 1;
|
||||
let chars_before = text[..index].split('\n').last().unwrap_or_default().len();
|
||||
|
||||
CodempRowCol {
|
||||
row: lines_before as i32,
|
||||
col: chars_before as i32,
|
||||
}
|
||||
}
|
||||
|
||||
fn cursor_to_table(lua: &Lua, cur: CodempCursorEvent) -> LuaResult<LuaTable> {
|
||||
let pos = cur.position.unwrap_or_default();
|
||||
let start = lua.create_table()?;
|
||||
|
@ -96,45 +106,6 @@ impl LuaUserData for LuaCursorController {
|
|||
}
|
||||
}
|
||||
|
||||
// #[derive(derive_more::From)]
|
||||
// struct LuaCursorEvent(CodempCursorEvent);
|
||||
// impl LuaUserData for LuaCursorEvent {
|
||||
// fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
// fields.add_field_method_get("user", |_, this| Ok(this.0.user));
|
||||
// fields.add_field_method_set("user", |_, this, val| Ok(this.0.user = val));
|
||||
//
|
||||
// fields.add_field_method_get("user", |_, this| Ok(this.0.user));
|
||||
// fields.add_field_method_set("user", |_, this, val| Ok(this.0.user = val));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[derive(derive_more::From)]
|
||||
// struct LuaCursorPosition(CodempCursorPosition);
|
||||
// impl LuaUserData for LuaCursorPosition {
|
||||
// fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
// fields.add_field_method_get("buffer", |_, this| Ok(this.0.buffer));
|
||||
// fields.add_field_method_set("buffer", |_, this, val| Ok(this.0.buffer = val));
|
||||
//
|
||||
// fields.add_field_method_get("start", |_, this| Ok(this.0.start.into()));
|
||||
// fields.add_field_method_set("start", |_, this, (val,):(LuaRowCol,)| Ok(this.0.start = Some(val.0)));
|
||||
//
|
||||
// fields.add_field_method_get("end", |_, this| Ok(this.0.end.unwrap_or_default()));
|
||||
// fields.add_field_method_set("end", |_, this, val| Ok(this.0.end = Some(val)));
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #[derive(derive_more::From)]
|
||||
// struct LuaRowCol(CodempRowCol);
|
||||
// impl LuaUserData for LuaRowCol {
|
||||
// fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
// fields.add_field_method_get("row", |_, this| Ok(this.0.col));
|
||||
// fields.add_field_method_set("row", |_, this, val| Ok(this.0.col = val));
|
||||
//
|
||||
// fields.add_field_method_get("col", |_, this| Ok(this.0.col));
|
||||
// fields.add_field_method_set("col", |_, this, val| Ok(this.0.col = val));
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
/// create a new buffer in current workspace
|
||||
|
@ -161,7 +132,7 @@ impl LuaUserData for LuaBufferController {
|
|||
methods.add_method("delta", |_, this, (start, txt, end):(usize, String, usize)| {
|
||||
match this.0.delta(start, &txt, end) {
|
||||
Some(op) => Ok(this.0.send(op).map_err(LuaCodempError::from)?),
|
||||
None => Ok(()),
|
||||
None => Err(LuaError::RuntimeError("wtf".into())),
|
||||
}
|
||||
});
|
||||
methods.add_method("replace", |_, this, txt:String| {
|
||||
|
@ -184,6 +155,10 @@ impl LuaUserData for LuaBufferController {
|
|||
.map_err(LuaCodempError::from)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
methods.add_method("byte2rowcol", |_, this, (byte,)| {
|
||||
Ok(LuaRowCol(byte_to_rowcol(&this.0.content(), byte)))
|
||||
});
|
||||
}
|
||||
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
|
@ -205,6 +180,15 @@ impl LuaUserData for LuaTextChange {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::From)]
|
||||
struct LuaRowCol(CodempRowCol);
|
||||
impl LuaUserData for LuaRowCol {
|
||||
fn add_fields<'lua, F: LuaUserDataFields<'lua, Self>>(fields: &mut F) {
|
||||
fields.add_field_method_get("row", |_, this| Ok(this.0.row));
|
||||
fields.add_field_method_get("col", |_, this| Ok(this.0.col));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[mlua::lua_module]
|
||||
|
@ -216,5 +200,17 @@ fn libcodemp_nvim(lua: &Lua) -> LuaResult<LuaTable> {
|
|||
exports.set("attach", lua.create_function(attach)?)?;
|
||||
exports.set("get_cursor", lua.create_function(get_cursor)?)?;
|
||||
exports.set("get_buffer", lua.create_function(get_buffer)?)?;
|
||||
exports.set("byte2rowcol",lua.create_function(byte2rowcol)?)?;
|
||||
Ok(exports)
|
||||
}
|
||||
|
||||
|
||||
// TODO this is wasteful because, just to calculate two indices, we clone a
|
||||
// potentially big string. this is necessary because vim doesn't provide an
|
||||
// api equivalent of byte2line (we need to specify arbitrary buffers).
|
||||
fn byte2rowcol(_: &Lua, (txt, index): (String, usize)) -> LuaResult<(usize, usize)> {
|
||||
let lines = txt[..index].split('\n');
|
||||
let col = lines.clone().last().unwrap_or("").len();
|
||||
let row = lines.count() - 1;
|
||||
Ok((row, col))
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue