Skip to content

Syntax highlighting lost after unmount/remount of DiffView #63

@voglster

Description

@voglster

Bug Description

Syntax highlighting is lost when a DiffView component is unmounted and then remounted with the same file content. The first render shows correct syntax highlighting, but subsequent renders show plain text without any highlight classes.

Steps to Reproduce

  1. Render a DiffView with diffViewHighlight enabled and a registerHighlighter (e.g., lowlight)
  2. Unmount the component (e.g., navigate away)
  3. Remount the component with the same diff data
  4. Expected: Syntax highlighting appears as on the first render
  5. Actual: No syntax highlighting — all code is plain text

Root Cause

The issue is in the syntax initialization effect across all framework packages (React, Vue, Svelte, Solid).

When DiffView remounts, the following sequence occurs:

  1. useMemo creates a new DiffFile instance
  2. initRaw() calls #doFile()getFile(), which returns a cached File from the global _cacheMap. This cached File carries highlighterName and highlighterType from the first render.
  3. initRaw()#syncSyntax() copies these values to the new DiffFile
  4. The syntax effect checks:
    if (
      registerHighlighter.name !== diffFile._getHighlighterName() ||
      registerHighlighter.type !== diffFile._getHighlighterType() ||
      registerHighlighter.type !== "class"
    )
  5. Since #syncSyntax already copied "lowlight" and "class" from the cached File, all three conditions are false
  6. initSyntax() is never called, leaving #newFileSyntaxLines and #oldFileSyntaxLines as null
  7. The diff renders with diff-line-content-raw (plain text) instead of diff-line-syntax-raw (highlighted)

DOM Evidence

First render (working):

<span class="diff-line-syntax-raw">
  <span class="hljs-keyword">class</span>
  <span class="hljs-title class_">CherryPickInput</span>
  ...
</span>

Second render (broken):

<span class="diff-line-content-raw">
  <span data-template="true">class CherryPickInput(BaseModel):</span>
</span>

Suggested Fix

The effect's guard condition is too aggressive. initSyntax() is already idempotent — when syntax is already initialized with a matching highlighter, it efficiently re-syncs syntax line references without recomputing. The effect should always call initSyntax() when highlighting is enabled, rather than trying to skip it based on highlighter metadata that may have been inherited from the global File cache.

Affected files:

  • packages/react/src/components/DiffView.tsx
  • packages/vue/src/components/DiffView.tsx
  • packages/svelte/src/lib/components/DiffView.svelte
  • packages/solid/src/components/DiffView.tsx

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions