Merge #30085 #trim! all whitespace

This commit is contained in:
Justin M. Keyes 2024-12-06 10:08:20 -08:00 committed by GitHub
commit ba7370a902
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 168 additions and 39 deletions

View File

@ -280,6 +280,8 @@ TREESITTER
• |LanguageTree:node_for_range()| gets anonymous and named nodes for a range
• |vim.treesitter.get_node()| now takes an option `include_anonymous`, default
false, which allows it to return anonymous nodes as well as named nodes.
• |treesitter-directive-trim!| can trim all whitespace (not just empty lines)
from both sides of a node.
TUI

View File

@ -245,15 +245,32 @@ The following directives are built in:
(#gsub! @_node ".*%.(.*)" "%1")
<
`trim!` *treesitter-directive-trim!*
Trim blank lines from the end of the node. This will set a new
`metadata[capture_id].range`.
Trims whitespace from the node. Sets a new
`metadata[capture_id].range`. Takes a capture ID and, optionally, four
integers to customize trimming behavior (`1` meaning trim, `0` meaning
don't trim). When only given a capture ID, trims blank lines (lines
that contain only whitespace, or are empty) from the end of the node
(for backwards compatibility). Can trim all whitespace from both sides
of the node if parameters are given.
Examples: >query
; only trim blank lines from the end of the node
; (equivalent to (#trim! @fold 0 0 1 0))
(#trim! @fold)
; trim blank lines from both sides of the node
(#trim! @fold 1 0 1 0)
; trim all whitespace around the node
(#trim! @fold 1 1 1 1)
<
Parameters: ~
{capture_id}
{trim_start_linewise}
{trim_start_charwise}
{trim_end_linewise} (default `1` if only given {capture_id})
{trim_end_charwise}
Example: >query
(#trim! @fold)
<
Further directives can be added via |vim.treesitter.query.add_directive()|.
Use |vim.treesitter.query.list_directives()| to list all available directives.

View File

@ -572,13 +572,17 @@ local directive_handlers = {
metadata[id].text = text:gsub(pattern, replacement)
end,
-- Trim blank lines from end of the node
-- Example: (#trim! @fold)
-- TODO(clason): generalize to arbitrary whitespace removal
-- Trim whitespace from both sides of the node
-- Example: (#trim! @fold 1 1 1 1)
['trim!'] = function(match, _, bufnr, pred, metadata)
local capture_id = pred[2]
assert(type(capture_id) == 'number')
local trim_start_lines = pred[3] == '1'
local trim_start_cols = pred[4] == '1'
local trim_end_lines = pred[5] == '1' or not pred[3] -- default true for backwards compatibility
local trim_end_cols = pred[6] == '1'
local nodes = match[capture_id]
if not nodes or #nodes == 0 then
return
@ -588,20 +592,36 @@ local directive_handlers = {
local start_row, start_col, end_row, end_col = node:range()
-- Don't trim if region ends in middle of a line
if end_col ~= 0 then
return
local node_text = vim.split(vim.treesitter.get_node_text(node, bufnr), '\n')
local end_idx = #node_text
local start_idx = 1
if trim_end_lines then
while end_idx > 0 and node_text[end_idx]:find('^%s*$') do
end_idx = end_idx - 1
end_row = end_row - 1
end
end
if trim_end_cols then
if end_idx == 0 then
end_row = start_row
end_col = start_col
else
local whitespace_start = node_text[end_idx]:find('(%s*)$')
end_col = (whitespace_start - 1) + (end_idx == 1 and start_col or 0)
end
end
while end_row >= start_row do
-- As we only care when end_col == 0, always inspect one line above end_row.
local end_line = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1]
if end_line ~= '' then
break
if trim_start_lines then
while start_idx <= end_idx and node_text[start_idx]:find('^%s*$') do
start_idx = start_idx + 1
start_row = start_row + 1
end
end_row = end_row - 1
end
if trim_start_cols and node_text[start_idx] then
local _, whitespace_end = node_text[start_idx]:find('^(%s*)')
whitespace_end = whitespace_end or 0
start_col = (start_idx == 1 and start_col or 0) + whitespace_end
end
-- If this produces an invalid range, we just skip it.

View File

@ -1,5 +1,6 @@
local t = require('test.testutil')
local n = require('test.functional.testnvim')()
local ts_t = require('test.functional.treesitter.testutil')
local clear = n.clear
local dedent = t.dedent
@ -8,6 +9,7 @@ local insert = n.insert
local exec_lua = n.exec_lua
local pcall_err = t.pcall_err
local feed = n.feed
local run_query = ts_t.run_query
describe('treesitter parser API', function()
before_each(function()
@ -644,6 +646,82 @@ print()
end)
end)
describe('trim! directive', function()
it('can trim all whitespace', function()
-- luacheck: push ignore 611 613
insert([=[
print([[
f
helllo
there
asdf
asdfassd
]])
print([[
]])
print([[]])
print([[
]])
print([[ hello 😃 ]])
]=])
-- luacheck: pop
local query_text = [[
; query
((string_content) @str
(#trim! @str 1 1 1 1))
]]
exec_lua(function()
vim.treesitter.start(0, 'lua')
end)
eq({
{ 'str', { 2, 12, 6, 10 } },
{ 'str', { 11, 10, 11, 10 } },
{ 'str', { 17, 10, 17, 10 } },
{ 'str', { 19, 10, 19, 10 } },
{ 'str', { 22, 15, 22, 25 } },
}, run_query('lua', query_text))
end)
it('trims only empty lines by default (backwards compatible)', function()
insert [[
## Heading
With some text
## And another
With some more]]
local query_text = [[
; query
((section) @fold
(#trim! @fold))
]]
exec_lua(function()
vim.treesitter.start(0, 'markdown')
end)
eq({
{ 'fold', { 0, 0, 3, 0 } },
{ 'fold', { 4, 0, 7, 0 } },
}, run_query('markdown', query_text))
end)
end)
it('tracks the root range properly (#22911)', function()
insert([[
int main() {
@ -659,32 +737,19 @@ print()
vim.treesitter.start(0, 'c')
end)
local function run_query()
return exec_lua(function()
local query = vim.treesitter.query.parse('c', query0)
local parser = vim.treesitter.get_parser()
local tree = parser:parse()[1]
local res = {}
for id, node in query:iter_captures(tree:root()) do
table.insert(res, { query.captures[id], node:range() })
end
return res
end)
end
eq({
{ 'function', 0, 0, 2, 1 },
{ 'declaration', 1, 2, 1, 12 },
}, run_query())
{ 'function', { 0, 0, 2, 1 } },
{ 'declaration', { 1, 2, 1, 12 } },
}, run_query('c', query0))
n.command 'normal ggO'
insert('int a;')
eq({
{ 'declaration', 0, 0, 0, 6 },
{ 'function', 1, 0, 3, 1 },
{ 'declaration', 2, 2, 2, 12 },
}, run_query())
{ 'declaration', { 0, 0, 0, 6 } },
{ 'function', { 1, 0, 3, 1 } },
{ 'declaration', { 2, 2, 2, 12 } },
}, run_query('c', query0))
end)
it('handles ranges when source is a multiline string (#20419)', function()

View File

@ -0,0 +1,25 @@
local n = require('test.functional.testnvim')()
local exec_lua = n.exec_lua
local M = {}
---@param language string
---@param query_string string
function M.run_query(language, query_string)
return exec_lua(function(lang, query_str)
local query = vim.treesitter.query.parse(lang, query_str)
local parser = vim.treesitter.get_parser()
local tree = parser:parse()[1]
local res = {}
for id, node, metadata in query:iter_captures(tree:root(), 0) do
table.insert(
res,
{ query.captures[id], metadata[id] and metadata[id].range or { node:range() } }
)
end
return res
end, language, query_string)
end
return M