mirror of
https://github.com/neovim/neovim.git
synced 2026-03-31 21:02:11 +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`.
|
||||
• |gx| in help buffers opens the online documentation for the tag under the
|
||||
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|
|
||||
• |:wall| permits a |++p| option for creating parent directories when writing
|
||||
changed buffers.
|
||||
|
||||
@@ -63,4 +63,22 @@ function M.read_chunk(file, size)
|
||||
return tostring(chunk)
|
||||
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
|
||||
|
||||
@@ -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
|
||||
|
||||
// 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
|
||||
&& (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")))) {
|
||||
// Source lines from the current buffer as lua
|
||||
nlua_exec_ga(&cookie.buflines, fname_exp);
|
||||
|
||||
@@ -296,6 +296,35 @@ describe(':source', function()
|
||||
eq(nil, result:find('E484'))
|
||||
os.remove(test_file)
|
||||
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)
|
||||
|
||||
it('$HOME is not shortened in filepath in v:stacktrace from sourced file', function()
|
||||
|
||||
Reference in New Issue
Block a user