direnv.nvim/lua/direnv/integrations/treesitter.lua
NotAShelf 691bcef684
direnv: init and populate 'integrations' namespace; add TS highlighting
Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I78cd545cb9c3b85d7405e9f4723f15066a6a6964
2025-10-22 13:18:57 +03:00

259 lines
No EOL
6.4 KiB
Lua

local M = {}
-- Direnv keyword groups based on syntax.vim
local direnv_keywords = {
-- Command functions (takes CLI command argument)
command_funcs = {
"has",
},
-- Path functions (takes file/dir path argument)
path_funcs = {
"dotenv",
"dotenv_if_exists",
"env_vars_required",
"fetchurl",
"join_args",
"user_rel_path",
"on_git_branch",
"find_up",
"has",
"source_env",
"source_env_if_exists",
"source_up",
"source_up_if_exists",
"source_url",
"PATH_add",
"MANPATH_add",
"load_prefix",
"watch_file",
"watch_dir",
"semver_search",
"strict_env",
"unstrict_env",
},
-- Expand path functions
expand_path_funcs = {
"expand_path",
},
-- Path add functions (takes variable name and dir path)
path_add_funcs = {
"PATH_add",
"MANPATH_add",
"PATH_rm",
"path_rm",
"path_add",
},
-- Use functions
use_funcs = {
"use",
"use_flake",
"use_guix",
"use_julia",
"use_nix",
"use_node",
"use_nodenv",
"use_rbenv",
"use_vim",
"rvm",
},
-- Layout functions
layout_funcs = {
"layout",
"layout_anaconda",
"layout_go",
"layout_julia",
"layout_node",
"layout_perl",
"layout_php",
"layout_pipenv",
"layout_pyenv",
"layout_python",
"layout_python2",
"layout_python3",
"layout_ruby",
},
-- Layout languages
layout_languages = {
"go",
"node",
"perl",
"python3",
"ruby",
},
-- Layout language paths
layout_language_paths = {
"python",
},
-- Other functions
other_funcs = {
"direnv_apply_dump",
"direnv_layout_dir",
"direnv_load",
"direnv_version",
"log_error",
"log_status",
},
}
-- Check if Treesitter is available
local function has_treesitter()
return vim.fn.has("nvim-0.8.0") == 1 and pcall(require, "nvim-treesitter")
end
-- Check if bash parser is available
local function has_bash_parser()
local ok, parsers = pcall(require, "nvim-treesitter.parsers")
return ok and parsers.has_parser("bash")
end
-- Setup Treesitter highlighting for direnv
M.setup = function(config)
if not config.integrations.treesitter.enabled then
return
end
if not has_treesitter() then
return
end
if not has_bash_parser() then
return
end
-- Create highlight groups
local highlights = {
-- Command functions
["@direnv.command_func"] = { link = "Function" },
["@direnv.command"] = { link = "Identifier" },
-- Path functions
["@direnv.path_func"] = { link = "Function" },
["@direnv.path"] = { link = "Directory" },
["@direnv.expand_path_func"] = { link = "Function" },
["@direnv.expand_path_rel"] = { link = "Directory" },
["@direnv.path_add_func"] = { link = "Function" },
["@direnv.var"] = { link = "Identifier" },
-- Use functions
["@direnv.use_func"] = { link = "Function" },
["@direnv.use_command"] = { link = "Identifier" },
-- Layout functions
["@direnv.layout_func"] = { link = "Function" },
["@direnv.layout_language"] = { link = "Identifier" },
["@direnv.layout_language_path"] = { link = "Identifier" },
-- Other functions
["@direnv.func"] = { link = "Function" },
}
for group, hl in pairs(highlights) do
vim.api.nvim_set_hl(0, group, hl)
end
-- Setup autocmd for .envrc files
local group = vim.api.nvim_create_augroup("DirenvTreesitter", { clear = true })
vim.api.nvim_create_autocmd({ "BufRead", "BufNewFile" }, {
group = group,
pattern = ".envrc",
callback = function()
M.setup_buffer()
end,
})
end
-- Setup Treesitter highlighting for current buffer
M.setup_buffer = function()
if not has_treesitter() or not has_bash_parser() then
return
end
local bufnr = vim.api.nvim_get_current_buf()
local ft = vim.bo[bufnr].filetype
-- Only apply to .envrc files or bash files
if ft ~= "sh" and ft ~= "bash" and not vim.fn.expand("%:t"):match("%.envrc$") then
return
end
-- Set filetype to bash if it's .envrc
if vim.fn.expand("%:t"):match("%.envrc$") then
vim.bo[bufnr].filetype = "bash"
end
-- Use the proper Treesitter API for highlighting
local ok, ts = pcall(require, "vim.treesitter")
if not ok then
return
end
-- Create queries for direnv keywords
local query = M.create_queries()
-- Add the queries to the buffer
ts.query.set("bash", "highlights", query)
-- Force Treesitter to re-parse and re-highlight the buffer
vim.schedule(function()
local parser = ts.get_parser(bufnr, "bash")
if parser then
parser:parse()
end
end)
end
-- Create Treesitter queries for direnv keywords
M.create_queries = function()
local patterns = {}
-- Create query patterns for each keyword group
local function create_pattern(keywords, capture_name)
for _, keyword in ipairs(keywords) do
table.insert(patterns, string.format(
'((call_expression function: (identifier) @%s) (#eq? @%s "%s"))',
capture_name, capture_name, keyword
))
end
end
-- Add patterns for all keyword groups
create_pattern(direnv_keywords.command_funcs, "direnv.command_func")
create_pattern(direnv_keywords.path_funcs, "direnv.path_func")
create_pattern(direnv_keywords.expand_path_funcs, "direnv.expand_path_func")
create_pattern(direnv_keywords.path_add_funcs, "direnv.path_add_func")
create_pattern(direnv_keywords.use_funcs, "direnv.use_func")
create_pattern(direnv_keywords.layout_funcs, "direnv.layout_func")
create_pattern(direnv_keywords.other_funcs, "direnv.func")
-- Layout languages
create_pattern(direnv_keywords.layout_languages, "direnv.layout_language")
create_pattern(direnv_keywords.layout_language_paths, "direnv.layout_language_path")
return table.concat(patterns, "\n")
end
-- Get all direnv keywords for completion
M.get_keywords = function()
local all_keywords = {}
for _, group in pairs(direnv_keywords) do
if type(group) == "table" then
for _, keyword in ipairs(group) do
table.insert(all_keywords, keyword)
end
end
end
return all_keywords
end
return M