mirror of
https://github.com/neovim/neovim.git
synced 2026-05-04 04:55:16 +00:00
feat(editor): :source can run Lua codeblock / ts injection #36799
Problem: Can't use `:source` to run a Lua codeblock (treesitter injection) in a help (vimdoc) file. Solution: Use treesitter to parse the range and treat it as Lua if detected as such.
This commit is contained in:
@@ -215,6 +215,8 @@ EDITOR
|
|||||||
"(v)iew" then run `:trust`.
|
"(v)iew" then run `:trust`.
|
||||||
• |gx| in help buffers opens the online documentation for the tag under the
|
• |gx| in help buffers opens the online documentation for the tag under the
|
||||||
cursor.
|
cursor.
|
||||||
|
• |:source| with a range in non-Lua files (e.g., vimdoc) now detects Lua
|
||||||
|
codeblocks via treesitter and executes them as Lua instead of Vimscript.
|
||||||
• |:Undotree| for visually navigating the |undo-tree|
|
• |:Undotree| for visually navigating the |undo-tree|
|
||||||
• |:wall| permits a |++p| option for creating parent directories when writing
|
• |:wall| permits a |++p| option for creating parent directories when writing
|
||||||
changed buffers.
|
changed buffers.
|
||||||
|
|||||||
@@ -63,4 +63,22 @@ function M.read_chunk(file, size)
|
|||||||
return tostring(chunk)
|
return tostring(chunk)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Check if a range in a buffer is inside a Lua codeblock via treesitter injection.
|
||||||
|
--- Used by :source to detect Lua code in non-Lua files (e.g., vimdoc).
|
||||||
|
--- @param bufnr integer Buffer number
|
||||||
|
--- @param line1 integer Start line (1-indexed)
|
||||||
|
--- @param line2 integer End line (1-indexed)
|
||||||
|
--- @return boolean True if the range is in a Lua injection
|
||||||
|
function M.source_is_lua(bufnr, line1, line2)
|
||||||
|
local ok, parser = pcall(vim.treesitter.get_parser, bufnr)
|
||||||
|
if not ok or not parser then
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
-- Parse from buffer start through one line past line2 to include injection closing markers
|
||||||
|
local range = { line1 - 1, 0, line2 - 1, -1 }
|
||||||
|
parser:parse({ 0, 0, line2, -1 })
|
||||||
|
local lang_tree = parser:language_for_range(range)
|
||||||
|
return lang_tree:lang() == 'lua'
|
||||||
|
end
|
||||||
|
|
||||||
return M
|
return M
|
||||||
|
|||||||
@@ -2275,8 +2275,26 @@ static int do_source_ext(char *const fname, const bool check_other, const int is
|
|||||||
|
|
||||||
cookie.conv.vc_type = CONV_NONE; // no conversion
|
cookie.conv.vc_type = CONV_NONE; // no conversion
|
||||||
|
|
||||||
|
// Check if treesitter detects this range as Lua (for injections like vimdoc codeblocks)
|
||||||
|
bool ts_lua = false;
|
||||||
|
if (fname == NULL && eap != NULL && !ex_lua
|
||||||
|
&& !strequal(curbuf->b_p_ft, "lua")
|
||||||
|
&& !(curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua"))) {
|
||||||
|
MAXSIZE_TEMP_ARRAY(args, 3);
|
||||||
|
ADD_C(args, INTEGER_OBJ(curbuf->handle));
|
||||||
|
ADD_C(args, INTEGER_OBJ(eap->line1));
|
||||||
|
ADD_C(args, INTEGER_OBJ(eap->line2));
|
||||||
|
Error err = ERROR_INIT;
|
||||||
|
Object result = NLUA_EXEC_STATIC("return require('vim._core.util').source_is_lua(...)",
|
||||||
|
args, kRetNilBool, NULL, &err);
|
||||||
|
if (!ERROR_SET(&err) && LUARET_TRUTHY(result)) {
|
||||||
|
ts_lua = true;
|
||||||
|
}
|
||||||
|
api_clear_error(&err);
|
||||||
|
}
|
||||||
|
|
||||||
if (fname == NULL
|
if (fname == NULL
|
||||||
&& (ex_lua || strequal(curbuf->b_p_ft, "lua")
|
&& (ex_lua || ts_lua || strequal(curbuf->b_p_ft, "lua")
|
||||||
|| (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua")))) {
|
|| (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua")))) {
|
||||||
// Source lines from the current buffer as lua
|
// Source lines from the current buffer as lua
|
||||||
nlua_exec_ga(&cookie.buflines, fname_exp);
|
nlua_exec_ga(&cookie.buflines, fname_exp);
|
||||||
|
|||||||
@@ -296,6 +296,35 @@ describe(':source', function()
|
|||||||
eq(nil, result:find('E484'))
|
eq(nil, result:find('E484'))
|
||||||
os.remove(test_file)
|
os.remove(test_file)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
it('sources Lua/Vimscript codeblocks based on treesitter injection', function()
|
||||||
|
insert([[
|
||||||
|
*test.txt* Test help file
|
||||||
|
|
||||||
|
Lua example: >lua
|
||||||
|
vim.g.test_lua = 42
|
||||||
|
<
|
||||||
|
|
||||||
|
Vim example: >vim
|
||||||
|
let g:test_vim = 99
|
||||||
|
<]])
|
||||||
|
command('setlocal filetype=help')
|
||||||
|
|
||||||
|
-- Source Lua codeblock (line 4 contains the Lua code)
|
||||||
|
command(':4source')
|
||||||
|
eq(42, eval('g:test_lua'))
|
||||||
|
|
||||||
|
-- Source Vimscript codeblock (line 8 contains the Vim code)
|
||||||
|
command(':8source')
|
||||||
|
eq(99, eval('g:test_vim'))
|
||||||
|
|
||||||
|
-- Test fallback without treesitter
|
||||||
|
command('enew')
|
||||||
|
insert([[let g:test_no_ts = 123]])
|
||||||
|
command('setlocal filetype=')
|
||||||
|
command('source')
|
||||||
|
eq(123, eval('g:test_no_ts'))
|
||||||
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
it('$HOME is not shortened in filepath in v:stacktrace from sourced file', function()
|
it('$HOME is not shortened in filepath in v:stacktrace from sourced file', function()
|
||||||
|
|||||||
Reference in New Issue
Block a user