ronantakizawa/codereview-bench huggingface dataset:
Repository: neovim/neovim
File: runtime/lua/vim/shared.lua (Lua)

Code before:
--- for i = start, end - 1 do
---   print(t[i])
--- end
--- -- { 3, 3, 3 }
--- ```
---
--- To find the first occurrence, use |vim.list.lower_bound()|
---@see |vim.list.lower_bound()|
---@generic T
---@param t T[] A comparable list.
---@param val T The value to search.
---@param opts? vim.list.bisect.Opts
---@return integer i in range such that `t[j]` <= {val} for all j < i,
---                and `t[j]` > {val} for all j >= i,
---                or return {hi} if no such index is found.
function vim.list.upper_bound(t, val, opts)
  vim.validate('t', t, 'table')
  vim.validate('opts', opts, 'table', true)

  opts = opts or {}
  local lo = opts.lo or 1
  local hi = opts.hi or #t + 1

  while lo < hi do
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2).
    if key_fn(val, opts.key) < key_fn(t[mid], opts.key) then
      hi = mid
    else
      lo = mid + 1
    end
  end

  return lo
end

--- Checks if a table is empty.
---
---@see https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
---@param t table Table to check
---@return boolean `true` if `t` is empty
function vim.tbl_isempty(t)
  vim.validate('t', t, 'table')
  return next(t) == nil
end

--- We only merge empty tables or tables that are not list-like (indexed by consecutive integers
--- starting from 1)
local function can_merge(v)
  return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.islist(v))
end

Review comment (suggestion):
```suggestion
  local val_key = key_fn(val, key)
  
  while lo < hi do
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2).
    if val_key < key_fn(t[mid], key) then
