codemp-nvim/src/codemp.lua

254 lines
7.6 KiB
Lua
Raw Normal View History

local codemp = require("libcodemp_nvim")
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
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 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] } }
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
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)
return order_tuples({ { cur[1]-1, cur[2] }, { cur[1]-1, cur[2]+1 } })
end
end
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
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 multiline_highlight(buf, ns, group, start, fini)
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)
else
vim.api.nvim_buf_add_highlight(buf, ns, group, i, 0, -1)
end
end
end
local buffer_mappings = {}
local buffer_mappings_reverse = {} -- TODO maybe not???
local user_mappings = {}
local available_colors = { -- TODO these are definitely not portable!
"ErrorMsg",
"WarningMsg",
"MatchParen",
"SpecialMode",
"CmpItemKindFunction",
"CmpItemKindValue",
"CmpItemKindInterface",
}
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
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()
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
end
})
-- hook clientbound callbacks
register_controller_handler(nil, controller, function(event)
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
local buffer = buffer_mappings_reverse[event.position.buffer]
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
end)
print(" ++ joined workspace " .. args.args)
end,
{ nargs = 1 }
)
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 }
)
vim.api.nvim_create_user_command(
"Attach",
function (args)
local controller = codemp.attach(args.args)
-- TODO map name to uuid
local buffer = vim.api.nvim_get_current_buf()
buffer_mappings[buffer] = args.args
buffer_mappings_reverse[args.args] = buffer
buffer_set_content(buffer, controller.content)
-- hook serverbound callbacks
vim.api.nvim_buf_attach(buffer, false, {
on_lines = function (_, buf, tick, firstline, lastline, new_lastline, old_byte_size)
local content = buffer_get_content(buf)
controller:send(0, #content - 1, content)
2023-09-04 18:35:24 +02:00
-- 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_controller_handler(args.args, controller, function(event)
codemp_changed_tick = vim.api.nvim_buf_get_changedtick(buffer) + 1
2023-09-04 18:35:24 +02:00
-- local start = event.start
-- local finish = 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")
-- )
buffer_set_content(buffer, event)
end)
print(" ++ joined workspace " .. args.args)
end,
{ nargs = 1 }
)
-- 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)
return {
lib = codemp,
utils = {
buffer = buffer_get_content,
cursor = cursor_position,
split = split_without_trim,
}
}