DEV Community

taku25
taku25

Posted on

Custom Parser Highlighting Not Working in nvim-treesitter? The Cause and a Solution Using the `after/` Directory

Title: Custom Parser Highlighting Not Working in nvim-treesitter? The Cause and a Solution Using the after/ Directory

custom tree-sitter-unreal-cpp


TL;DR

If highlights for your custom tree-sitter parser aren't applying in Neovim, it's likely because nvim-treesitter isn't loading the highlight queries from your repository. You can solve this by placing your highlight definitions in an after/queries/{filetype}/ directory and loading the parser repository itself as a plugin.


Introduction

To make developing for Unreal Engine in Neovim more comfortable, I created a custom tree-sitter parser, tree-sitter-unreal-cpp, to support UE-specific macros like UCLASS and UPROPERTY.

Creating and testing the parser went smoothly, but when I tried to integrate it with nvim-treesitter, I ran into a problem where the syntax highlighting wouldn't apply at all. In this article, I'll detail the process of figuring out the cause and the final solution.


The Problem: The Parser Works, but Highlights Don't Apply

First, using lazy.nvim, I configured nvim-treesitter to override the default cpp parser with my custom one.

  {
    "nvim-treesitter/nvim-treesitter",
    -- ...
    config = function(_,opts)
      local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
      -- Override the cpp parser config with my custom parser
      parser_config.cpp = {
        install_info = {
          url = "https://github.com/taku25/tree-sitter-unreal-cpp",
          files = {"src/parser.c", "src/scanner.c"},
          branch = "master",
        },
        filetype = "cpp",
      }
      require("nvim-treesitter.configs").setup(opts)
    end,
  },
Enter fullscreen mode Exit fullscreen mode

After this setup, running :TSPlaygroundToggle confirmed that custom nodes like uclass_macro were being parsed correctly. However, no highlights were applied in the editor. Checking with :TSCaptureUnderCursor also showed no highlight groups being detected, which made it difficult to diagnose the problem.


The Cause: nvim-treesitter's Query Loading Specification

After some investigation, I discovered that the problem was caused by a specific behavior of nvim-treesitter.

While nvim-treesitter will build and use a parser from a specified URL in install_info, it does not look for query files (like .scm for highlights) inside your custom repository. Instead, it always uses the query files bundled within its own queries/{filetype}/ directory.

In other words, the queries/highlights.scm file in my tree-sitter-unreal-cpp repository was being completely ignored by nvim-treesitter. That was the root cause of the issue.


The Solution: Extending Queries Using the after Directory

The solution to this behavior is to use Neovim's after directory mechanism. Files placed in an after directory are loaded after the standard configuration files.

This allows us to extend the default cpp queries rather than trying to replace them.

Step 1: Create Query Files in an after Directory Structure

Inside your custom parser repository, create a file at the path after/queries/cpp/highlights.scm. In this file, you should only include the highlight definitions for the new syntax you want to add, such as Unreal Engine macros.

;; extends

; Unreal Engine macros
(uclass_macro "UCLASS" @attribute)
(uproperty_macro "UPROPERTY" @attribute)
; ... other UE-specific definitions
Enter fullscreen mode Exit fullscreen mode

Step 2: Load the Parser Repository as a Plugin

To make Neovim aware of this new after directory, you need to load the tree-sitter-unreal-cpp repository as a standalone plugin in lazy.nvim.

return {
  -- Add your custom parser repository as a plugin
  -- This will add its `after` directory to Neovim's runtime path
  {
    "taku25/tree-sitter-unreal-cpp",
  },

  -- Your existing nvim-treesitter configuration
  {
    "nvim-treesitter/nvim-treesitter",
    -- ...
    config = function(_,opts)
      -- The parser override is still necessary
      local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
      parser_config.cpp = {
        install_info = {
          url = "https://github.com/taku25/tree-sitter-unreal-cpp",
          files = {"src/parser.c", "src/scanner.c"},
          branch = "master",
        },
        filetype = "cpp",
      }
      require("nvim-treesitter.configs").setup(opts)
    end,
  },
}
Enter fullscreen mode Exit fullscreen mode

With this setup, nvim-treesitter first loads the standard C++ highlights, and then our plugin's after/queries/cpp/highlights.scm is loaded, achieving the exact highlighting we want.


Conclusion

If you're facing an issue where highlights aren't applying for your custom tree-sitter parser, the cause is often nvim-treesitter's query loading behavior.

The key to solving this is to place your extension queries in an after/queries/{filetype}/ directory and load your parser repository as a plugin in Neovim. This approach allows you to safely add functionality without breaking existing highlight definitions.

I hope this article helps other developers who run into the same problem.


Have you ever run into similar issues with nvim-treesitter's behavior? If you know of a better approach, please share it in the comments!

P.S. This article was created with the help of Gemini 2.5 Pro (deep research) and GitHub Copilot.

Top comments (0)