```

Code after:
--- Use {bound} to determine whether to return the first or the last position,
--- defaults to "lower", i.e., the first position.
---
--- NOTE: Behavior is undefined on unsorted lists!
---
--- Example:
--- ```lua
---
--- local t = { 1, 2, 2, 3, 3, 3 }
--- local first = vim.list.bisect(t, 3)
--- -- `first` is `val`'s first index if found,
--- -- useful for existence checks.
--- print(t[first]) -- 3
---
--- local last = vim.list.bisect(t, 3, { bound = 'upper' })
--- -- Note that `last` is 7, not 6,
--- -- this is suitable for insertion.
---
--- table.insert(t, last, 4)
--- -- t is now { 1, 2, 2, 3, 3, 3, 4 }
---
--- -- You can use lower bound and upper bound together
--- -- to obtain the range of occurrences of `val`.
---
--- -- 3 is in [first, last)
--- for i = first, last - 1 do
---   print(t[i]) -- { 3, 3, 3 }
--- end
--- ```
---@generic T
---@param t T[] A comparable list.
---@param val T The value to search.
---@param opts? vim.list.bisect.Opts
---@return integer index serves as either the lower bound or the upper bound position.
function vim.list.bisect(t, val, opts)
  vim.validate('t', t, 'table')
  vim.validate('opts', opts, 'table', true)

  opts = opts or {}
  local lo = opts.lo or 1
  local hi = opts.hi or #t + 1
  local key = opts.key

  if opts.bound == 'upper' then
    return upper_bound(t, val, lo, hi, key)
  else
    return lower_bound(t, val, lo, hi, key)
  end
end

--- Checks if a table is empty.
Repository: neovim/neovim
File: test/functional/lua/list_spec.lua (Lua)

Code before:
-- Test suite for testing interactions with API bindings
local t = require('test.testutil')
local eq = t.eq

describe('vim.list', function()
  it('vim.list.unique()', function()
    eq({ 1, 2, 3, 4, 5 }, vim.list.unique({ 1, 2, 2, 3, 4, 4, 5 }))
    eq({ 1, 2, 3, 4, 5 }, vim.list.unique({ 1, 2, 3, 4, 4, 5, 1, 2, 3, 2, 1, 2, 3, 4, 5 }))
    eq({ 1, 2, 3, 4, 5, field = 1 }, vim.list.unique({ 1, 2, 2, 3, 4, 4, 5, field = 1 }))

    -- Not properly defined, but test anyway
    -- luajit evaluates #t as 7, whereas Lua 5.1 evaluates it as 12
    local r = vim.list.unique({ 1, 2, 2, 3, 4, 4, 5, nil, 6, 6, 7, 7 })
    if jit then
      eq({ 1, 2, 3, 4, 5, nil, nil, nil, 6, 6, 7, 7 }, r)
    else
      eq({ 1, 2, 3, 4, 5, nil, 6, 7 }, r)
    end

    eq(
      { { 1 }, { 2 }, { 3 } },
      vim.list.unique({ { 1 }, { 1 }, { 2 }, { 2 }, { 3 }, { 3 } }, function(x)
        return x[1]
      end)
    )
  end)

Review comment (suggestion):
thanks for creating list_spec 👍 
```suggestion
-- Test suite for vim.list
```

Code after:
-- Test suite for vim.list
local t = require('test.testutil')
local eq = t.eq

describe('vim.list', function()
  it('vim.list.unique()', function()
    eq({ 1, 2, 3, 4, 5 }, vim.list.unique({ 1, 2, 2, 3, 4, 4, 5 }))
    eq({ 1, 2, 3, 4, 5 }, vim.list.unique({ 1, 2, 3, 4, 4, 5, 1, 2, 3, 2, 1, 2, 3, 4, 5 }))
    eq({ 1, 2, 3, 4, 5, field = 1 }, vim.list.unique({ 1, 2, 2, 3, 4, 4, 5, field = 1 }))

    -- Not properly defined, but test anyway
    -- luajit evaluates #t as 7, whereas Lua 5.1 evaluates it as 12
    local r = vim.list.unique({ 1, 2, 2, 3, 4, 4, 5, nil, 6, 6, 7, 7 })
    if jit then
      eq({ 1, 2, 3, 4, 5, nil, nil, nil, 6, 6, 7, 7 }, r)
    else
      eq({ 1, 2, 3, 4, 5, nil, 6, 7 }, r)
    end

    eq(
      { { 1 }, { 2 }, { 3 } },
      vim.list.unique({ { 1 }, { 1 }, { 2 }, { 2 }, { 3 }, { 3 } }, function(x)
        return x[1]
      end)
    )
  end)
Repository: neovim/neovim
File: runtime/lua/vim/shared.lua (Lua)

Code before:
  local val_key = key_fn(val, key)
  while lo < hi do
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi)
    if key_fn(t[mid], key) < val_key then
      lo = mid + 1
    else
      hi = mid
    end
  end
  return lo
end

---@generic T
---@param t T[]
---@param val T
---@param key? fun(val: any): any
---@param lo integer
---@param hi integer
---@return integer i in range such that `t[j]` <= {val} for all j < i,
---                and `t[j]` > {val} for all j >= i,
---                or return {hi} if no such index is found.
local function upper_bound(t, val, lo, hi, key)
  local bit = require('bit') -- Load bitop on demand
  local val_key = key_fn(val, key)
  while lo < hi do
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi)
    if val_key < key_fn(t[mid], key) then
      hi = mid
    else
      lo = mid + 1
    end
  end
  return lo
end

--- Search for a position in a sorted list {t}
--- where {val} can be inserted while keeping the list sorted.
---
--- Use {bound} to determine whether to return the first or the last position,
--- defaults to "lower", i.e., the first position.
---
--- Example:
--- ```lua
---
--- local t = { 1, 2, 2, 3, 3, 3 }
--- local first = vim.list.bisect(t, 3)
--- -- `first` is `val`'s first index if found,
--- -- useful for existence checks.
--- print(t[first]) -- 3
---
--- local last = vim.list.bisect(t, 3, { bound = 'upper' })

Review comment (suggestion):
Shouldn't this be

```suggestion
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2)
```


Code after:
  local val_key = key_fn(val, key)
  while lo < hi do
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2)
    if key_fn(t[mid], key) < val_key then
      lo = mid + 1
    else
      hi = mid
    end
  end
  return lo
