mirror of
https://github.com/hexedtech/codemp-nvim.git
synced 2024-11-22 15:34:53 +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 codemp = require("libcodemp_nvim")
|
||||||
|
|
||||||
local function register_async_waker(target, cb)
|
local codemp_changed_tick = nil -- TODO this doesn't work when events are coalesced
|
||||||
local async = vim.loop.new_async(cb)
|
|
||||||
vim.loop.new_thread(function(_async, _target)
|
local function register_controller_handler(target, controller, handler)
|
||||||
local _codemp = require("libcodemp_nvim")
|
local async = vim.loop.new_async(function()
|
||||||
local _cntrl
|
|
||||||
if _target ~= nil then
|
|
||||||
_cntrl = _codemp.get_buffer(_target)
|
|
||||||
else
|
|
||||||
_cntrl = _codemp.get_cursor()
|
|
||||||
end
|
|
||||||
while true do
|
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()
|
_async:send()
|
||||||
end
|
end
|
||||||
end, async, target)
|
end, async, target)
|
||||||
end
|
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...
|
local function order_tuples(x) -- TODO send help...
|
||||||
if x[1][1] < x[2][1] then
|
if x[1][1] < x[2][1] then
|
||||||
return { { x[1][1], x[1][2] }, { x[2][1], x[2][2] } }
|
return { { x[1][1], x[1][2] }, { x[2][1], x[2][2] } }
|
||||||
|
@ -31,10 +61,20 @@ end
|
||||||
|
|
||||||
local function cursor_position()
|
local function cursor_position()
|
||||||
local mode = vim.api.nvim_get_mode().mode
|
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 _, ls, cs = unpack(vim.fn.getpos('v'))
|
||||||
local _, le, ce = unpack(vim.fn.getpos('.'))
|
local _, le, ce = unpack(vim.fn.getpos('.'))
|
||||||
return order_tuples({ { ls-1, cs-1 }, { le-1, ce } })
|
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
|
else
|
||||||
local win = vim.api.nvim_get_current_win()
|
local win = vim.api.nvim_get_current_win()
|
||||||
local cur = vim.api.nvim_win_get_cursor(win)
|
local cur = vim.api.nvim_win_get_cursor(win)
|
||||||
|
@ -51,7 +91,7 @@ local function buffer_get_content(buf)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function buffer_set_content(buf, content)
|
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)
|
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -86,7 +126,7 @@ vim.api.nvim_create_user_command(
|
||||||
local ns = vim.api.nvim_create_namespace("codemp-cursors")
|
local ns = vim.api.nvim_create_namespace("codemp-cursors")
|
||||||
|
|
||||||
-- hook serverbound callbacks
|
-- 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 }),
|
group = vim.api.nvim_create_augroup("codemp-workspace-" .. args.args, { clear = true }),
|
||||||
callback = function (_)
|
callback = function (_)
|
||||||
local cur = cursor_position()
|
local cur = cursor_position()
|
||||||
|
@ -95,15 +135,9 @@ vim.api.nvim_create_user_command(
|
||||||
})
|
})
|
||||||
|
|
||||||
-- hook clientbound callbacks
|
-- hook clientbound callbacks
|
||||||
register_async_waker(nil, function()
|
register_controller_handler(nil, controller, function(event)
|
||||||
while true do
|
vim.api.nvim_buf_clear_namespace(buffer, ns, 0, -1)
|
||||||
local event = controller:try_recv()
|
multiline_highlight(buffer, ns, "ErrorMsg", event.start, event.finish)
|
||||||
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
|
|
||||||
end)
|
end)
|
||||||
|
|
||||||
print(" ++ joined workspace " .. args.args)
|
print(" ++ joined workspace " .. args.args)
|
||||||
|
@ -130,24 +164,42 @@ vim.api.nvim_create_user_command(
|
||||||
"Attach",
|
"Attach",
|
||||||
function (args)
|
function (args)
|
||||||
local controller = codemp.attach(args.args)
|
local controller = codemp.attach(args.args)
|
||||||
|
|
||||||
local buffer = vim.api.nvim_get_current_buf()
|
local buffer = vim.api.nvim_get_current_buf()
|
||||||
|
|
||||||
buffer_set_content(buffer, controller.content)
|
buffer_set_content(buffer, controller.content)
|
||||||
|
|
||||||
-- hook serverbound callbacks
|
-- hook serverbound callbacks
|
||||||
vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, {
|
vim.api.nvim_buf_attach(buffer, false, {
|
||||||
group = vim.api.nvim_create_augroup("codemp-buffer-" .. args.args, { clear = true }),
|
on_lines = function (_, buf, tick, firstline, lastline, new_lastline, old_byte_size)
|
||||||
buffer = buffer,
|
if tick == codemp_changed_tick then return end
|
||||||
callback = function (_)
|
print(string.format(">[%s] %s:%s|%s (%s)", tick, firstline, lastline, new_lastline, old_byte_size))
|
||||||
controller:replace(buffer_get_content(buffer))
|
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
|
end
|
||||||
})
|
})
|
||||||
|
|
||||||
-- hook clientbound callbacks
|
-- hook clientbound callbacks
|
||||||
register_async_waker(args.args, function()
|
register_controller_handler(args.args, controller, function(event)
|
||||||
vim.schedule(function()
|
codemp_changed_tick = vim.api.nvim_buf_get_changedtick(buffer) + 1
|
||||||
buffer_set_content(buffer, controller.content)
|
local start = controller:byte2rowcol(event.start)
|
||||||
end)
|
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)
|
end)
|
||||||
|
|
||||||
print(" ++ joined workspace " .. args.args)
|
print(" ++ joined workspace " .. args.args)
|
||||||
|
@ -160,5 +212,6 @@ return {
|
||||||
utils = {
|
utils = {
|
||||||
buffer = buffer_get_content,
|
buffer = buffer_get_content,
|
||||||
cursor = cursor_position,
|
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> {
|
fn cursor_to_table(lua: &Lua, cur: CodempCursorEvent) -> LuaResult<LuaTable> {
|
||||||
let pos = cur.position.unwrap_or_default();
|
let pos = cur.position.unwrap_or_default();
|
||||||
let start = lua.create_table()?;
|
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
|
/// 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)| {
|
methods.add_method("delta", |_, this, (start, txt, end):(usize, String, usize)| {
|
||||||
match this.0.delta(start, &txt, end) {
|
match this.0.delta(start, &txt, end) {
|
||||||
Some(op) => Ok(this.0.send(op).map_err(LuaCodempError::from)?),
|
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| {
|
methods.add_method("replace", |_, this, txt:String| {
|
||||||
|
@ -184,6 +155,10 @@ impl LuaUserData for LuaBufferController {
|
||||||
.map_err(LuaCodempError::from)?;
|
.map_err(LuaCodempError::from)?;
|
||||||
Ok(())
|
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) {
|
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]
|
#[mlua::lua_module]
|
||||||
|
@ -216,5 +200,17 @@ fn libcodemp_nvim(lua: &Lua) -> LuaResult<LuaTable> {
|
||||||
exports.set("attach", lua.create_function(attach)?)?;
|
exports.set("attach", lua.create_function(attach)?)?;
|
||||||
exports.set("get_cursor", lua.create_function(get_cursor)?)?;
|
exports.set("get_cursor", lua.create_function(get_cursor)?)?;
|
||||||
exports.set("get_buffer", lua.create_function(get_buffer)?)?;
|
exports.set("get_buffer", lua.create_function(get_buffer)?)?;
|
||||||
|
exports.set("byte2rowcol",lua.create_function(byte2rowcol)?)?;
|
||||||
Ok(exports)
|
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