2023-08-21 04:07:25 +02:00
|
|
|
local codemp = require("libcodemp_nvim")
|
|
|
|
|
2023-11-16 06:54:32 +01:00
|
|
|
local codemp_changed_tick = 0 -- TODO this doesn't work when events are coalesced
|
2023-09-04 03:22:57 +02:00
|
|
|
|
2023-11-17 06:02:55 +01:00
|
|
|
local function register_controller_handler(target, controller, handler, delay)
|
2023-09-04 03:22:57 +02:00
|
|
|
local async = vim.loop.new_async(function()
|
|
|
|
while true do
|
|
|
|
local event = controller:try_recv()
|
|
|
|
if event == nil then break end
|
|
|
|
vim.schedule(function() handler(event) end)
|
2023-08-21 04:07:25 +02:00
|
|
|
end
|
2023-09-04 03:22:57 +02:00
|
|
|
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
|
2023-11-17 06:02:55 +01:00
|
|
|
vim.loop.new_thread({}, function(_async, _target, _delay)
|
|
|
|
if _delay ~= nil then vim.loop.sleep(_delay) end
|
2023-09-04 03:22:57 +02:00
|
|
|
local _codemp = require("libcodemp_nvim")
|
|
|
|
local _controller = _target ~= nil and _codemp.get_buffer(_target) or _codemp.get_cursor()
|
2023-08-21 04:07:25 +02:00
|
|
|
while true do
|
2023-09-04 03:22:57 +02:00
|
|
|
_controller:poll()
|
2023-08-21 04:07:25 +02:00
|
|
|
_async:send()
|
|
|
|
end
|
2023-11-17 06:02:55 +01:00
|
|
|
end, async, target, delay)
|
2023-08-21 04:07:25 +02:00
|
|
|
end
|
|
|
|
|
2023-09-04 03:22:57 +02:00
|
|
|
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
|
|
|
|
|
2023-08-21 04:07:25 +02:00
|
|
|
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] } }
|
|
|
|
elseif x[1][1] > x[2][1] then
|
|
|
|
return { { x[2][1], x[2][2] }, { x[1][1], x[1][2] } }
|
|
|
|
elseif x[1][2] < x[2][2] then
|
|
|
|
return { { x[1][1], x[1][2] }, { x[2][1], x[2][2] } }
|
|
|
|
else
|
|
|
|
return { { x[2][1], x[2][2] }, { x[1][1], x[1][2] } }
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
local function cursor_position()
|
|
|
|
local mode = vim.api.nvim_get_mode().mode
|
2023-09-04 03:22:57 +02:00
|
|
|
if mode == "v" then
|
2023-08-21 04:07:25 +02:00
|
|
|
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 } })
|
2023-09-04 03:22:57 +02:00
|
|
|
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
|
2023-08-21 04:07:25 +02:00
|
|
|
else
|
|
|
|
local win = vim.api.nvim_get_current_win()
|
|
|
|
local cur = vim.api.nvim_win_get_cursor(win)
|
|
|
|
return order_tuples({ { cur[1]-1, cur[2] }, { cur[1]-1, cur[2]+1 } })
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-08-22 12:51:35 +02:00
|
|
|
local function buffer_get_content(buf)
|
|
|
|
if buf == nil then
|
|
|
|
buf = vim.api.nvim_get_current_buf()
|
|
|
|
end
|
|
|
|
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
|
|
|
return table.concat(lines, '\n')
|
|
|
|
end
|
|
|
|
|
2023-11-17 03:55:45 +01:00
|
|
|
-- local function buffer_set_content(buf, content)
|
|
|
|
-- local lines = split_without_trim(content, "\n")
|
|
|
|
-- vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
|
|
|
|
-- end
|
|
|
|
|
|
|
|
local function buffer_replace_content(buffer, first, last, content)
|
|
|
|
-- TODO send help it works but why is lost knowledge
|
|
|
|
local start_row = vim.fn.byte2line(first + 1) - 1
|
|
|
|
if start_row < 0 then start_row = 0 end
|
|
|
|
local start_row_byte = vim.fn.line2byte(start_row + 1) - 1
|
|
|
|
if start_row_byte < 0 then start_row_byte = 0 end
|
|
|
|
local end_row = vim.fn.byte2line(last + 1) - 1
|
|
|
|
if end_row < 0 then end_row = 0 end
|
|
|
|
local end_row_byte = vim.fn.line2byte(end_row + 1) - 1
|
|
|
|
if end_row_byte < 0 then end_row_byte = 0 end
|
|
|
|
vim.api.nvim_buf_set_text(
|
|
|
|
buffer,
|
|
|
|
start_row,
|
|
|
|
first - start_row_byte,
|
|
|
|
end_row,
|
|
|
|
last - end_row_byte,
|
|
|
|
vim.fn.split(content, '\n', true)
|
|
|
|
)
|
2023-08-22 12:51:35 +02:00
|
|
|
end
|
|
|
|
|
2023-08-21 04:07:25 +02:00
|
|
|
local function multiline_highlight(buf, ns, group, start, fini)
|
2023-09-06 00:09:38 +02:00
|
|
|
for i=start.row,fini.row do
|
|
|
|
if i == start.row and i == fini.row then
|
|
|
|
local fini_col = fini.col
|
|
|
|
if start.col == fini.col then fini_col = fini_col + 1 end
|
|
|
|
vim.api.nvim_buf_add_highlight(buf, ns, group, i, start.col, fini_col)
|
|
|
|
elseif i == start.row then
|
|
|
|
vim.api.nvim_buf_add_highlight(buf, ns, group, i, start.col, -1)
|
|
|
|
elseif i == fini.row then
|
|
|
|
vim.api.nvim_buf_add_highlight(buf, ns, group, i, 0, fini.col)
|
2023-08-21 04:07:25 +02:00
|
|
|
else
|
|
|
|
vim.api.nvim_buf_add_highlight(buf, ns, group, i, 0, -1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2023-09-05 02:51:15 +02:00
|
|
|
local buffer_mappings = {}
|
2023-09-06 00:09:38 +02:00
|
|
|
local buffer_mappings_reverse = {} -- TODO maybe not???
|
2023-09-05 03:05:20 +02:00
|
|
|
local user_mappings = {}
|
2023-11-10 05:35:18 +01:00
|
|
|
local available_colors = { -- TODO these are definitely not portable!
|
2023-09-05 03:05:20 +02:00
|
|
|
"ErrorMsg",
|
|
|
|
"WarningMsg",
|
|
|
|
"MatchParen",
|
|
|
|
"SpecialMode",
|
2023-11-10 05:35:18 +01:00
|
|
|
"CmpItemKindFunction",
|
|
|
|
"CmpItemKindValue",
|
|
|
|
"CmpItemKindInterface",
|
2023-09-05 03:05:20 +02:00
|
|
|
}
|
2023-09-05 02:51:15 +02:00
|
|
|
|
2023-08-21 04:07:25 +02:00
|
|
|
vim.api.nvim_create_user_command(
|
|
|
|
"Connect",
|
|
|
|
function (args)
|
|
|
|
codemp.connect(#args.args > 0 and args.args or nil)
|
|
|
|
print(" ++ connected")
|
|
|
|
end,
|
|
|
|
{ nargs = "?" }
|
|
|
|
)
|
|
|
|
|
|
|
|
vim.api.nvim_create_user_command(
|
|
|
|
"Join",
|
|
|
|
function (args)
|
|
|
|
local controller = codemp.join(args.args)
|
|
|
|
|
|
|
|
-- hook serverbound callbacks
|
2023-09-04 03:22:57 +02:00
|
|
|
vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI", "ModeChanged"}, {
|
2023-08-22 12:51:35 +02:00
|
|
|
group = vim.api.nvim_create_augroup("codemp-workspace-" .. args.args, { clear = true }),
|
2023-08-21 04:07:25 +02:00
|
|
|
callback = function (_)
|
|
|
|
local cur = cursor_position()
|
2023-09-05 02:51:15 +02:00
|
|
|
local buf = vim.api.nvim_get_current_buf()
|
|
|
|
if buffer_mappings[buf] ~= nil then
|
|
|
|
controller:send(buffer_mappings[buf], cur[1][1], cur[1][2], cur[2][1], cur[2][2])
|
|
|
|
end
|
2023-08-21 04:07:25 +02:00
|
|
|
end
|
|
|
|
})
|
|
|
|
|
|
|
|
-- hook clientbound callbacks
|
2023-09-04 03:22:57 +02:00
|
|
|
register_controller_handler(nil, controller, function(event)
|
2023-09-05 03:05:20 +02:00
|
|
|
if user_mappings[event.user] == nil then
|
|
|
|
user_mappings[event.user] = {
|
|
|
|
ns = vim.api.nvim_create_namespace("codemp-cursor-" .. event.user),
|
|
|
|
hi = available_colors[ math.random( #available_colors ) ],
|
|
|
|
}
|
|
|
|
end
|
2023-09-06 00:09:38 +02:00
|
|
|
local buffer = buffer_mappings_reverse[event.position.buffer]
|
2023-09-05 23:59:20 +02:00
|
|
|
if buffer ~= nil then
|
|
|
|
vim.api.nvim_buf_clear_namespace(buffer, user_mappings[event.user].ns, 0, -1)
|
|
|
|
multiline_highlight(
|
|
|
|
buffer,
|
|
|
|
user_mappings[event.user].ns,
|
|
|
|
user_mappings[event.user].hi,
|
|
|
|
event.position.start,
|
|
|
|
event.position.finish
|
|
|
|
)
|
|
|
|
end
|
2023-08-21 04:07:25 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
print(" ++ joined workspace " .. args.args)
|
|
|
|
end,
|
|
|
|
{ nargs = 1 }
|
|
|
|
)
|
|
|
|
|
2023-08-22 12:51:35 +02:00
|
|
|
vim.api.nvim_create_user_command(
|
|
|
|
"Create",
|
|
|
|
function (args)
|
|
|
|
local content = nil
|
|
|
|
if args.bang then
|
|
|
|
local buf = vim.api.nvim_get_current_buf()
|
|
|
|
content = buffer_get_content(buf)
|
|
|
|
end
|
|
|
|
codemp.create(args.args, content)
|
|
|
|
|
|
|
|
print(" ++ created buffer " .. args.args)
|
|
|
|
end,
|
|
|
|
{ nargs = 1, bang = true }
|
|
|
|
)
|
|
|
|
|
2023-08-21 04:07:25 +02:00
|
|
|
vim.api.nvim_create_user_command(
|
|
|
|
"Attach",
|
|
|
|
function (args)
|
2023-08-22 12:51:35 +02:00
|
|
|
local controller = codemp.attach(args.args)
|
2023-09-04 03:22:57 +02:00
|
|
|
|
2023-11-10 05:35:18 +01:00
|
|
|
-- TODO map name to uuid
|
|
|
|
|
2023-08-22 12:51:35 +02:00
|
|
|
local buffer = vim.api.nvim_get_current_buf()
|
2023-09-05 02:51:15 +02:00
|
|
|
buffer_mappings[buffer] = args.args
|
2023-09-06 00:09:38 +02:00
|
|
|
buffer_mappings_reverse[args.args] = buffer
|
2023-08-22 12:51:35 +02:00
|
|
|
|
|
|
|
-- hook serverbound callbacks
|
2023-09-04 03:22:57 +02:00
|
|
|
vim.api.nvim_buf_attach(buffer, false, {
|
|
|
|
on_lines = function (_, buf, tick, firstline, lastline, new_lastline, old_byte_size)
|
2023-11-16 06:54:32 +01:00
|
|
|
if tick <= codemp_changed_tick then return end
|
2023-11-16 22:07:09 +01:00
|
|
|
local start = vim.api.nvim_buf_get_offset(buf, firstline)
|
|
|
|
local content = table.concat(vim.api.nvim_buf_get_lines(buf, firstline, new_lastline, false), '\n')
|
2023-11-17 03:44:57 +01:00
|
|
|
if start == -1 then start = 0 end
|
2023-11-16 22:07:09 +01:00
|
|
|
if new_lastline < lastline then old_byte_size = old_byte_size + 1 end
|
|
|
|
controller:send(start, start + old_byte_size - 1, content)
|
2023-08-22 12:51:35 +02:00
|
|
|
end
|
|
|
|
})
|
|
|
|
|
2023-11-17 06:02:55 +01:00
|
|
|
-- This is an ugly as hell fix: basically we receive all operations real fast at the start
|
|
|
|
-- so the buffer changes rapidly and it messes up tracking our delta/diff state and we
|
|
|
|
-- get borked translated TextChanges (the underlying CRDT is fine)
|
|
|
|
-- basically delay a bit so that it has time to sync and we can then get "normal slow" changes
|
|
|
|
-- vim.loop.sleep(200) -- moved inside poller thread to at least not block ui
|
|
|
|
|
2023-08-22 12:51:35 +02:00
|
|
|
-- hook clientbound callbacks
|
2023-09-04 03:22:57 +02:00
|
|
|
register_controller_handler(args.args, controller, function(event)
|
|
|
|
codemp_changed_tick = vim.api.nvim_buf_get_changedtick(buffer) + 1
|
2023-11-17 03:55:45 +01:00
|
|
|
buffer_replace_content(buffer, event.first, event.last, event.content)
|
2023-11-17 06:02:55 +01:00
|
|
|
end, 200) -- delay by 200 ms as ugly fix
|
2023-08-22 12:51:35 +02:00
|
|
|
|
|
|
|
print(" ++ joined workspace " .. args.args)
|
2023-08-21 04:07:25 +02:00
|
|
|
end,
|
|
|
|
{ nargs = 1 }
|
|
|
|
)
|
|
|
|
|
2023-11-10 05:33:20 +01:00
|
|
|
-- TODO nvim docs say that we should stop all threads before exiting nvim
|
|
|
|
-- but we like to live dangerously (:
|
|
|
|
vim.loop.new_thread({}, function()
|
|
|
|
local _codemp = require("libcodemp_nvim")
|
|
|
|
local logger = _codemp.setup_tracing()
|
|
|
|
while true do
|
|
|
|
print(logger:recv())
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2023-08-22 12:51:35 +02:00
|
|
|
return {
|
|
|
|
lib = codemp,
|
|
|
|
utils = {
|
|
|
|
buffer = buffer_get_content,
|
|
|
|
cursor = cursor_position,
|
2023-09-04 03:22:57 +02:00
|
|
|
split = split_without_trim,
|
2023-08-22 12:51:35 +02:00
|
|
|
}
|
|
|
|
}
|