end

---@generic T
---@param t T[]
---@param val T
---@param key? fun(val: any): any
---@param lo integer
---@param hi integer
---@return integer i in range such that `t[j]` <= {val} for all j < i,
---                and `t[j]` > {val} for all j >= i,
---                or return {hi} if no such index is found.
local function upper_bound(t, val, lo, hi, key)
  local bit = require('bit') -- Load bitop on demand
  local val_key = key_fn(val, key)
  while lo < hi do
    local mid = bit.rshift(lo + hi, 1) -- Equivalent to floor((lo + hi) / 2)
    if val_key < key_fn(t[mid], key) then
      hi = mid
    else
      lo = mid + 1
    end
  end
  return lo
end

--- Search for a position in a sorted list {t}
--- where {val} can be inserted while keeping the list sorted.
---
--- Use {bound} to determine whether to return the first or the last position,
--- defaults to "lower", i.e., the first position.
---
--- NOTE: Behavior is undefined on unsorted lists!
---
--- Example:
--- ```lua
---
--- local t = { 1, 2, 2, 3, 3, 3 }
--- local first = vim.list.bisect(t, 3)
--- -- `first` is `val`'s first index if found,
--- -- useful for existence checks.
--- print(t[first]) -- 3
Repository: neovim/neovim
File: runtime/lua/vim/shared.lua (Lua)

Code before:
---
--- -- You can use lower bound and upper bound together
--- -- to obtain the range of occurrences of `val`.
---
--- -- 3 is in [first, last)
--- for i = first, last - 1 do
---   print(t[i]) -- { 3, 3, 3 }
--- end
--- ```
---@generic T
---@param t T[] A comparable list.
---@param val T The value to search.
---@param opts? vim.list.bisect.Opts
---@return integer index serves as either the lower bound or the upper bound position.
function vim.list.bisect(t, val, opts)
  vim.validate('t', t, 'table')
  vim.validate('opts', opts, 'table', true)

  opts = opts or {}
  local lo = opts.lo or 1
  local hi = opts.hi or #t + 1
  local key = opts.key

  -- Lightweight check, will not catch every kind of invalid input.
  if key_fn(t[hi - 1], key) < key_fn(t[lo], key) then
    error('bisect operation on an unsorted list is undefined')
  end

  if opts.bound == 'upper' then
    return upper_bound(t, val, lo, hi, key)
  else
    return lower_bound(t, val, lo, hi, key)
  end
end

--- Checks if a table is empty.
---
---@see https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
---@param t table Table to check
---@return boolean `true` if `t` is empty
function vim.tbl_isempty(t)
  vim.validate('t', t, 'table')
  return next(t) == nil
end

--- We only merge empty tables or tables that are not list-like (indexed by consecutive integers
--- starting from 1)
local function can_merge(v)
  return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.islist(v))
end

Review comment (suggestion):
```suggestion
    error('bisect operation on an unsorted list is undefined', 2)
```


