diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/doc/lsp.txt | 21 | ||||
-rw-r--r-- | runtime/doc/news.txt | 3 | ||||
-rw-r--r-- | runtime/doc/options.txt | 10 | ||||
-rw-r--r-- | runtime/doc/quickref.txt | 1 | ||||
-rw-r--r-- | runtime/lua/vim/filetype.lua | 1 | ||||
-rw-r--r-- | runtime/lua/vim/lsp/semantic_tokens.lua | 45 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/_fold.lua | 115 | ||||
-rw-r--r-- | runtime/lua/vim/treesitter/languagetree.lua | 2 | ||||
-rw-r--r-- | runtime/optwin.vim | 3 |
9 files changed, 151 insertions, 50 deletions
diff --git a/runtime/doc/lsp.txt b/runtime/doc/lsp.txt index 01edbe7cbd..e07bfc2209 100644 --- a/runtime/doc/lsp.txt +++ b/runtime/doc/lsp.txt @@ -2131,27 +2131,6 @@ start({cmd}, {cmd_args}, {dispatchers}, {extra_spawn_params}) ============================================================================== -Lua module: vim.lsp.sync *lsp-sync* - - *vim.lsp.sync.compute_diff()* -compute_diff({___MissingCloseParenHere___}) - Returns the range table for the difference between prev and curr lines - - Parameters: ~ - • {prev_lines} (table) list of lines - • {curr_lines} (table) list of lines - • {firstline} (integer) line to begin search for first difference - • {lastline} (integer) line to begin search in old_lines for - last difference - • {new_lastline} (integer) line to begin search in new_lines for - last difference - • {offset_encoding} (string) encoding requested by language server - - Return: ~ - (table) TextDocumentContentChangeEvent see https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocumentContentChangeEvent - - -============================================================================== Lua module: vim.lsp.protocol *lsp-protocol* *vim.lsp.protocol.make_client_capabilities()* diff --git a/runtime/doc/news.txt b/runtime/doc/news.txt index f33cffa22e..bc357ac534 100644 --- a/runtime/doc/news.txt +++ b/runtime/doc/news.txt @@ -49,6 +49,9 @@ iterators |luaref-in|. • Added |vim.treesitter.query.omnifunc()| for treesitter query files (set by default). +• |'smoothscroll'| option to scroll by screen line rather than by text line +when |'wrap'| is set. + ============================================================================== CHANGED FEATURES *news-changed* diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index ba73d79cd3..ab86e56a62 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -5652,6 +5652,16 @@ A jump table for the options with a short description can be found at |Q_op|. option. Also see |ins-expandtab|. When 'expandtab' is not set, the number of spaces is minimized by using <Tab>s. + *'smoothscroll'* *'sms'* *'nosmoothscroll'* *'nosms'* +'smoothscroll' 'sms' boolean (default off) + local to window + Scrolling works with screen lines. When 'wrap' is set and the first + line in the window wraps part of it may not be visible, as if it is + above the window. "<<<" is displayed at the start of the first line, + highlighted with |hl-NonText|. + NOTE: only partly implemented, currently works with CTRL-E, CTRL-Y + and scrolling with the mouse. + *'softtabstop'* *'sts'* 'softtabstop' 'sts' number (default 0) local to buffer diff --git a/runtime/doc/quickref.txt b/runtime/doc/quickref.txt index 952f0064e6..c166ecd79d 100644 --- a/runtime/doc/quickref.txt +++ b/runtime/doc/quickref.txt @@ -870,6 +870,7 @@ Short explanation of each option: *option-list* 'smartcase' 'scs' no ignore case when pattern has uppercase 'smartindent' 'si' smart autoindenting for C programs 'smarttab' 'sta' use 'shiftwidth' when inserting <Tab> +'smoothscroll' 'sms' scroll by screen lines when 'wrap' is set 'softtabstop' 'sts' number of spaces that <Tab> uses while editing 'spell' enable spell checking 'spellcapcheck' 'spc' pattern to locate end of a sentence 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 diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 0d10ac4758..b7b9c61123 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -305,6 +305,9 @@ call <SID>Header(gettext("displaying text")) call <SID>AddOption("scroll", gettext("number of lines to scroll for CTRL-U and CTRL-D")) call append("$", "\t" .. s:local_to_window) call <SID>OptionL("scr") +call <SID>AddOption("smoothscroll", gettext("scroll by screen line")) +call append("$", "\t" .. s:local_to_window) +call <SID>BinOptionL("sms") call <SID>AddOption("scrolloff", gettext("number of screen lines to show around the cursor")) call append("$", " \tset so=" . &so) call <SID>AddOption("wrap", gettext("long lines wrap")) |