plugin.lua
Branches > main > f198332 > files / nvim / lua / parinfer / plugin.lua
--- Modified version of https://github.com/gpanders/nvim-parinfer
--- See ./LICENSE
vim.g.parinfer_mode = "smart"
vim.g.parinfer_enabled = true
vim.g.parinfer_force_balance = false
vim.g.parinfer_comment_chars = {";"}
local api = vim.api
local diff = vim.text.diff or vim.diff
local parinfer = require("parinfer.parinfer")
local modes = {indent = parinfer.indentMode,
paren = parinfer.parenMode,
smart = parinfer.smartMode}
local ns = api.nvim_create_namespace("parinfer")
local state = setmetatable({}, {__index = function(t, k)
t[k] = {}
return t[k]
end})
-- Returns true if 'val' is truthy, and not 0, otherwise returns false
-- 0 and 1 are commonly used in vim functions for true/false, but 0 is a truthy value in Lua
local function normalize_bool(val)
if type(val) == "boolean" or val == nil then
return val
else
return val ~= 0
end
end
-- Looks up and returns 'opt' in the buffer-local and global option tables vim.b and vim.g
local function get_option(opt)
local b = vim.b[opt]
if b == nil then
return vim.g[opt]
else
return b
end
end
local function invoke_parinfer(bufnr, text, lnum, col)
local prev_lnum, prev_col = unpack(state[bufnr]["prev-cursor"])
local changes = state[bufnr].changes
local request = {commentChars = get_option("parinfer_comment_chars"),
prevCursorLine = prev_lnum,
prevCursorX = prev_col,
cursorLine = lnum,
cursorX = col + 1,
changes = changes,
forceBalance = normalize_bool(get_option("parinfer_force_balance"))}
local response = modes[get_option("parinfer_mode")](text, request)
state[bufnr].changes = {}
return response
end
local function build_replacement_lines(new_lines, start_b, count_b)
local t = {}
local i = 0
for num = start_b, start_b + count_b do
local line = new_lines[num]
if line ~= nil then
i = i + 1
t[i] = line
end
end
return t
end
local function apply_hunk(bufnr, new_lines, hunk)
local start_a, count_a, start_b, count_b = unpack(hunk)
api.nvim_buf_set_lines(bufnr, start_a - 1, start_a + count_a, false, build_replacement_lines(new_lines, start_b, count_b))
end
local function update_buffer(bufnr, old_contents, new_contents)
api.nvim_command("silent! undojoin")
state[bufnr].locked = true
local new_lines = vim.split(new_contents, "\n")
local hunks = diff(old_contents, new_contents, {result_type = "indices"})
for _, hunk in ipairs(hunks) do
apply_hunk(bufnr, new_lines, hunk)
end
return vim.schedule(function()
state[bufnr].locked = false
end)
end
local function highlight_error(bufnr, err)
api.nvim_buf_clear_namespace(bufnr, ns, 0, -1)
if err then
local lnum, col = err.lineNo - 1, err.x - 1
return vim.highlight.range(bufnr, ns, "Error", {lnum, col}, {lnum, col + 1}, "c")
end
end
local function is_undo_leaf()
local undotree = vim.fn.undotree()
local seq_cur = undotree["seq_cur"]
local seq_last = undotree["seq_last"]
return seq_cur == seq_last
end
local function should_run(bufnr)
return normalize_bool(get_option("parinfer_enabled"))
and not state[bufnr].locked
and not vim.o.paste
and not vim.bo.readonly
and vim.bo.modifiable
and vim.b[bufnr].changedtick ~= state[bufnr].changedtick
and is_undo_leaf()
end
local elapsed_times = setmetatable({}, {__index = function(t, k)
t[k] = {}
return rawget(t, k)
end})
local function process_buffer(bufnr)
if should_run(bufnr) then
local start = vim.loop.hrtime()
local winnr = api.nvim_get_current_win()
local cursor = api.nvim_win_get_cursor(winnr)
local lnum = cursor[1]
local col = cursor[2]
local contents = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
local text = table.concat(contents, "\n")
local response = invoke_parinfer(bufnr, text, lnum, col)
local new_lnum = response["cursorLine"]
local new_col = response["cursorX"]
state[bufnr]["changedtick"] = vim.b[bufnr].changedtick
state[bufnr]["tabstops"] = response.tabStops
state[bufnr]["prev-cursor"] = {new_lnum, new_col}
if (response.text ~= text) then
vim.schedule(function ()
update_buffer(bufnr, text, response.text)
return api.nvim_win_set_cursor(winnr, {new_lnum, (new_col - 1)})
end
)
end
highlight_error(bufnr, response.error)
return table.insert(elapsed_times[bufnr], (vim.loop.hrtime() - start))
end
end
local function slice(lines, start_row, start_col, row_offset, col_offset)
local start_row0 = start_row + 1
local start_col0 = start_col + 1
local function get_offset_or_something()
if row_offset == 0 then
return start_col0 + col_offset - 1
else
return -1
end
end
local first_line = string.sub(lines[start_row0], start_col0, get_offset_or_something())
local out = {first_line}
for i = (start_row0 + 1), ((start_row0 + row_offset) - 1) do
local line = lines[i]
table.insert(out, line)
end
if row_offset ~= 0 then
local function still_dont_know()
if ((0 < col_offset) and lines[(start_row0 + row_offset)]) then
return string.sub(lines[(start_row0 + row_offset)], 1, col_offset)
else
return ""
end
end
table.insert(out, still_dont_know())
end
return out
end
-- TODO bad function
local function on_bytes(_, bufnr, _0, start_row, start_col, _1, old_end_row, old_end_col, _2, new_end_row, new_end_col)
if (normalize_bool(get_option("parinfer_enabled")) and not state[bufnr].locked) then
local prev_contents = state[bufnr]["prev-contents"]
local contents = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
if (start_row < #prev_contents) then
local old_text = slice(prev_contents, start_row, start_col, old_end_row, old_end_col)
local new_text
if (start_row < #contents) then
new_text = slice(contents, start_row, start_col, new_end_row, new_end_col)
else
new_text = {""}
end
if not state[bufnr].changes then
state[bufnr]["changes"] = {}
else
end
table.insert(state[bufnr].changes, {oldText = table.concat(old_text, "\n"), newText = table.concat(new_text, "\n"), lineNo = (start_row + 1), x = (start_col + 1)})
else
end
state[bufnr]["prev-contents"] = contents
end
end
local function enter_buffer(bufnr)
local contents = vim.api.nvim_buf_get_lines(bufnr, 0, -1, true)
state[bufnr]["changedtick"] = -1
state[bufnr]["prev-contents"] = contents
vim.api.nvim_buf_attach(bufnr, false, {on_bytes = on_bytes})
local mode = vim.g.parinfer_mode
vim.g.parinfer_mode = "paren"
process_buffer(bufnr)
vim.g.parinfer_mode = mode
end
local function cursor_moved(bufnr)
return vim.schedule(function()
local cursor = vim.api.nvim_win_get_cursor(0)
local lnum = cursor[1]
local col = cursor[2]
state[bufnr]["prev-cursor"] = {lnum, (col + 1)}
end)
end
local function setup_buffer(bufnr)
enter_buffer(bufnr)
vim.api.nvim_exec_autocmds("User", {pattern = "Parinfer"})
local ag = vim.api.nvim_create_augroup("parinfer", {clear = true})
vim.api.nvim_create_autocmd({"TextChanged", "TextChangedI", "TextChangedP"}, {buffer = bufnr, group = ag, callback = function() process_buffer(bufnr) end})
vim.api.nvim_create_autocmd({"CursorMoved", "CursorMovedI"}, {buffer = bufnr, group = ag, callback = function() cursor_moved(bufnr) end})
end
local function should_enable()
return not vim.wo.previewwindow and vim.bo.buftype ~= "prompt"
end
--- Sets up parinfer in the current buffer
local function setup()
print("alright")
if should_enable() then
print("okay")
local bufnr = vim.api.nvim_get_current_buf()
setup_buffer(bufnr)
end
end
return {setup = setup}