Skip to content

Diff split view: syntax highlighting fails on side with few lines (tree-sitter gets fragmented code) #771

@rsbmk

Description

@rsbmk

Problem

In split diff view, syntax highlighting often fails on the side that has fewer lines (typically the "old" / removed side), while the other side with more lines highlights correctly.

Root Cause

The DiffRenderable.buildSplitView() method reconstructs two separate content strings for the left and right CodeRenderable instances:

const preLeftContent = leftLogicalLines.map((l) => l.content).join('\n')
const preRightContent = rightLogicalLines.map((l) => l.content).join('\n')

Each CodeRenderable runs tree-sitter independently on its fragment. When one side has very few lines (e.g., 3-4 removed lines), tree-sitter receives incomplete/invalid code like:

</box>

<KeybindingBar />
<GlobalKeyHandler />

This isn't valid TSX — there's a closing tag without an opening tag, standalone components without a return statement or function wrapper. Tree-sitter can't parse it correctly, so no syntax tokens are emitted and the text renders unstyled.

Meanwhile, the other side might have 30+ lines of complete JSX that tree-sitter can successfully parse.

Expected Behavior

Both sides of a split diff should have correct syntax highlighting, regardless of how many lines each side has.

Suggested Fix

Instead of passing only the diff-visible lines to tree-sitter, the DiffRenderable could:

  1. Reconstruct the full old/new file content from the diff (context lines + removed/added lines can reconstruct both versions)
  2. Run tree-sitter on the complete file content for each side
  3. Map the highlighting results back to only the diff-visible lines

This is how editors like VS Code handle diff syntax highlighting — they highlight the full file and then render only the relevant lines in the diff view.

An alternative (simpler but less complete) approach would be to pad the tree-sitter input with additional context beyond what's shown in the diff, to give the parser more syntactic structure to work with.

Environment

  • OpenTUI core: installed via npm
  • Using <diff> component in Solid.js with view="split", filetype="tsx", and a SyntaxStyle
  • Default git diff context of 3 lines (-U3)

Reproduction

Any diff where one side has significantly fewer lines than the other, especially when the removed/added code is a small fragment (like closing tags, short component declarations) without enough surrounding structure for tree-sitter to identify the language.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsolidThis relates to the solid package

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions