Commit Graph

3989 Commits

Author SHA1 Message Date
Olivia Kinnear
e62dd13f83 fix(lsp): default ClientConfig.exit_timeout to false #36811 2025-12-03 00:58:46 -05:00
zeertzjq
a702534c50 fix(lsp): close timer when client exits (#36795)
Also, don't start the timer at all when a previous shutdown failed, as
in this case a forced shutdown is used and no timer is needed.

This fixes most of the delays caused by #36750.
The delays caused by #36378 still seem to remain.
2025-12-03 07:44:27 +08:00
Justin M. Keyes
672f6e60c1 fix(health): bug-report formatting, version check #36809
Problem:
Version check failed because of "equality" comparison, so a version
string of "123abc" would not match "123abcdef".

Solution:
- Adjust verison check.
- Improve bug-report formatting.
2025-12-02 17:36:27 +00:00
Chris Grieser
4e2ed1d03c fix(treesitter): missing nowait for :InspectTree keymaps #36804 2025-12-02 10:49:58 -05:00
zeertzjq
832ce0ac4b vim-patch:c3cfdef: runtime(doc): clarify the use of v:errormsg (#36789)
fixes: vim/vim#18825

c3cfdefdee

Co-authored-by: Christian Brabandt <cb@256bit.org>
2025-12-02 00:38:18 +00:00
Tristan Knight
1de77c608f fix(lsp): handle nil request in semantic tokens #36780
Allow the `request` parameter in `tokens_to_ranges` to be `nil` and
update version checking logic accordingly. This prevents errors when
the request is not present and improves robustness of semantic token
handling.
2025-12-01 12:51:04 -05:00
Olivia Kinnear
acfb9bc614 feat(lsp): graduate ClientConfig exit_timeout #36750
Problem:
The `flags` field calls its sub-fields "experimental".
But `exit_timeout` is now used for multiple purposes.

Solution:
Graduate `exit_timeout` to a top-level ClientConfig field.
2025-11-30 21:41:43 -05:00
zeertzjq
3f8e51cee7 vim-patch:9.1.1936: filetype: Erlang lexical files are not recognized
Problem:  filetype: Erlang lexical files are not recognized
Solution: Detect *.xrl files as leex filetype, include syntax and
          filetype plugins (Jon Parise).

leex is the lexical analyzer generator for Erlang. Its input file format
follows a section-based structure and uses the `.xrl` file extension.

This initial work includes file detection, an ftplugin (which inherits
the Erlang configuration), and a syntax definition.

Reference:
-  https://www.erlang.org/doc/apps/parsetools/leex.html

related: vim/vim#18819
closes: vim/vim#18832

b087c5452b

Co-authored-by: Jon Parise <jon@indelible.org>
2025-12-01 10:11:34 +08:00
zeertzjq
8310f20526 vim-patch:9.1.1935: filetype: not all Erlang files are recognized
Problem:  filetype: not all Erlang files are recognized
Solution: Detect *.app.src and rebar.config files as erlang filetype
          (John Parise).

*.app.src files contain Erlang application definitions. (There are also
*.app files, which are similar but more often build artifacts, and that
file extension is too ambiguous to be recognized by default.)

Reference:
- https://www.erlang.org/doc/system/applications.html

Rebar is the Erlang build tool. rebar.config uses Erlang syntax.

Reference:
- https://rebar3.org/docs/configuration/configuration/

closes: vim/vim#18835

2b2580e61a

Co-authored-by: Jon Parise <jon@indelible.org>
2025-12-01 10:11:34 +08:00
Tristan Knight
23ddb2028b feat(lsp): semanticTokens/range #36705
Problem:
Nvim supports `textDocument/semanticTokens/full` and `…/full/delta`
already, but most servers don't support `…/full/delta` and Nvim will try
to request and process full semantic tokens response on every buffer
change. Even though the request is debounced, there is noticeable lag if
the token response is large (in a big file).

Solution:
Support `textDocument/semanticTokens/range`, which requests semantic
tokens for visible screen only.
2025-11-30 21:06:56 -05:00
ymich9963
c87d92c3b4 fix(ui.open): use "start" instead of deprecated "rundll32" #36731
Problem:
The rundll32 utility is a leftover from Windows 95, and it has
been deprecated since at least Windows Vista.

Solution:
Use the start command through Command Prompt instead.
2025-11-30 00:11:43 -05:00
Riley Bruins
03d6cf7aae feat(lsp): support version in textDocument/publishDiagnostics #36754
This commit makes it so that push diagnostics received for an outdated
document version are ignored.
2025-11-29 23:38:11 -05:00
Olivia Kinnear
a4b2192690 feat(lsp): lsp.enable() auto-escalates forced shutdown #36458
Problem:
LSP server may not exit even after the client was stopped/disabled via enable(false).

Solution:
Automatically force-stop after a timeout, unless `client.flags.exit_timeout = false`.
2025-11-29 14:36:29 -05:00
zeertzjq
da6edcb91b vim-patch:9.1.1934: filetype: not all starlark files are recognized (#36743)
Problem:  filetype: not all starlark files are recognized
Solution: Detect *.sky files as starlark filetype (Bruno Belanyi)

References:
- https://docs.bazel.build/versions/0.17.1/skylark/spec.html

closes: vim/vim#18807

3ba6a97fea

Co-authored-by: Bruno Belanyi <bruno@belanyi.fr>
2025-11-29 00:31:52 +00:00
zeertzjq
2c6469aca4 vim-patch:9.1.1933: completion: complete_match() is not useful (#36726)
Problem:  completion: complete_match() Vim script function and
          'isexpand' option are not that useful and confusing
          (after v9.1.1341)
Solution: Remove function and option and clean up code and documentation
          (Girish Palya).

complete_match() and 'isexpand' add no real functionality to Vim. They
duplicate what `strridx()` already does, yet pretend to be part of the
completion system. They have nothing to do with the completion mechanism.

* `f_complete_match()` in `insexpand.c` does not call any completion code.
   It’s just a `STRNCMP()` wrapper with fluff logic.
* `'isexpand'` exists only as a proxy argument to that function.
   It does nothing on its own and amounts to misuse of a new option.

The following Vim script function can be used to implement the same
functionality:

```vim
  func CompleteMatch(triggers, sep=',')
    let line = getline('.')->strpart(0, col('.') - 1)
    let result = []
    for trig in split(a:triggers, a:sep)
      let idx = strridx(line, trig)
      if l:idx >= 0
        call add(result, [idx + 1, trig])
      endif
    endfor
    return result
  endfunc
```

related: vim/vim#16716
fixes: vim/vim#18563
closes: vim/vim#18790

cbcbff8712

Co-authored-by: Girish Palya <girishji@gmail.com>
2025-11-28 10:10:31 +08:00
zeertzjq
0b888ea039 vim-patch:b217ffb: runtime(doc): remove outdated help about 'completeopt' "fuzzy"
closes: vim/vim#18815

b217ffbef2
2025-11-28 07:53:49 +08:00
zeertzjq
812186e2dc vim-patch:9.1.1930: completion: 'completefuzzycollect' is too obscure
Problem:  completion: 'completefuzzycollect' option is too obscure
Solution: Deprecate the option, but don't error out for existing scripts,
          behave like 'completefuzzycollect' is set when fuzzy
          completion is enabled (Girish Palya).

fixes: vim/vim#18498
closes: vim/vim#18788

33fbfe003c

Remove this option completely, as it's introduced in Nvim v0.12 cycle.

Co-authored-by: Girish Palya <girishji@gmail.com>
2025-11-28 07:53:49 +08:00
glepnir
e4ce0c7270 fix(health): git check and autocmd clean #36713
Problem: Incorrect Git check and improper autocmd cleanup

Solution: Add once to the autocmd and fix the Git check condition
2025-11-26 20:12:50 -08:00
Miika Tuominen
7ebfc50775 fix(lsp): ignore invalid fold ranges (#36708) 2025-11-26 18:47:07 -05:00
glepnir
8a626e5c4a feat(float): 'statusline' in floating windows #36521
Problem:
Can't show 'statusline' in floating windows.

Solution:
Use window-local 'statusline' to control floating window statusline visibility.
2025-11-26 09:10:45 -08:00
glepnir
13ed225054 feat(health): create a bug report #31842
Problem:
Creating a bug report is somewhat tedious for users.
Bug reports sometimes are missing useful info.

Solution:
Add a feature to :checkhealth which automatically gets
system info and includes it in a new bug report.
2025-11-25 21:21:32 -08:00
Maria Solano
7e09fedf43 feat(diagnostic): config.status #36693
Problem:
`diagnostic.status()` is configured via `config.signs`, but users may
want diagnostics only in statusline, not in the gutter (signs).

Solution:
Add `config.status`.
2025-11-25 21:00:00 -08:00
Riley Bruins
4107442103 feat(diagnostic): highlights in diagnostic.status() #36685
Applies the appropriate `DiagnosticSign*` highlight to each group,
resetting the highlights at the end of the expression.
2025-11-25 10:14:46 -08:00
Riley Bruins
8d8f17c924 refactor(defaults): deduplicate selection_range() mappings #36686
We don't need to specify the timeout ms here anymore, because the
implementation was changed to use it by default.
2025-11-25 10:00:28 -08:00
Branden Call
e82aef2e22 feat(lsp): incremental-selection operator-pending mode #36575
Problem:
LSP incremental selection provides default visual-mode keymaps for `an`
and `in`. Operator-pending mode is not supported, so `dan` and `can` do
not apply the operation.

Solution:
Modify selection_range() to be synchronous.
Add operator-pending mappings.
2025-11-24 17:10:50 -08:00
Justin M. Keyes
60c35cc4c7 docs: vimdoc parsing errors #36681
Error: .tests/neovim/runtime/doc/dev_test.txt
    (MISSING "`" [420, 79] - [420, 79])
    Error: .tests/neovim/runtime/doc/news.txt
    (MISSING "`" [137, 80] - [137, 80])
    Error: .tests/neovim/runtime/doc/nvim.txt
    (MISSING "<" [106, 0] - [106, 0])
    Error: .tests/neovim/runtime/doc/vimfn.txt
    (MISSING "}" [2610, 26] - [2610, 26])
2025-11-24 15:10:05 -08:00
zeertzjq
dbd7f45873 vim-patch:2190036: runtime(doc): Add environment variable expansion note to options (#36675)
Add "Environment variables are expanded |:set_env|" documentation to
options that have the P_EXPAND flag but were missing this note.

Updated options:
- 'cdpath'
- 'dictionary'
- 'mkspellmem'
- 'packpath'
- 'runtimepath'
- 'spellfile'
- 'spellsuggest'
- 'thesaurus'
- 'ttytype'
- 'undodir'
- 'verbosefile'
- 'viewdir'
- 'viminfofile'

These options support environment variable expansion in their values
(e.g., $HOME, $USER) but the documentation didn't explicitly mention
this capability. This brings their documentation in line with other
options like backupdir, directory, and makeprg that already include
this note.

closes: vim/vim#18791

2190036c8c

Co-authored-by: Alex Plate <AlexPl292@gmail.com>
2025-11-24 10:29:39 +08:00
Justin M. Keyes
3fc72f4ef1 docs: misc
Close #36441
Close #36631
Close #36656

Co-authored-by: Maria José Solano <majosolano99@gmail.com>
Co-authored-by: glepnir <glephunter@gmail.com>
Co-authored-by: "Mike J. McGuirk" <mike.j.mcguirk@gmail.com>
2025-11-23 20:03:13 -05:00
Justin M. Keyes
bf820b1b94 docs: misc, build, lsp 2025-11-22 19:32:45 -05:00
Oleh Volynets
ea70d2ad85 fix(ui2): unset Search highlighting (#36633)
Problem:
Trying to match the search highlight groups to the Normal highlight for
the window can fail when the message highlighting contains a fg/bg that
the Normal highlight doesn't (like an error message in cmd will have
ErrorMsg highlight instead of MsgArea - which is Normal in cmd.)

Solution:
Link the search highlight groups to an empty group in 'winhighlight'
thus disabling them instead of overriding them with Normal/MsgArea/etc.
2025-11-20 17:19:31 +00:00
zeertzjq
a04c73cc17 fix(input): discard following keys when discarding <Cmd>/K_LUA (#36498)
Technically the current behavior does match documentation. However, the
keys following <Cmd>/K_LUA aren't normally received by vim.on_key()
callbacks either, so it does makes sense to discard them along with the
preceding key.

One may also argue that vim.on_key() callbacks should instead receive
the following keys together with the <Cmd>/K_LUA, but doing that may
cause some performance problems, and even in that case the keys should
still be discarded together.
2025-11-20 12:33:02 +08:00
luukvbaal
ba6fc90b6f fix(ui2): hide search highlights in msg window #36626
fix(ui2): hide search highlights in msg window.

Problem:  Search highlighting is shown in the msg (and dialog) window.
Solution: Hide search highlighting in all but the pager window.
2025-11-19 17:02:32 -08:00
Jeff Martin
ff792f8e69 fix(lsp): enable insertReplaceSupport for use in adjust_start_col #36569
Problem:
With the typescript LSes typescript-language-server and vtsls,
omnicompletion on partial tokens for certain types, such as array
methods, and functions that are attached as attributes to other
functions, either results in no entries populated in the completion menu
(typescript-language-server), or an unfiltered completion menu with all
array methods included, even if they don't share the same prefix as the
partial token being completed (vtsls).

Solution:
Enable insertReplaceSupport and uses the insert portion of the lsp
completion response in adjust_start_col if it's included in the
response.

Completion results are still filtered client side.
2025-11-18 23:03:40 -08:00
glepnir
b65aadc03e docs(diagnostic): diagnostic.Opts.Float extend open_floating_preview.Opts #30058
Problem: the opts table also is param of util.open_floating_preview,
vim.diagnostic.Opts.Float missing some fields of open_floating_preview.

Solution: diagnostic.Opts.Float extend util.open_floating_preview.Opts

Fix #29267
2025-11-18 21:52:30 -08:00
glepnir
c22b03c771 feat(lsp): user-specified sorting of lsp.completion multi-server results #36401
Problem: No way to customize completion order across multiple servers.

Solution: Add `cmp` function to `vim.lsp.completion.enable()` options
for custom sorting logic.
2025-11-18 21:38:53 -08:00
glepnir
4998b8d7b5 feat(api): nvim_win_set_config accepts unchanged "noautocmd" #36463
Problem: Cannot reuse same config with noautocmd for both window
creation and updates, even when value is unchanged.

Solution: Only reject noautocmd changes for existing windows.
2025-11-18 20:23:50 -08:00
Riley Bruins
098da1fc2c perf(treesitter): parse multiple ranges in languagetree, eliminate flickering #36503
**Problem:** Whenever `LanguageTree:parse()` is called, injection trees
from previously parsed ranges are dropped.

**Solution:** Allow the function to accept a list of ranges, so it can
return injection trees for all the given ranges.

Co-authored-by: Jaehwang Jung <tomtomjhj@gmail.com>
2025-11-18 10:09:49 -08:00
skewb1k
4b8980949c fix(lsp): set concealcursor='' in LSP floating windows #36596
Problem:
Users often jump and navigate through LSP windows to yank text.
Concealed markdown can make navigation through hyperlinks and code
blocks more difficult.

Solution:
Change 'concealcursor' from 'n' to '' to preserve clean display
while improving navigation and selection of the LSP response.

Closes #36537
2025-11-17 17:49:14 -08:00
luukvbaal
c4ac36bfd9 fix(ui2): only redraw when necessary #36457
Problem:  Until now the UI callback called nvim__redraw() liberally.
          It should only be needed when Nvim does not update the screen
          in its own event loop.
Solution: Identify which UI events require immediate redrawing.
2025-11-17 10:34:02 -08:00
Justin M. Keyes
5df1112e5f Merge #36338 vim.pack: lockfile synchronization 2025-11-17 09:55:11 -08:00
Grzegorz Rozdzialik
2767eac320 feat(diagnostics): stack DiagnosticUnnecessary,DiagnosticDeprecated highlights #36590
Problem: unnecessary and deprecated diagnostics use their own highlight
groups (`DiagnosticUnnecessary` and `DiagnosticDeprecated`) which
override the typical severity-based highlight groups (like
`DiagnosticUnderlineWarn`).

This can be misleading, since diagnostics about unused variables which
are warnings or errors, are shown like comments, since then only the
`DiagnosticUnnecessary` highlight group is used. Users do not see the
more eye-catching red/yellow highlight.

Solution: Instead of overriding the highlight group to
`DiagnosticUnnecessary` or `DiagnosticDeprecated`, set them in addition
to the normal severity-based highlights.
2025-11-17 09:37:59 -08:00
Evgeni Chasnovski
f492f62c3d fix(pack): rename confirmation buffer to again use nvim-pack:// scheme
Problem: `nvim://` scheme feels more like a generalized interface that
  may be requested externally, and it acts like CLI args (roughly).
  This is how `vscode://` works.

  Anything that behaves like an "app" or a "protocol" deserves its own
  scheme. For such Nvim-owned things they will be called `nvim-xx://`.

Solution: Use `nvim-pack://confirm#<bufnr>` template for confirmation
  buffer name instead of `nvim://pack-confirm#<bufnr>`.
2025-11-17 12:47:29 +02:00
Evgeni Chasnovski
b151aa761f feat(pack)!: synchronize lockfile with installed plugins when reading it
Problem: Lockfile can become out of sync with what is actually installed
  on disk when user performs (somewhat reasonable) manual actions like:
    - Delete lockfile and expect it to regenerate.
    - Delete plugin directory without `vim.pack.del()`.
    - Manually edit lock data in a bad way.

Solution: Synchronize lockfile data with installed plugins on every
  lockfile read. In particular:

    1. Install immediately all missing plugins with valid lock data.
       This helps with "manually delete plugin directory" case by
       prompting user to figure out how to properly delete a plugin.

    2. Repair lock data for properly installed plugins.
       This helps with "manually deleted lockfile", "manually edited
       lockfile in an unexpected way", "installation terminated due to
       timeout" cases.

    3. Remove unrepairable corrupted lock data and their plugins. This
       includes bad lock data for missing plugins and any lock data
       for corrupted plugins (right now this only means that plugin
       path is not a directory, but can be built upon).

  Step 1 also improves usability in case there are lazy loaded plugins
  that are rarely loaded (like on `FileType` event, for example):
    - Previously starting with config+lockfile on a new machine only
      installs rare `vim.pack.add()` plugin after it is called (while
      an entry in lockfile would still be present). This could be
      problematic if there is no Internet connection, for example.
    - Now all plugins from the lockfile are installed before actually
      executing the first `vim.pack.add()` call in 'init.lua'. And later
      they are only loaded on a rare `vim.pack.add()` call.

  ---

  Synchronizing lockfile on its every read makes it work more robustly
  if other `vim.pack` functions are called without any `vim.pack.add()`.

  ---

  Performance for a regular startup (good lockfile, everything is
  installed) is not affected and usually even increased. The bottleneck
  in this area is figuring out which plugins need to be installed.

  Previously the check was done by `vim.uv.fs_stat()` for every plugin
  in `vim.pack.add()`. Now it is replaced with a single `vim.fs.dir()`
  traversal during lockfile sync while later using lockfile data to
  figure out if plugin needs to be installed.

  The single `vim.fs.dir` approach scales better than `vim.uv.fs_stat`,
  but might be less performant if there are many plugins that will be
  not loaded via `vim.pack.add()` during startup.

  Rough estimate of how long the same steps (read lockfile and normalize
  plugin array) take with a single `vim.pack.add()` filled with 43
  plugins benchmarking:
  - Before commit: ~700 ms
  - After commit:  ~550 ms
2025-11-17 12:47:29 +02:00
Evgeni Chasnovski
60bfc741ed refactor(pack): rearrange lockfile code to be able to use other locals 2025-11-17 12:47:29 +02:00
Evgeni Chasnovski
c3ac329c7a fix(pack)!: ensure plugin is fully absent if not fully installed
Problem: Currently it is possible to have plugin in a "partial install"
  state when `git clone` was successfull but `git checkout` was not.
  This was done to not checkout default branch by default in these
  situations (for security reasons).

  The problem is that it adds complexity when both dealing with lockfile
  (plugin's `rev` might be `nil`) and in how `src` and `version` are
  treated (wrong `src` - no plugin on disk; wrong `version` - "partial"
  plugin on disk).

Solution: Treat plugin as "installed" if both `git clone` and
  `git checkout` are successful, while ensuring that not installed
  plugins are not on disk and in lockfile.

  This also means that if in 'init.lua' there is a `vim.pack.add()` with
  bad `version`, for first install there will be an informative error
  about it BUT next session will also try to install it. The solution is
  the same - adjust `version` beforehand.
2025-11-17 12:47:29 +02:00
Evgeni Chasnovski
9e2599df05 fix(pack)!: adjust install confirm (no error on "No", show names)
Problem: Installation confirmation has several usability issues:
    - Choosing "No" results in a `vim.pack.add()` error. This was by
      design to ensure that all later code that *might* reference
      presumably installed plugin will not get executed. However, this
      is often too restrictive since there might be no such code (like
      if plugin's effects are automated in its 'plugin/' directory).
      Instead the potential code using not installed plugin will throw
      an error.

      No error on "No" will also be useful for planned lockfile repair.

    - List of soon-to-be-installed plugins doesn't mention plugin names.
      This might be confusing if plugins are installed under different
      name.

Solution: Silently drop installation step if user chose "No" and show
  plugin names in confirmation text (together with their pretty aligned
  sources).
2025-11-17 12:46:22 +02:00
Justin M. Keyes
1f9d9cb2e5 fix(vim.fs): abspath(".") returns "/…/." #36583 2025-11-16 22:36:03 -08:00
Cameron Ring
f11f8546e7 fix(vim.fs): root() should always return absolute path #36466 2025-11-16 21:41:26 -08:00
Justin M. Keyes
c8b6852363 docs: misc #36580
Co-authored-by: nguyenkd27 <nguyenkd27@gmail.com>
Co-authored-by: dundargoc <gocdundar@gmail.com>
Co-authored-by: Yochem van Rosmalen <git@yochem.nl>
Co-authored-by: Tuure Piitulainen <tuure.piitulainen@gmail.com>
Co-authored-by: Maria Solano <majosolano99@gmail.com>
Co-authored-by: tao <2471314@gmail.com>
2025-11-16 20:36:07 -08:00
tao
654303079b feat(lsp): skip invalid header lines #36402
Problem:
Some servers write log to stdout and there's no way to avoid it.
See https://github.com/neovim/neovim/pull/35743#pullrequestreview-3379705828

Solution:
We can extract `content-length` field byte by byte and skip invalid
lines via a simple state machine (name/colon/value/invalid), with minimal
performance impact.

I chose byte parsing here instead of pattern. Although it's a bit more complex,
it provides more stable performance and allows for more accurate error info when
needed.

Here is a bench result and script:

    parse header1 by pattern: 59.52377ms 45
    parse header1 by byte: 7.531128ms 45

    parse header2 by pattern: 26.06936ms 45
    parse header2 by byte: 5.235724ms 45

    parse header3 by pattern: 9.348495ms 45
    parse header3 by byte: 3.452389ms 45

    parse header4 by pattern: 9.73156ms 45
    parse header4 by byte: 3.638386ms 45

Script:

```lua
local strbuffer = require('string.buffer')

--- @param header string
local function get_content_length(header)
  for line in header:gmatch('(.-)\r?\n') do
    if line == '' then
      break
    end
    local key, value = line:match('^%s*(%S+)%s*:%s*(%d+)%s*$')
    if key and key:lower() == 'content-length' then
      return assert(tonumber(value))
    end
  end
  error('Content-Length not found in header: ' .. header)
end

--- @param header string
local function get_content_length_by_byte(header)
  local state = 'name'
  local i, len = 1, #header
  local j, name = 1, 'content-length'
  local buf = strbuffer.new()
  local digit = true
  while i <= len do
    local c = header:byte(i)
    if state == 'name' then
      if c >= 65 and c <= 90 then -- lower case
        c = c + 32
      end
      if (c == 32 or c == 9) and j == 1 then
        -- skip OWS for compatibility only
      elseif c == name:byte(j) then
        j = j + 1
      elseif c == 58 and j == 15 then
        state = 'colon'
      else
        state = 'invalid'
      end
    elseif state == 'colon' then
      if c ~= 32 and c ~= 9 then -- skip OWS normally
        state = 'value'
        i = i - 1
      end
    elseif state == 'value' then
      if c == 13 and header:byte(i + 1) == 10 then -- must end with \r\n
        local value = buf:get()
        return assert(digit and tonumber(value), 'value of Content-Length is not number: ' .. value)
      else
        buf:put(string.char(c))
      end
      if c < 48 and c ~= 32 and c ~= 9 or c > 57 then
        digit = false
      end
    elseif state == 'invalid' then
      if c == 10 then -- reset for next line
        state, j = 'name', 1
      end
    end
    i = i + 1
  end
  error('Content-Length not found in header: ' .. header)
end

--- @param fn fun(header: string): number
local function bench(label, header, fn, count)
  local start = vim.uv.hrtime()
  local value --- @type number
  for _ = 1, count do
    value = fn(header)
  end
  local elapsed = (vim.uv.hrtime() - start) / 1e6
  print(label .. ':', elapsed .. 'ms', value)
end

-- header starting with log lines
local header1 =
  'WARN: no common words file defined for Khmer - this language might not be correctly auto-detected\nWARN: no common words file defined for Japanese - this language might not be correctly auto-detected\nContent-Length: 45  \r\n\r\n'
-- header starting with content-type
local header2 = 'Content-Type: application/json-rpc; charset=utf-8\r\nContent-Length: 45  \r\n'
-- regular header
local header3 = '  Content-Length: 45\r\n'
-- regular header ending with content-type
local header4 = '  Content-Length: 45 \r\nContent-Type: application/json-rpc; charset=utf-8\r\n'

local count = 10000

collectgarbage('collect')
bench('parse header1 by pattern', header1, get_content_length, count)
collectgarbage('collect')
bench('parse header1 by byte', header1, get_content_length_by_byte, count)

collectgarbage('collect')
bench('parse header2 by pattern', header2, get_content_length, count)
collectgarbage('collect')
bench('parse header2 by byte', header2, get_content_length_by_byte, count)

collectgarbage('collect')
bench('parse header3 by pattern', header3, get_content_length, count)
collectgarbage('collect')
bench('parse header3 by byte', header3, get_content_length_by_byte, count)

collectgarbage('collect')
bench('parse header4 by pattern', header4, get_content_length, count)
collectgarbage('collect')
bench('parse header4 by byte', header4, get_content_length_by_byte, count)
```

Also, I removed an outdated test
accd392f4d/test/functional/plugin/lsp_spec.lua (L1950)
and tweaked the boilerplate in two other tests for reusability while keeping the final assertions the same.
accd392f4d/test/functional/plugin/lsp_spec.lua (L5704)
accd392f4d/test/functional/plugin/lsp_spec.lua (L5721)
2025-11-16 17:23:52 -08:00