rainbow-delimiters.nvim

repository·master·Indexed 21 days ago

https://github.com/hiphish/rainbow-delimiters.nvim

A Neovim plugin that provides alternating syntax highlighting for delimiters such as parentheses, brackets, and tags using Tree-sitter. It supports custom strategies and queries per filetype, handles nested languages, and provides a stack-based algorithm to determine nesting levels for both sandwiching and non-sandwiching block delimiters.

Tokens
2.7K
Snippets
8
Records
17
Agent score
75%

What's inside rainbow-delimiters.nvim

  1. Handling foreign extmarks in nested languages

    master

    When using nested languages (e.g., Lua code blocks inside a Markdown file), moving text can cause 'foreign extmarks' to persist. For example, moving a line of Lua code out of a Markdown code block might leave Lua-specific rainbow delimiter highlighting active in the Markdown context.

    Solution: On every change, the plugin deletes all rainbow delimiter extmarks that do not belong to the current language context.

  2. Handling overwritten extmarks in nested languages

    master

    When moving lines in a nested language (e.g., moving a line within a C block inside a Markdown file), the changes to the parent language (Markdown) might span the entire range of the nested language (C). If the plugin simply deletes foreign extmarks in that range, it might wipe out the C highlighting entirely.

    Solution: The plugin overwrites the changes of nested languages. If changes belong to a language tree with a parent language, the plugin replaces those changes with a range that spans the entire tree for that nested language, ensuring highlighting is correctly reapplied to the whole block.

  3. What a container node is in Tree-sitter queries

    master

    In Tree-sitter queries, every query must define a @container capture in addition to @opening and @closing captures.

    While a human might see an HTML tag like <div> as a single unit, Tree-sitter sees it as a tree of nodes (e.g., start_tag, tag_name, >). The @container node (such as the element node) is used to determine nesting levels and relationships between delimiters, but the @container node itself is not highlighted.

  4. How the local highlight strategy works

    master

    The plugin determines which delimiters to highlight based on the cursor position. It does not simply highlight all parents of the current cursor position, as that would include irrelevant siblings.

    Instead, it uses the traversal order:

    1. The first match that contains the cursor is identified as the lowest node containing the cursor.
    2. Any other match is highlighted if it is either a parent of that lowest node or a descendant of that lowest node (to handle cases where the cursor is inside a nested structure).
  5. How the plugin determines nesting levels of containers

    master

    The plugin calculates the nesting level of delimiters to apply correct highlighting. It uses the order of matches returned by the Tree-sitter iter_matches method.

    There are two primary behaviors based on how delimiters are structured:

    1. Sandwiching Delimiters: For delimiters like parentheses () or begin/end blocks, the match is completed when the iterator exits the node (because the child nodes are 'sandwiched' between the delimiters).
    2. Non-Sandwiching Delimiters: For block-level delimiters like Python's def or while, the child nodes are not sandwiched between delimiters. In these cases, the match is returned upon entering the node.

    To build a consistent tree structure from these matches, the plugin uses a stack-based algorithm that ensures no ancestor is skipped, allowing it to determine absolute nesting levels even when matches are returned in a depth-first traversal.

  6. How the plugin handles non-sandwiching block delimiters

    master

    In languages like Python, block-level delimiters (e.g., def, for) often consist of a single keyword rather than a pair of symbols. This creates a mix of 'sandwiching' and 'non-sandwiching' delimiters.

    To ensure logical grouping (e.g., ensuring the head of a for loop and its body are treated as related), the plugin uses a @body capture in Tree-sitter queries. A match is considered a child of a parent if and only if the @container of the child is contained within the @body of the parent.

    Example query pattern for non-sandwiching blocks:

    (for_statement
      "for" @delimiter
      "in" @delimiter
      body: _ @body) @container
    
    (list
      "[" @delimiter
      _ @body
      "]" @delimiter) @container
  7. How highlighting definitions work

    master

    Highlighting for a language is composed of two parts:

    1. Sample file: A syntactically correct file in the target language (stored in test/highlight/samples/<lang>).
    2. Specification (spec): A Lua file that records all rainbow delimiter extmarks for a specific combination of sample file and query.

    Specs are implemented as simple Lua tables containing no logic, which allows for easy generation and reading.

  8. Record and test highlighting

    master

    Highlighting is verified by comparing current highlights in a sample file against a recorded 'spec' (a Lua file containing extmarks).

    Recording new highlights

    If you modify a sample file or a query, you must record the new state. Use the record-highlight target and provide the language as a variable:

    make record-highlight LANGUAGE=lua

    Running highlight tests

    To verify that existing highlighting remains correct after changes, run:

    make highlight-test