diff --git a/plugins/languages/treesitter/injections.scm b/plugins/languages/treesitter/injections.scm new file mode 100644 index 00000000..88233422 --- /dev/null +++ b/plugins/languages/treesitter/injections.scm @@ -0,0 +1,17 @@ +;; extends + +(binding + attrpath: (attrpath (identifier) @_path) + expression: [ + (string_expression (string_fragment) @lua) + (indented_string_expression (string_fragment) @lua) + ] + (#match? @_path "^extraConfigLua(Pre|Post)?$")) + +(binding + attrpath: (attrpath (identifier) @_path) + expression: [ + (string_expression (string_fragment) @vim) + (indented_string_expression (string_fragment) @vim) + ] + (#match? @_path "^extraConfigVim(Pre|Post)?$")) diff --git a/plugins/languages/treesitter/treesitter.nix b/plugins/languages/treesitter/treesitter.nix index 9999c2cf..75905344 100644 --- a/plugins/languages/treesitter/treesitter.nix +++ b/plugins/languages/treesitter/treesitter.nix @@ -6,210 +6,309 @@ ... }: with lib; -let - cfg = config.plugins.treesitter; -in -{ - options = { - plugins.treesitter = { - enable = mkEnableOption "tree-sitter syntax highlighting"; +helpers.neovim-plugin.mkNeovimPlugin config { + name = "treesitter"; + originalName = "nvim-treesitter"; + luaName = "nvim-treesitter.configs"; + defaultPackage = pkgs.vimPlugins.nvim-treesitter; - package = mkOption { - type = types.package; - default = pkgs.vimPlugins.nvim-treesitter; - description = "Plugin to use for nvim-treesitter. If using nixGrammars, it should include a `withPlugins` function"; - }; + maintainers = [ lib.maintainers.khaneliman ]; - nixGrammars = mkOption { - type = types.bool; - default = true; - description = "Install grammars with Nix"; - }; + # TODO introduced 2024-07-06: remove after 24.11 + optionsRenamedToSettings = [ + "ensureInstalled" + "ignoreInstall" + "parserInstallDir" + [ + "incrementalSelection" + "enable" + ] + [ + "incrementalSelection" + "keymaps" + "initSelection" + "nodeDecremental" + ] + [ + "incrementalSelection" + "keymaps" + "initSelection" + "nodeIncremental" + ] + [ + "incrementalSelection" + "keymaps" + "initSelection" + "scopeIncremental" + ] + ]; - ensureInstalled = mkOption { - type = - with types; - oneOf [ - (enum [ "all" ]) - (listOf str) - ]; - default = "all"; - description = "Either \"all\" or a list of languages"; - }; + imports = + let + basePluginPath = [ + "plugins" + "treesitter" + ]; + settingsPath = basePluginPath ++ [ "settings" ]; + in + [ + (lib.mkRenamedOptionModule (basePluginPath ++ [ "moduleConfig" ]) settingsPath) + (lib.mkRenamedOptionModule (basePluginPath ++ [ "customCaptures" ]) ( + settingsPath + ++ [ + "highlight" + "custom_captures" + ] + )) + (lib.mkRenamedOptionModule (basePluginPath ++ [ "disabledLanguages" ]) ( + settingsPath + ++ [ + "highlight" + "disable" + ] + )) + (lib.mkRenamedOptionModule (basePluginPath ++ [ "indent" ]) ( + settingsPath + ++ [ + "indent" + "enable" + ] + )) + ]; - gccPackage = helpers.mkPackageOption { - default = if cfg.nixGrammars then null else pkgs.gcc; - description = '' - Which package (if any) to be added as the GCC compiler. - This is required to build grammars if you are not using `nixGrammars`. - To disable the installation of GCC, set this option to `null`. - ''; - }; + settingsOptions = { + auto_install = helpers.defaultNullOpts.mkBool false '' + Whether to automatically install missing parsers when entering a buffer. + ''; - parserInstallDir = mkOption { - type = types.nullOr types.str; - default = if cfg.nixGrammars then null else "$XDG_DATA_HOME/nvim/treesitter"; - description = '' - Location of the parsers to be installed by the plugin (only needed when nixGrammars is disabled). - This default might not work on your own install, please make sure that $XDG_DATA_HOME is set if you want to use the default. Otherwise, change it to something that will work for you! - ''; - }; + highlight = { + additional_vim_regex_highlighting = + helpers.defaultNullOpts.mkNullableWithRaw + (with helpers.nixvimTypes; either bool (listOf (maybeRaw str))) + false + '' + Setting this to true will run `syntax` and tree-sitter at the same time. \ + Set this to `true` if you depend on 'syntax' being enabled (e.g. for indentation). \ + See `:h syntax`. - ignoreInstall = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "List of parsers to ignore installing (for \"all\")"; - }; + Using this option may slow down your editor, and you may see some duplicate highlights. \ + Instead of true, it can also be a list of languages. + ''; - disabledLanguages = mkOption { - type = types.listOf types.str; - default = [ ]; - description = "A list of languages to disable"; - }; + enable = helpers.defaultNullOpts.mkBool false '' + Whether to enable treesitter highlighting. + ''; - customCaptures = mkOption { - type = types.attrsOf types.str; - default = { }; - description = "Custom capture group highlighting"; - }; + disable = + helpers.defaultNullOpts.mkStrLuaFnOr (with helpers.nixvimTypes; listOf (maybeRaw str)) null + '' + Can either be a list of the names of parsers you wish to disable or + a lua function that returns a boolean indicating the parser should be disabled. + ''; - incrementalSelection = + custom_captures = helpers.defaultNullOpts.mkAttrsOf types.str { } '' + Custom capture group highlighting. + ''; + }; + + incremental_selection = { + enable = helpers.defaultNullOpts.mkBool false '' + Incremental selection based on the named nodes from the grammar. + ''; + + keymaps = let - keymap = - default: - mkOption { - type = types.str; - inherit default; - description = "Key shortcut"; - }; + mkKeymap = default: helpers.defaultNullOpts.mkStr default "Key shortcut"; in { - enable = mkEnableOption "incremental selection based on the named nodes from the grammar"; - keymaps = { - initSelection = keymap "gnn"; - nodeIncremental = keymap "grn"; - scopeIncremental = keymap "grc"; - nodeDecremental = keymap "grm"; - }; + init_selection = mkKeymap "gnn"; + node_incremental = mkKeymap "grn"; + scope_incremental = mkKeymap "grc"; + node_decremental = mkKeymap "grm"; }; + }; - indent = mkEnableOption "tree-sitter based indentation"; + indent = { + enable = helpers.defaultNullOpts.mkBool false '' + Whether to enable treesitter indentation. + ''; + }; - folding = mkEnableOption "tree-sitter based folding"; + ensure_installed = helpers.defaultNullOpts.mkListOf types.str [ ] '' + Either "all" or a list of languages to ensure installing. + ''; - languageRegister = mkOption { - type = with types; attrsOf (either str (listOf str)); - description = '' - This is a wrapping of the `vim.treesitter.language.register` function. - Register specific parsers to one or several filetypes. - The keys are the parser names and the values are either one or several filetypes. - ''; - default = { }; - example = { - cpp = "onelab"; - python = [ - "myFiletype" - "anotherFiletype" - ]; - }; + ignore_install = helpers.defaultNullOpts.mkListOf types.str [ ] '' + List of parsers to ignore installing. Used when `ensure_installed` is set to "all". + ''; + + parser_install_dir = helpers.defaultNullOpts.mkStr "$XDG_DATA_HOME/nvim/treesitter" '' + Location of the parsers to be installed by the plugin (only needed when `nixGrammars` is disabled). + + This default might not work on your own install, please make sure that `$XDG_DATA_HOME` is set if you want to use the default. + Otherwise, change it to something that will work for you! + ''; + + sync_install = helpers.defaultNullOpts.mkBool false '' + Install parsers synchronously (only applied to `ensure_installed`). + ''; + }; + + settingsExample = { + additional_vim_regex_highlighting = true; + ensure_installed = true; + ignore_install = [ "rust" ]; + indent = true; + parser_install_dir = "$XDG_DATA_HOME/nvim/treesitter"; + + highlight = { + enable = true; + disable = [ "rust" ]; + custom_captures = { }; + }; + + incremental_selection = { + enable = true; + + keymaps = { + init_selection = "gnn"; + node_incremental = "grn"; + scope_incremental = "grc"; + node_decremental = "grm"; }; - - grammarPackages = mkOption { - type = with types; listOf package; - default = cfg.package.passthru.allGrammars; - description = "Grammar packages to install"; - }; - - moduleConfig = mkOption { - type = types.attrsOf types.anything; - default = { }; - description = "This is the configuration for extra modules. It should not be used directly"; - }; - - nixvimInjections = mkEnableOption "nixvim specific injections, like lua highlighting in extraConfigLua"; }; }; - config = - let - tsOptions = { - highlight = { - inherit (cfg) enable; - disable = if (cfg.disabledLanguages != [ ]) then cfg.disabledLanguages else null; + extraOptions = { + folding = mkOption { + type = types.bool; + default = true; + example = false; + description = "Whether to enable treesitter folding."; + }; - custom_captures = if (cfg.customCaptures != { }) then cfg.customCaptures else null; - }; + gccPackage = helpers.mkPackageOption { + name = "gcc"; + default = pkgs.gcc; + defaultText = literalExpression "pkgs.gcc"; + example = literalExpression "pkgs.gcc14"; + description = '' + Which package (if any) to be added as the GCC compiler. - incremental_selection = - if cfg.incrementalSelection.enable then - { - enable = true; - keymaps = { - init_selection = cfg.incrementalSelection.keymaps.initSelection; - node_incremental = cfg.incrementalSelection.keymaps.nodeIncremental; - scope_incremental = cfg.incrementalSelection.keymaps.scopeIncremental; - node_decremental = cfg.incrementalSelection.keymaps.nodeDecremental; - }; - } - else - null; + This is required to build grammars if you are not using `nixGrammars`. + To disable the installation of GCC, set this option to `null`. + ''; + }; - indent = if cfg.indent then { enable = true; } else null; + grammarPackages = mkOption { + type = with types; listOf package; + default = config.plugins.treesitter.package.passthru.allGrammars; + example = literalExpression "pkgs.vimPlugins.nvim-treesitter.passthru.allGrammars"; + defaultText = literalExpression "config.plugins.treesitter.package.passthru.allGrammars"; + description = "Grammar packages to install"; + }; - ensure_installed = if cfg.nixGrammars then [ ] else cfg.ensureInstalled; - ignore_install = cfg.ignoreInstall; - parser_install_dir = cfg.parserInstallDir; - } // cfg.moduleConfig; - in - mkIf cfg.enable { - extraConfigLua = - (optionalString (cfg.parserInstallDir != null) '' - vim.opt.runtimepath:append("${cfg.parserInstallDir}") - '') - + '' - require('nvim-treesitter.configs').setup(${helpers.toLuaObject tsOptions}) - '' - + (optionalString (cfg.languageRegister != { }) '' - __parserFiletypeMappings = ${helpers.toLuaObject cfg.languageRegister} + # TODO: Implement rawLua support to be passed into extraConfigLua. + languageRegister = mkOption { + type = with types; attrsOf (coercedTo str toList (listOf str)); + default = { }; + example = { + cpp = "onelab"; + python = [ + "foo" + "bar" + ]; + }; + description = '' + This is a wrapping of the `vim.treesitter.language.register` function. + + Register specific parsers to one or several filetypes. + + The keys are the parser names and the values are either one or several filetypes. + ''; + }; + + nixGrammars = mkOption { + type = types.bool; + default = true; + example = false; + description = "Whether to install grammars defined in `grammarPackages`."; + }; + + nixvimInjections = mkOption { + type = types.bool; + default = true; + example = false; + description = "Whether to enable Nixvim injections, e.g. highlighting `extraConfigLua` as lua."; + }; + + nodejsPackage = helpers.mkPackageOption { + name = "nodejs"; + default = pkgs.nodejs; + defaultText = literalExpression "pkgs.nodejs"; + example = literalExpression "pkgs.nodejs_22"; + description = '' + Which package (if any) to be added as the nodejs package. + + This is required to build grammars if you are not using `nixGrammars`. + To disable the installation of NodeJS, set this option to `null`. + ''; + }; + + treesitterPackage = helpers.mkPackageOption { + name = "tree-sitter"; + default = pkgs.tree-sitter; + defaultText = literalExpression "pkgs.tree-sitter"; + description = '' + Which package (if any) to be added as the tree-sitter binary. + + This is required to build grammars if you are not using `nixGrammars`. + To disable the installation of tree-sitter, set this option to `null`. + ''; + }; + }; + + # NOTE: We call setup manually below. + callSetup = false; + # NOTE: We install cfg.package manually so we can install grammars using it. + installPackage = false; + + extraConfig = cfg: { + extraConfigLua = + # NOTE: Upstream state that the parser MUST be at the beginning of runtimepath. + # Otherwise the parsers from Neovim takes precedent, which may be incompatible with some queries. + (optionalString (cfg.settings.parser_install_dir != null) '' + vim.opt.runtimepath:prepend("${cfg.settings.parser_install_dir}") + '') + + '' + require('nvim-treesitter.configs').setup(${helpers.toLuaObject cfg.settings}) + '' + + (optionalString (cfg.languageRegister != { }) '' + do + local __parserFiletypeMappings = ${helpers.toLuaObject cfg.languageRegister} for parser_name, ft in pairs(__parserFiletypeMappings) do require('vim.treesitter.language').register(parser_name, ft) end - ''); + end + ''); - extraFiles = mkIf cfg.nixvimInjections { - "queries/nix/injections.scm".text = '' - ;; extends + extraFiles = mkIf cfg.nixvimInjections { "queries/nix/injections.scm".source = ./injections.scm; }; - (binding - attrpath: (attrpath (identifier) @_path) - expression: [ - (string_expression (string_fragment) @lua) - (indented_string_expression (string_fragment) @lua) - ] - (#match? @_path "^extraConfigLua(Pre|Post)?$")) + extraPlugins = mkIf (cfg.package != null) [ + (mkIf cfg.nixGrammars (cfg.package.withPlugins (_: cfg.grammarPackages))) + (mkIf (!cfg.nixGrammars) cfg.package) + ]; - (binding - attrpath: (attrpath (identifier) @_path) - expression: [ - (string_expression (string_fragment) @vim) - (indented_string_expression (string_fragment) @vim) - ] - (#match? @_path "^extraConfigVim(Pre|Post)?$")) - ''; - }; + extraPackages = [ + cfg.gccPackage + cfg.nodejsPackage + cfg.treesitterPackage + ]; - extraPlugins = - if cfg.nixGrammars then [ (cfg.package.withPlugins (_: cfg.grammarPackages)) ] else [ cfg.package ]; - extraPackages = with pkgs; [ - tree-sitter - nodejs - cfg.gccPackage - ]; - - opts = mkIf cfg.folding { - foldmethod = "expr"; - foldexpr = "nvim_treesitter#foldexpr()"; - }; - }; + opts = mkIf cfg.folding (mkDefault { + foldmethod = "expr"; + foldexpr = "nvim_treesitter#foldexpr()"; + }); + }; } diff --git a/tests/test-sources/plugins/languages/treesitter/treesitter.nix b/tests/test-sources/plugins/languages/treesitter/treesitter.nix index 0a8c3dde..11ea7c05 100644 --- a/tests/test-sources/plugins/languages/treesitter/treesitter.nix +++ b/tests/test-sources/plugins/languages/treesitter/treesitter.nix @@ -1,18 +1,70 @@ +{ pkgs, ... }: { + default = { + plugins.treesitter = { + enable = true; + + settings = { + auto_install = false; + ensure_installed = [ ]; + ignore_install = [ ]; + parser_install_dir = null; + sync_install = false; + + highlight = { + additional_vim_regex_highlighting = false; + enable = false; + custom_captures = { }; + disable = null; + }; + + incremental_selection = { + enable = false; + keymaps = { + init_selection = "gnn"; + node_incremental = "grn"; + scope_incremental = "grc"; + node_decremental = "grm"; + }; + }; + + indent = { + enable = false; + }; + }; + }; + }; + empty = { plugins.treesitter.enable = true; }; - nonix = { - # TODO: See if we can build parsers (legacy way) - tests.dontRun = true; + empty-grammar-packages = { plugins.treesitter = { enable = true; - nixGrammars = false; + + grammarPackages = [ ]; }; }; - nixvimInjections = { + highlight-disable-function = { + plugins.treesitter = { + enable = true; + + settings = { + highlight = { + enable = true; + disable = '' + function(lang, bufnr) + return api.nvim_buf_line_count(bufnr) > 50000 + end + ''; + }; + }; + }; + }; + + nixvim-injections = { plugins.treesitter = { enable = true; nixvimInjections = true; @@ -27,18 +79,43 @@ }; }; - # This needs a custom input - # custom = { - # plugins.treesitter = { - # enable = true; - # nixGrammars = true; - # grammarPackages = [ - # (build-ts.lib.buildGrammar pkgs { - # language = "gleam"; - # version = "0.25.0"; - # source = gleam; - # }) - # ]; - # }; - # }; + no-nix = { + # TODO: See if we can build parsers (legacy way) + tests.dontRun = true; + plugins.treesitter = { + enable = true; + nixGrammars = false; + }; + }; + + specific-grammars = { + plugins.treesitter = { + enable = true; + + grammarPackages = with pkgs.vimPlugins.nvim-treesitter.builtGrammars; [ + bash + git_config + git_rebase + gitattributes + gitcommit + gitignore + json + jsonc + lua + make + markdown + meson + ninja + nix + readline + regex + ssh-config + toml + vim + vimdoc + xml + yaml + ]; + }; + }; }