feat: more capillar but broken text changes

requires more help from codemp lib
This commit is contained in:
əlemi 2023-09-04 03:22:57 +02:00
parent 773d76e7f8
commit ec873ee8b4
2 changed files with 121 additions and 72 deletions

View file

@ -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,
}
}

View file

@ -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))
}