From d36f4abfe57b465540002d89eb974cf93f97f3b7 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 6 Jun 2025 11:44:40 -0400 Subject: [PATCH 01/16] refactor!: drop support for Neovim v0.10 --- README.md | 2 +- lua/astrocore/config.lua | 4 +--- lua/astrocore/toggles.lua | 2 -- 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8153315..4e6bedb 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ AstroCore provides the core Lua API that powers [AstroNvim](https://github.com/A ## ⚡️ Requirements -- Neovim >= 0.10 +- Neovim >= 0.11 - [lazy.nvim](https://github.com/folke/lazy.nvim) - [resession.nvim][resession] (_optional_) diff --git a/lua/astrocore/config.lua b/lua/astrocore/config.lua index 9944b2b..1e507b6 100644 --- a/lua/astrocore/config.lua +++ b/lua/astrocore/config.lua @@ -54,11 +54,9 @@ ---@field filetypes string[]? filetypes to ignore ---@field buftypes string[]? buffer types to ignore --- TODO: remove note about version after dropping support for Neovim v0.10 - ---@class AstroCoreDiagnosticsFeature ---@field virtual_text boolean? show virtual text on startup ----@field virtual_lines boolean? show virtual lines on startup (Neovim v0.11+ only) +---@field virtual_lines boolean? show virtual lines on startup --- ---@class AstroCoreSessionOpts ---Session autosaving options diff --git a/lua/astrocore/toggles.lua b/lua/astrocore/toggles.lua index c222d54..698ef32 100644 --- a/lua/astrocore/toggles.lua +++ b/lua/astrocore/toggles.lua @@ -245,8 +245,6 @@ local previous_virtual_lines ---@param silent? boolean if true then don't sent a notification function M.virtual_lines(silent) local virtual_lines = vim.diagnostic.config().virtual_lines - -- TODO: remove check when dropping support for Neovim v0.10 - if virtual_lines == nil then ui_notify(silent, "Virtual lines not available") end local new_virtual_lines = false if virtual_lines then previous_virtual_lines = virtual_lines From 0542bbe47cb6fbd696cdd7eef1bd81472a9315ce Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 12:21:25 -0400 Subject: [PATCH 02/16] feat: add tooling for configuring and setting up treesitter features in Neovim --- lua/astrocore/config.lua | 55 +++++++++++++ lua/astrocore/init.lua | 2 + lua/astrocore/treesitter.lua | 147 +++++++++++++++++++++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 lua/astrocore/treesitter.lua diff --git a/lua/astrocore/config.lua b/lua/astrocore/config.lua index 1e507b6..41c3ceb 100644 --- a/lua/astrocore/config.lua +++ b/lua/astrocore/config.lua @@ -81,6 +81,45 @@ ---``` ---@field ignore AstroCoreSessionIgnore? +---@alias AstroCoreTreesitterFeature boolean|string[]|(fun(lang: string, bufnr: integer): (boolean|nil)) + +---@class AstroCoreTreesitterOpts +---Whether or not to enable treesitter based highlighting. Can be one of the following: +--- +--- - A boolean to apply to all languages +--- - A list of languages to enable +--- - A function that takes a language and a buffer number and returns a boolean +---Examples: +--- +---```lua +---highlight = true -- enables for all languages +---highlight = { "c", "rust" } -- only enables for some languages +---highlight = function(lang, bufnr) -- use a function to decide, for example setting a max filesize +--- local max_filesize = 100 * 1024 -- 100KB +--- local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(bufnr)) +--- if ok and stats and stats.size > max_filesize then return true +---end +---``` +---@field highlight AstroCoreTreesitterFeature? +---Whether or not to enable treesitter based indentation. Can be one of the following: +--- +--- - A boolean to apply to all languages +--- - A list of languages to enable +--- - A function that takes a language and a buffer number and returns a boolean +---Examples: +--- +---```lua +---indent = true -- enables for all languages +---indent = { "c", "rust" } -- only enables for some languages +---indent = function(lang, bufnr) -- use a function to decide, for example setting a max filesize +--- local max_filesize = 100 * 1024 -- 100KB +--- local ok, stats = pcall(vim.loop.fs_stat, vim.api.nvim_buf_get_name(bufnr)) +--- if ok and stats and stats.size > max_filesize then return true +---end +---``` +---@field indent AstroCoreTreesitterFeature? +---@field ensure_installed string[]|"all"|"auto"? a list of treesitter parsers to ensure are installed, "all" will install all parsers, "auto" will install when opening a filetype with an available parser + ---@class AstroCoreFeatureOpts ---@field autopairs boolean? enable or disable autopairs on start (boolean; default = true) ---@field cmp boolean? enable or disable cmp on start (boolean; default = true) @@ -299,6 +338,17 @@ ---} ---``` ---@field sessions AstroCoreSessionOpts? +---Configuration table of treesitter options for AstroNvim +---Example: +-- +---```lua +---treesitter = { +--- highlight = true, +--- indent = true, +--- ensure_installed = { "lua", "vim", "vimdoc" } +---} +---``` +---@field treesitter AstroCoreTreesitterOpts? ---@type AstroCoreOpts local M = { @@ -328,6 +378,11 @@ local M = { buftypes = {}, }, }, + --treesitter = { + -- highlight = true, + -- indent = true, + -- ensure_installed = {}, + --} } return M diff --git a/lua/astrocore/init.lua b/lua/astrocore/init.lua index bf1b74e..8dddd7c 100644 --- a/lua/astrocore/init.lua +++ b/lua/astrocore/init.lua @@ -687,6 +687,8 @@ function M.setup(opts) end end + if vim.tbl_get(M.config, "treesitter") then require("astrocore.treesitter").setup(M.config.treesitter) end + local astroui_avail, astroui = pcall(require, "astroui") if astroui_avail then astroui.set_colorscheme() end end diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua new file mode 100644 index 0000000..37e2b2f --- /dev/null +++ b/lua/astrocore/treesitter.lua @@ -0,0 +1,147 @@ +---AstroNvim Treesitter Utilities +--- +---Utilities necessary for configuring treesitter in Neovim +--- +---This module can be loaded with `local astrocore_treesitter = require "astrocore.treesitter"` +--- +---copyright 2025 +---license GNU General Public License v3.0 +---@class astrocore.treesitter +local M = {} + +---@type AstroCoreTreesitterOpts +local config = {} + +local available +local installed = {} +local queries = {} + +--- Get list of treesitter parsers installed with `nvim-treesitter` +---@param update boolean? whether or not to refresh installed parsers +---@return string[] # the list of installed parsers +function M.installed(update) + if update then + local treesitter_avail, treesitter = pcall(require, "nvim-treesitter") + if treesitter_avail then + installed, queries = {}, {} + for _, lang in ipairs(treesitter.get_installed "parsers") do + installed[lang] = true + end + end + end + return installed +end + +-- Get list of available treesitter parers in `nvim-treesitter` +function M.available() + if available == nil then + available = {} + local treesitter_avail, treesitter = pcall(require, "nvim-treesitter") + if treesitter_avail then + for _, parser in ipairs(treesitter.get_available()) do + available[parser] = true + end + end + end + return available +end + +--- Install the provided parsers with `nvim-treesitter` +---@param languages? "auto"|"all"|string[] a list of languages to install, automatically detect the current language to install, or install all available parsers (default: "auto") +---@param cb? function optional callback function to execute after installation finishes +function M.install(languages, cb) + local patch_func = require("astrocore").patch_func + cb = patch_func(cb, function() M.installed(true) end) + local treesitter_avail, treesitter = pcall(require, "nvim-treesitter") + if not treesitter_avail then return end + if not languages or languages == "auto" then + local bufnr = vim.api.nvim_get_current_buf() + local lang = vim.treesitter.language.get_lang(vim.bo[bufnr].filetype) + if M.available()[lang] then + cb = patch_func(cb, function() M.enable(bufnr) end) + languages = { lang } + else + languages = {} + end + elseif languages == "all" then + languages = treesitter.get_available() + end + if + next(languages --[[ @as string[] ]]) + then + treesitter.install(languages, { summary = true }):await(function() + if cb then cb() end + M.installed(true) + end) + end +end + +--- Check if query is supported for given treesitter parser language +---@param lang string the parser language to check against +---@param query string the query type to check for support of +---@return boolean # whether or not a query is supported by the given parser +function M.has_query(lang, query) + local key = lang .. ":" .. query + if queries[key] == nil then queries[key] = vim.treesitter.query.get(lang, query) ~= nil end + return queries[key] +end + +--- Check if parser exists for filetype with optional query check +---@param filetype? string|integer the filetype to check or a buffer number to get the filetype of (defaults to current buffer) +---@param query? string the query type to check for support of +---@return boolean +function M.has_parser(filetype, query) + if not filetype then filetype = vim.api.nvim_get_current_buf() end + if type(filetype) == "number" then filetype = vim.bo[filetype].filetype end + local lang = vim.treesitter.language.get_lang(filetype --[[ @as string ]]) + if not lang or not M.installed()[lang] then return false end + if query and not M.has_query(lang, query) then return false end + return true +end + +--- Initialize treesitter configuration +---@param opts AstroCoreTreesitterOpts +function M.setup(opts) + local astrocore = require "astrocore" + config = astrocore.extend_tbl(config, opts) --[[ @as AstroCoreTreesitterOpts ]] + astrocore.on_load("nvim-treesitter", function() M.installed(true) end) + vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("astrocore_treesitter", { clear = true }), + callback = function(args) + if not M.has_parser(args.match) then + if config.ensure_installed == "auto" then M.install() end + else + M.enable(args.buf) + end + end, + }) +end + +--- Enable treesitter features in buffer +---@param bufnr integer the buffer to enable treesitter in +function M.enable(bufnr) + local ft = vim.bo[bufnr].filetype + local lang = vim.treesitter.language.get_lang(ft) + if not M.has_parser(ft) or not lang then return end + + ---@param feat string + ---@param query string + local function is_enabled(feat, query) + local enabled = config[feat] ---@type AstroCoreTreesitterFeature? + if type(enabled) == "table" then + enabled = vim.tbl_contains(enabled, lang) + elseif type(enabled) == "function" then + enabled = enabled(lang, bufnr) + end + return enabled and M.has_parser(ft, query) + end + + -- highlighting + if is_enabled("highlight", "highlights") then pcall(vim.treesitter.start, bufnr) end + + -- indents + -- FIX: fix to only run if indenexpr is not set by a plugin + if is_enabled("indent", "indents") then vim.bo[bufnr].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" end +end + +return M From 7e7a55c13902a8b96ddb17b550d1fbdac19a71a1 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 14:36:38 -0400 Subject: [PATCH 03/16] feat(treesitter): add the ability to configure textobjects --- lua/astrocore/config.lua | 73 +++++++++++++++++++++++++++++++++--- lua/astrocore/treesitter.lua | 50 +++++++++++++++++++++++- 2 files changed, 117 insertions(+), 6 deletions(-) diff --git a/lua/astrocore/config.lua b/lua/astrocore/config.lua index 41c3ceb..5753d05 100644 --- a/lua/astrocore/config.lua +++ b/lua/astrocore/config.lua @@ -83,6 +83,31 @@ ---@alias AstroCoreTreesitterFeature boolean|string[]|(fun(lang: string, bufnr: integer): (boolean|nil)) +---@class AstroCoreTreesitterTextObjectsKey +---@field query string The textobject query capture group to perform against +---@field group string? The textobject query group to capture from (default: "textobjects") +---@field desc string The description of the keymap + +---@alias AstroCoreTreesitterTextObjectsKeys table + +---@class AstroCoreTreesitterTextObjectsSelectOpts +---@field select_textobject AstroCoreTreesitterTextObjectsKeys? Keymaps for selecting a given treesitter capture group + +---@class AstroCoreTreesitterTextObjectsMoveOpts +---@field goto_next_start AstroCoreTreesitterTextObjectsKeys? Keymaps for going to the start of the next treesitter capture group +---@field goto_next_end AstroCoreTreesitterTextObjectsKeys? Keymaps for going to the end of the next treesitter capture group +---@field goto_previous_start AstroCoreTreesitterTextObjectsKeys? Keymaps for going to the start of the previous treesitter capture group +---@field goto_previous_end AstroCoreTreesitterTextObjectsKeys? Keymaps for going to the end of the previous treesitter capture group + +---@class AstroCoreTreesitterTextObjectsSwapOpts +---@field swap_next AstroCoreTreesitterTextObjectsKeys? Keymaps for swapping with the next treesitter capture group +---@field swap_previous AstroCoreTreesitterTextObjectsKeys? Keymaps for swapping with the previous treesitter capture group + +---@class AstroCoreTreesitterTextObjects +---@field select AstroCoreTreesitterTextObjectsSelectOpts? Keymaps for selection of treesitter capture groups +---@field move AstroCoreTreesitterTextObjectsMoveOpts? Keymaps for moving treesitter capture groups +---@field swap AstroCoreTreesitterTextObjectsSwapOpts? Keymaps for swapping treesitter capture groups + ---@class AstroCoreTreesitterOpts ---Whether or not to enable treesitter based highlighting. Can be one of the following: --- @@ -119,6 +144,43 @@ ---``` ---@field indent AstroCoreTreesitterFeature? ---@field ensure_installed string[]|"all"|"auto"? a list of treesitter parsers to ensure are installed, "all" will install all parsers, "auto" will install when opening a filetype with an available parser +---Configuration of textobject mappings to create using `nvim-treesitter-textobjects` +--- +---Examples: +--- +---```lua +---textobjects = { +--- select = { +--- select_textobject = { +--- ["af"] = { query = "@function.outer", desc = "around function" }, +--- ["if"] = { query = "@function.inner", desc = "around function" }, +--- }, +--- }, +--- move = { +--- goto_next_start = { +--- ["]f"] = { query = "@function.outer", "Next function start" }, +--- }, +--- goto_next_end = { +--- ["]F"] = { query = "@function.outer", "Next function end" }, +--- }, +--- goto_previous_start = { +--- ["[f"] = { query = "@function.outer", "Previous function start" }, +--- }, +--- goto_previous_end = { +--- ["[F"] = { query = "@function.outer", "Previous function end" }, +--- }, +--- }, +--- swap = { +--- swap_next = { +--- [">F"] = { query = "@function.outer", desc = "Swap next function" }, +--- }, +--- swap_previous = { +--- [" Date: Fri, 10 Oct 2025 14:58:05 -0400 Subject: [PATCH 04/16] fix(treesitter): resolve incorrect callbacks in installation --- lua/astrocore/treesitter.lua | 64 +++++++++++++++++++++++++++++------- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index bf3c17d..43179e0 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -59,14 +59,16 @@ end ---@param cb? function optional callback function to execute after installation finishes function M.install(languages, cb) local patch_func = require("astrocore").patch_func - cb = patch_func(cb, function() M.installed(true) end) local treesitter_avail, treesitter = pcall(require, "nvim-treesitter") if not treesitter_avail then return end if not languages or languages == "auto" then local bufnr = vim.api.nvim_get_current_buf() local lang = vim.treesitter.language.get_lang(vim.bo[bufnr].filetype) if M.available()[lang] then - cb = patch_func(cb, function() M.enable(bufnr) end) + cb = patch_func(cb, function(orig) + M.enable(bufnr) + orig() + end) languages = { lang } else languages = {} @@ -77,10 +79,11 @@ function M.install(languages, cb) if next(languages --[[ @as string[] ]]) then - treesitter.install(languages, { summary = true }):await(function() - if cb then cb() end + cb = patch_func(cb, function(orig) M.installed(true) + orig() end) + treesitter.install(languages, { summary = true }):await(cb) end end @@ -124,15 +127,12 @@ function M.has_parser(filetype, query) return true end ---- Initialize treesitter configuration ----@param opts AstroCoreTreesitterOpts -function M.setup(opts) - local astrocore = require "astrocore" - config = astrocore.extend_tbl(config, opts) --[[ @as AstroCoreTreesitterOpts ]] - astrocore.on_load("nvim-treesitter", function() +local function _setup() + require("astrocore").on_load("nvim-treesitter", function() M.installed(true) - M.install(opts.ensure_installed) + M.install(config.ensure_installed) end) + vim.api.nvim_create_autocmd("FileType", { group = vim.api.nvim_create_augroup("astrocore_treesitter", { clear = true }), callback = function(args) @@ -145,6 +145,48 @@ function M.setup(opts) }) end +--- Initialize treesitter configuration +---@param opts AstroCoreTreesitterOpts +function M.setup(opts) + local astrocore = require "astrocore" + config = astrocore.extend_tbl(config, opts) --[[ @as AstroCoreTreesitterOpts ]] + + if vim.fn.executable "tree-sitter" ~= 1 then + if pcall(require, "mason") and vim.fn.executable "tree-sitter" ~= 1 then + local mr = require "mason-registry" + mr.refresh(function() + local p = mr.get_package "tree-sitter-cli" + if not p:is_installed() then + astrocore.notify "Installing `tree-sitter-cli` with `mason.nvim`..." + p:install( + nil, + vim.schedule_wrap(function(success) + if success then + astrocore.notify "Installed `tree-sitter-cli` with `mason.nvim`." + _setup() + else + astrocore.notify( + "Failed to install `tree-sitter-cli` with `mason.nvim\n\nCheck `:Mason` UI for details.", + vim.log.levels.ERROR + ) + end + end) + ) + end + end) + return + end + if vim.fn.executable "tree-sitter" ~= 1 then + astrocore.notify( + "`tree-sitter` CLI is required for using `nvim-treesitter`\n\nInstall to enable treesitter features.", + vim.log.levels.WARN + ) + return + end + end + _setup() +end + --- Enable treesitter features in buffer ---@param bufnr integer the buffer to enable treesitter in function M.enable(bufnr) From 00729fc9e335de2d85316ba6afb9fc985c316d95 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 15:31:48 -0400 Subject: [PATCH 05/16] feat(treesitter): add ability to disable treesitter --- lua/astrocore/config.lua | 5 +++- lua/astrocore/treesitter.lua | 53 ++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/lua/astrocore/config.lua b/lua/astrocore/config.lua index 5753d05..6f915f6 100644 --- a/lua/astrocore/config.lua +++ b/lua/astrocore/config.lua @@ -81,7 +81,9 @@ ---``` ---@field ignore AstroCoreSessionIgnore? ----@alias AstroCoreTreesitterFeature boolean|string[]|(fun(lang: string, bufnr: integer): (boolean|nil)) +---@alias AstroCoreTreesitterDisable boolean|(fun(lang: string, bufnr: integer): (boolean|nil)) + +---@alias AstroCoreTreesitterFeature string[]|AstroCoreTreesitterDisable ---@class AstroCoreTreesitterTextObjectsKey ---@field query string The textobject query capture group to perform against @@ -109,6 +111,7 @@ ---@field swap AstroCoreTreesitterTextObjectsSwapOpts? Keymaps for swapping treesitter capture groups ---@class AstroCoreTreesitterOpts +---@field disabled AstroCoreTreesitterDisable? Control over the global disabling of treesitter features ---Whether or not to enable treesitter based highlighting. Can be one of the following: --- --- - A boolean to apply to all languages diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index 43179e0..e1681b2 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -17,6 +17,9 @@ local installed = {} local queries = {} local captures = {} +local enabled = {} +local indentexprs = {} + -- Configure the keymap modes for each textobject type M.textobject_modes = { select = { "x", "o" }, @@ -136,6 +139,14 @@ local function _setup() vim.api.nvim_create_autocmd("FileType", { group = vim.api.nvim_create_augroup("astrocore_treesitter", { clear = true }), callback = function(args) + local lang = vim.treesitter.language.get_lang(vim.bo[args.buf].filetype) + if not lang then return end + local disabled = config.disabled + if type(disabled) == "function" then disabled = disabled(lang, args.buf) end + if disabled then + pcall(vim.treesitter.stop, args.buf) -- force disabling treesitter for built in languages + return + end if not M.has_parser(args.match) then if config.ensure_installed == "auto" then M.install() end else @@ -188,30 +199,34 @@ function M.setup(opts) end --- Enable treesitter features in buffer ----@param bufnr integer the buffer to enable treesitter in +---@param bufnr? integer the buffer to enable treesitter in function M.enable(bufnr) + if not bufnr then bufnr = vim.api.nvim_get_current_buf() end local ft = vim.bo[bufnr].filetype local lang = vim.treesitter.language.get_lang(ft) if not M.has_parser(ft) or not lang then return end + enabled[bufnr] = true ---@param feat string ---@param query string - local function is_enabled(feat, query) - local enabled = config[feat] ---@type AstroCoreTreesitterFeature? - if type(enabled) == "table" then - enabled = vim.tbl_contains(enabled, lang) - elseif type(enabled) == "function" then - enabled = enabled(lang, bufnr) + local function feature_enabled(feat, query) + local enable = config[feat] ---@type AstroCoreTreesitterFeature? + if type(enable) == "table" then + enable = vim.tbl_contains(enable, lang) + elseif type(enable) == "function" then + enable = enable(lang, bufnr) end - return enabled and M.has_parser(ft, query) + return enable and M.has_parser(ft, query) end -- highlighting - if is_enabled("highlight", "highlights") then pcall(vim.treesitter.start, bufnr) end + if feature_enabled("highlight", "highlights") then pcall(vim.treesitter.start, bufnr) end -- indents - -- FIX: fix to only run if indenexpr is not set by a plugin - if is_enabled("indent", "indents") then vim.bo[bufnr].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" end + if feature_enabled("indent", "indents") then + indentexprs[bufnr] = vim.bo[bufnr].indentexpr + vim.bo[bufnr].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" + end -- treesitter text objects if config.textobjects and pcall(require, "nvim-treesitter-textobjects") then @@ -234,4 +249,20 @@ function M.enable(bufnr) end end +--- Disable treesitter features in buffer +---@param bufnr? integer the buffer to disable treesitter in +function M.disable(bufnr) + if not bufnr then bufnr = vim.api.nvim_get_current_buf() end + enabled[bufnr] = false + pcall(vim.treesitter.stop, bufnr) + if indentexprs[bufnr] then vim.bo[bufnr].indentexpr = indentexprs[bufnr] end +end + +--- Check if treesitter features in buffer +---@param bufnr? integer the buffer to check if treesitter is enabled for +function M.is_enabled(bufnr) + if not bufnr then bufnr = vim.api.nvim_get_current_buf() end + return enabled[bufnr] == true +end + return M From 69e43afd762ab88323c7df9213a02e8e48e82f63 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 15:37:18 -0400 Subject: [PATCH 06/16] fix(toggles): update treesitter syntax toggling --- lua/astrocore/toggles.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lua/astrocore/toggles.lua b/lua/astrocore/toggles.lua index 698ef32..4ce9561 100644 --- a/lua/astrocore/toggles.lua +++ b/lua/astrocore/toggles.lua @@ -185,14 +185,14 @@ end function M.buffer_syntax(bufnr, silent) -- HACK: this should just be `bufnr = bufnr or 0` but it looks like `vim.treesitter.stop` has a bug with `0` being current bufnr = (bufnr and bufnr ~= 0) and bufnr or vim.api.nvim_win_get_buf(0) - local ts_avail, parsers = pcall(require, "nvim-treesitter.parsers") + local treesitter = require "astrocore.treesitter" local astrolsp_avail, lsp_toggle = pcall(require, "astrolsp.toggles") if vim.bo[bufnr].syntax == "off" then - if ts_avail and parsers.has_parser() then vim.treesitter.start(bufnr) end + if treesitter.has_parser() then vim.treesitter.start(bufnr) end vim.bo[bufnr].syntax = "on" if astrolsp_avail and not vim.b[bufnr].semantic_tokens then lsp_toggle.buffer_semantic_tokens(bufnr, true) end else - if ts_avail and parsers.has_parser() then vim.treesitter.stop(bufnr) end + if treesitter.has_parser() then vim.treesitter.stop(bufnr) end vim.bo[bufnr].syntax = "off" if astrolsp_avail and vim.b[bufnr].semantic_tokens then lsp_toggle.buffer_semantic_tokens(bufnr, true) end end From 8b81198e8db01e7f4692d40cfde14b072feba484 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 16:06:34 -0400 Subject: [PATCH 07/16] fix(treesitter): filter already installed languages --- lua/astrocore/treesitter.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index e1681b2..ae329be 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -79,6 +79,7 @@ function M.install(languages, cb) elseif languages == "all" then languages = treesitter.get_available() end + languages = vim.tbl_filter(function(lang) return not M.has_parser(lang) end, languages --[[ @as string[] ]]) if next(languages --[[ @as string[] ]]) then From b820ada7fa600081e764b136e93ba67af8418618 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 16:10:20 -0400 Subject: [PATCH 08/16] fix(treesitter): separate automatic installation option --- lua/astrocore/config.lua | 3 ++- lua/astrocore/treesitter.lua | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lua/astrocore/config.lua b/lua/astrocore/config.lua index 6f915f6..39327ed 100644 --- a/lua/astrocore/config.lua +++ b/lua/astrocore/config.lua @@ -146,7 +146,8 @@ ---end ---``` ---@field indent AstroCoreTreesitterFeature? ----@field ensure_installed string[]|"all"|"auto"? a list of treesitter parsers to ensure are installed, "all" will install all parsers, "auto" will install when opening a filetype with an available parser +---@field auto_install boolean? whether or not to automatically detect and install missing treesitter parsers +---@field ensure_installed string[]|"all"? a list of treesitter parsers to ensure are installed, "all" will install all parsers, "auto" will install when opening a filetype with an available parser ---Configuration of textobject mappings to create using `nvim-treesitter-textobjects` --- ---Examples: diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index ae329be..87812a4 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -58,13 +58,13 @@ function M.available() end --- Install the provided parsers with `nvim-treesitter` ----@param languages? "auto"|"all"|string[] a list of languages to install, automatically detect the current language to install, or install all available parsers (default: "auto") +---@param languages? "all"|string[] a list of languages to install, automatically detect the current language to install, or install all available parsers (default: "auto") ---@param cb? function optional callback function to execute after installation finishes function M.install(languages, cb) local patch_func = require("astrocore").patch_func local treesitter_avail, treesitter = pcall(require, "nvim-treesitter") if not treesitter_avail then return end - if not languages or languages == "auto" then + if not languages then local bufnr = vim.api.nvim_get_current_buf() local lang = vim.treesitter.language.get_lang(vim.bo[bufnr].filetype) if M.available()[lang] then @@ -149,7 +149,7 @@ local function _setup() return end if not M.has_parser(args.match) then - if config.ensure_installed == "auto" then M.install() end + if config.auto_install then M.install() end else M.enable(args.buf) end From f904bdf95dd8e0bb68913c2a42304f359619b55e Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 16:11:22 -0400 Subject: [PATCH 09/16] fix: fix formatting --- lua/astrocore/config.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/astrocore/config.lua b/lua/astrocore/config.lua index 39327ed..91e732e 100644 --- a/lua/astrocore/config.lua +++ b/lua/astrocore/config.lua @@ -449,7 +449,7 @@ local M = { indent = true, ensure_installed = {}, textobjects = nil, - } + }, } return M From e126c77486ed2b92f2b764a7441cd4e7afde9db4 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Fri, 10 Oct 2025 16:36:26 -0400 Subject: [PATCH 10/16] refactor(treesitter): code cleanup, reuse disable function --- lua/astrocore/treesitter.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index 87812a4..aae37a0 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -145,7 +145,7 @@ local function _setup() local disabled = config.disabled if type(disabled) == "function" then disabled = disabled(lang, args.buf) end if disabled then - pcall(vim.treesitter.stop, args.buf) -- force disabling treesitter for built in languages + M.disable(args.buf) -- force disabling treesitter for built in languages return end if not M.has_parser(args.match) then From 401fed372e65ffd28a1d6f588b9b89951a8a95e9 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Sat, 11 Oct 2025 08:15:45 -0400 Subject: [PATCH 11/16] fix(treesitter): force refresh folds on loading treesitter --- lua/astrocore/treesitter.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index aae37a0..1db23bc 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -229,6 +229,9 @@ function M.enable(bufnr) vim.bo[bufnr].indentexpr = "v:lua.require'nvim-treesitter'.indentexpr()" end + -- if folds are present force update of folds after loading + if M.has_parser(ft, "folds") then vim.schedule(function() vim.cmd "normal! zx" end) end + -- treesitter text objects if config.textobjects and pcall(require, "nvim-treesitter-textobjects") then for type, methods in pairs(config.textobjects) do @@ -257,6 +260,7 @@ function M.disable(bufnr) enabled[bufnr] = false pcall(vim.treesitter.stop, bufnr) if indentexprs[bufnr] then vim.bo[bufnr].indentexpr = indentexprs[bufnr] end + vim.schedule(function() vim.cmd "normal! zx" end) end --- Check if treesitter features in buffer From d1d3adca175b20e211f4e5cc83e41913eaeb1f5d Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Mon, 13 Oct 2025 07:27:08 -0400 Subject: [PATCH 12/16] fix(treesitter): don't automatically enable treesitter if user has disabled it specifically --- lua/astrocore/treesitter.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index 1db23bc..ea66bb0 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -140,6 +140,7 @@ local function _setup() vim.api.nvim_create_autocmd("FileType", { group = vim.api.nvim_create_augroup("astrocore_treesitter", { clear = true }), callback = function(args) + if enabled[args.buf] == false then return end local lang = vim.treesitter.language.get_lang(vim.bo[args.buf].filetype) if not lang then return end local disabled = config.disabled From 3ba2ad53796a1f75d2231df34c180c53edadf5e6 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Mon, 13 Oct 2025 07:50:48 -0400 Subject: [PATCH 13/16] refactor(treesitter): improve code quality and maintainability --- README.md | 48 ++++++++++++++++++++++++++++++++++++ lua/astrocore/config.lua | 15 +++++------ lua/astrocore/treesitter.lua | 31 +++++++++-------------- 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 4e6bedb..c63669a 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,54 @@ local opts = { buftypes = {}, -- buffer types to ignore sessions }, }, + -- Configuration of treesitter features in Neovim + treesitter = { + -- Globally enable or disable treesitter features + -- can be a boolean or a function (`fun(lang: string, bufnr: integer): boolean`) + enabled = true, + -- Enable or disable treesitter based highlighting + -- can be a boolean, list of parsers, or a function (`fun(lang: string, bufnr: integer): boolean`) + highlight = true, + -- Enable or disable treesitter based indenting + -- can be a boolean, list of parsers, or a function (`fun(lang: string, bufnr: integer): boolean`) + indent = true, + -- List of treesitter parsers that should be installed automatically + -- ("all" can be used to install all available parsers) + ensure_installed = { "lua", "vim", "vimdoc" }, + -- Automatically detect missing treesitter parser and install when editing file + auto_install = false, + -- Configure treesitter based text objects + textobjects = { + select = { + select_textobject = { + ["af"] = { query = "@function.outer", desc = "around function" }, + ["if"] = { query = "@function.inner", desc = "around function" }, + }, + }, + move = { + goto_next_start = { + ["]f"] = { query = "@function.outer", desc = "Next function start" }, + }, + goto_next_end = { + ["]F"] = { query = "@function.outer", desc = "Next function end" }, + }, + goto_previous_start = { + ["[f"] = { query = "@function.outer", desc = "Previous function start" }, + }, + goto_previous_end = { + ["[F"] = { query = "@function.outer", desc = "Previous function end" }, + }, + }, + swap = { + swap_next = { + [">F"] = { query = "@function.outer", desc = "Swap next function" }, + }, + swap_previous = { + [" Date: Tue, 14 Oct 2025 09:47:38 -0400 Subject: [PATCH 14/16] docs(treesitter): improve documentation for new treesitter API --- lua/astrocore/treesitter.lua | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lua/astrocore/treesitter.lua b/lua/astrocore/treesitter.lua index c396f48..ff2b48a 100644 --- a/lua/astrocore/treesitter.lua +++ b/lua/astrocore/treesitter.lua @@ -20,7 +20,7 @@ local captures = {} local enabled = {} local indentexprs = {} --- Configure the keymap modes for each textobject type +--- Configure the keymap modes for each textobject type M.textobject_modes = { select = { "x", "o" }, swap = { "n" }, @@ -43,7 +43,8 @@ function M.installed(update) return installed end --- Get list of available treesitter parers in `nvim-treesitter` +--- Get available treesitter parers in `nvim-treesitter` +---@return table # a lookup table of available parsers function M.available() if available == nil then available = {} @@ -112,7 +113,7 @@ end --- Check if parser exists for filetype with optional query check ---@param filetype? string|integer the filetype to check or a buffer number to get the filetype of (defaults to current buffer) ---@param query? string the query type to check for support of ----@return boolean +---@return boolean # whether or not a parser is supported function M.has_parser(filetype, query) if not filetype then filetype = vim.api.nvim_get_current_buf() end if type(filetype) == "number" then filetype = vim.bo[filetype].filetype end @@ -130,6 +131,7 @@ local function _setup() vim.api.nvim_create_autocmd("FileType", { group = vim.api.nvim_create_augroup("astrocore_treesitter", { clear = true }), + desc = "Automatically detect available treesitter parsers and enable necessary features", callback = function(args) if enabled[args.buf] == false then return end local lang = vim.treesitter.language.get_lang(vim.bo[args.buf].filetype) @@ -257,6 +259,7 @@ end --- Check if treesitter features in buffer ---@param bufnr? integer the buffer to check if treesitter is enabled for +---@return boolean # whether or not treesitter is enabled in buffer function M.is_enabled(bufnr) if not bufnr then bufnr = vim.api.nvim_get_current_buf() end return enabled[bufnr] == true From cb55fde6131949dcf48b7021b63f76212b6c8192 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Wed, 18 Feb 2026 09:37:54 -0500 Subject: [PATCH 15/16] fix(buffer): require all restored files to have a filename and be listed --- lua/astrocore/buffer.lua | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/lua/astrocore/buffer.lua b/lua/astrocore/buffer.lua index 8746b56..a07fe23 100644 --- a/lua/astrocore/buffer.lua +++ b/lua/astrocore/buffer.lua @@ -77,12 +77,8 @@ end function M.is_restorable(bufnr) if not M.is_valid(bufnr) or vim.bo[bufnr].bufhidden ~= "" then return false end - if vim.bo[bufnr].buftype == "" then - -- Normal buffer, check if it listed. - if not vim.bo[bufnr].buflisted then return false end - -- Check if it has a filename. - if vim.api.nvim_buf_get_name(bufnr) == "" then return false end - end + -- Check if it has a filename. + if vim.api.nvim_buf_get_name(bufnr) == "" then return false end local session_ignore = vim.tbl_get(astro.config, "sessions", "ignore") or {} if @@ -91,7 +87,7 @@ function M.is_restorable(bufnr) then return false end - return true + return vim.bo[bufnr].buflisted end --- Check if the current buffers form a valid session From 07b6017b55d8cd3003b269bf07d87007513ec591 Mon Sep 17 00:00:00 2001 From: Micah Halter Date: Thu, 19 Feb 2026 10:51:42 -0500 Subject: [PATCH 16/16] feat: update treesitter parsers when updating plugins and packages --- lua/astrocore/init.lua | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lua/astrocore/init.lua b/lua/astrocore/init.lua index 8dddd7c..58129f8 100644 --- a/lua/astrocore/init.lua +++ b/lua/astrocore/init.lua @@ -24,9 +24,14 @@ function M.extend_tbl(default, opts) return default and vim.tbl_deep_extend("force", default, opts) or opts end ---- Sync Lazy and then update Mason +--- Sync Lazy plugins, Treesitter parsers, and Mason packages function M.update_packages() + -- plugins require("lazy").sync { wait = true } + -- parsers + local treesitter_avail, treesitter = pcall(require, "nvim-treesitter") + if treesitter_avail then treesitter.update():wait() end + -- packages if vim.fn.exists ":MasonToolsUpdate" > 0 then vim.api.nvim_create_autocmd("User", { pattern = "MasonToolsUpdateCompleted",