diff options
-rw-r--r-- | runtime/doc/lua.txt | 14 | ||||
-rw-r--r-- | runtime/doc/news.txt | 8 | ||||
-rw-r--r-- | runtime/doc/repeat.txt | 8 | ||||
-rw-r--r-- | runtime/doc/treesitter.txt | 10 | ||||
-rw-r--r-- | runtime/ftplugin/hurl.vim | 11 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter.lua | 12 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_fold.lua | 93 | ||||
-rw-r--r-- | runtime/syntax/ant.vim | 94 | ||||
-rw-r--r-- | src/nvim/api/private/helpers.c | 2 | ||||
-rw-r--r-- | src/nvim/decoration.c | 6 | ||||
-rw-r--r-- | src/nvim/ex_cmds.lua | 2 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 47 | ||||
-rw-r--r-- | src/nvim/lua/executor.c | 37 | ||||
-rw-r--r-- | src/nvim/runtime.c | 9 | ||||
-rw-r--r-- | src/nvim/terminal.c | 3 | ||||
-rw-r--r-- | src/nvim/window.c | 2 | ||||
-rw-r--r-- | test/functional/api/vim_spec.lua | 50 | ||||
-rw-r--r-- | test/functional/autocmd/win_scrolled_resized_spec.lua | 17 | ||||
-rw-r--r-- | test/functional/lua/commands_spec.lua | 41 | ||||
-rw-r--r-- | test/functional/terminal/tui_spec.lua | 58 | ||||
-rw-r--r-- | test/functional/treesitter/fold_spec.lua | 123 |
21 files changed, 275 insertions, 372 deletions
diff --git a/runtime/doc/lua.txt b/runtime/doc/lua.txt index 433a9fc266..7aa2fd5a6f 100644 --- a/runtime/doc/lua.txt +++ b/runtime/doc/lua.txt @@ -254,6 +254,14 @@ arguments separated by " " (space) instead of "\t" (tab). < To see the LuaJIT version: >vim :lua =jit.version < +:{range}lua + Executes buffer lines in {range} as Lua code. Unlike |:source|, this + always treats the lines as Lua code. + + Example: select the following code and type ":lua<Enter>" to execute it: >lua + print(string.format( + 'unix time: %s', os.time())) +< *:lua-heredoc* :lua << [trim] [{endmarker}] {script} @@ -266,10 +274,8 @@ arguments separated by " " (space) instead of "\t" (tab). function! CurrentLineInfo() lua << EOF local linenr = vim.api.nvim_win_get_cursor(0)[1] - local curline = vim.api.nvim_buf_get_lines( - 0, linenr - 1, linenr, false)[1] - print(string.format("Current line [%d] has %d bytes", - linenr, #curline)) + local curline = vim.api.nvim_buf_get_lines(0, linenr - 1, linenr, false)[1] + print(string.format('Line [%d] has %d bytes', linenr, #curline)) EOF endfunction < diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index a7c4e8147c..40717f8ecf 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -123,7 +123,8 @@ BREAKING CHANGES IN HEAD *news-breaking-dev* The following breaking changes were made during the development cycle to unreleased features on Nvim HEAD. -• ... +• Removed `vim.treesitter.foldtext` as transparent foldtext is now supported + https://github.com/neovim/neovim/pull/20750 • ... ============================================================================== @@ -223,8 +224,6 @@ The following new APIs and features were added. • |vim.treesitter.query.edit()| allows live editing of treesitter queries. • Improved error messages for query parsing. - • |vim.treesitter.foldtext()| applies treesitter highlighting to - foldtext. • |vim.ui.open()| opens URIs using the system default handler (macOS `open`, Windows `explorer`, Linux `xdg-open`, etc.) @@ -341,6 +340,9 @@ The following changes to existing APIs or features add new behavior. • |:source| without arguments treats a buffer with its 'filetype' set to "lua" as Lua code regardless of its extension. +• |:lua| with a |[range]| executes that range in the current buffer as Lua code + regardless of its extension. + • |:checkhealth| buffer now implements |folding|. The initial folding status is defined by the 'foldenable' option. diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt index 726d7a9591..ae827fa06f 100644 --- a/runtime/doc/repeat.txt +++ b/runtime/doc/repeat.txt @@ -192,11 +192,11 @@ Using Vim scripts *using-scripts* For writing a Vim script, see chapter 41 of the user manual |usr_41.txt|. *:so* *:source* *load-vim-script* -:[range]so[urce] [file] Runs |Ex| commands or Lua code (".lua" files) from +:[range]so[urce] [file] Runs |Ex-commands| or Lua code (".lua" files) from [file]. - If no [file], the current buffer is used, and it is - treated as Lua code if its 'filetype' is "lua" or its - file name ends with ".lua". + If no [file], the current buffer is used and treated + as Lua code if 'filetype' is "lua" or its filename + ends with ".lua". Triggers the |SourcePre| autocommand. *:source!* :[range]so[urce]! {file} diff --git a/runtime/doc/treesitter.txt b/runtime/doc/treesitter.txt index 956b7d6ae5..0f4462b109 100644 --- a/runtime/doc/treesitter.txt +++ b/runtime/doc/treesitter.txt @@ -652,16 +652,6 @@ foldexpr({lnum}) *vim.treesitter.foldexpr()* Return: ~ (`string`) -foldtext() *vim.treesitter.foldtext()* - Returns the highlighted content of the first line of the fold or falls - back to |foldtext()| if no treesitter parser is found. Can be set directly - to 'foldtext': >lua - vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()' -< - - Return: ~ - (`{ [1]: string, [2]: string[] }[]|string`) - *vim.treesitter.get_captures_at_cursor()* get_captures_at_cursor({winnr}) Returns a list of highlight capture names under the cursor diff --git a/runtime/ftplugin/hurl.vim b/runtime/ftplugin/hurl.vim new file mode 100644 index 0000000000..10a3131d3a --- /dev/null +++ b/runtime/ftplugin/hurl.vim @@ -0,0 +1,11 @@ +" Vim filetype plugin file +" Language: hurl +" Maintainer: Melker Ulander <melker.ulander@pm.me> +" Last Changed: 2024 01 26 + +if exists("b:did_ftplugin") | finish | endif + +let b:did_ftplugin = 1 +setlocal commentstring=#\ %s + +let b:undo_ftplugin = "setlocal commentstring<" diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 3c91be7acf..9d96ab33fa 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -517,16 +517,4 @@ function M.foldexpr(lnum) return require('vim.treesitter._fold').foldexpr(lnum) end ---- Returns the highlighted content of the first line of the fold or falls back to |foldtext()| ---- if no treesitter parser is found. Can be set directly to 'foldtext': ---- ---- ```lua ---- vim.wo.foldtext = 'v:lua.vim.treesitter.foldtext()' ---- ``` ---- ----@return { [1]: string, [2]: string[] }[] | string -function M.foldtext() - return require('vim.treesitter._fold').foldtext() -end - return M diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 735627d29f..d96cc966de 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -397,97 +397,4 @@ api.nvim_create_autocmd('OptionSet', { end end, }) - ----@package ----@return { [1]: string, [2]: string[] }[]|string -function M.foldtext() - local foldstart = vim.v.foldstart - local bufnr = api.nvim_get_current_buf() - - ---@type boolean, LanguageTree - local ok, parser = pcall(ts.get_parser, bufnr) - if not ok then - return vim.fn.foldtext() - end - - local query = ts.query.get(parser:lang(), 'highlights') - if not query then - return vim.fn.foldtext() - end - - local tree = parser:parse({ foldstart - 1, foldstart })[1] - - local line = api.nvim_buf_get_lines(bufnr, foldstart - 1, foldstart, false)[1] - if not line then - return vim.fn.foldtext() - end - - ---@type { [1]: string, [2]: string[], range: { [1]: integer, [2]: integer } }[] | { [1]: string, [2]: string[] }[] - local result = {} - - local line_pos = 0 - - for id, node, metadata in query:iter_captures(tree:root(), 0, foldstart - 1, foldstart) do - local name = query.captures[id] - local start_row, start_col, end_row, end_col = node:range() - - local priority = tonumber(metadata.priority or vim.highlight.priorities.treesitter) - - if start_row == foldstart - 1 and end_row == foldstart - 1 then - -- check for characters ignored by treesitter - if start_col > line_pos then - table.insert(result, { - line:sub(line_pos + 1, start_col), - {}, - range = { line_pos, start_col }, - }) - end - line_pos = end_col - - local text = line:sub(start_col + 1, end_col) - table.insert(result, { text, { { '@' .. name, priority } }, range = { start_col, end_col } }) - end - end - - local i = 1 - while i <= #result do - -- find first capture that is not in current range and apply highlights on the way - local j = i + 1 - while - j <= #result - and result[j].range[1] >= result[i].range[1] - and result[j].range[2] <= result[i].range[2] - do - for k, v in ipairs(result[i][2]) do - if not vim.tbl_contains(result[j][2], v) then - table.insert(result[j][2], k, v) - end - end - j = j + 1 - end - - -- remove the parent capture if it is split into children - if j > i + 1 then - table.remove(result, i) - else - -- highlights need to be sorted by priority, on equal prio, the deeper nested capture (earlier - -- in list) should be considered higher prio - if #result[i][2] > 1 then - table.sort(result[i][2], function(a, b) - return a[2] < b[2] - end) - end - - result[i][2] = vim.tbl_map(function(tbl) - return tbl[1] - end, result[i][2]) - result[i] = { result[i][1], result[i][2] } - - i = i + 1 - end - end - - return result -end - return M diff --git a/runtime/syntax/ant.vim b/runtime/syntax/ant.vim index 6696f96eb1..b0231236dd 100644 --- a/runtime/syntax/ant.vim +++ b/runtime/syntax/ant.vim @@ -1,9 +1,9 @@ " Vim syntax file -" Language: ANT build file (xml) -" Maintainer: Johannes Zellner <johannes@zellner.org> -" Last Change: Tue Apr 27 13:05:59 CEST 2004 -" Filenames: build.xml -" $Id: ant.vim,v 1.1 2004/06/13 18:13:18 vimboss Exp $ +" Language: ANT build file (xml) +" Maintainer: Doug Kearns <dougkearns@gmail.com> +" Previous Maintainer: Johannes Zellner <johannes@zellner.org> +" Last Change: 2024 Jan 27 +" Filenames: build.xml " Quit when a syntax file was already loaded if exists("b:current_syntax") @@ -44,48 +44,48 @@ call AntSyntaxScript('jpython', 'python.vim') syn cluster xmlTagHook add=antElement -syn keyword antElement display WsdlToDotnet addfiles and ant antcall antstructure apply archives arg argument -syn keyword antElement display assertions attrib attribute available basename bcc blgenclient bootclasspath -syn keyword antElement display borland bottom buildnumber buildpath buildpathelement bunzip2 bzip2 cab -syn keyword antElement display catalogpath cc cccheckin cccheckout cclock ccmcheckin ccmcheckintask ccmcheckout -syn keyword antElement display ccmcreatetask ccmkattr ccmkbl ccmkdir ccmkelem ccmklabel ccmklbtype -syn keyword antElement display ccmreconfigure ccrmtype ccuncheckout ccunlock ccupdate checksum chgrp chmod -syn keyword antElement display chown classconstants classes classfileset classpath commandline comment -syn keyword antElement display compilerarg compilerclasspath concat concatfilter condition copy copydir -syn keyword antElement display copyfile coveragepath csc custom cvs cvschangelog cvspass cvstagdiff cvsversion -syn keyword antElement display daemons date defaultexcludes define delete deletecharacters deltree depend -syn keyword antElement display depends dependset depth description different dirname dirset disable dname -syn keyword antElement display doclet doctitle dtd ear echo echoproperties ejbjar element enable entity entry -syn keyword antElement display env equals escapeunicode exclude excludepackage excludesfile exec execon -syn keyword antElement display existing expandproperties extdirs extension extensionSet extensionset factory -syn keyword antElement display fail filelist filename filepath fileset filesmatch filetokenizer filter -syn keyword antElement display filterchain filterreader filters filterset filtersfile fixcrlf footer format -syn keyword antElement display from ftp generic genkey get gjdoc grant group gunzip gzip header headfilter http -syn keyword antElement display ignoreblank ilasm ildasm import importtypelib include includesfile input iplanet -syn keyword antElement display iplanet-ejbc isfalse isreference isset istrue jar jarlib-available -syn keyword antElement display jarlib-manifest jarlib-resolve java javac javacc javadoc javadoc2 jboss jdepend -syn keyword antElement display jjdoc jjtree jlink jonas jpcoverage jpcovmerge jpcovreport jsharpc jspc -syn keyword antElement display junitreport jvmarg lib libfileset linetokenizer link loadfile loadproperties -syn keyword antElement display location macrodef mail majority manifest map mapper marker mergefiles message -syn keyword antElement display metainf method mimemail mkdir mmetrics modified move mparse none not options or -syn keyword antElement display os outputproperty package packageset parallel param patch path pathconvert -syn keyword antElement display pathelement patternset permissions prefixlines present presetdef project -syn keyword antElement display property propertyfile propertyref propertyset pvcs pvcsproject record reference -syn keyword antElement display regexp rename renameext replace replacefilter replaceregex replaceregexp -syn keyword antElement display replacestring replacetoken replacetokens replacevalue replyto report resource -syn keyword antElement display revoke rmic root rootfileset rpm scp section selector sequential serverdeploy -syn keyword antElement display setproxy signjar size sleep socket soscheckin soscheckout sosget soslabel source -syn keyword antElement display sourcepath sql src srcfile srcfilelist srcfiles srcfileset sshexec stcheckin -syn keyword antElement display stcheckout stlabel stlist stringtokenizer stripjavacomments striplinebreaks -syn keyword antElement display striplinecomments style subant substitution support symlink sync sysproperty -syn keyword antElement display syspropertyset tabstospaces tag taglet tailfilter tar tarfileset target -syn keyword antElement display targetfile targetfilelist targetfileset taskdef tempfile test testlet text title -syn keyword antElement display to token tokenfilter touch transaction translate triggers trim tstamp type -syn keyword antElement display typedef unjar untar unwar unzip uptodate url user vbc vssadd vsscheckin -syn keyword antElement display vsscheckout vsscp vsscreate vssget vsshistory vsslabel waitfor war wasclasspath -syn keyword antElement display webapp webinf weblogic weblogictoplink websphere whichresource wlclasspath -syn keyword antElement display wljspc wsdltodotnet xmlcatalog xmlproperty xmlvalidate xslt zip zipfileset -syn keyword antElement display zipgroupfileset +syn keyword antElement WsdlToDotnet addfiles and ant antcall antstructure apply archives arg argument +syn keyword antElement assertions attrib attribute available basename bcc blgenclient bootclasspath +syn keyword antElement borland bottom buildnumber buildpath buildpathelement bunzip2 bzip2 cab +syn keyword antElement catalogpath cc cccheckin cccheckout cclock ccmcheckin ccmcheckintask ccmcheckout +syn keyword antElement ccmcreatetask ccmkattr ccmkbl ccmkdir ccmkelem ccmklabel ccmklbtype +syn keyword antElement ccmreconfigure ccrmtype ccuncheckout ccunlock ccupdate checksum chgrp chmod +syn keyword antElement chown classconstants classes classfileset classpath commandline comment +syn keyword antElement compilerarg compilerclasspath concat concatfilter condition copy copydir +syn keyword antElement copyfile coveragepath csc custom cvs cvschangelog cvspass cvstagdiff cvsversion +syn keyword antElement daemons date defaultexcludes define delete deletecharacters deltree depend +syn keyword antElement depends dependset depth description different dirname dirset disable dname +syn keyword antElement doclet doctitle dtd ear echo echoproperties ejbjar element enable entity entry +syn keyword antElement env equals escapeunicode exclude excludepackage excludesfile exec execon +syn keyword antElement existing expandproperties extdirs extension extensionSet extensionset factory +syn keyword antElement fail filelist filename filepath fileset filesmatch filetokenizer filter +syn keyword antElement filterchain filterreader filters filterset filtersfile fixcrlf footer format +syn keyword antElement from ftp generic genkey get gjdoc grant group gunzip gzip header headfilter http +syn keyword antElement ignoreblank ilasm ildasm import importtypelib include includesfile input iplanet +syn keyword antElement iplanet-ejbc isfalse isreference isset istrue jar jarlib-available +syn keyword antElement jarlib-manifest jarlib-resolve java javac javacc javadoc javadoc2 jboss jdepend +syn keyword antElement jjdoc jjtree jlink jonas jpcoverage jpcovmerge jpcovreport jsharpc jspc +syn keyword antElement junitreport jvmarg lib libfileset linetokenizer link loadfile loadproperties +syn keyword antElement location macrodef mail majority manifest map mapper marker mergefiles message +syn keyword antElement metainf method mimemail mkdir mmetrics modified move mparse none not options or +syn keyword antElement os outputproperty package packageset parallel param patch path pathconvert +syn keyword antElement pathelement patternset permissions prefixlines present presetdef project +syn keyword antElement property propertyfile propertyref propertyset pvcs pvcsproject record reference +syn keyword antElement regexp rename renameext replace replacefilter replaceregex replaceregexp +syn keyword antElement replacestring replacetoken replacetokens replacevalue replyto report resource +syn keyword antElement revoke rmic root rootfileset rpm scp section selector sequential serverdeploy +syn keyword antElement setproxy signjar size sleep socket soscheckin soscheckout sosget soslabel source +syn keyword antElement sourcepath sql src srcfile srcfilelist srcfiles srcfileset sshexec stcheckin +syn keyword antElement stcheckout stlabel stlist stringtokenizer stripjavacomments striplinebreaks +syn keyword antElement striplinecomments style subant substitution support symlink sync sysproperty +syn keyword antElement syspropertyset tabstospaces tag taglet tailfilter tar tarfileset target +syn keyword antElement targetfile targetfilelist targetfileset taskdef tempfile test testlet text title +syn keyword antElement to token tokenfilter touch transaction translate triggers trim tstamp type +syn keyword antElement typedef unjar untar unwar unzip uptodate url user vbc vssadd vsscheckin +syn keyword antElement vsscheckout vsscp vsscreate vssget vsshistory vsslabel waitfor war wasclasspath +syn keyword antElement webapp webinf weblogic weblogictoplink websphere whichresource wlclasspath +syn keyword antElement wljspc wsdltodotnet xmlcatalog xmlproperty xmlvalidate xslt zip zipfileset +syn keyword antElement zipgroupfileset hi def link antElement Statement diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 8f30ac7c8f..9ce1786fa0 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -151,7 +151,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (did_throw) { + } else if (did_throw || need_rethrow) { if (*current_exception->throw_name != NUL) { if (current_exception->throw_lnum != 0) { api_set_error(err, kErrorTypeException, "%s, line %" PRIdLINENR ": %s", diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c index 8d9b234bbc..755655856d 100644 --- a/src/nvim/decoration.c +++ b/src/nvim/decoration.c @@ -629,9 +629,9 @@ next_mark: } else if (item.data.sh.flags & kSHSpellOff) { spell = kFalse; } - } - if (active && item.data.sh.url != NULL) { - attr = hl_add_url(attr, item.data.sh.url); + if (item.data.sh.url != NULL) { + attr = hl_add_url(attr, item.data.sh.url); + } } if (item.start_row == state->row && item.start_col <= col && decor_virt_pos(&item) && item.draw_col == -10) { diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 551d228862..1318eda5eb 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -1612,7 +1612,7 @@ module.cmds = { }, { command = 'lua', - flags = bit.bor(RANGE, EXTRA, NEEDARG, CMDWIN, LOCK_OK), + flags = bit.bor(RANGE, EXTRA, CMDWIN, LOCK_OK), addr_type = 'ADDR_LINES', func = 'ex_lua', }, diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 7c3eb47247..12e746d49e 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -309,6 +309,33 @@ static void msg_verbose_cmd(linenr_T lnum, char *cmd) no_wait_return--; } +static int cmdline_call_depth = 0; ///< recursiveness + +/// Start executing an Ex command line. +/// +/// @return FAIL if too recursive, OK otherwise. +static int do_cmdline_start(void) +{ + assert(cmdline_call_depth >= 0); + // It's possible to create an endless loop with ":execute", catch that + // here. The value of 200 allows nested function calls, ":source", etc. + // Allow 200 or 'maxfuncdepth', whatever is larger. + if (cmdline_call_depth >= 200 && cmdline_call_depth >= p_mfd) { + return FAIL; + } + cmdline_call_depth++; + start_batch_changes(); + return OK; +} + +/// End executing an Ex command line. +static void do_cmdline_end(void) +{ + cmdline_call_depth--; + assert(cmdline_call_depth >= 0); + end_batch_changes(); +} + /// Execute a simple command line. Used for translated commands like "*". int do_cmdline_cmd(const char *cmd) { @@ -359,7 +386,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) char *(*cmd_getline)(int, void *, int, bool); void *cmd_cookie; struct loop_cookie cmd_loop_cookie; - static int call_depth = 0; // recursiveness // For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory // location for storing error messages to be converted to an exception. @@ -371,10 +397,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) msg_list = &private_msg_list; private_msg_list = NULL; - // It's possible to create an endless loop with ":execute", catch that - // here. The value of 200 allows nested function calls, ":source", etc. - // Allow 200 or 'maxfuncdepth', whatever is larger. - if (call_depth >= 200 && call_depth >= p_mfd) { + if (do_cmdline_start() == FAIL) { emsg(_(e_command_too_recursive)); // When converting to an exception, we do not include the command name // since this is not an error of the specific command. @@ -382,8 +405,6 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) msg_list = saved_msg_list; return FAIL; } - call_depth++; - start_batch_changes(); ga_init(&lines_ga, (int)sizeof(wcmd_T), 10); @@ -884,8 +905,7 @@ int do_cmdline(char *cmdline, LineGetter fgetline, void *cookie, int flags) did_endif = false; // in case do_cmdline used recursively - call_depth--; - end_batch_changes(); + do_cmdline_end(); return retval; } @@ -1669,9 +1689,13 @@ static int execute_cmd0(int *retv, exarg_T *eap, const char **errormsg, bool pre /// @param preview Execute command preview callback instead of actual command int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview) { - const char *errormsg = NULL; int retv = 0; + if (do_cmdline_start() == FAIL) { + emsg(_(e_command_too_recursive)); + return retv; + } + const char *errormsg = NULL; #undef ERROR #define ERROR(msg) \ do { \ @@ -1738,9 +1762,12 @@ end: if (errormsg != NULL && *errormsg != NUL) { emsg(errormsg); } + // Undo command modifiers undo_cmdmod(&cmdmod); cmdmod = save_cmdmod; + + do_cmdline_end(); return retv; #undef ERROR } diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index f48cab6739..62e82175c3 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1641,27 +1641,38 @@ bool nlua_is_deferred_safe(void) return in_fast_callback == 0; } -/// Run lua string +/// Executes Lua code. /// -/// Used for :lua. +/// Implements `:lua` and `:lua ={expr}`. /// -/// @param eap Vimscript command being run. +/// @param eap Vimscript `:lua {code}`, `:{range}lua`, or `:lua ={expr}` command. void ex_lua(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { + // ":{range}lua" + if (eap->addr_count > 0 || *eap->arg == NUL) { + if (eap->addr_count > 0 && *eap->arg == NUL) { + cmd_source_buffer(eap, true); + } else { + semsg(_(e_invarg2), "exactly one of {chunk} or {range} required"); + } + return; + } + size_t len; char *code = script_get(eap, &len); if (eap->skip || code == NULL) { xfree(code); return; } - // When =expr is used transform it to vim.print(expr) + + // ":lua {code}", ":={expr}" or ":lua ={expr}" + // + // When "=expr" is used transform it to "vim.print(expr)". if (eap->cmdidx == CMD_equal || code[0] == '=') { size_t off = (eap->cmdidx == CMD_equal) ? 0 : 1; len += sizeof("vim.print()") - 1 - off; - // code_buf needs to be 1 char larger then len for null byte in the end. - // lua nlua_typval_exec doesn't expect null terminated string so len - // needs to end before null byte. + // `nlua_typval_exec` doesn't expect NUL-terminated string so `len` must end before NUL byte. char *code_buf = xmallocz(len); vim_snprintf(code_buf, len + 1, "vim.print(%s)", code + off); xfree(code); @@ -1673,11 +1684,11 @@ void ex_lua(exarg_T *const eap) xfree(code); } -/// Run lua string for each line in range +/// Executes Lua code for-each line in a buffer range. /// -/// Used for :luado. +/// Implements `:luado`. /// -/// @param eap Vimscript command being run. +/// @param eap Vimscript `:luado {code}` command. void ex_luado(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { @@ -1754,11 +1765,11 @@ void ex_luado(exarg_T *const eap) redraw_curbuf_later(UPD_NOT_VALID); } -/// Run lua file +/// Executes Lua code from a file location. /// -/// Used for :luafile. +/// Implements `:luafile`. /// -/// @param eap Vimscript command being run. +/// @param eap Vimscript `:luafile {file}` command. void ex_luafile(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c index 3f8e467118..98e9d6c9e6 100644 --- a/src/nvim/runtime.c +++ b/src/nvim/runtime.c @@ -1779,7 +1779,7 @@ freeall: static void cmd_source(char *fname, exarg_T *eap) { if (eap != NULL && *fname == NUL) { - cmd_source_buffer(eap); + cmd_source_buffer(eap, false); } else if (eap != NULL && eap->forceit) { // ":source!": read Normal mode commands // Need to execute the commands directly. This is required at least @@ -1989,7 +1989,7 @@ static int source_using_linegetter(void *cookie, LineGetter fgetline, const char return retval; } -static void cmd_source_buffer(const exarg_T *const eap) +void cmd_source_buffer(const exarg_T *const eap, bool ex_lua) FUNC_ATTR_NONNULL_ALL { if (curbuf == NULL) { @@ -2012,9 +2012,10 @@ static void cmd_source_buffer(const exarg_T *const eap) .buf = ga.ga_data, .offset = 0, }; - if (strequal(curbuf->b_p_ft, "lua") + if (ex_lua || strequal(curbuf->b_p_ft, "lua") || (curbuf->b_fname && path_with_extension(curbuf->b_fname, "lua"))) { - nlua_source_using_linegetter(get_str_line, (void *)&cookie, ":source (no file)"); + char *name = ex_lua ? ":{range}lua" : ":source (no file)"; + nlua_source_using_linegetter(get_str_line, (void *)&cookie, name); } else { source_using_linegetter((void *)&cookie, get_str_line, ":source (no file)"); } diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 00acbfb602..499f31454e 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -93,6 +93,7 @@ #include "nvim/types_defs.h" #include "nvim/ui.h" #include "nvim/vim_defs.h" +#include "nvim/window.h" typedef struct { VimState state; @@ -615,6 +616,8 @@ static int terminal_check(VimState *state) curbuf->b_locked--; } + may_trigger_win_scrolled_resized(); + if (need_maketitle) { // Update title in terminal-mode. #7248 maketitle(); } diff --git a/src/nvim/window.c b/src/nvim/window.c index 66d090b0c8..23779d1e7b 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -5367,7 +5367,7 @@ static int check_window_scroll_resize(int *size_count, win_T **first_scroll_win, FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { // Skip floating windows that do not have a snapshot (usually because they are newly-created), - // as unlike split windows, creating floating windows do not cause other windows to resize. + // as unlike split windows, creating floating windows doesn't cause other windows to resize. if (wp->w_floating && wp->w_last_topline == 0) { wp->w_last_topline = wp->w_topline; wp->w_last_topfill = wp->w_topfill; diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 91f61b5053..5cf48412a8 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -399,6 +399,19 @@ describe('API', function() ]], } end) + + it('errors properly when command too recursive', function() + exec_lua([[ + _G.success = false + vim.api.nvim_create_user_command('Test', function() + vim.api.nvim_exec2('Test', {}) + _G.success = true + end, {}) + ]]) + pcall_err(command, 'Test') + assert_alive() + eq(false, exec_lua('return _G.success')) + end) end) describe('nvim_command', function() @@ -4560,6 +4573,7 @@ describe('API', function() line6 ]] end) + it('works with count', function() insert [[ line1 @@ -4577,6 +4591,7 @@ describe('API', function() line6 ]] end) + it('works with register', function() insert [[ line1 @@ -4599,11 +4614,13 @@ describe('API', function() line6 ]] end) + it('works with bang', function() api.nvim_create_user_command('Foo', 'echo "<bang>"', { bang = true }) eq('!', api.nvim_cmd({ cmd = 'Foo', bang = true }, { output = true })) eq('', api.nvim_cmd({ cmd = 'Foo', bang = false }, { output = true })) end) + it('works with modifiers', function() -- with silent = true output is still captured eq( @@ -4659,6 +4676,7 @@ describe('API', function() feed(':call<CR><CR>') eq('E471: Argument required', api.nvim_cmd({ cmd = 'messages' }, { output = true })) end) + it('works with magic.file', function() exec_lua([[ vim.api.nvim_create_user_command("Foo", function(opts) @@ -4673,6 +4691,7 @@ describe('API', function() ) ) end) + it('splits arguments correctly', function() exec([[ function! FooFunc(...) @@ -4695,6 +4714,7 @@ describe('API', function() ) ) end) + it('splits arguments correctly for Lua callback', function() api.nvim_exec_lua( [[ @@ -4721,6 +4741,7 @@ describe('API', function() ) ) end) + it('works with buffer names', function() command('edit foo.txt | edit bar.txt') api.nvim_cmd({ cmd = 'buffer', args = { 'foo.txt' } }, {}) @@ -4728,6 +4749,7 @@ describe('API', function() api.nvim_cmd({ cmd = 'buffer', args = { 'bar.txt' } }, {}) eq('bar.txt', fn.fnamemodify(api.nvim_buf_get_name(0), ':t')) end) + it('triggers CmdUndefined event if command is not found', function() api.nvim_exec_lua( [[ @@ -4742,13 +4764,16 @@ describe('API', function() ) eq('foo', api.nvim_cmd({ cmd = 'Foo' }, { output = true })) end) + it('errors if command is not implemented', function() eq('Command not implemented: winpos', pcall_err(api.nvim_cmd, { cmd = 'winpos' }, {})) end) + it('works with empty arguments list', function() api.nvim_cmd({ cmd = 'update' }, {}) api.nvim_cmd({ cmd = 'buffer', count = 0 }, {}) end) + it("doesn't suppress errors when used in keymapping", function() api.nvim_exec_lua( [[ @@ -4760,6 +4785,7 @@ describe('API', function() feed('[l') neq(nil, string.find(eval('v:errmsg'), 'E5108:')) end) + it('handles 0 range #19608', function() api.nvim_buf_set_lines(0, 0, -1, false, { 'aa' }) api.nvim_cmd({ cmd = 'delete', range = { 0 } }, {}) @@ -4767,12 +4793,14 @@ describe('API', function() eq({ 'aa' }, api.nvim_buf_get_lines(0, 0, 1, false)) assert_alive() end) + it('supports filename expansion', function() api.nvim_cmd({ cmd = 'argadd', args = { '%:p:h:t', '%:p:h:t' } }, {}) local arg = fn.expand('%:p:h:t') eq({ arg, arg }, fn.argv()) end) - it("'make' command works when argument count isn't 1 #19696", function() + + it(":make command works when argument count isn't 1 #19696", function() command('set makeprg=echo') command('set shellquote=') matches('^:!echo ', api.nvim_cmd({ cmd = 'make' }, { output = true })) @@ -4789,6 +4817,7 @@ describe('API', function() ) assert_alive() end) + it("doesn't display messages when output=true", function() local screen = Screen.new(40, 6) screen:attach() @@ -4817,31 +4846,37 @@ describe('API', function() ]], } end) + it('works with non-String args', function() eq('2', api.nvim_cmd({ cmd = 'echo', args = { 2 } }, { output = true })) eq('1', api.nvim_cmd({ cmd = 'echo', args = { true } }, { output = true })) end) + describe('first argument as count', function() it('works', function() command('vsplit | enew') api.nvim_cmd({ cmd = 'bdelete', args = { api.nvim_get_current_buf() } }, {}) eq(1, api.nvim_get_current_buf()) end) + it('works with :sleep using milliseconds', function() local start = uv.now() api.nvim_cmd({ cmd = 'sleep', args = { '100m' } }, {}) ok(uv.now() - start <= 300) end) end) + it(':call with unknown function does not crash #26289', function() eq( 'Vim:E117: Unknown function: UnknownFunc', pcall_err(api.nvim_cmd, { cmd = 'call', args = { 'UnknownFunc()' } }, {}) ) end) + it(':throw does not crash #24556', function() eq('42', pcall_err(api.nvim_cmd, { cmd = 'throw', args = { '42' } }, {})) end) + it('can use :return #24556', function() exec([[ func Foo() @@ -4854,5 +4889,18 @@ describe('API', function() eq('before', api.nvim_get_var('pos')) eq({ 1, 2, 3 }, api.nvim_get_var('result')) end) + + it('errors properly when command too recursive #27210', function() + exec_lua([[ + _G.success = false + vim.api.nvim_create_user_command('Test', function() + vim.api.nvim_cmd({ cmd = 'Test' }, {}) + _G.success = true + end, {}) + ]]) + pcall_err(command, 'Test') + assert_alive() + eq(false, exec_lua('return _G.success')) + end) end) end) diff --git a/test/functional/autocmd/win_scrolled_resized_spec.lua b/test/functional/autocmd/win_scrolled_resized_spec.lua index c2a534ab27..d40dc37103 100644 --- a/test/functional/autocmd/win_scrolled_resized_spec.lua +++ b/test/functional/autocmd/win_scrolled_resized_spec.lua @@ -39,6 +39,23 @@ describe('WinResized', function() eq(2, eval('g:resized')) eq({ windows = { 1002, 1001, 1000 } }, eval('g:v_event')) end) + + it('is triggered in terminal mode #21197 #27207', function() + exec([[ + autocmd TermOpen * startinsert + let g:resized = 0 + autocmd WinResized * let g:resized += 1 + ]]) + eq(0, eval('g:resized')) + + command('vsplit term://') + eq({ mode = 't', blocking = false }, api.nvim_get_mode()) + eq(1, eval('g:resized')) + + command('split') + eq({ mode = 't', blocking = false }, api.nvim_get_mode()) + eq(2, eval('g:resized')) + end) end) describe('WinScrolled', function() diff --git a/test/functional/lua/commands_spec.lua b/test/functional/lua/commands_spec.lua index 28a99a86f8..b759d0e2c4 100644 --- a/test/functional/lua/commands_spec.lua +++ b/test/functional/lua/commands_spec.lua @@ -22,7 +22,7 @@ local remove_trace = helpers.remove_trace before_each(clear) -describe(':lua command', function() +describe(':lua', function() it('works', function() eq('', exec_capture('lua vim.api.nvim_buf_set_lines(1, 1, 2, false, {"TEST"})')) eq({ '', 'TEST' }, api.nvim_buf_get_lines(0, 0, 100, false)) @@ -54,7 +54,14 @@ describe(':lua command', function() ) ) end) + it('throws catchable errors', function() + for _, cmd in ipairs({ 'lua', '1lua chunk' }) do + eq( + 'Vim(lua):E475: Invalid argument: exactly one of {chunk} or {range} required', + pcall_err(command, cmd) + ) + end eq( [[Vim(lua):E5107: Error loading lua [string ":lua"]:0: unexpected symbol near ')']], pcall_err(command, 'lua ()') @@ -69,9 +76,11 @@ describe(':lua command', function() ) eq({ '' }, api.nvim_buf_get_lines(0, 0, 100, false)) end) + it('works with NULL errors', function() eq([=[Vim(lua):E5108: Error executing lua [NULL]]=], exc_exec('lua error(nil)')) end) + it('accepts embedded NLs without heredoc', function() -- Such code is usually used for `:execute 'lua' {generated_string}`: -- heredocs do not work in this case. @@ -83,12 +92,14 @@ describe(':lua command', function() ]]) eq({ '', 'ETTS', 'TTSE', 'STTE' }, api.nvim_buf_get_lines(0, 0, 100, false)) end) + it('preserves global and not preserves local variables', function() eq('', exec_capture('lua gvar = 42')) eq('', exec_capture('lua local lvar = 100500')) eq(NIL, fn.luaeval('lvar')) eq(42, fn.luaeval('gvar')) end) + it('works with long strings', function() local s = ('x'):rep(100500) @@ -192,6 +203,34 @@ describe(':lua command', function() exec_capture('=x(false)') ) end) + + it('with range', function() + local screen = Screen.new(40, 10) + screen:attach() + api.nvim_buf_set_lines(0, 0, 0, 0, { 'nonsense', 'function x() print "hello" end', 'x()' }) + + -- ":{range}lua" fails on invalid Lua code. + eq( + [[:{range}lua: Vim(lua):E5107: Error loading lua [string ":{range}lua"]:0: '=' expected near '<eof>']], + pcall_err(command, '1lua') + ) + + -- ":{range}lua" executes valid Lua code. + feed(':2,3lua<CR>') + screen:expect { + grid = [[ + nonsense | + function x() print "hello" end | + x() | + ^ | + {1:~ }|*5 + hello | + ]], + attr_ids = { + [1] = { foreground = Screen.colors.Blue, bold = true }, + }, + } + end) end) describe(':luado command', function() diff --git a/test/functional/terminal/tui_spec.lua b/test/functional/terminal/tui_spec.lua index 5bfc7efbb3..06285d91b5 100644 --- a/test/functional/terminal/tui_spec.lua +++ b/test/functional/terminal/tui_spec.lua @@ -8,7 +8,6 @@ local helpers = require('test.functional.helpers')(after_each) local thelpers = require('test.functional.terminal.helpers') local Screen = require('test.functional.ui.screen') local eq = helpers.eq -local feed_command = helpers.feed_command local feed_data = thelpers.feed_data local clear = helpers.clear local command = helpers.command @@ -2810,17 +2809,13 @@ end) describe('TUI bg color', function() before_each(clear) - local attr_ids = { - [1] = { reverse = true }, - [2] = { bold = true }, - [3] = { reverse = true, bold = true }, - [4] = { foreground = tonumber('0x00000a') }, - } - it('is properly set in a nested Nvim instance when background=dark', function() command('highlight clear Normal') command('set background=dark') -- set outer Nvim background + local child_server = new_pipename() local screen = thelpers.setup_child_nvim({ + '--listen', + child_server, '-u', 'NONE', '-i', @@ -2830,26 +2825,20 @@ describe('TUI bg color', function() '--cmd', 'set noswapfile', }) - screen:set_default_attr_ids(attr_ids) - retry(nil, 30000, function() -- wait for automatic background processing - screen:sleep(20) - feed_command('set background?') -- check nested Nvim background - screen:expect([[ - {1: } | - {2:~} | - {2:~} | - {2:~} | - {3:[No Name] 0,0-1 All}| - background=dark | - {4:-- TERMINAL --} | - ]]) + screen:expect({ any = '%[No Name%]' }) + local child_session = helpers.connect(child_server) + retry(nil, nil, function() + eq({ true, 'dark' }, { child_session:request('nvim_eval', '&background') }) end) end) it('is properly set in a nested Nvim instance when background=light', function() command('highlight clear Normal') command('set background=light') -- set outer Nvim background + local child_server = new_pipename() local screen = thelpers.setup_child_nvim({ + '--listen', + child_server, '-u', 'NONE', '-i', @@ -2859,18 +2848,10 @@ describe('TUI bg color', function() '--cmd', 'set noswapfile', }) - retry(nil, 30000, function() -- wait for automatic background processing - screen:sleep(20) - feed_command('set background?') -- check nested Nvim background - screen:expect([[ - {1: } | - {3:~} | - {3:~} | - {3:~} | - {5:[No Name] 0,0-1 All}| - background=light | - {3:-- TERMINAL --} | - ]]) + screen:expect({ any = '%[No Name%]' }) + local child_session = helpers.connect(child_server) + retry(nil, nil, function() + eq({ true, 'light' }, { child_session:request('nvim_eval', '&background') }) end) end) @@ -2914,18 +2895,13 @@ describe('TUI bg color', function() '-c', 'autocmd OptionSet background echo "did OptionSet, yay!"', }) - retry(nil, 30000, function() -- wait for automatic background processing - screen:sleep(20) - screen:expect([[ + screen:expect([[ {1: } | - {3:~} | - {3:~} | - {3:~} | + {3:~} |*3 {5:[No Name] 0,0-1 All}| did OptionSet, yay! | {3:-- TERMINAL --} | - ]]) - end) + ]]) end) end) diff --git a/test/functional/treesitter/fold_spec.lua b/test/functional/treesitter/fold_spec.lua index ac9d227bb6..9428432f66 100644 --- a/test/functional/treesitter/fold_spec.lua +++ b/test/functional/treesitter/fold_spec.lua @@ -675,126 +675,3 @@ t2]]) } end) end) - -describe('treesitter foldtext', function() - local test_text = [[ -void qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *)) -{ - int width = INT_MAX, height = INT_MAX; - bool ext_widgets[kUIExtCount]; - for (UIExtension i = 0; (int)i < kUIExtCount; i++) { - ext_widgets[i] = true; - } - - bool inclusive = ui_override(); - for (size_t i = 0; i < ui_count; i++) { - UI *ui = uis[i]; - width = MIN(ui->width, width); - height = MIN(ui->height, height); - foo = BAR(ui->bazaar, bazaar); - for (UIExtension j = 0; (int)j < kUIExtCount; j++) { - ext_widgets[j] &= (ui->ui_ext[j] || inclusive); - } - } -}]] - local screen - - before_each(function() - screen = Screen.new(60, 5) - screen:set_default_attr_ids({ - [0] = { foreground = Screen.colors.Blue, bold = true }, - [1] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.LightGray }, - [2] = { - bold = true, - background = Screen.colors.LightGray, - foreground = Screen.colors.SeaGreen, - }, - [3] = { foreground = Screen.colors.DarkCyan, background = Screen.colors.LightGray }, - [4] = { foreground = Screen.colors.SlateBlue, background = Screen.colors.LightGray }, - [5] = { bold = true, background = Screen.colors.LightGray, foreground = Screen.colors.Brown }, - [6] = { background = Screen.colors.Red1 }, - [7] = { foreground = Screen.colors.DarkBlue, background = Screen.colors.Red }, - [8] = { foreground = Screen.colors.Brown, bold = true, background = Screen.colors.Red }, - [9] = { foreground = Screen.colors.SlateBlue, background = Screen.colors.Red }, - [10] = { bold = true }, - }) - screen:attach() - end) - - it('displays highlighted content', function() - command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext() updatetime=50]]) - insert(test_text) - exec_lua([[vim.treesitter.get_parser(0, "c")]]) - - feed('ggVGzf') - screen:expect { - grid = [[ - {4:^void}{1: }{3:qsort}{4:(void}{1: }{5:*}{3:base}{4:,}{1: }{4:size_t}{1: }{3:nel}{4:,}{1: }{4:size_t}{1: }{3:width}{4:,}{1: }{4:int}{1: }{4:(}{5:*}{3:compa}| - {0:~ }|*3 - | - ]], - } - end) - - it('handles deep nested captures', function() - command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext() updatetime=50]]) - insert([[ -function FoldInfo.new() - return setmetatable({ - start_counts = {}, - stop_counts = {}, - levels0 = {}, - levels = {}, - }, FoldInfo) -end]]) - exec_lua([[vim.treesitter.get_parser(0, "lua")]]) - - feed('ggjVGkzfgg') - screen:expect { - grid = [[ - ^function FoldInfo.new() | - {1: }{5:return}{1: }{4:setmetatable({}{1:·····································}| - end | - {0:~ }| - | - ]], - } - - command('hi! Visual guibg=Red') - feed('GVgg') - screen:expect { - grid = [[ - ^f{6:unction FoldInfo.new()} | - {7: }{8:return}{7: }{9:setmetatable({}{7:·····································}| - {6:end} | - {0:~ }| - {10:-- VISUAL LINE --} | - ]], - } - - feed('10l<C-V>') - screen:expect { - grid = [[ - {6:function F}^oldInfo.new() | - {7: }{8:return}{7: }{9:se}{4:tmetatable({}{1:·····································}| - {6:end} | - {0:~ }| - {10:-- VISUAL BLOCK --} | - ]], - } - end) - - it('falls back to default', function() - command([[set foldmethod=manual foldtext=v:lua.vim.treesitter.foldtext()]]) - insert(test_text) - - feed('ggVGzf') - screen:expect { - grid = [[ - {1:^+-- 19 lines: void qsort(void *base, size_t nel, size_t widt}| - {0:~ }|*3 - | - ]], - } - end) -end) |