neovim: swap out a filetype plugin in place of tree-sitter-nix-shell for handling #!nix-shell scripts

This commit is contained in:
2025-03-16 06:38:34 +00:00
parent 9b4ff72758
commit ca6c6f7b57
3 changed files with 98 additions and 2 deletions

View File

@@ -4,7 +4,9 @@ moduleArgs@{ lib, pkgs, ... }:
let
plugins = import ./plugins.nix moduleArgs;
plugin-packages = builtins.map (p: p.plugin) plugins;
plugin-packages = builtins.filter (x: x != null) (
builtins.map (p: p.plugin or null) plugins
);
plugin-configs = lib.concatMapStrings (p:
lib.optionalString
(p ? config) (

View File

@@ -0,0 +1,78 @@
-- add to ~/.vimrc to enable file-type detection for `nix-shell` scripts,
-- e.g. files with `#!nix-shell -i python3 -p python3 -p ...` are recognized as `python` files
-- see <https://neovim.io/doc/user/lua.html#vim.filetype.add()>
vim.filetype.add {
pattern = {
['.*'] = {
function(path, bufnr)
function test_for_nix_shell_shebang(maybe_hashbang)
local bang_payload = string.match(maybe_hashbang, '^#!(.*)$')
if not bang_payload then
return false -- not a shebang
end
-- look for `nix-shell` _as its own word_ anywhere in the shebang line
for word in string.gmatch(bang_payload, "[^ ]+") do
if word == "nix-shell" then
return true
end
end
end
-- extract `$interpreter` from some `#!nix-shell -i $interpreter ...` line
function parse_nix_shell(maybe_nix_shell)
local shell_payload = string.match(maybe_nix_shell, "^#!nix%-shell(.*)$")
if not shell_payload then
return
end
local interpreters = {}
local context = nil
for word in string.gmatch(shell_payload, "[^ ]+") do
if context == "-i" then
table.insert(interpreters, word)
context = nil
elseif word == "-i" then
context = "-i"
end
-- this parser doesn't consider _all_ nix flags, and especially things like quotes, etc.
-- just keep your nix-shell lines simple...
end
return interpreters[1]
end
function filetype_from_interpreter(i)
if string.match(i, "^python") then
-- python3, python2.7, etc
return "python"
else
-- very common for interpreter name to be the same as filetype
return i
end
end
-- docs: <https://neovim.io/doc/user/api.html#nvim_buf_get_lines()>
-- nvim_buf_get_lines({buffer}, {start}, {end}, {strict_indexing})
-- `start` and `end` are inclusive
local first_few_lines = vim.api.nvim_buf_get_lines(bufnr, 0, 5, false)
local maybe_hashbang = first_few_lines[1] or ''
if not test_for_nix_shell_shebang(maybe_hashbang) then
return
end
-- search for `#!nix-shell -i $interpreter ...` anywhere in the first few lines of the file
for _, line in ipairs(first_few_lines) do
local interpreter = parse_nix_shell(line)
if interpreter then
return filetype_from_interpreter(interpreter)
end
end
end,
-- high priority, to overrule vim's native detection (which gives ft=nix to all nix-shell files)
{ priority = math.huge },
},
},
}

View File

@@ -194,7 +194,18 @@ with pkgs.vimPlugins;
# nvim-treesitter ships its own queries which may be distinct from e.g. helix.
# the queries aren't included when i ship the grammar in this manner.
# maybe check: <https://github.com/nvim-treesitter/nvim-treesitter/wiki/Extra-modules-and-plugins> ?
pkgs.tree-sitter-nix-shell
#
# however: tree-sitter for `#!nix-shell` is the WRONG APPROACH.
# - because it works via "injection"s, i don't get proper LSP integration.
# i.e. no undefined variable checks, or language-aware function completions
# upstream vim showed interest in a similar approach as mine, but w/o the tree-sitter integration:
# - <https://groups.google.com/g/vim_dev/c/c-VXsJu-EKA>
# this likely still has the same problem w.r.t. LSP integration.
# vim-nix project also has a solution:
# - <https://github.com/LnL7/vim-nix/pull/51>
# this overrides the active filetype, so likely *is* what i want.
# and i've implemented my own pure-lua .vimrc integration further below
# pkgs.tree-sitter-nix-shell
]);
type = "lua";
config = ''
@@ -228,6 +239,11 @@ with pkgs.vimPlugins;
vim.o.foldexpr = 'nvim_treesitter#foldexpr()'
'';
}
{
# detect `#!nix-shell -i $interpreter ...` files as filetype=$interpreter
type = "lua";
config = builtins.readFile ./nix_shell.lua;
}
{
# show commit which last modified text under the cursor.
# trigger with `:GitMessenger` or `<Leader>gm` (i.e. `\gm`)