Code after:
---
--- -- You can use lower bound and upper bound together
--- -- to obtain the range of occurrences of `val`.
---
--- -- 3 is in [first, last)
--- for i = first, last - 1 do
---   print(t[i]) -- { 3, 3, 3 }
--- end
--- ```
---@generic T
---@param t T[] A comparable list.
---@param val T The value to search.
---@param opts? vim.list.bisect.Opts
---@return integer index serves as either the lower bound or the upper bound position.
function vim.list.bisect(t, val, opts)
  vim.validate('t', t, 'table')
  vim.validate('opts', opts, 'table', true)

  opts = opts or {}
  local lo = opts.lo or 1
  local hi = opts.hi or #t + 1
  local key = opts.key

  if opts.bound == 'upper' then
    return upper_bound(t, val, lo, hi, key)
  else
    return lower_bound(t, val, lo, hi, key)
  end
end

--- Checks if a table is empty.
---
---@see https://github.com/premake/premake-core/blob/master/src/base/table.lua
---
---@param t table Table to check
---@return boolean `true` if `t` is empty
function vim.tbl_isempty(t)
  vim.validate('t', t, 'table')
  return next(t) == nil
end

--- We only merge empty tables or tables that are not list-like (indexed by consecutive integers
--- starting from 1)
local function can_merge(v)
  return type(v) == 'table' and (vim.tbl_isempty(v) or not vim.islist(v))
end

--- Recursive worker for tbl_extend
--- @param behavior 'error'|'keep'|'force'|fun(key:any, prev_value:any?, value:any): any
--- @param deep_extend boolean
--- @param ... table<any,any>
Repository: neovim/neovim
File: .github/scripts/env.ps1 (PowerShell)

Code before:
# This script enables Developer Command Prompt
# See https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell
$arch = "${env:BUILD_ARCH}"
$installationPath = vswhere.exe -latest -requiresAny -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -requires Microsoft.VisualStudio.Component.VC.Tools.arm64 -property installationPath

if ($installationPath) {
  & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=$arch -no_logo && set" |
    ForEach-Object {
      $name, $value = $_ -split '=', 2
      "$name=$value" >> $env:GITHUB_ENV
    }
}


Review comment (suggestion):
Oh, I didn't realize that `matrix.arch` from the GHA yaml was feeding into this `-arch` param. I assume this won't work with `-arch=x86_64`, since the old code had `-arch=x64`.

Code after:
# This script enables Developer Command Prompt
# See https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell
if ($env:BUILD_ARCH -eq "arm64") {
  $arch = "arm64"
  $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.arm64 -property installationPath
} else {
  $arch = "x64"
  $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
}

if ($installationPath) {
  & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=$arch -no_logo && set" |
    ForEach-Object {
      $name, $value = $_ -split '=', 2
      "$name=$value" >> $env:GITHUB_ENV
    }
}
Repository: neovim/neovim
File: .github/scripts/env.ps1 (PowerShell)

Code before:
# This script enables Developer Command Prompt
# See https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell
$arch = if ($env:BUILD_ARCH -eq "x86_64") { "x64" } else { "arm64" } 
$installationPath = vswhere.exe -latest -requiresAny -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -requires Microsoft.VisualStudio.Component.VC.Tools.arm64 -property installationPath

if ($installationPath) {
  & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=$arch -no_logo && set" |
    ForEach-Object {
      $name, $value = $_ -split '=', 2
      "$name=$value" >> $env:GITHUB_ENV
    }
}


Review comment (suggestion):
just a guess, maybe `-requiresAny` is causing problems. try something more explicit (didn't test this code, just showing the idea): 

(also, I suggest checking for "arm64" and fallback to "x64", not vice versa.

```
if ($env:BUILD_ARCH -eq "arm64") {
  $arch = "arm64"
  $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.arm64 -property installationPath
} else {
  $arch = "x64"
  $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
}
```

Code after:
# This script enables Developer Command Prompt
# See https://github.com/microsoft/vswhere/wiki/Start-Developer-Command-Prompt#using-powershell
if ($env:BUILD_ARCH -eq "arm64") {
  $arch = "arm64"
  $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.arm64 -property installationPath
} else {
  $arch = "x64"
  $installationPath = vswhere.exe -latest -requires Microsoft.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath
}

if ($installationPath) {
  & "${env:COMSPEC}" /s /c "`"$installationPath\Common7\Tools\vsdevcmd.bat`" -arch=$arch -no_logo && set" |
    ForEach-Object {
      $name, $value = $_ -split '=', 2
      "$name=$value" >> $env:GITHUB_ENV
    }
}
Repository: neovim/neovim
File: src/nvim/memline.h (C/C++)

Code before:
#pragma once

#include "nvim/ascii_defs.h"
#include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
#include "nvim/memline_defs.h"  // IWYU pragma: keep
#include "nvim/pos_defs.h"  // IWYU pragma: keep
#include "nvim/types_defs.h"  // IWYU pragma: keep

#include "memline.h.generated.h"

/// LINEEMPTY() - return true if the line is empty
#define LINEEMPTY(p) (*ml_get(p) == NUL)

