aboutsummaryrefslogtreecommitdiff
path: root/runtime/lua/vim
diff options
context:
space:
mode:
Diffstat (limited to 'runtime/lua/vim')
-rw-r--r--runtime/lua/vim/filetype.lua1
-rw-r--r--runtime/lua/vim/lsp/semantic_tokens.lua45
-rw-r--r--runtime/lua/vim/treesitter/_fold.lua115
-rw-r--r--runtime/lua/vim/treesitter/languagetree.lua2
4 files changed, 134 insertions, 29 deletions
diff --git a/runtime/lua/vim/filetype.lua b/runtime/lua/vim/filetype.lua
index 4fafc4e2e2..5c799b23f2 100644
--- a/runtime/lua/vim/filetype.lua
+++ b/runtime/lua/vim/filetype.lua
@@ -637,6 +637,7 @@ local extension = {
nse = 'lua',
rockspec = 'lua',
lua = 'lua',
+ luau = 'luau',
lrc = 'lyrics',
m = function(path, bufnr)
return require('vim.filetype.detect').m(bufnr)
diff --git a/runtime/lua/vim/lsp/semantic_tokens.lua b/runtime/lua/vim/lsp/semantic_tokens.lua
index 03532c33b7..b6b09c58b1 100644
--- a/runtime/lua/vim/lsp/semantic_tokens.lua
+++ b/runtime/lua/vim/lsp/semantic_tokens.lua
@@ -1,7 +1,8 @@
local api = vim.api
+local bit = require('bit')
local handlers = require('vim.lsp.handlers')
local util = require('vim.lsp.util')
-local bit = require('bit')
+local uv = vim.loop
--- @class STTokenRange
--- @field line integer line number 0-based
@@ -94,15 +95,38 @@ end
---
---@private
---@return STTokenRange[]
-local function tokens_to_ranges(data, bufnr, client)
+local function tokens_to_ranges(data, bufnr, client, request)
local legend = client.server_capabilities.semanticTokensProvider.legend
local token_types = legend.tokenTypes
local token_modifiers = legend.tokenModifiers
local ranges = {}
+ local start = uv.hrtime()
+ local ms_to_ns = 1000 * 1000
+ local yield_interval_ns = 5 * ms_to_ns
+ local co, is_main = coroutine.running()
+
local line
local start_char = 0
for i = 1, #data, 5 do
+ -- if this function is called from the main coroutine, let it run to completion with no yield
+ if not is_main then
+ local elapsed_ns = uv.hrtime() - start
+
+ if elapsed_ns > yield_interval_ns then
+ vim.schedule(function()
+ coroutine.resume(co, util.buf_versions[bufnr])
+ end)
+ if request.version ~= coroutine.yield() then
+ -- request became stale since the last time the coroutine ran.
+ -- abandon it by yielding without a way to resume
+ coroutine.yield()
+ end
+
+ start = uv.hrtime()
+ end
+ end
+
local delta_line = data[i]
line = line and line + delta_line or delta_line
local delta_start = data[i + 1]
@@ -280,7 +304,7 @@ function STHighlighter:send_request()
local c = vim.lsp.get_client_by_id(ctx.client_id)
local highlighter = STHighlighter.active[ctx.bufnr]
if not err and c and highlighter then
- highlighter:process_response(response, c, version)
+ coroutine.wrap(STHighlighter.process_response)(highlighter, response, c, version)
end
end, self.bufnr)
@@ -315,11 +339,9 @@ function STHighlighter:process_response(response, client, version)
return
end
- -- reset active request
- state.active_request = {}
-
-- skip nil responses
if response == nil then
+ state.active_request = {}
return
end
@@ -347,12 +369,19 @@ function STHighlighter:process_response(response, client, version)
tokens = response.data
end
- -- Update the state with the new results
+ -- convert token list to highlight ranges
+ -- this could yield and run over multiple event loop iterations
+ local highlights = tokens_to_ranges(tokens, self.bufnr, client, state.active_request)
+
+ -- reset active request
+ state.active_request = {}
+
+ -- update the state with the new results
local current_result = state.current_result
current_result.version = version
current_result.result_id = response.resultId
current_result.tokens = tokens
- current_result.highlights = tokens_to_ranges(tokens, self.bufnr, client)
+ current_result.highlights = highlights
current_result.namespace_cleared = false
-- redraw all windows displaying buffer
diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua
index 7df93d1b2e..f6425d7cb9 100644
--- a/runtime/lua/vim/treesitter/_fold.lua
+++ b/runtime/lua/vim/treesitter/_fold.lua
@@ -1,3 +1,5 @@
+local ts = vim.treesitter
+
local Range = require('vim.treesitter._range')
local api = vim.api
@@ -32,15 +34,58 @@ function FoldInfo:invalidate_range(srow, erow)
end
end
+--- Efficiently remove items from middle of a list a list.
+---
+--- Calling table.remove() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+local function list_remove(t, first, last)
+ local n = #t
+ for i = 0, n - first do
+ t[first + i] = t[last + 1 + i]
+ t[last + 1 + i] = nil
+ end
+end
+
---@package
---@param srow integer
---@param erow integer
function FoldInfo:remove_range(srow, erow)
- for i = erow - 1, srow, -1 do
- table.remove(self.levels, i + 1)
- table.remove(self.levels0, i + 1)
- table.remove(self.start_counts, i + 1)
- table.remove(self.stop_counts, i + 1)
+ list_remove(self.levels, srow + 1, erow)
+ list_remove(self.levels0, srow + 1, erow)
+ list_remove(self.start_counts, srow + 1, erow)
+ list_remove(self.stop_counts, srow + 1, erow)
+end
+
+--- Efficiently insert items into the middle of a list.
+---
+--- Calling table.insert() in a loop will re-index the tail of the table on
+--- every iteration, instead this function will re-index the table exactly
+--- once.
+---
+--- Based on https://stackoverflow.com/questions/12394841/safely-remove-items-from-an-array-table-while-iterating/53038524#53038524
+---
+---@param t any[]
+---@param first integer
+---@param last integer
+---@param v any
+local function list_insert(t, first, last, v)
+ local n = #t
+
+ -- Shift table forward
+ for i = n - first, 0, -1 do
+ t[last + 1 + i] = t[first + i]
+ end
+
+ -- Fill in new values
+ for i = first, last do
+ t[i] = v
end
end
@@ -48,12 +93,10 @@ end
---@param srow integer
---@param erow integer
function FoldInfo:add_range(srow, erow)
- for i = srow, erow - 1 do
- table.insert(self.levels, i + 1, '-1')
- table.insert(self.levels0, i + 1, -1)
- table.insert(self.start_counts, i + 1, nil)
- table.insert(self.stop_counts, i + 1, nil)
- end
+ list_insert(self.levels, srow + 1, erow, '-1')
+ list_insert(self.levels0, srow + 1, erow, -1)
+ list_insert(self.start_counts, srow + 1, erow, nil)
+ list_insert(self.stop_counts, srow + 1, erow, nil)
end
---@package
@@ -90,21 +133,41 @@ local function trim_level(level)
return level
end
+--- If a parser doesn't have any ranges explicitly set, treesitter will
+--- return a range with end_row and end_bytes with a value of UINT32_MAX,
+--- so clip end_row to the max buffer line.
+---
+--- TODO(lewis6991): Handle this generally
+---
+--- @param bufnr integer
+--- @param erow integer?
+--- @return integer
+local function normalise_erow(bufnr, erow)
+ local max_erow = api.nvim_buf_line_count(bufnr) - 1
+ return math.min(erow or max_erow, max_erow)
+end
+
---@param bufnr integer
---@param info TS.FoldInfo
---@param srow integer?
---@param erow integer?
local function get_folds_levels(bufnr, info, srow, erow)
srow = srow or 0
- erow = erow or api.nvim_buf_line_count(bufnr)
+ erow = normalise_erow(bufnr, erow)
info:invalidate_range(srow, erow)
local prev_start = -1
local prev_stop = -1
- vim.treesitter.get_parser(bufnr):for_each_tree(function(tree, ltree)
- local query = vim.treesitter.query.get(ltree:lang(), 'folds')
+ local parser = ts.get_parser(bufnr)
+
+ if not parser:is_valid() then
+ return
+ end
+
+ parser:for_each_tree(function(tree, ltree)
+ local query = ts.query.get(ltree:lang(), 'folds')
if not query then
return
end
@@ -112,9 +175,9 @@ local function get_folds_levels(bufnr, info, srow, erow)
-- erow in query is end-exclusive
local q_erow = erow and erow + 1 or -1
- for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow or 0, q_erow) do
+ for id, node, metadata in query:iter_captures(tree:root(), bufnr, srow, q_erow) do
if query.captures[id] == 'fold' then
- local range = vim.treesitter.get_range(node, bufnr, metadata[id])
+ local range = ts.get_range(node, bufnr, metadata[id])
local start, _, stop, stop_col = Range.unpack4(range)
if stop_col == 0 then
@@ -184,13 +247,25 @@ local function recompute_folds()
vim._foldupdate()
end
+--- Schedule a function only if bufnr is loaded
+---@param bufnr integer
+---@param fn function
+local function schedule_if_loaded(bufnr, fn)
+ vim.schedule(function()
+ if not api.nvim_buf_is_loaded(bufnr) then
+ return
+ end
+ fn()
+ end)
+end
+
---@param bufnr integer
---@param foldinfo TS.FoldInfo
---@param tree_changes Range4[]
local function on_changedtree(bufnr, foldinfo, tree_changes)
-- For some reason, queries seem to use the old buffer state in on_bytes.
-- Get around this by scheduling and manually updating folds.
- vim.schedule(function()
+ schedule_if_loaded(bufnr, function()
for _, change in ipairs(tree_changes) do
local srow, _, erow = Range.unpack4(change)
get_folds_levels(bufnr, foldinfo, srow, erow)
@@ -212,7 +287,7 @@ local function on_bytes(bufnr, foldinfo, start_row, old_row, new_row)
foldinfo:remove_range(end_row_new, end_row_old)
elseif new_row > old_row then
foldinfo:add_range(start_row, end_row_new)
- vim.schedule(function()
+ schedule_if_loaded(bufnr, function()
get_folds_levels(bufnr, foldinfo, start_row, end_row_new)
recompute_folds()
end)
@@ -226,7 +301,7 @@ function M.foldexpr(lnum)
lnum = lnum or vim.v.lnum
local bufnr = api.nvim_get_current_buf()
- if not vim.treesitter._has_parser(bufnr) or not lnum then
+ if not ts._has_parser(bufnr) or not lnum then
return '0'
end
@@ -234,7 +309,7 @@ function M.foldexpr(lnum)
foldinfos[bufnr] = FoldInfo.new()
get_folds_levels(bufnr, foldinfos[bufnr])
- local parser = vim.treesitter.get_parser(bufnr)
+ local parser = ts.get_parser(bufnr)
parser:register_cbs({
on_changedtree = function(tree_changes)
on_changedtree(bufnr, foldinfos[bufnr], tree_changes)
diff --git a/runtime/lua/vim/treesitter/languagetree.lua b/runtime/lua/vim/treesitter/languagetree.lua
index 19cea32367..1adf6759fa 100644
--- a/runtime/lua/vim/treesitter/languagetree.lua
+++ b/runtime/lua/vim/treesitter/languagetree.lua
@@ -262,7 +262,7 @@ function LanguageTree:parse()
tcall(self._parser.parse, self._parser, self._trees[i], self._source, true)
-- Pass ranges if this is an initial parse
- local cb_changes = self._trees[i] and tree_changes or ranges
+ local cb_changes = self._trees[i] and tree_changes or tree:included_ranges(true)
self:_do_callback('changedtree', cb_changes, tree)
self._trees[i] = tree