Skip to content

Syntax highlighting lost after unmount/remount of DiffView #63

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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