// Values for the flags argument of ml_delete_flags().
enum {
  ML_DEL_MESSAGE = 1,  // may give a "No lines in buffer" message
  ML_DEL_UNDO = 2,     // called from undo, do not update textprops
};

// Values for the flags argument of ml_append_int().
enum {
  ML_APPEND_NEW = 1,   // starting to edit a new file
  ML_APPEND_MARK = 2,  // mark the new line
  ML_APPEND_UNDO = 4,  // called from undo
};


Review comment (suggestion):
Not sure about this, but `ML_DEL_UNDO` actually doesn't do anything in Nvim...
```suggestion
  ML_DEL_UNDO = 2,     // called from undo
```

Code after:
#pragma once

#include "nvim/ascii_defs.h"
#include "nvim/eval/typval_defs.h"  // IWYU pragma: keep
#include "nvim/memline_defs.h"  // IWYU pragma: keep
#include "nvim/pos_defs.h"  // IWYU pragma: keep
#include "nvim/types_defs.h"  // IWYU pragma: keep

#include "memline.h.generated.h"

/// LINEEMPTY() - return true if the line is empty
#define LINEEMPTY(p) (*ml_get(p) == NUL)

// Values for the flags argument of ml_delete_flags().
enum {
  ML_DEL_MESSAGE = 1,  // may give a "No lines in buffer" message
  // ML_DEL_UNDO = 2,  // called from undo
};

// Values for the flags argument of ml_append_int().
enum {
  ML_APPEND_NEW = 1,   // starting to edit a new file
  ML_APPEND_MARK = 2,  // mark the new line
  // ML_APPEND_UNDO = 4,  // called from undo
};
Repository: neovim/neovim
File: runtime/lua/vim/provider/health.lua (Lua)

Code before:

  if vim.version.lt(current_cpan, latest_cpan) then
    local message = 'Module "Neovim::Ext" is out-of-date. Installed: '
      .. current_cpan
      .. ', latest: '
      .. latest_cpan
    health.warn(message, 'Run in shell: cpanm -n Neovim::Ext')
  else
    health.ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan)
  end
end

local function is(path, ty)
  if not path then
    return false
  end
  local stat = vim.uv.fs_stat(path)
  if not stat then
    return false
  end
  return stat.type == ty
end

-- Resolves Python executable path by invoking and checking `sys.executable`.
local function python_exepath(invocation)
  if invocation == "" or invocation == nil then
    return nil
  end
  local p = vim.system({ invocation, '-c', 'import sys; sys.stdout.write(sys.executable)' }):wait()
  if p.code ~= 0 then
    health.warn(p.stderr)
    return nil
  end
  return vim.fs.normalize(vim.trim(p.stdout))
end

--- Check if pyenv is available and a valid pyenv root can be found, then return
--- their respective paths. If either of those is invalid, return two empty
--- strings, effectively ignoring pyenv.
---
--- @return [string, string]
local function check_for_pyenv()
  local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv'))

  if pyenv_path == '' then
    return { '', '' }
  end

  health.info('pyenv: Path: ' .. pyenv_path)

  local pyenv_root = vim.fn.resolve(os.getenv('PYENV_ROOT') or '')

Review comment (suggestion):
```suggestion
  if invocation == '' or invocation == nil then
```

Code after:

  if vim.version.lt(current_cpan, latest_cpan) then
    local message = 'Module "Neovim::Ext" is out-of-date. Installed: '
      .. current_cpan
      .. ', latest: '
      .. latest_cpan
    health.warn(message, 'Run in shell: cpanm -n Neovim::Ext')
  else
    health.ok('Latest "Neovim::Ext" cpan module is installed: ' .. current_cpan)
  end
end

local function is(path, ty)
  if not path then
    return false
  end
  local stat = vim.uv.fs_stat(path)
  if not stat then
    return false
  end
  return stat.type == ty
end

-- Resolves Python executable path by invoking and checking `sys.executable`.
local function python_exepath(invocation)
  if invocation == '' or invocation == nil then
    return nil
  end
  local p = vim.system({ invocation, '-c', 'import sys; sys.stdout.write(sys.executable)' }):wait()
  if p.code ~= 0 then
    health.warn(p.stderr)
    return nil
  end
  return vim.fs.normalize(vim.trim(p.stdout))
