mirror of
https://github.com/nvim-treesitter/nvim-treesitter.git
synced 2026-07-12 00:10:04 -04:00
refactor(indent): backport improvements from main
* replace ts_utils.memoize_by_buftick by simpler implementation * simplify construction of hash map * prefer Neovim APIs
This commit is contained in:
parent
aa44e5fc5f
commit
fa23ff4bd7
1 changed files with 71 additions and 52 deletions
|
|
@ -1,7 +1,5 @@
|
||||||
local parsers = require "nvim-treesitter.parsers"
|
|
||||||
local queries = require "nvim-treesitter.query"
|
|
||||||
local tsutils = require "nvim-treesitter.ts_utils"
|
|
||||||
local ts = vim.treesitter
|
local ts = vim.treesitter
|
||||||
|
local parsers = require "lua.nvim-treesitter.parsers"
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
|
|
@ -15,6 +13,10 @@ M.comment_parsers = {
|
||||||
phpdoc = true,
|
phpdoc = true,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local function getline(lnum)
|
||||||
|
return vim.api.nvim_buf_get_lines(0, lnum - 1, lnum, false)[1] or ""
|
||||||
|
end
|
||||||
|
|
||||||
---@param root TSNode
|
---@param root TSNode
|
||||||
---@param lnum integer
|
---@param lnum integer
|
||||||
---@param col? integer
|
---@param col? integer
|
||||||
|
|
@ -29,10 +31,18 @@ end
|
||||||
---@param col? integer
|
---@param col? integer
|
||||||
---@return TSNode
|
---@return TSNode
|
||||||
local function get_last_node_at_line(root, lnum, col)
|
local function get_last_node_at_line(root, lnum, col)
|
||||||
col = col or (#vim.fn.getline(lnum) - 1)
|
col = col or (#getline(lnum) - 1)
|
||||||
return root:descendant_for_range(lnum - 1, col, lnum - 1, col)
|
return root:descendant_for_range(lnum - 1, col, lnum - 1, col)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
---@param node TSNode
|
||||||
|
---@return number
|
||||||
|
local function node_length(node)
|
||||||
|
local _, _, start_byte = node:start()
|
||||||
|
local _, _, end_byte = node:end_()
|
||||||
|
return end_byte - start_byte
|
||||||
|
end
|
||||||
|
|
||||||
---@param bufnr integer
|
---@param bufnr integer
|
||||||
---@param node TSNode
|
---@param node TSNode
|
||||||
---@param delimiter string
|
---@param delimiter string
|
||||||
|
|
@ -52,42 +62,50 @@ local function find_delimiter(bufnr, node, delimiter)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local get_indents = tsutils.memoize_by_buf_tick(function(bufnr, root, lang)
|
---Memoize a function using hash_fn to hash the arguments.
|
||||||
|
---@generic F: function
|
||||||
|
---@param fn F
|
||||||
|
---@param hash_fn fun(...): any
|
||||||
|
---@return F
|
||||||
|
local function memoize(fn, hash_fn)
|
||||||
|
local cache = setmetatable({}, { __mode = "kv" }) ---@type table<any,any>
|
||||||
|
|
||||||
|
return function(...)
|
||||||
|
local key = hash_fn(...)
|
||||||
|
if cache[key] == nil then
|
||||||
|
local v = fn(...) ---@type any
|
||||||
|
cache[key] = v ~= nil and v or vim.NIL
|
||||||
|
end
|
||||||
|
|
||||||
|
local v = cache[key]
|
||||||
|
return v ~= vim.NIL and v or nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local get_indents = memoize(function(bufnr, root, lang)
|
||||||
local map = {
|
local map = {
|
||||||
indent = {
|
["indent.auto"] = {},
|
||||||
auto = {},
|
["indent.begin"] = {},
|
||||||
begin = {},
|
["indent.end"] = {},
|
||||||
["end"] = {},
|
["indent.dedent"] = {},
|
||||||
dedent = {},
|
["indent.branch"] = {},
|
||||||
branch = {},
|
["indent.ignore"] = {},
|
||||||
ignore = {},
|
["indent.align"] = {},
|
||||||
align = {},
|
["indent.zero"] = {},
|
||||||
zero = {},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local function split(to_split)
|
--TODO(clason): remove when dropping Nvim 0.8 compat
|
||||||
local t = {}
|
local query = (ts.query.get or ts.get_query)(lang, "indents")
|
||||||
for str in string.gmatch(to_split, "([^.]+)") do
|
for id, node, metadata in query:iter_captures(root, bufnr) do
|
||||||
table.insert(t, str)
|
if query.captures[id]:sub(1, 1) ~= "_" then
|
||||||
|
map[query.captures[id]][node:id()] = metadata or {}
|
||||||
end
|
end
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
for name, node, metadata in queries.iter_captures(bufnr, "indents", root, lang) do
|
|
||||||
local path = split(name)
|
|
||||||
-- node may contain a period so append directly.
|
|
||||||
table.insert(path, node:id())
|
|
||||||
queries.insert_to_path(map, path, metadata or {})
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return map
|
return map
|
||||||
end, {
|
end, function(bufnr, root, lang)
|
||||||
-- Memoize by bufnr and lang together.
|
return tostring(bufnr) .. root:id() .. "_" .. lang
|
||||||
key = function(bufnr, root, lang)
|
end)
|
||||||
return tostring(bufnr) .. root:id() .. "_" .. lang
|
|
||||||
end,
|
|
||||||
})
|
|
||||||
|
|
||||||
---@param lnum number (1-indexed)
|
---@param lnum number (1-indexed)
|
||||||
function M.get_indent(lnum)
|
function M.get_indent(lnum)
|
||||||
|
|
@ -97,6 +115,7 @@ function M.get_indent(lnum)
|
||||||
return -1
|
return -1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--TODO(clason): replace when dropping Nvim 0.8 compat
|
||||||
local root_lang = parsers.get_buf_lang(bufnr)
|
local root_lang = parsers.get_buf_lang(bufnr)
|
||||||
|
|
||||||
-- some languages like Python will actually have worse results when re-parsing at opened new line
|
-- some languages like Python will actually have worse results when re-parsing at opened new line
|
||||||
|
|
@ -113,7 +132,7 @@ function M.get_indent(lnum)
|
||||||
end
|
end
|
||||||
local local_root = tstree:root()
|
local local_root = tstree:root()
|
||||||
if ts.is_in_node_range(local_root, lnum - 1, 0) then
|
if ts.is_in_node_range(local_root, lnum - 1, 0) then
|
||||||
if not root or tsutils.node_length(root) >= tsutils.node_length(local_root) then
|
if not root or node_length(root) >= node_length(local_root) then
|
||||||
root = local_root
|
root = local_root
|
||||||
lang_tree = tree
|
lang_tree = tree
|
||||||
end
|
end
|
||||||
|
|
@ -126,12 +145,12 @@ function M.get_indent(lnum)
|
||||||
end
|
end
|
||||||
|
|
||||||
local q = get_indents(vim.api.nvim_get_current_buf(), root, lang_tree:lang())
|
local q = get_indents(vim.api.nvim_get_current_buf(), root, lang_tree:lang())
|
||||||
local is_empty_line = string.match(vim.fn.getline(lnum), "^%s*$") ~= nil
|
local is_empty_line = string.match(getline(lnum), "^%s*$") ~= nil
|
||||||
local node ---@type TSNode
|
local node ---@type TSNode
|
||||||
if is_empty_line then
|
if is_empty_line then
|
||||||
local prevlnum = vim.fn.prevnonblank(lnum)
|
local prevlnum = vim.fn.prevnonblank(lnum)
|
||||||
local indent = vim.fn.indent(prevlnum)
|
local indent = vim.fn.indent(prevlnum)
|
||||||
local prevline = vim.trim(vim.fn.getline(prevlnum))
|
local prevline = vim.trim(getline(prevlnum))
|
||||||
-- The final position can be trailing spaces, which should not affect indentation
|
-- The final position can be trailing spaces, which should not affect indentation
|
||||||
node = get_last_node_at_line(root, prevlnum, indent + #prevline - 1)
|
node = get_last_node_at_line(root, prevlnum, indent + #prevline - 1)
|
||||||
if node:type():match "comment" then
|
if node:type():match "comment" then
|
||||||
|
|
@ -147,7 +166,7 @@ function M.get_indent(lnum)
|
||||||
node = get_last_node_at_line(root, prevlnum, col)
|
node = get_last_node_at_line(root, prevlnum, col)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
if q.indent["end"][node:id()] then
|
if q["indent.end"][node:id()] then
|
||||||
node = get_first_node_at_line(root, lnum)
|
node = get_first_node_at_line(root, lnum)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
|
|
@ -165,16 +184,16 @@ function M.get_indent(lnum)
|
||||||
-- tracks to ensure multiple indent levels are not applied for same line
|
-- tracks to ensure multiple indent levels are not applied for same line
|
||||||
local is_processed_by_row = {}
|
local is_processed_by_row = {}
|
||||||
|
|
||||||
if q.indent.zero[node:id()] then
|
if q["indent.zero"][node:id()] then
|
||||||
return 0
|
return 0
|
||||||
end
|
end
|
||||||
|
|
||||||
while node do
|
while node do
|
||||||
-- do 'autoindent' if not marked as @indent
|
-- do 'autoindent' if not marked as @indent
|
||||||
if
|
if
|
||||||
not q.indent.begin[node:id()]
|
not q["indent.begin"][node:id()]
|
||||||
and not q.indent.align[node:id()]
|
and not q["indent.align"][node:id()]
|
||||||
and q.indent.auto[node:id()]
|
and q["indent.auto"][node:id()]
|
||||||
and node:start() < lnum - 1
|
and node:start() < lnum - 1
|
||||||
and lnum - 1 <= node:end_()
|
and lnum - 1 <= node:end_()
|
||||||
then
|
then
|
||||||
|
|
@ -185,8 +204,8 @@ function M.get_indent(lnum)
|
||||||
-- If a node spans from L1,C1 to L2,C2, we know that lines where L1 < line <= L2 would
|
-- 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.
|
-- have their indentations contained by the node.
|
||||||
if
|
if
|
||||||
not q.indent.begin[node:id()]
|
not q["indent.begin"][node:id()]
|
||||||
and q.indent.ignore[node:id()]
|
and q["indent.ignore"][node:id()]
|
||||||
and node:start() < lnum - 1
|
and node:start() < lnum - 1
|
||||||
and lnum - 1 <= node:end_()
|
and lnum - 1 <= node:end_()
|
||||||
then
|
then
|
||||||
|
|
@ -199,7 +218,7 @@ function M.get_indent(lnum)
|
||||||
|
|
||||||
if
|
if
|
||||||
not is_processed_by_row[srow]
|
not is_processed_by_row[srow]
|
||||||
and ((q.indent.branch[node:id()] and srow == lnum - 1) or (q.indent.dedent[node:id()] and srow ~= lnum - 1))
|
and ((q["indent.branch"][node:id()] and srow == lnum - 1) or (q["indent.dedent"][node:id()] and srow ~= lnum - 1))
|
||||||
then
|
then
|
||||||
indent = indent - indent_size
|
indent = indent - indent_size
|
||||||
is_processed = true
|
is_processed = true
|
||||||
|
|
@ -215,16 +234,16 @@ function M.get_indent(lnum)
|
||||||
if
|
if
|
||||||
should_process
|
should_process
|
||||||
and (
|
and (
|
||||||
q.indent.begin[node:id()]
|
q["indent.begin"][node:id()]
|
||||||
and (srow ~= erow or is_in_err or q.indent.begin[node:id()]["indent.immediate"])
|
and (srow ~= erow or is_in_err or q["indent.begin"][node:id()]["indent.immediate"])
|
||||||
and (srow ~= lnum - 1 or q.indent.begin[node:id()]["indent.start_at_same_line"])
|
and (srow ~= lnum - 1 or q["indent.begin"][node:id()]["indent.start_at_same_line"])
|
||||||
)
|
)
|
||||||
then
|
then
|
||||||
indent = indent + indent_size
|
indent = indent + indent_size
|
||||||
is_processed = true
|
is_processed = true
|
||||||
end
|
end
|
||||||
|
|
||||||
if is_in_err and not q.indent.align[node:id()] then
|
if is_in_err and not q["indent.align"][node:id()] then
|
||||||
-- only when the node is in error, promote the
|
-- only when the node is in error, promote the
|
||||||
-- first child's aligned indent to the error node
|
-- first child's aligned indent to the error node
|
||||||
-- to work around ((ERROR "X" . (_)) @aligned_indent (#set! "delimeter" "AB"))
|
-- to work around ((ERROR "X" . (_)) @aligned_indent (#set! "delimeter" "AB"))
|
||||||
|
|
@ -232,15 +251,15 @@ function M.get_indent(lnum)
|
||||||
-- (ERROR "X" @aligned_indent (#set! "delimeter" "AB") . (_))
|
-- (ERROR "X" @aligned_indent (#set! "delimeter" "AB") . (_))
|
||||||
-- and we will fish it out here.
|
-- and we will fish it out here.
|
||||||
for c in node:iter_children() do
|
for c in node:iter_children() do
|
||||||
if q.indent.align[c:id()] then
|
if q["indent.align"][c:id()] then
|
||||||
q.indent.align[node:id()] = q.indent.align[c:id()]
|
q["indent.align"][node:id()] = q["indent.align"][c:id()]
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
-- do not indent for nodes that starts-and-ends on same line and starts on target line (lnum)
|
-- do not indent for nodes that starts-and-ends on same line and starts on target line (lnum)
|
||||||
if should_process and q.indent.align[node:id()] and (srow ~= erow or is_in_err) and (srow ~= lnum - 1) then
|
if should_process and q["indent.align"][node:id()] and (srow ~= erow or is_in_err) and (srow ~= lnum - 1) then
|
||||||
local metadata = q.indent.align[node:id()]
|
local metadata = q["indent.align"][node:id()]
|
||||||
local o_delim_node, o_is_last_in_line ---@type TSNode|nil, boolean|nil
|
local o_delim_node, o_is_last_in_line ---@type TSNode|nil, boolean|nil
|
||||||
local c_delim_node, c_is_last_in_line ---@type TSNode|nil, boolean|nil, boolean|nil
|
local c_delim_node, c_is_last_in_line ---@type TSNode|nil, boolean|nil, boolean|nil
|
||||||
local indent_is_absolute = false
|
local indent_is_absolute = false
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue