aboutsummaryrefslogtreecommitdiff
path: root/test/functional/lua
Commit message (Collapse)AuthorAge
* fix(snippet): preserve the full TM_DIRECTORY path (#42408)Volodymyr Chernetskyi3 days
| | | | | | | | | | Problem: TM_DIRECTORY expands to the directory's basename, losing its parent path. Solution: Remove the tail modifier when expanding the current file's directory. Extend the existing known-variable test to check the full directory path. AI-assisted
* refactor(watch): extract watchdirs into a classLewis Russell3 days
| | | | | | | | | | | | Problem: watchdirs() keeps its state in large nested callbacks in _watch.lua, which makes its directory tracking logic harder to change. Solution: Move the backend into its own module and store its state in a WatchDirs instance. Split setup, event handling and cleanup into methods. Extend the existing test to cover cancellation while a directory stat is pending. AI-assisted
* fix(diagnostic): use logical positions for counted jumps #42394Volodymyr Chernetskyi3 days
| | | | | | | | | | | | | Problem: After edits move diagnostic ranges, counted jumps resume searching from their original positions. This can revisit the same diagnostic and hang the default first/last diagnostic mappings. Solution: Advance the search origin using the diagnostic's current logical position. Test forward and backward counted jumps after inserting or deleting a line, including jumps to the last or first diagnostic without wrapping. AI-assisted
* feat(inspector): support ranges in vim.inspect_posLewis Russell3 days
| | | | | | | | | | | | | | | | Problem: Highlight inspection is limited to a single position, and its result types do not document the fields shared by different highlight sources. Solution: Accept end_row/end_col in vim.inspect_pos to inspect a range. Keep range ends exclusive and retain the existing single-position behavior. Support Ex line ranges in :Inspect, including Visual selections. Document named result types, expose shared highlight fields, and keep the existing extmark details and buffer field for compatibility. AI-assisted
* feat(diagnostic): add source option to virtual_lines handler (#42371)Aradhya Jain4 days
| | | | | | | | | | | | vim.diagnostic.Opts.VirtualText has a `source` option to prefix diagnostic messages with their source (`'if_many'` or always), but the newer virtual_lines handler never got the same option, even though the shared helpers it needs (diagnostic_shared.prefix_source/count_sources) are already render-target-agnostic and used by virtual_text as-is. Add the same `source` field to vim.diagnostic.Opts.VirtualLines and apply it in the virtual_lines handler right after message formatting, mirroring virtual_text's own ordering (format first, then prefix the source onto the already-formatted message).
* fix(api): nvim_buf_call() from expression for window in non-current tabpage ↵Justin M. Keyes5 days
| | | | | | | | | | | | | | | | #42352 Problem: - nvim_buf_call() aborts (assert) when called from an expression evaluated for a window in another tabpage ('foldexpr', 'statusline'), because `curwin` is not in `curtab`. - After it returns, `curwin` is wrong if it switched windows. - `vim._with{win=…, keepcwd=true}` does not undo `:lcd` if `win` is in another tabpage. Solution: - Allow `curwin` outside `curtab`, and restore if it was outside `curtab` at `ctx_switch()`. - Find the target window in any tabpage when restoring its dirs.
* fix(api): nvim_set_option_value() "remove" ignores non-adjacent items #42351Justin M. Keyes5 days
| | | | | | | | | | | | | Problem: `nvim_set_option_value('fo', {'c','r','o'}, {operation='remove'})` does nothing on "jcqrol": the items are joined into one `:set-=` value, which only matches a contiguous substring. Removing map keys, e.g. {'eol','space'} from 'listchars', never matches. `vim.opt:remove{…}` inherits this. (Regression from f34ee3da80e8; 429338e74bf4 fixed the case where the flags are adjacent and in order.) Solution: Remove each item separately.
* fix(diagnostic): "Failed to delete autocmd" after noautocmd bwipeout #42303Justin M. Keyes7 days
| | | | | | | | | | | Problem: Handlers defer `show()` on unloaded buffers with a buffer-local BufRead autocmd whose id is stored per-namespace: - `noautocmd bwipeout` deletes the autocmd, so the next show/hide fails. - Showing another buffer in the same namespace cancels the pending show. Solution: Handlers skip unloaded buffers. The existing global BufRead autocmd shows cached diagnostics when the buffer loads.
* Merge pull request #42198 from lewis6991/watch-pruningLewis Russell13 days
|\ | | | | perf(watch): skip directories outside include glob prefixes
| * perf(watch): skip directories outside include glob prefixesLewis Russell2026-10-05
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: watchdirs() scans and watches every directory that is not excluded, even when a glob only matches files in one subtree. Some language servers register absolute dependency filenames separately. Neovim uses the workspace root for these string globs, so each extra registration can scan and allocate libuv handles across the whole tree. Solution: Keep each glob's literal directory prefix with its compiled matcher. A directory must be allowed by both the include filter and at least one watch rule. Skip unrelated subtrees, including new directories created later. Keep ancestors watched so matching directories can still be found. Report events only when they match the full patterns. For example, watching /project for /project/src/**/*.lua only needs the src tree and /project itself: /project/ watched |-- src/ watched | `-- lib/ watched |-- tests/ skipped | `-- fixtures/ skipped `-- vendor/ skipped `-- pkg/ skipped With no exclusions, this uses three watches instead of seven. The gain can be large, but depends on the server's patterns and the workspace layout. Separate watches for absolute dependency filenames can skip most directories. For example, a test workspace has 213 directories, including 201 under build/. The two dependency files in this example are under packages/ and styles/, so their watches can skip build/. Before this change, each watch starts at the workspace root, scans all 213 directories and creates 213 libuv handles. With this change: - Haskell Language Server: a separate watch for /project/packages/demo/demo.cabal watches four directories: the project root, packages/, demo/ and its src/ child. This cuts that watch's scans and handles by 98%. With one broad source watch and this dependency watch, the total falls by 49%. - Tailwind CSS v4: a separate watch for the imported /project/styles/themes/colors.css watches three directories: the project root, styles/ and themes/. This cuts that watch's scans and handles by 99%. With two broad watches and this dependency watch, the total falls by 33%. The broad watches still cover all 213 directories. The saving comes from avoiding repeated scans and extra handles for each dependency watch. Most servers with watch registrations in the survey do not benefit. Their patterns match throughout the watch root, or their watches already start at the relevant directory. In both cases, the glob prefix cannot exclude more directories. AI-assisted
| * fix(glob): match leading hash characters literallyLewis Russell2026-10-05
| | | | | | | | | | | | | | | | | | | | | | | | Problem: Globs such as '#generated/*.lua' fail to match their literal paths. The parser consumes a leading '#' as its internal in-segment flag. Solution: Pass the flag as an LPeg match argument instead of inserting it into the glob text. Cover literal and braced patterns with leading hashes. AI-assisted
* | fix(vim.fs): find{follow=true} skips symlink targets of the given `type` #42246Justin M. Keyes2026-10-05
| | | | | | | | | | | | | | | | | | Problem: `vim.fs.find()` checks `type` against the symlink itself, so `{ type='file', follow=true }` skips symlinked files (e.g. `:helptags`). Solution: With `follow=true`, a symlink has the type of its target. Thus `type='link'` only matches broken symlinks (like `find -L -type l`).
* | test: simplify functional test expectationsLewis Russell2026-10-05
| | | | | | | | | | | | | | | | | | | | | | | | Problem: Repeated default fields and field-by-field assertions obscure the values that each functional test is meant to check. Solution: Add eq_partial() to check selected record fields while preserving list length and order. Use local helpers to fill fixed expected defaults where tests need full equality. Keep explicit absence checks and test-specific values at the call sites. AI-assisted
* | fix(lua): vim._with({ buf, win }) assertion failure #42244Justin M. Keyes2026-10-05
|/ | | | | | | | | Problem: `vim._with({ buf = 0, win = 0 }, f)` fails an assert in ctx_switch(). vim._with() allows `buf` with `win` when `win` shows `buf`, but nlua_with() passes both, and ctx_switch() takes only one target. Solution: Ignore `buf` if `win` is given; the window implies its buffer.
* Merge #41944 from epithet/stl-hl-scopesJustin M. Keyes2026-09-30
|\
| * fix(statusline): combine diagnostic highlights, use highlight scopesSébastien Hoffmann2026-09-26
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: when the highlighting surrounding the diagnostics section is not StatusLine[NC], the in-section highlights are not combined and not correctly reset afterwards. The spaces between the diagnostic items have the highlight of the item to the left, which looks bad when the Diagnostic* highlight groups are customised with background colour or underlines. Solution: use %$ to combine, and %#(...%#) to reset. ref #36685 ref #35806
* | Merge pull request #42134 from lewis6991/emmyluaLewis Russell2026-09-30
|\ \ | | | | | | fix(lua): enable param-type-mismatch diagnostic
| * | fix(lua): enable param-type-mismatch diagnosticLewis Russell2026-09-28
| | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Argument type mismatches are excluded from EmmyLua checks. Solution: Correct runtime annotations and calls, document local checker workarounds, and enable the diagnostic. AI-assisted
* | | fix(mpack): wrong encoding of integers beyond the int32 range #42085Suliman Abdulrazzaq2026-09-29
|/ / | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: vim.mpack.encode() picks the msgpack size of a negative integer from its low 32 bits only, so -(2^32 + 5) is written as a negative fixint and decodes as -5. Numbers beyond +-2^53 trip an assertion in debug builds. In release builds, numbers below -2^63 or from 2^64 up go through out-of-range conversions: -1.5 * 2^63 decodes as 2^62. Solution: Use int64 whenever the high word is not a sign extension. Encode numbers outside [-2^63, 2^64), including NaN and infinities, as floats. AI-assisted
* / fix(lua): str_byteindex() can return an index past the end #41766Qatadaha Bin Matloob2026-09-27
|/ | | | | | | | | | | | | | | | Problem: For an incomplete sequence at the end of the string, utf_ptr2len_len() reports more bytes than are there, and mb_utf_index_to_bytes() returns that position unclamped: vim.str_byteindex('ab\xf0\x9f', 'utf-16', 3, false) --> 6 on a 4 byte string. LSP callers use the result as a column, so nvim_buf_set_extmark() then fails with "Invalid 'col': out of range". Solution: Clamp the returned byte index to the length of the string. AI-assisted
* fix(diagnostic): default absent quickfix coordinatesLewis Russell2026-09-25
| | | | | | | | | Quickfix entries can omit their start and end positions. Default absent start coordinates to the first position and absent end coordinates to the start, avoiding arithmetic and comparisons on nil. Cover both cases in the conversion tests. AI-assisted
* perf(lua): share compatible file watchersLewis Russell2026-09-24
| | | | | | | | | | | | | | | | | | Problem: LSP servers can register identical file watchers before removing old registrations. Each registration creates another watcher tree, making setup and teardown expensive in large workspaces. Solution: Move glob compilation into vim._watch and accept rules for multiple roots alongside existing single-path and LPeg inputs. Share compatible backends until their last subscription is cancelled. Ref: #23291 AI-assisted
* fix(ui): ui.open() modifies opts.cmd #41885Volodymyr Chernetskyi2026-09-24
| | | | | | | | Problem: ui.open() appends the path to the caller's opts.cmd, so reusing the opts opens previously supplied paths again. Solution: Copy opt.cmd before appending the path.
* fix(options): vim.opt_local:remove({...}) ignored for flag lists #42001Vinicius2026-09-21
| | | | | | | | | | | Problem: vim.opt_local.formatoptions:remove({ 'c', 'r', 'o' }) silently does nothing since f34ee3da. Solution: Join an Array with no separator for a bare flag list, matching the Dict branch in optval_from_obj(). AI-assisted
* refactor(option): simplify error buffer handlingLewis Russell2026-09-21
| | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Option validators and setters repeatedly pass error buffers and their sizes separately. Some setters return messages in static storage. Solution: - Add CharBuf for borrowed writable storage, with its length including space for a terminating NUL. - Move option error buffers to the callers that report errors. Preserve existing buffer sizes, truncation, and calls that do not need a message. - Consolidate option error formatting and the duplicated code that lists valid option values. - Return error messages directly from do_one_set_option(). - Remove unused arguments from get_option_newval() and set_option_value_for(). - Handle legacy TTY assignments in the existing callers and remove the redundant compatibility wrapper. - Reuse CharBuf for Tree-sitter query errors. - Pass CharBuf through the statusline and statuscolumn builders, retaining the buffer sizes provided by their callers, including title formatting. AI-assisted
* test(vim.net): replace httpbingo with a local serverLewis Russell2026-09-18
| | | | | | | | | | | Problem: Requests to httpbingo can outlast the helper's two-second wait and leave a nil result, causing CI failures. Solution: Serve the basic HTTP tests locally and check the received methods, headers and bodies. Wait for callbacks using the test harness and run these tests without NVIM_TEST_INTEG. AI-assisted
* fix(range): preserve single-character intersections #41881Volodymyr Chernetskyi2026-09-12
| | | | | | | | | | | Problem: Intersection rejects ranges whose inclusive end equals the other start, discarding a shared character and even a one-character self-intersection. Solution: Use strict comparisons when checking inclusive ends for non-overlap. Cover two ranges that share a single character. AI-assisted
* fix(range): use the preceding line for inclusive end marks #41880Volodymyr Chernetskyi2026-09-12
| | | | | | | | | | | Problem: Converting an exclusive end at column zero moves to the preceding row but calculates the inclusive column from the original row's text. Solution: Read the preceding line after decrementing the row. Cover adjacent lines with unequal lengths. AI-assisted
* feat(watch): add on_error callbackLewis Russell2026-09-09
| | | | | | | | | | | | | | Problem: Watcher failures are reported inconsistently, and callers cannot detect when a watcher is no longer usable. Solution: Route startup and event failures and unexpected inotifywait exits through an optional on_error callback. Log to nvim-watch.log by default. Ignore disappearing child directories and exits caused by cancellation. Let LSP log failures and notify once per message, using INFO for missing roots and ERROR otherwise, without interrupting registration. AI-assisted
* Merge pull request #41751 from bfredl/stringly_nilsbfredl2026-09-08
|\ | | | | test(ci): non fatal errors for [NULL] safety in vim_snprintf
| * fix(strings): non fatal errors for [NULL] safety in vim_snprintfbfredl2026-09-08
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | context: vim_snprintf provides a "portable" replacement for a system vsnprintf(). However we rely on system vsnprintf() in places. This is quite arbitrary. I want to use system vsnprintf() only, and reduce the machinery in string.c for typval printf only, which will delet a lot of duplicated code. The biggest hurdle here is not "portably" (we only support sane c runtimes) but the following discrepancy: standard libc printf() considers NULL args to %s to be undefined behavior strictly while vim_snprintf() _defacto_ allows this by replacement to "[NULL]" IMO, this should be seen as "graceful" error handling (nicer than crashing on the user), not a valid means to have the string "[NULL]" intentionally be shoved in the users face. more robust code should explicitly check for NULL and replace it with a _appropriate_ fallback for the situation, depending on what a NULL string actually means in the context (unnamed buffer? anonymous namespace? global augroup?) soo, this PR provides the most _gently_ incremental nudge towards considering this case again as an error one could possible imagine. If sanitizers are complied in, this minor incursion gets printed into the sanitizers' log as a "report-and-continue" error with a traceback. This doesn't annoy the end user or "stop the world" in the test suite, but the CI will see the reported error and fail the build, just like other non-fatal CI errors.
* | perf(watch): prune excluded subtrees during setupLewis Russell2026-09-07
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: The LSP client excludes paths such as Git objects and installed dependencies from file watching. However, watchdirs() still traverses these subtrees before filtering which directories receive watchers, paying the filesystem and Lua traversal cost for excluded content. Solution: Prune excluded subdirectories during the initial walk, and document that excluding a directory also excludes its descendants. Using the LSP client's existing **/node_modules/*/** exclusion on a synthetic macOS tree with 100 installed packages and 4,000 files: Before After Directory scans 503 103 Entries inspected 4502 202 Directory watchers 103 103 The measurements use real filesystem calls and watcher handles, with identical watcher paths for this exclusion. Ref: #23291 AI-assisted
* | test(lua): avoid async task completion flake #41702Lewis Russell2026-09-04
| | | | | | | | | | | | | | | | Allow enough time for the timer and scheduled continuation to run on Windows. fixes #41699 AI-assisted
* | feat(lua): add vim.asyncLewis Russell2026-09-03
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: Nvim has many Lua APIs that start callback-driven work: timers, jobs, libuv handles, and other event-loop tasks. Callers that need to sequence or cancel that work have to build their own coroutine wrappers, task bookkeeping, and cleanup rules. This makes async control flow hard to share, test, and document. Solution: Add `vim.async`, a structured-concurrency module vendored from async.nvim. It provides task handles, await/pawait helpers, sleep/timeout helpers, completion-order iteration, and semaphores on top of Nvim's event loop. The API follows the same broad model as Trio: async work has an owner, tasks are awaited explicitly, and cancellation is cooperative. Include generated vimdoc with an introductory overview and examples, a news entry, and functional tests for the new module. AI-assisted
* | fix(ssh): compare table length in the SSH config #41637Volodymyr Chernetskyi2026-09-03
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Problem: `parse_ssh_config()` compares tables against a freshly allocated empty table. - In `parse_multiple_values()`, the guard which avoids flushing an empty accumulator never applies. Runs of separators and trailing whitespace push empty strings into the results, and `is_valid()` does not filter them. `Host alpha beta ` parses as `{ 'alpha', '', 'beta' }`. - In `parse_value()`, the condition reduces to `chr == '"' and quoted`. `quoted` starts false and only that branch sets it, so it can never become true: quotes are never recognised and are inserted literally, and the unterminated-quote check is unreachable. Solution: Compare `#val` instead. Add a test for repeated and trailing separators. AI-assisted
* | fix(iter): keep take() predicate within bounds #41635Volodymyr Chernetskyi2026-09-03
|/ | | | | | | | | | | | | Problem: `IterArray:take()` iterates up to `self._tail`, but `_tail` is exclusive. When no element fails the predicate, the loop reads one index past the last element and calls the predicate with `nil`. Solution: Stop at `self._tail - inc`, which is the last in-range index for both iteration directions. Add tests using a predicate which dereferences its argument and matches every element, forward and reversed. AI-assisted
* fix(vim.version): fix metatable for intersections #41615Volodymyr Chernetskyi2026-09-02
| | | | | | | | | | | Problem: `vim.version.intersect()` returns tables with `VersionRange` (method table) as their metatable. Although the calculated bounds are correct, the results do not expose `VersionRange` methods. Solution: Construct intersection tables with `range_mt`, matching `vim.version.range()`. Add assertions covering method access and repeated intersection.
* fix(loader): remove all cached loaders on disable #41613Volodymyr Chernetskyi2026-09-02
| | | | | | | | | | | | Problem: When disabling, the loader list is traversed forwards while entries are removed. As a result, some entries are skipped and remain active. Thus, disabling `vim.loader` restores `_G.loadfile`, but does not fully restore the original package loader chain. Solution: Traverse `package.loaders` in reverse when removing cached loaders. Capture the original `package.loaders` list in the test, and assert that the list is restored properly.
* fix(vim.hl): convert finish column independently #41618Volodymyr Chernetskyi2026-09-02
| | | | | | | | | | | Problem: Coordinates-to-position conversion for the finish column in `vim.hl.range()` checks the start column. This results in an incorrect finish column when only one of the column coordinates is `vim.v.maxcol`. Solution: Check the finish column when converting the finish position. Update the existing screen test so that a `vim.v.maxcol` finish highlights the end-of-line marker, matching the existing `-1` behavior.
* feat(multicursor): MC HAMMER #41587