Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/mini-ai.txt
Original file line number Diff line number Diff line change
Expand Up @@ -798,6 +798,10 @@ Notes:
Verify with `:=vim.treesitter.query.get('lang', 'textobjects')` and see
if the target capture is recognized as one.
- It uses buffer's |filetype| to determine query language.
- It first searches the language under cursor for matches. If no matches are
found, it fallbacks to searching parent languages (up to the buffer's root
language). If no matches are found again, it fallbacks to recursively
searching all children languages (from the language under cursor).
- On large files it is slower than pattern-based textobjects. Still very
fast though (one search should be magnitude of milliseconds or tens of
milliseconds on really large file).
Expand Down
4 changes: 4 additions & 0 deletions doc/mini-surround.txt
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,10 @@ Notes:
Verify with `:=vim.treesitter.query.get('lang', 'textobjects')` and see
if the target capture is recognized as one.
- It uses buffer's |filetype| to determine query language.
- It first searches the language under cursor for matches. If no matches are
found, it fallbacks to searching parent languages (up to the buffer's root
language). If no matches are found again, it fallbacks to recursively
searching all children languages (from the language under cursor).
- On large files it is slower than pattern-based textobjects. Still very
fast though (one search should be magnitude of milliseconds or tens of
milliseconds on really large file).
Expand Down
33 changes: 26 additions & 7 deletions lua/mini/ai.lua
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,10 @@ end
--- Verify with `:=vim.treesitter.query.get('lang', 'textobjects')` and see
--- if the target capture is recognized as one.
--- - It uses buffer's |filetype| to determine query language.
--- - It first searches the language under cursor for matches. If no matches are
--- found, it fallbacks to searching parent languages (up to the buffer's root
--- language). If no matches are found again, it fallbacks to recursively
--- searching all children languages (from the language under cursor).
--- - On large files it is slower than pattern-based textobjects. Still very
--- fast though (one search should be magnitude of milliseconds or tens of
--- milliseconds on really large file).
Expand Down Expand Up @@ -1608,22 +1612,29 @@ H.get_matched_ranges_builtin = function(captures)
-- Get parser (LanguageTree) at cursor (important for injected languages)
local pos = vim.api.nvim_win_get_cursor(0)
local lang_tree = parser:language_for_range({ pos[1] - 1, pos[2], pos[1] - 1, pos[2] })
local init_lang_tree = lang_tree

local missing_query_langs = {}
local res = {}
-- Maybe go up parent trees to work with injected languages
while vim.tbl_isempty(res) and lang_tree ~= nil do
local lang = lang_tree:lang()
-- Get query file depending on the local language
local query = vim.treesitter.query.get(lang, 'textobjects')
H.append_lang_ranges(res, missing_query_langs, buf_id, captures, lang_tree)

if query ~= nil then H.append_ranges(res, buf_id, query, captures, lang_tree) end
if query == nil then missing_query_langs[lang] = true end

-- `LanguageTree:parent()` was added in Neovim<0.10
-- `LanguageTree:parent()` was added in Neovim=0.10
-- TODO: Drop extra check after compatibility with Neovim=0.9 is dropped
lang_tree = lang_tree.parent and lang_tree:parent() or nil
end
-- Fallback to children trees for injected languages
if vim.tbl_isempty(res) then
local check_children
check_children = function(l_tree)
for _, child in pairs(l_tree:children()) do
H.append_lang_ranges(res, missing_query_langs, buf_id, captures, child)
check_children(child)
end
end
check_children(init_lang_tree)
end

if vim.tbl_isempty(res) and not vim.tbl_isempty(missing_query_langs) then
H.error_treesitter('query', vim.tbl_keys(missing_query_langs))
Expand All @@ -1632,6 +1643,14 @@ H.get_matched_ranges_builtin = function(captures)
return res
end

H.append_lang_ranges = function(res, missing_query_langs, buf_id, captures, lang_tree)
local lang = lang_tree:lang()
local query = vim.treesitter.query.get(lang, 'textobjects')

if query ~= nil then H.append_ranges(res, buf_id, query, captures, lang_tree) end
if query == nil then missing_query_langs[lang] = true end
end

H.append_ranges = function(res, buf_id, query, captures, lang_tree)
-- Compute ranges of matched captures
local capture_is_requested = vim.tbl_map(function(c) return vim.tbl_contains(captures, '@' .. c) end, query.captures)
Expand Down
53 changes: 37 additions & 16 deletions lua/mini/surround.lua
Original file line number Diff line number Diff line change
Expand Up @@ -1006,6 +1006,10 @@ MiniSurround.gen_spec = { input = {}, output = {} }
--- Verify with `:=vim.treesitter.query.get('lang', 'textobjects')` and see
--- if the target capture is recognized as one.
--- - It uses buffer's |filetype| to determine query language.
--- - It first searches the language under cursor for matches. If no matches are
--- found, it fallbacks to searching parent languages (up to the buffer's root
--- language). If no matches are found again, it fallbacks to recursively
--- searching all children languages (from the language under cursor).
--- - On large files it is slower than pattern-based textobjects. Still very
--- fast though (one search should be magnitude of milliseconds or tens of
--- milliseconds on really large file).
Expand Down Expand Up @@ -1545,35 +1549,36 @@ H.get_matched_range_pairs_builtin = function(captures)
-- Get parser (LanguageTree) at cursor (important for injected languages)
local pos = vim.api.nvim_win_get_cursor(0)
local lang_tree = parser:language_for_range({ pos[1] - 1, pos[2], pos[1] - 1, pos[2] })
local init_lang_tree = lang_tree

