2021-07-04 16:12:17 -05:00
|
|
|
local parsers = require "nvim-treesitter.parsers"
|
|
|
|
|
local queries = require "nvim-treesitter.query"
|
|
|
|
|
local tsutils = require "nvim-treesitter.ts_utils"
|
2020-10-13 01:02:30 +02:00
|
|
|
|
2022-01-19 02:58:07 +06:00
|
|
|
local function get_first_node_at_line(root, lnum)
|
|
|
|
|
local col = vim.fn.indent(lnum)
|
|
|
|
|
return root:descendant_for_range(lnum - 1, col, lnum - 1, col)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local function get_last_node_at_line(root, lnum)
|
|
|
|
|
local col = #vim.fn.getline(lnum) - 1
|
|
|
|
|
return root:descendant_for_range(lnum - 1, col, lnum - 1, col)
|
|
|
|
|
end
|
|
|
|
|
|
2020-10-13 01:02:30 +02:00
|
|
|
local M = {}
|
|
|
|
|
|
2021-03-30 08:18:24 -05:00
|
|
|
local get_indents = tsutils.memoize_by_buf_tick(function(bufnr, root, lang)
|
2022-01-19 02:22:29 +06:00
|
|
|
local map = {
|
|
|
|
|
auto = {},
|
|
|
|
|
indent = {},
|
|
|
|
|
dedent = {},
|
|
|
|
|
branch = {},
|
|
|
|
|
ignore = {},
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-19 02:58:07 +06:00
|
|
|
for name, node, metadata in queries.iter_captures(bufnr, "indents", root, lang) do
|
|
|
|
|
map[name][node:id()] = metadata or {}
|
2020-10-13 23:55:37 +02:00
|
|
|
end
|
2020-10-13 01:02:30 +02:00
|
|
|
|
2022-01-19 02:22:29 +06:00
|
|
|
return map
|
2021-03-30 08:18:24 -05:00
|
|
|
end, {
|
|
|
|
|
-- Memoize by bufnr and lang together.
|
2022-01-18 21:17:26 +06:00
|
|
|
key = function(bufnr, root, lang)
|
|
|
|
|
return tostring(bufnr) .. root:id() .. "_" .. lang
|
2021-07-04 16:12:17 -05:00
|
|
|
end,
|
2021-03-30 08:18:24 -05:00
|
|
|
})
|
2020-10-13 01:02:30 +02:00
|
|
|
|
2022-01-18 21:17:26 +06:00
|
|
|
---@param lnum number (1-indexed)
|
2020-10-13 23:55:37 +02:00
|
|
|
function M.get_indent(lnum)
|
|
|
|
|
local parser = parsers.get_parser()
|
2021-07-04 16:12:17 -05:00
|
|
|
if not parser or not lnum then
|
|
|
|
|
return -1
|
|
|
|
|
end
|
2020-10-13 23:55:37 +02:00
|
|
|
|
2021-11-27 08:28:40 +08:00
|
|
|
-- get_root_for_position is 0-based.
|
|
|
|
|
local root, _, lang_tree = tsutils.get_root_for_position(lnum - 1, 0, parser)
|
2021-03-30 08:18:24 -05:00
|
|
|
|
|
|
|
|
-- Not likely, but just in case...
|
2021-07-04 16:12:17 -05:00
|
|
|
if not root then
|
|
|
|
|
return 0
|
|
|
|
|
end
|
2021-03-30 08:18:24 -05:00
|
|
|
|
|
|
|
|
local q = get_indents(vim.api.nvim_get_current_buf(), root, lang_tree:lang())
|
2022-01-18 21:17:26 +06:00
|
|
|
local is_empty_line = string.match(vim.fn.getline(lnum), "^%s*$") ~= nil
|
|
|
|
|
local node
|
|
|
|
|
if is_empty_line then
|
|
|
|
|
local prevlnum = vim.fn.prevnonblank(lnum)
|
2022-01-19 02:58:07 +06:00
|
|
|
node = get_last_node_at_line(root, prevlnum)
|
2022-01-18 21:17:26 +06:00
|
|
|
else
|
2022-01-19 02:58:07 +06:00
|
|
|
node = get_first_node_at_line(root, lnum)
|
2020-11-06 23:13:50 +01:00
|
|
|
end
|
2020-10-13 23:55:37 +02:00
|
|
|
|
2022-01-18 21:17:26 +06:00
|
|
|
local indent_size = vim.fn.shiftwidth()
|
|
|
|
|
local indent = 0
|
|
|
|
|
if root:start() ~= 0 then
|
|
|
|
|
-- injected tree
|
|
|
|
|
indent = vim.fn.indent(root:start() + 1)
|
2020-10-13 23:55:37 +02:00
|
|
|
end
|
2020-10-13 01:02:30 +02:00
|
|
|
|
2022-01-18 21:17:26 +06:00
|
|
|
-- tracks to ensure multiple indent levels are not applied for same line
|
|
|
|
|
local is_processed_by_row = {}
|
2020-11-06 23:13:50 +01:00
|
|
|
|
2020-10-13 23:55:37 +02:00
|
|
|
while node do
|
2022-01-18 21:17:26 +06:00
|
|
|
-- do 'autoindent' if not marked as @indent
|
2022-01-19 02:22:29 +06:00
|
|
|
if not q.indent[node:id()] and q.auto[node:id()] and node:start() < lnum - 1 and lnum - 1 <= node:end_() then
|
2022-01-18 21:17:26 +06:00
|
|
|
return -1
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-27 08:28:40 +08:00
|
|
|
-- Do not indent if we are inside an @ignore block.
|
|
|
|
|
-- If a node spans from L1,C1 to L2,C2, we know that lines where L1 < line <= L2 would
|
|
|
|
|
-- have their indentations contained by the node.
|
2022-01-19 02:22:29 +06:00
|
|
|
if not q.indent[node:id()] and q.ignore[node:id()] and node:start() < lnum - 1 and lnum - 1 <= node:end_() then
|
2022-01-18 21:17:26 +06:00
|
|
|
return 0
|
2021-01-08 00:52:30 +01:00
|
|
|
end
|
|
|
|
|
|
2022-01-18 21:17:26 +06:00
|
|
|
local srow, _, erow = node:range()
|
|
|
|
|
|
|
|
|
|
local is_processed = false
|
|
|
|
|
|
|
|
|
|
if
|
|
|
|
|
not is_processed_by_row[srow]
|
2022-01-19 02:22:29 +06:00
|
|
|
and ((q.branch[node:id()] and srow == lnum - 1) or (q.dedent[node:id()] and srow ~= lnum - 1))
|
2022-01-18 21:17:26 +06:00
|
|
|
then
|
|
|
|
|
indent = indent - indent_size
|
|
|
|
|
is_processed = true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- do not indent for nodes that starts-and-ends on same line and starts on target line (lnum)
|
2022-01-19 02:22:29 +06:00
|
|
|
if not is_processed_by_row[srow] and (q.indent[node:id()] and srow ~= erow and srow ~= lnum - 1) then
|
2020-11-06 23:13:50 +01:00
|
|
|
indent = indent + indent_size
|
2022-01-18 21:17:26 +06:00
|
|
|
is_processed = true
|
2020-10-13 01:02:30 +02:00
|
|
|
end
|
2021-01-08 00:52:30 +01:00
|
|
|
|
2022-01-18 21:17:26 +06:00
|
|
|
is_processed_by_row[srow] = is_processed_by_row[srow] or is_processed
|
|
|
|
|
|
2021-01-08 00:52:30 +01:00
|
|
|
node = node:parent()
|
2020-10-13 01:02:30 +02:00
|
|
|
end
|
|
|
|
|
|
2020-11-06 23:13:50 +01:00
|
|
|
return indent
|
2020-10-13 01:02:30 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local indent_funcs = {}
|
|
|
|
|
|
|
|
|
|
function M.attach(bufnr)
|
|
|
|
|
indent_funcs[bufnr] = vim.bo.indentexpr
|
2021-07-04 16:12:17 -05:00
|
|
|
vim.bo.indentexpr = "nvim_treesitter#indent()"
|
|
|
|
|
vim.api.nvim_command("au Filetype " .. vim.bo.filetype .. " setlocal indentexpr=nvim_treesitter#indent()")
|
2020-10-13 01:02:30 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function M.detach(bufnr)
|
|
|
|
|
vim.bo.indentexpr = indent_funcs[bufnr]
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return M
|