end

--- Check if pyenv is available and a valid pyenv root can be found, then return
--- their respective paths. If either of those is invalid, return two empty
--- strings, effectively ignoring pyenv.
---
--- @return [string, string]
local function check_for_pyenv()
  local pyenv_path = vim.fn.resolve(vim.fn.exepath('pyenv'))

  if pyenv_path == '' then
    return { '', '' }
  end

  health.info('pyenv: Path: ' .. pyenv_path)

  local pyenv_root = vim.fn.resolve(os.getenv('PYENV_ROOT') or '')
Repository: neovim/neovim
File: runtime/lua/vim/treesitter/highlighter.lua (Lua)

Code before:
    vim.cmd.runtime({ 'syntax/synload.vim', bang = true })
    api.nvim_create_augroup('syntaxset', { clear = false })
  end

  vim._with({ buf = self.bufnr }, function()
    vim.opt_local.spelloptions:append('noplainbuffer')
  end)

  return self
end

--- @nodoc
--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
  TSHighlighter.active[self.bufnr] = nil

  if api.nvim_buf_is_loaded(self.bufnr) then
    vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
    vim.b[self.bufnr].ts_highlight = nil
    api.nvim_buf_clear_namespace(self.bufnr, ns, 0, -1)
    if vim.g.syntax_on == 1 then
      api.nvim_exec_autocmds(
        'FileType',
        { group = 'syntaxset', buffer = self.bufnr, modeline = false }
      )
    end
  end
end

---@param win integer
---@param srow integer
---@param erow integer exclusive
---@private
function TSHighlighter:prepare_highlight_states(win, srow, erow)
  self._highlight_states[win] = {}

  self.tree:for_each_tree(function(tstree, tree)
    if not tstree then
      return
    end

    local root_node = tstree:root()
    local root_start_row, _, root_end_row, _ = root_node:range()

    -- Only consider trees within the visible range
    if root_start_row > erow or root_end_row < srow then
      return
    end

    local hl_query = self:get_query(tree:lang())
    -- Some injected languages may not have highlight queries.

Review comment (suggestion):
not a blocker for this pr, but `nvim_exec_autocmds` should just do this implicitly. it's completely silly that it needs to be wrapped when the user already passed `buffer=bufnr`.

Code after:
    vim.cmd.runtime({ 'syntax/synload.vim', bang = true })
    api.nvim_create_augroup('syntaxset', { clear = false })
  end

  vim._with({ buf = self.bufnr }, function()
    vim.opt_local.spelloptions:append('noplainbuffer')
  end)

  return self
end

--- @nodoc
--- Removes all internal references to the highlighter
function TSHighlighter:destroy()
  TSHighlighter.active[self.bufnr] = nil

  if api.nvim_buf_is_loaded(self.bufnr) then
    vim.bo[self.bufnr].spelloptions = self.orig_spelloptions
    vim.b[self.bufnr].ts_highlight = nil
    api.nvim_buf_clear_namespace(self.bufnr, ns, 0, -1)
    if vim.g.syntax_on == 1 then
      -- FileType autocmds commonly assume curbuf is the target buffer, so nvim_buf_call.
      api.nvim_buf_call(self.bufnr, function()
        api.nvim_exec_autocmds(
          'FileType',
          { group = 'syntaxset', buffer = self.bufnr, modeline = false }
        )
      end)
    end
  end
end

---@param win integer
---@param srow integer
---@param erow integer exclusive
---@private
function TSHighlighter:prepare_highlight_states(win, srow, erow)
  self._highlight_states[win] = {}

  self.tree:for_each_tree(function(tstree, tree)
    if not tstree then
      return
    end

    local root_node = tstree:root()
    local root_start_row, _, root_end_row, _ = root_node:range()

    -- Only consider trees within the visible range
    if root_start_row > erow or root_end_row < srow then
      return
    end