aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--runtime/doc/news.txt4
-rw-r--r--runtime/doc/treesitter.txt13
-rw-r--r--runtime/lua/vim/_comment.lua12
-rw-r--r--runtime/lua/vim/treesitter/query.lua1
-rw-r--r--test/functional/lua/comment_spec.lua134
5 files changed, 164 insertions, 0 deletions
diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt
index 1a6537fa8d..a131934a8e 100644
--- a/runtime/doc/news.txt
+++ b/runtime/doc/news.txt
@@ -359,6 +359,10 @@ PLUGINS
• spelling_language property is now supported.
• 'inccommand' incremental preview can run on 'nomodifiable' buffers and
restores their 'modifiable' state
+• Commenting
+ • 'commentstring' values can now be specified in a Treesitter capture's
+ `bo.commentstring` metadata field, providing finer grained support for
+ languages like `JSX`.
STARTUP
diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt
index 5916ec9371..3918188f1b 100644
--- a/runtime/doc/treesitter.txt
+++ b/runtime/doc/treesitter.txt
@@ -531,6 +531,19 @@ attribute: >query
((super_important_node) @superimportant (#set! priority 105))
<
+ *treesitter-highlight-commentstring*
+Treesitter highlighting supports finer-grained 'commentstring's, used by the
+built-in |commenting| plugin. When the cursor is within a node that sets the
+`bo.commentstring` metadata property (|treesitter-directive-set!|), that
+property defines the comment delimiter (where "innermost wins"). This is
+useful for languages like `JSX` that have different comment syntax depending
+on the code region, for example: >query
+
+ ((jsx_element) @_tag (#set! @_tag bo.commentstring "{/* %s */}"))
+<
+When multiple captures set this metadata over a region, only the innermost
+(most specific) one is applied to a given area.
+
==============================================================================
TREESITTER LANGUAGE INJECTIONS *treesitter-language-injections*
<
diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua
index de7f62632c..57fd3d73d6 100644
--- a/runtime/lua/vim/_comment.lua
+++ b/runtime/lua/vim/_comment.lua
@@ -19,6 +19,18 @@ local function get_commentstring(ref_position)
local row, col = ref_position[1] - 1, ref_position[2]
local ref_range = { row, col, row, col + 1 }
+ -- Get 'commentstring' from tree-sitter captures' metadata.
+ -- Traverse backwards to prefer narrower captures.
+ local caps = vim.treesitter.get_captures_at_pos(0, row, col)
+ for i = #caps, 1, -1 do
+ local id, metadata = caps[i].id, caps[i].metadata
+ local md_cms = metadata['bo.commentstring'] or metadata[id] and metadata[id]['bo.commentstring']
+
+ if md_cms then
+ return md_cms
+ end
+ end
+
-- - Get 'commentstring' from the deepest LanguageTree which both contains
-- reference range and has valid 'commentstring' (meaning it has at least
-- one associated 'filetype' with valid 'commentstring').
diff --git a/runtime/lua/vim/treesitter/query.lua b/runtime/lua/vim/treesitter/query.lua
index 1b72151aac..17088ac0eb 100644
--- a/runtime/lua/vim/treesitter/query.lua
+++ b/runtime/lua/vim/treesitter/query.lua
@@ -608,6 +608,7 @@ predicate_handlers['any-vim-match?'] = predicate_handlers['any-match?']
---@class vim.treesitter.query.TSMetadata
---@field range? Range
---@field conceal? string
+---@field bo.commentstring? string
---@field [integer]? vim.treesitter.query.TSMetadata
---@field [string]? integer|string
diff --git a/test/functional/lua/comment_spec.lua b/test/functional/lua/comment_spec.lua
index bbf061a2ab..9ae9ce84d0 100644
--- a/test/functional/lua/comment_spec.lua
+++ b/test/functional/lua/comment_spec.lua
@@ -586,6 +586,140 @@ describe('commenting', function()
feed('.')
eq(get_lines(), { '"set background=dark', 'lua << EOF', '-- print(1)', 'EOF' })
end)
+
+ it('respects tree-sitter commentstring metadata', function()
+ exec_lua [=[
+ vim.treesitter.query.set('vim', 'highlights', [[
+ ((list) @_list (#set! @_list bo.commentstring "!! %s"))
+ ]])
+ ]=]
+ setup_treesitter()
+
+ local lines = {
+ 'set background=dark',
+ 'let mylist = [',
+ [[ \"a",]],
+ [[ \"b",]],
+ [[ \"c",]],
+ ' \\]',
+ }
+ set_lines(lines)
+
+ set_cursor(1, 0)
+ feed('gcc')
+ eq(
+ { '"set background=dark', 'let mylist = [', [[ \"a",]], [[ \"b",]], [[ \"c",]], ' \\]' },
+ get_lines()
+ )
+
+ -- Should work with dot-repeat
+ set_cursor(4, 0)
+ feed('.')
+ eq({
+ '"set background=dark',
+ 'let mylist = [',
+ [[ \"a",]],
+ [[ !! \"b",]],
+ [[ \"c",]],
+ ' \\]',
+ }, get_lines())
+ end)
+
+ it('only applies the innermost tree-sitter commentstring metadata', function()
+ exec_lua [=[
+ vim.treesitter.query.set('vim', 'highlights', [[
+ ((list) @_list (#gsub! @_list "(.*)" "%1") (#set! bo.commentstring "!! %s"))
+ ((script_file) @_src (#set! @_src bo.commentstring "## %s"))
+ ]])
+ ]=]
+ setup_treesitter()
+
+ local lines = {
+ 'set background=dark',
+ 'let mylist = [',
+ [[ \"a",]],
+ [[ \"b",]],
+ [[ \"c",]],
+ ' \\]',
+ }
+ set_lines(lines)
+
+ set_cursor(1, 0)
+ feed('gcc')
+ eq({
+ '## set background=dark',
+ 'let mylist = [',
+ [[ \"a",]],
+ [[ \"b",]],
+ [[ \"c",]],
+ ' \\]',
+ }, get_lines())
+
+ -- Should work with dot-repeat
+ set_cursor(4, 0)
+ feed('.')
+ eq({
+ '## set background=dark',
+ 'let mylist = [',
+ [[ \"a",]],
+ [[ !! \"b",]],
+ [[ \"c",]],
+ ' \\]',
+ }, get_lines())
+ end)
+
+ it('respects injected tree-sitter commentstring metadata', function()
+ exec_lua [=[
+ vim.treesitter.query.set('lua', 'highlights', [[
+ ((string) @string (#set! @string bo.commentstring "; %s"))
+ ]])
+ ]=]
+ setup_treesitter()
+
+ local lines = {
+ 'set background=dark',
+ 'lua << EOF',
+ 'print[[',
+ 'Inside string',
+ ']]',
+ 'EOF',
+ }
+ set_lines(lines)
+
+ set_cursor(1, 0)
+ feed('gcc')
+ eq({
+ '"set background=dark',
+ 'lua << EOF',
+ 'print[[',
+ 'Inside string',
+ ']]',
+ 'EOF',
+ }, get_lines())
+
+ -- Should work with dot-repeat
+ set_cursor(4, 0)
+ feed('.')
+ eq({
+ '"set background=dark',
+ 'lua << EOF',
+ 'print[[',
+ '; Inside string',
+ ']]',
+ 'EOF',
+ }, get_lines())
+
+ set_cursor(3, 0)
+ feed('.')
+ eq({
+ '"set background=dark',
+ 'lua << EOF',
+ '-- print[[',
+ '; Inside string',
+ ']]',
+ 'EOF',
+ }, get_lines())
+ end)
end)
describe('Textobject', function()