Skip to content

Commit 390a3e8

Browse files
TheLeoPechasnovski
andcommitted
feat(surround): query parent LanguageTree until a surrounding is found
Related to #2006 Co-authored-by: Evgeni Chasnovski <[email protected]>
1 parent 0679dba commit 390a3e8

File tree

2 files changed

+50
-13
lines changed

2 files changed

+50
-13
lines changed

lua/mini/surround.lua

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,18 +1536,28 @@ H.get_matched_range_pairs_builtin = function(captures)
15361536
-- Get parser (LanguageTree) at cursor (important for injected languages)
15371537
local pos = vim.api.nvim_win_get_cursor(0)
15381538
local lang_tree = parser:language_for_range({ pos[1] - 1, pos[2], pos[1] - 1, pos[2] })
1539-
local lang = lang_tree:lang()
1540-
1541-
-- Get query file depending on the local language
1542-
local query = vim.treesitter.query.get(lang, 'textobjects')
1543-
if query == nil then H.error_treesitter('query') end
15441539

1540+
local missing_query_langs = {}
15451541
-- Compute matched ranges for both outer and inner captures
1542+
-- Maybe go up parent trees to work with injected languages
15461543
local outer_ranges, inner_ranges = {}, {}
1547-
for _, tree in ipairs(lang_tree:trees()) do
1548-
local root = tree:root()
1549-
vim.list_extend(outer_ranges, H.get_match_ranges_builtin(root, buf_id, query, captures.outer:sub(2)))
1550-
vim.list_extend(inner_ranges, H.get_match_ranges_builtin(root, buf_id, query, captures.inner:sub(2)))
1544+
while (vim.tbl_isempty(inner_ranges) or vim.tbl_isempty(outer_ranges)) and lang_tree ~= nil do
1545+
local lang = lang_tree:lang()
1546+
-- Get query file depending on the local language
1547+
local query = vim.treesitter.query.get(lang, 'textobjects')
1548+
1549+
if query ~= nil then
1550+
for _, tree in ipairs(lang_tree:trees()) do
1551+
local root = tree:root()
1552+
vim.list_extend(outer_ranges, H.get_match_ranges_builtin(root, buf_id, query, captures.outer:sub(2)))
1553+
vim.list_extend(inner_ranges, H.get_match_ranges_builtin(root, buf_id, query, captures.inner:sub(2)))
1554+
end
1555+
end
1556+
if query == nil then missing_query_langs[lang] = true end
1557+
1558+
-- `LanguageTree:parent()` was added in Neovim<0.10
1559+
-- TODO: Drop extra check after compatibility with Neovim=0.9 is dropped
1560+
lang_tree = lang_tree.parent and lang_tree:parent() or nil
15511561
end
15521562

15531563
-- Match outer and inner ranges: for each outer range pick the biggest inner
@@ -1556,6 +1566,11 @@ H.get_matched_range_pairs_builtin = function(captures)
15561566
for i, outer in ipairs(outer_ranges) do
15571567
res[i] = { outer = outer, inner = H.get_biggest_nested_range(inner_ranges, outer) }
15581568
end
1569+
1570+
if vim.tbl_isempty(res) and not vim.tbl_isempty(missing_query_langs) then
1571+
H.error_treesitter('query', vim.tbl_keys(missing_query_langs))
1572+
end
1573+
15591574
return res
15601575
end
15611576

@@ -1601,11 +1616,18 @@ H.get_biggest_nested_range = function(ranges, parent)
16011616
return best_range
16021617
end
16031618

1604-
H.error_treesitter = function(failed_get)
1619+
H.error_treesitter = function(failed_get, langs)
16051620
local buf_id, ft = vim.api.nvim_get_current_buf(), vim.bo.filetype
1606-
local has_lang, lang = pcall(vim.treesitter.language.get_lang, ft)
1607-
lang = has_lang and lang or ft
1608-
local msg = string.format('Can not get %s for buffer %d and language "%s".', failed_get, buf_id, lang)
1621+
if langs == nil then
1622+
local has_lang, ft_lang = pcall(vim.treesitter.language.get_lang, ft)
1623+
-- `vim.treesitter.language.get_lang()` defaults to `ft` on Neovim>0.11
1624+
-- TODO: Drop check after compatibility with Neovim=0.10 is dropped
1625+
langs = (has_lang and ft_lang ~= nil) and { ft_lang } or { ft }
1626+
end
1627+
table.sort(langs)
1628+
local langs_str = table.concat(vim.tbl_map(vim.inspect, langs), ', ')
1629+
local langs_noun = #langs == 1 and 'language' or 'languages'
1630+
local msg = string.format('Can not get %s for buffer %d and %s %s.', failed_get, buf_id, langs_noun, langs_str)
16091631
H.error(msg)
16101632
end
16111633

tests/test_surround.lua

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,21 @@ T['gen_spec']['input']['treesitter()']['works with no inner captures'] = functio
327327
validate_find(lines, { 10, 2 }, { { 10, 12 }, { 10, 2 } }, type_keys, 'sf', 'o')
328328
end
329329

330+
T['gen_spec']['input']['treesitter()']['works with parent of injected language'] = function()
331+
if child.fn.has('nvim-0.10') == 0 then MiniTest.skip('`LanguageTree:parent()` requires Neovim>=0.10') end
332+
333+
local lines = {
334+
'local foo = function()',
335+
' vim.cmd([[',
336+
'set cursorline',
337+
']])',
338+
'end',
339+
}
340+
341+
validate_find(lines, { 3, 0 }, { { 4, 2 }, { 5, 2 }, { 1, 12 }, { 2, 1 } }, type_keys, 'sf', 'F')
342+
validate_no_find(lines, { 1, 0 }, type_keys, 'sf', 'F')
343+
end
344+
330345
T['gen_spec']['input']['treesitter()']['respects `opts.use_nvim_treesitter`'] = function()
331346
child.lua([[MiniSurround.config.custom_surroundings = {
332347
F = { input = MiniSurround.gen_spec.input.treesitter({ outer = '@function.outer', inner = '@function.inner' }) },

0 commit comments

Comments
 (0)