local missing_query_langs = {}
-- Compute matched ranges for both outer and inner captures
-- Maybe go up parent trees to work with injected languages
local outer_ranges, inner_ranges = {}, {}
while (vim.tbl_isempty(inner_ranges) or vim.tbl_isempty(outer_ranges)) and lang_tree ~= nil do
local lang = lang_tree:lang()
-- Get query file depending on the local language
local query = vim.treesitter.query.get(lang, 'textobjects')

if query ~= nil then
for _, tree in ipairs(lang_tree:trees()) do
local root = tree:root()
vim.list_extend(outer_ranges, H.get_match_ranges_builtin(root, buf_id, query, captures.outer:sub(2)))
vim.list_extend(inner_ranges, H.get_match_ranges_builtin(root, buf_id, query, captures.inner:sub(2)))
end
end
if query == nil then missing_query_langs[lang] = true end
local outer, inner = {}, {}
while (vim.tbl_isempty(inner) or vim.tbl_isempty(outer)) and lang_tree ~= nil do
H.append_lang_ranges(outer, inner, missing_query_langs, buf_id, captures, lang_tree)

-- `LanguageTree:parent()` was added in Neovim<0.10
-- TODO: Drop extra check after compatibility with Neovim=0.9 is dropped
lang_tree = lang_tree.parent and lang_tree:parent() or nil
end
-- Fallback to children trees for injected languages
if vim.tbl_isempty(inner) or vim.tbl_isempty(outer) then
local check_children
check_children = function(l_tree)
for _, child in pairs(l_tree:children()) do
H.append_lang_ranges(outer, inner, missing_query_langs, buf_id, captures, child)
check_children(child)
end
end
check_children(init_lang_tree)
end

-- Match outer and inner ranges: for each outer range pick the biggest inner
-- range that lies within outer
local res = {}
for i, outer in ipairs(outer_ranges) do
res[i] = { outer = outer, inner = H.get_biggest_nested_range(inner_ranges, outer) }
for i, o in ipairs(outer) do
res[i] = { outer = o, inner = H.get_biggest_nested_range(inner, o) }
end

if vim.tbl_isempty(res) and not vim.tbl_isempty(missing_query_langs) then
Expand All @@ -1583,6 +1588,22 @@ H.get_matched_range_pairs_builtin = function(captures)
return res
end

H.append_lang_ranges = function(outer, inner, missing_query_langs, buf_id, captures, lang_tree)
local lang = lang_tree:lang()
local query = vim.treesitter.query.get(lang, 'textobjects')

if query ~= nil then H.append_ranges(outer, inner, buf_id, query, captures, lang_tree) end
if query == nil then missing_query_langs[lang] = true end
end

H.append_ranges = function(outer, inner, buf_id, query, captures, lang_tree)
for _, tree in ipairs(lang_tree:trees()) do
local root = tree:root()
vim.list_extend(outer, H.get_match_ranges_builtin(root, buf_id, query, captures.outer:sub(2)))
vim.list_extend(inner, H.get_match_ranges_builtin(root, buf_id, query, captures.inner:sub(2)))
end
end

H.get_match_ranges_builtin = function(root, buf_id, query, capture)
local res = {}
-- TODO: Remove `opts.all`after compatibility with Neovim=0.10 is dropped
Expand Down
10 changes: 10 additions & 0 deletions tests/test_ai.lua
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,16 @@ T['gen_spec']['treesitter()']['works with parent of injected language'] = functi
validate_find(lines, { 3, 0 }, { 'a', 'F' }, { { 1, 13 }, { 5, 3 } })
end

T['gen_spec']['treesitter()']['works with injected child language'] = function()
local lines = {
'vim.cmd([[',
'set cursorline',
'lua local a = function() return true end',
']])',
}
validate_find(lines, { 1, 0 }, { 'a', 'F' }, { { 3, 15 }, { 3, 40 } })
end

T['gen_spec']['treesitter()']['works with row-exclusive, col-0 end range'] = function()
child.lua([[MiniAi.config.custom_textobjects = {
c = MiniAi.gen_spec.treesitter({ a = '@chunk.outer', i = '@chunk.outer' }),
Expand Down
10 changes: 10 additions & 0 deletions tests/test_surround.lua
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,16 @@ T['gen_spec']['input']['treesitter()']['works with parent of injected language']
validate_no_find(lines, { 1, 0 }, type_keys, 'sf', 'F')
end

T['gen_spec']['input']['treesitter()']['works with injected child language'] = function()
local lines = {
'vim.cmd([[',
'set cursorline',
'lua local a = function() return true end',
']])',
}
validate_find(lines, { 1, 0 }, { { 3, 14 } }, type_keys, 'sfn', 'F')
end

T['gen_spec']['input']['treesitter()']['respects `opts.use_nvim_treesitter`'] = function()
child.lua([[MiniSurround.config.custom_surroundings = {
F = { input = MiniSurround.gen_spec.input.treesitter({ outer = '@function.outer', inner = '@function.inner' }) },
Expand Down
Loading