From b9b408a56c7e607972beaa7214719ff1494e384c Mon Sep 17 00:00:00 2001 From: Riley Bruins Date: Fri, 13 Sep 2024 05:09:11 -0700 Subject: [PATCH] feat(treesitter): start moving get_parser to return nil #30313 **Problem:** `vim.treesitter.get_parser` will throw an error if no parser can be found. - This means the caller is responsible for wrapping it in a `pcall`, which is easy to forget - It also makes it slightly harder to potentially memoize `get_parser` in the future - It's a bit unintuitive since many other `get_*` style functions conventionally return `nil` if no object is found (e.g. `get_node`, `get_lang`, `query.get`, etc.) **Solution:** Return `nil` if no parser can be found or created - This requires a function signature change, and some new assertions in places where the parser will always (or should always) be found. - This commit starts by making this change internally, since it is breaking. Eventually it will be rolled out to the public API. --- runtime/lua/vim/_comment.lua | 4 +- runtime/lua/vim/treesitter.lua | 53 ++++++++++++++------ runtime/lua/vim/treesitter/_fold.lua | 4 +- runtime/lua/vim/treesitter/_query_linter.lua | 2 +- runtime/lua/vim/treesitter/dev.lua | 13 +++-- runtime/lua/vim/vimhelp.lua | 2 +- scripts/gen_help_html.lua | 2 +- test/functional/treesitter/language_spec.lua | 8 ++- test/functional/treesitter/parser_spec.lua | 4 +- 9 files changed, 58 insertions(+), 34 deletions(-) diff --git a/runtime/lua/vim/_comment.lua b/runtime/lua/vim/_comment.lua index efe289b3e1..c821f01257 100644 --- a/runtime/lua/vim/_comment.lua +++ b/runtime/lua/vim/_comment.lua @@ -9,8 +9,8 @@ local function get_commentstring(ref_position) local buf_cs = vim.bo.commentstring - local has_ts_parser, ts_parser = pcall(vim.treesitter.get_parser) - if not has_ts_parser then + local ts_parser = vim.treesitter._get_parser() + if not ts_parser then return buf_cs end diff --git a/runtime/lua/vim/treesitter.lua b/runtime/lua/vim/treesitter.lua index 4629203138..809ea59b94 100644 --- a/runtime/lua/vim/treesitter.lua +++ b/runtime/lua/vim/treesitter.lua @@ -74,14 +74,14 @@ end --- Returns the parser for a specific buffer and attaches it to the buffer --- ---- If needed, this will create the parser. +--- If needed, this will create the parser. If no parser can be found or created, returns `nil`. --- ---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) ---@param lang (string|nil) Language of this parser (default: from buffer filetype) ---@param opts (table|nil) Options to pass to the created language tree --- ----@return vim.treesitter.LanguageTree object to use for parsing -function M.get_parser(bufnr, lang, opts) +---@return vim.treesitter.LanguageTree? object to use for parsing, or `nil` if not found +function M._get_parser(bufnr, lang, opts) opts = opts or {} if bufnr == nil or bufnr == 0 then @@ -94,18 +94,14 @@ function M.get_parser(bufnr, lang, opts) if not valid_lang(lang) then if not parsers[bufnr] then - error( - string.format( - 'There is no parser available for buffer %d and one could not be' - .. ' created because lang could not be determined. Either pass lang' - .. ' or set the buffer filetype', - bufnr - ) - ) + return nil end elseif parsers[bufnr] == nil or parsers[bufnr]:lang() ~= lang then - assert(lang, 'lang should be valid') - parsers[bufnr] = M._create_parser(bufnr, lang, opts) + local parser = vim.F.npcall(M._create_parser, bufnr, lang, opts) + if not parser then + return nil + end + parsers[bufnr] = parser end parsers[bufnr]:register_cbs(opts.buf_attach_cbs) @@ -113,6 +109,29 @@ function M.get_parser(bufnr, lang, opts) return parsers[bufnr] end +--- Returns the parser for a specific buffer and attaches it to the buffer +--- +--- If needed, this will create the parser. +--- +---@param bufnr (integer|nil) Buffer the parser should be tied to (default: current buffer) +---@param lang (string|nil) Language of this parser (default: from buffer filetype) +---@param opts (table|nil) Options to pass to the created language tree +--- +---@return vim.treesitter.LanguageTree object to use for parsing +function M.get_parser(bufnr, lang, opts) + -- TODO(ribru17): Remove _get_parser and move that logic back here once the breaking function + -- signature change is acceptable. + local parser = M._get_parser(bufnr, lang, opts) + if not parser then + vim.notify_once( + 'WARNING: vim.treesitter.get_parser will return nil instead of raising an error in Neovim 0.12', + vim.log.levels.WARN + ) + error('Parser not found.') + end + return parser +end + --- Returns a string parser --- ---@param str string Text to parse @@ -386,7 +405,7 @@ function M.get_node(opts) local ts_range = { row, col, row, col } - local root_lang_tree = M.get_parser(bufnr, opts.lang) + local root_lang_tree = M._get_parser(bufnr, opts.lang) if not root_lang_tree then return end @@ -419,7 +438,11 @@ end ---@param lang (string|nil) Language of the parser (default: from buffer filetype) function M.start(bufnr, lang) bufnr = bufnr or api.nvim_get_current_buf() - local parser = M.get_parser(bufnr, lang) + local parser = M._get_parser(bufnr, lang) + if not parser then + vim.notify('No parser for the given buffer.', vim.log.levels.WARN) + return + end M.highlighter.new(parser) end diff --git a/runtime/lua/vim/treesitter/_fold.lua b/runtime/lua/vim/treesitter/_fold.lua index 7375beaf9d..49792c3891 100644 --- a/runtime/lua/vim/treesitter/_fold.lua +++ b/runtime/lua/vim/treesitter/_fold.lua @@ -114,7 +114,7 @@ local function compute_folds_levels(bufnr, info, srow, erow, parse_injections) srow = srow or 0 erow = erow or api.nvim_buf_line_count(bufnr) - local parser = ts.get_parser(bufnr) + local parser = assert(ts._get_parser(bufnr)) parser:parse(parse_injections and { srow, erow } or nil) @@ -392,7 +392,7 @@ function M.foldexpr(lnum) lnum = lnum or vim.v.lnum local bufnr = api.nvim_get_current_buf() - local parser = vim.F.npcall(ts.get_parser, bufnr) + local parser = ts._get_parser(bufnr) if not parser then return '0' end diff --git a/runtime/lua/vim/treesitter/_query_linter.lua b/runtime/lua/vim/treesitter/_query_linter.lua index ea1ae5416a..30acef559e 100644 --- a/runtime/lua/vim/treesitter/_query_linter.lua +++ b/runtime/lua/vim/treesitter/_query_linter.lua @@ -172,7 +172,7 @@ function M.lint(buf, opts) --- @type (table|nil) local parser_info = vim.F.npcall(vim.treesitter.language.inspect, lang) - local parser = vim.treesitter.get_parser(buf) + local parser = assert(vim.treesitter._get_parser(buf), 'query parser not found.') parser:parse() parser:for_each_tree(function(tree, ltree) if ltree:lang() == 'query' then diff --git a/runtime/lua/vim/treesitter/dev.lua b/runtime/lua/vim/treesitter/dev.lua index e89aea2b85..606d5b27b9 100644 --- a/runtime/lua/vim/treesitter/dev.lua +++ b/runtime/lua/vim/treesitter/dev.lua @@ -76,10 +76,9 @@ end --- ---@package function TSTreeView:new(bufnr, lang) - local ok, parser = pcall(vim.treesitter.get_parser, bufnr or 0, lang) - if not ok then - local err = parser --[[ @as string ]] - return nil, 'No parser available for the given buffer:\n' .. err + local parser = vim.treesitter._get_parser(bufnr or 0, lang) + if not parser then + return nil, 'No parser available for the given buffer.' end -- For each child tree (injected language), find the root of the tree and locate the node within @@ -539,7 +538,7 @@ local edit_ns = api.nvim_create_namespace('treesitter/dev-edit') local function update_editor_highlights(query_win, base_win, lang) local base_buf = api.nvim_win_get_buf(base_win) local query_buf = api.nvim_win_get_buf(query_win) - local parser = vim.treesitter.get_parser(base_buf, lang) + local parser = assert(vim.treesitter._get_parser(base_buf, lang)) api.nvim_buf_clear_namespace(base_buf, edit_ns, 0, -1) local query_content = table.concat(api.nvim_buf_get_lines(query_buf, 0, -1, false), '\n') @@ -596,8 +595,8 @@ function M.edit_query(lang) end vim.cmd(cmd) - local ok, parser = pcall(vim.treesitter.get_parser, buf, lang) - if not ok then + local parser = vim.treesitter._get_parser(buf, lang) + if not parser then return nil, 'No parser available for the given buffer' end lang = parser:lang() diff --git a/runtime/lua/vim/vimhelp.lua b/runtime/lua/vim/vimhelp.lua index 33324602c9..01af5425c3 100644 --- a/runtime/lua/vim/vimhelp.lua +++ b/runtime/lua/vim/vimhelp.lua @@ -33,7 +33,7 @@ end --- Show a table of contents for the help buffer in a loclist function M.show_toc() local bufnr = vim.api.nvim_get_current_buf() - local parser = vim.treesitter.get_parser(bufnr, 'vimdoc') + local parser = assert(vim.treesitter._get_parser(bufnr, 'vimdoc'), 'vimdoc parser not found.') local query = vim.treesitter.query.parse( parser:lang(), [[ diff --git a/scripts/gen_help_html.lua b/scripts/gen_help_html.lua index 117e6c27d5..b1a6cb546a 100644 --- a/scripts/gen_help_html.lua +++ b/scripts/gen_help_html.lua @@ -786,7 +786,7 @@ local function parse_buf(fname, parser_path) if parser_path then vim.treesitter.language.add('vimdoc', { path = parser_path }) end - local lang_tree = vim.treesitter.get_parser(buf) + local lang_tree = assert(vim.treesitter._get_parser(buf), 'vimdoc parser not found.') return lang_tree, buf end diff --git a/test/functional/treesitter/language_spec.lua b/test/functional/treesitter/language_spec.lua index 6c211049f0..b9934a2e5f 100644 --- a/test/functional/treesitter/language_spec.lua +++ b/test/functional/treesitter/language_spec.lua @@ -8,6 +8,7 @@ local exec_lua = n.exec_lua local pcall_err = t.pcall_err local matches = t.matches local insert = n.insert +local NIL = vim.NIL before_each(clear) @@ -15,10 +16,12 @@ describe('treesitter language API', function() -- error tests not requiring a parser library it('handles missing language', function() eq( - ".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + '.../treesitter.lua:0: Parser not found.', pcall_err(exec_lua, "parser = vim.treesitter.get_parser(0, 'borklang')") ) + eq(NIL, exec_lua("return vim.treesitter._get_parser(0, 'borklang')")) + -- actual message depends on platform matches( "Failed to load parser for language 'borklang': uv_dlopen: .+", @@ -105,9 +108,10 @@ describe('treesitter language API', function() command('set filetype=borklang') -- Should throw an error when filetype changes to borklang eq( - ".../language.lua:0: no parser for 'borklang' language, see :help treesitter-parsers", + '.../treesitter.lua:0: Parser not found.', pcall_err(exec_lua, "new_parser = vim.treesitter.get_parser(0, 'borklang')") ) + eq(NIL, exec_lua("return vim.treesitter._get_parser(0, 'borklang')")) end ) diff --git a/test/functional/treesitter/parser_spec.lua b/test/functional/treesitter/parser_spec.lua index 4aa8beebae..61af007782 100644 --- a/test/functional/treesitter/parser_spec.lua +++ b/test/functional/treesitter/parser_spec.lua @@ -135,9 +135,7 @@ void ui_refresh(void) insert(test_text) eq( - '.../treesitter.lua:0: There is no parser available for buffer 1 and one' - .. ' could not be created because lang could not be determined. Either' - .. ' pass lang or set the buffer filetype', + '.../treesitter.lua:0: Parser not found.', pcall_err(exec_lua, 'vim.treesitter.get_parser(0)') )