mirror of
				https://github.com/neovim/neovim.git
				synced 2025-10-26 12:27:24 +00:00 
			
		
		
		
	Merge #30085 #trim! all whitespace
This commit is contained in:
		| @@ -280,6 +280,8 @@ TREESITTER | |||||||
| • |LanguageTree:node_for_range()| gets anonymous and named nodes for a range | • |LanguageTree:node_for_range()| gets anonymous and named nodes for a range | ||||||
| • |vim.treesitter.get_node()| now takes an option `include_anonymous`, default | • |vim.treesitter.get_node()| now takes an option `include_anonymous`, default | ||||||
|   false, which allows it to return anonymous nodes as well as named nodes. |   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 | TUI | ||||||
|  |  | ||||||
|   | |||||||
| @@ -245,15 +245,32 @@ The following directives are built in: | |||||||
|             (#gsub! @_node ".*%.(.*)" "%1") |             (#gsub! @_node ".*%.(.*)" "%1") | ||||||
| < | < | ||||||
|     `trim!`                                          *treesitter-directive-trim!* |     `trim!`                                          *treesitter-directive-trim!* | ||||||
|         Trim blank lines from the end of the node. This will set a new |         Trims whitespace from the node. Sets a new | ||||||
|         `metadata[capture_id].range`. |         `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: ~ |         Parameters: ~ | ||||||
|             {capture_id} |             {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()|. | Further directives can be added via |vim.treesitter.query.add_directive()|. | ||||||
| Use |vim.treesitter.query.list_directives()| to list all available directives. | Use |vim.treesitter.query.list_directives()| to list all available directives. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -572,13 +572,17 @@ local directive_handlers = { | |||||||
|  |  | ||||||
|     metadata[id].text = text:gsub(pattern, replacement) |     metadata[id].text = text:gsub(pattern, replacement) | ||||||
|   end, |   end, | ||||||
|   -- Trim blank lines from end of the node |   -- Trim whitespace from both sides of the node | ||||||
|   -- Example: (#trim! @fold) |   -- Example: (#trim! @fold 1 1 1 1) | ||||||
|   -- TODO(clason): generalize to arbitrary whitespace removal |  | ||||||
|   ['trim!'] = function(match, _, bufnr, pred, metadata) |   ['trim!'] = function(match, _, bufnr, pred, metadata) | ||||||
|     local capture_id = pred[2] |     local capture_id = pred[2] | ||||||
|     assert(type(capture_id) == 'number') |     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] |     local nodes = match[capture_id] | ||||||
|     if not nodes or #nodes == 0 then |     if not nodes or #nodes == 0 then | ||||||
|       return |       return | ||||||
| @@ -588,20 +592,36 @@ local directive_handlers = { | |||||||
|  |  | ||||||
|     local start_row, start_col, end_row, end_col = node:range() |     local start_row, start_col, end_row, end_col = node:range() | ||||||
|  |  | ||||||
|     -- Don't trim if region ends in middle of a line |     local node_text = vim.split(vim.treesitter.get_node_text(node, bufnr), '\n') | ||||||
|     if end_col ~= 0 then |     local end_idx = #node_text | ||||||
|       return |     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 |     end | ||||||
|  |  | ||||||
|     while end_row >= start_row do |     if trim_start_lines then | ||||||
|       -- As we only care when end_col == 0, always inspect one line above end_row. |       while start_idx <= end_idx and node_text[start_idx]:find('^%s*$') do | ||||||
|       local end_line = api.nvim_buf_get_lines(bufnr, end_row - 1, end_row, true)[1] |         start_idx = start_idx + 1 | ||||||
|  |         start_row = start_row + 1 | ||||||
|       if end_line ~= '' then |  | ||||||
|         break |  | ||||||
|       end |       end | ||||||
|  |     end | ||||||
|       end_row = end_row - 1 |     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 |     end | ||||||
|  |  | ||||||
|     -- If this produces an invalid range, we just skip it. |     -- If this produces an invalid range, we just skip it. | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| local t = require('test.testutil') | local t = require('test.testutil') | ||||||
| local n = require('test.functional.testnvim')() | local n = require('test.functional.testnvim')() | ||||||
|  | local ts_t = require('test.functional.treesitter.testutil') | ||||||
|  |  | ||||||
| local clear = n.clear | local clear = n.clear | ||||||
| local dedent = t.dedent | local dedent = t.dedent | ||||||
| @@ -8,6 +9,7 @@ local insert = n.insert | |||||||
| local exec_lua = n.exec_lua | local exec_lua = n.exec_lua | ||||||
| local pcall_err = t.pcall_err | local pcall_err = t.pcall_err | ||||||
| local feed = n.feed | local feed = n.feed | ||||||
|  | local run_query = ts_t.run_query | ||||||
|  |  | ||||||
| describe('treesitter parser API', function() | describe('treesitter parser API', function() | ||||||
|   before_each(function() |   before_each(function() | ||||||
| @@ -644,6 +646,82 @@ print() | |||||||
|     end) |     end) | ||||||
|   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() |   it('tracks the root range properly (#22911)', function() | ||||||
|     insert([[ |     insert([[ | ||||||
|       int main() { |       int main() { | ||||||
| @@ -659,32 +737,19 @@ print() | |||||||
|       vim.treesitter.start(0, 'c') |       vim.treesitter.start(0, 'c') | ||||||
|     end) |     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({ |     eq({ | ||||||
|       { 'function', 0, 0, 2, 1 }, |       { 'function', { 0, 0, 2, 1 } }, | ||||||
|       { 'declaration', 1, 2, 1, 12 }, |       { 'declaration', { 1, 2, 1, 12 } }, | ||||||
|     }, run_query()) |     }, run_query('c', query0)) | ||||||
|  |  | ||||||
|     n.command 'normal ggO' |     n.command 'normal ggO' | ||||||
|     insert('int a;') |     insert('int a;') | ||||||
|  |  | ||||||
|     eq({ |     eq({ | ||||||
|       { 'declaration', 0, 0, 0, 6 }, |       { 'declaration', { 0, 0, 0, 6 } }, | ||||||
|       { 'function', 1, 0, 3, 1 }, |       { 'function', { 1, 0, 3, 1 } }, | ||||||
|       { 'declaration', 2, 2, 2, 12 }, |       { 'declaration', { 2, 2, 2, 12 } }, | ||||||
|     }, run_query()) |     }, run_query('c', query0)) | ||||||
|   end) |   end) | ||||||
|  |  | ||||||
|   it('handles ranges when source is a multiline string (#20419)', function() |   it('handles ranges when source is a multiline string (#20419)', function() | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								test/functional/treesitter/testutil.lua
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								test/functional/treesitter/testutil.lua
									
									
									
									
									
										Normal 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 | ||||||
		Reference in New Issue
	
	Block a user
	 Justin M. Keyes
					Justin M. Keyes