Skip to content

feat(desktop): 大規模ファイル向け CodeMirror diff ビューア#5

Merged
MocA-Love merged 3 commits intomainfrom
feat/codemirror-diff-viewer
Mar 28, 2026
Merged

feat(desktop): 大規模ファイル向け CodeMirror diff ビューア#5
MocA-Love merged 3 commits intomainfrom
feat/codemirror-diff-viewer

Conversation

@MocA-Love
Copy link
Copy Markdown
Owner

@MocA-Love MocA-Love commented Mar 27, 2026

概要

既存の @pierre/diffs diff ビューアは全行を一括でDOMに出力するため、2000行以上のファイルでカクカクになる問題(~18万DOMノード)を解決。

CodeMirror 6 の @codemirror/merge 拡張を使った仮想化レンダリング対応の diff ビューアを追加。ビューポートに表示されている行だけをDOMに描画するため、15000行のファイルでもスムーズに表示できる。

仕組み

  • 合計行数(original + modified)が 2000行を超える場合、自動的に CodeMirrorDiffViewer に切り替え
  • 2000行以下は従来通り LightDiffViewer(@pierre/diffs)を使用
  • 既存の LightDiffViewer は一切変更なし
  • DiffViewerContextMenu でラップし、コンテキストメニュー機能(分割、移動、閉じる等)を維持

変更ファイル

新規 (2)

  • CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx — CodeMirror merge 拡張によるdiffビューア
  • CodeMirrorDiffViewer/index.ts — barrel export

変更 (3)

  • FileViewerContent.tsx — 行数判定の分岐を追加。大規模ファイルでも DiffViewerContextMenu で包んでコンテキストメニューを維持
  • package.json@codemirror/merge 依存追加
  • bun.lock — lockfile更新

特徴

  • 仮想化レンダリング(ビューポート分のDOMのみ)
  • 既存テーマ・フォント設定をそのまま再利用
  • 変更のない領域は自動で折りたたみ(前後3行のコンテキスト表示)
  • シンタックスハイライト対応(既存の言語サポートを再利用)
  • 読み取り専用のサイドバイサイド表示
  • scanLimit: 50000 / timeout: 5000ms で大規模ファイルの正確な差分検出
  • DiffViewerContextMenu によるコンテキストメニュー維持(分割・移動・閉じる等)

テスト

  • 2000行以下のファイルで従来の LightDiffViewer が使われることを確認
  • 2000行以上のファイル(例: 15000行のTS)でスムーズに diff 表示されることを確認
  • 差分箇所が正しくハイライトされることを確認(全体がハイライトされないこと)
  • スクロールがカクつかないことを確認
  • シンタックスハイライトが適用されることを確認
  • テーマ切り替えが反映されることを確認
  • 右クリックコンテキストメニューが動作することを確認

The existing @pierre/diffs diff viewer renders all lines at once,
causing severe lag on files with 2000+ lines (~180k+ DOM nodes).

Add CodeMirrorDiffViewer using @codemirror/merge which provides
virtualized rendering (only visible lines in DOM). Files with
>2000 total lines automatically use this viewer instead.

- Reuses existing CodeMirror theme, fonts, and syntax highlighting
- Unchanged regions are collapsed (margin: 3, minSize: 4)
- Read-only side-by-side view with line numbers
- No changes to LightDiffViewer (small files use it as before)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 27, 2026

📝 Walkthrough

Walkthrough

The pull request introduces a new CodeMirror-based diff viewer component that uses MergeView to render large diffs. When a diff exceeds 2000 lines in FileViewerContent, the application uses CodeMirrorDiffViewer instead of the existing LightDiffViewer. The new component handles language support loading, theme management, and cleanup.

Changes

Cohort / File(s) Summary
Dependencies
apps/desktop/package.json
Added @codemirror/merge dependency at version ^6.12.1 for diff visualization functionality.
New Component
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx, apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/index.ts
Introduced CodeMirrorDiffViewer component with MergeView-based diff rendering. Component initializes two editor panes (original/modified) with shared extensions, loads language support asynchronously, manages theme and font settings via compartments, and cleans up on unmount.
Integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx
Added conditional logic to render CodeMirrorDiffViewer for diffs exceeding 2000 total lines instead of using LightDiffViewer.

Sequence Diagram

sequenceDiagram
    participant FC as FileViewerContent
    participant CMDV as CodeMirrorDiffViewer
    participant LS as Language Support
    participant CM as CodeMirror/MergeView
    participant Settings as Editor Settings

    FC->>FC: Calculate totalLines from diff
    alt totalLines > 2000
        FC->>CMDV: Render with original/modified/language
        activate CMDV
        CMDV->>LS: loadLanguageSupport(language)
        activate LS
        LS-->>CMDV: Language loaded
        deactivate LS
        CMDV->>Settings: getFontSettings()
        activate Settings
        Settings-->>CMDV: Font settings
        deactivate Settings
        CMDV->>CMDV: Create compartments for language/theme
        CMDV->>CM: Initialize MergeView with panes
        activate CM
        CM->>CM: Apply base extensions + language + theme
        CM-->>CMDV: MergeView ready
        deactivate CM
        CMDV->>CMDV: Store MergeView ref
        deactivate CMDV
    else totalLines ≤ 2000
        FC->>FC: Use LightDiffViewer instead
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 A diff viewer hops into view,
With CodeMirror's merge magic so true!
For diffs that grow large and wide,
Two panes side-by-side shall reside,
With syntax and themes shining bright—
The code review feels just right! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The PR description is written in Japanese and provides comprehensive technical details about the changes, rationale, and testing considerations, but it does not follow the required English template structure. Rewrite the description following the required template with sections: Description, Related Issues, Type of Change, Testing, Screenshots (if applicable), and Additional Notes in English.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title is written in Japanese and refers to a CodeMirror diff viewer for large files, which directly aligns with the main change of adding a performance-optimized diff viewer component.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/codemirror-diff-viewer

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx (1)

27-32: viewMode currently causes expensive no-op remounts.

viewMode is in the creation effect deps (Line 105) but not used to configure MergeView, so toggling mode rebuilds the whole viewer without changing behavior.

♻️ Proposed refactor (remove no-op dependency until mode is implemented)
 interface CodeMirrorDiffViewerProps {
 	original: string;
 	modified: string;
 	language: string;
-	viewMode: DiffViewMode;
 }
 
 export function CodeMirrorDiffViewer({
 	original,
 	modified,
 	language,
-	viewMode,
 }: CodeMirrorDiffViewerProps) {
@@
-	}, [original, modified, language, viewMode]);
+	}, [original, modified, language]);

Also applies to: 105-105

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`
around lines 27 - 32, The effect that creates the CodeMirror MergeView is
currently listing the viewMode prop in its dependency array but viewMode is not
used to configure MergeView, causing unnecessary remounts when toggling it;
update the effect in CodeMirrorDiffViewer so it no longer includes viewMode in
the creation effect deps (or alternatively wire viewMode into the MergeView
configuration if you intend it to change runtime behavior) — locate the
useEffect that instantiates MergeView and remove viewMode from its dependencies
to prevent no-op rebuilds until viewMode is actually implemented.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`:
- Around line 94-99: Guard against stale async language loads by capturing the
current mergeViewRef and language (or a generation token) before calling
loadLanguageSupport(language), then when the promise resolves verify the
captured ref/token still matches the current mergeViewRef and language; if not,
bail without applying the extension. Also add rejection handling for
loadLanguageSupport(language) (try/catch or .catch) and log or ignore the error
instead of letting it throw. Apply this pattern around the loadLanguageSupport
call that updates mv.a/mv.b with langCompartmentA/B.reconfigure.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx`:
- Around line 323-335: The large-diff early return renders CodeMirrorDiffViewer
directly and omits the DiffViewerContextMenu wrapper used by the small-diff
path, which strips diff-pane actions; update the large-diff branch so that
instead of returning CodeMirrorDiffViewer alone you return it wrapped with the
same DiffViewerContextMenu component (preserving props like diffData.original,
diffData.modified, diffData.language and viewMode/diffViewMode) so the context
menu handlers (split/move/close/edit-at-location) remain available for large
diffs.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx`:
- Around line 27-32: The effect that creates the CodeMirror MergeView is
currently listing the viewMode prop in its dependency array but viewMode is not
used to configure MergeView, causing unnecessary remounts when toggling it;
update the effect in CodeMirrorDiffViewer so it no longer includes viewMode in
the creation effect deps (or alternatively wire viewMode into the MergeView
configuration if you intend it to change runtime behavior) — locate the
useEffect that instantiates MergeView and remove viewMode from its dependencies
to prevent no-op rebuilds until viewMode is actually implemented.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 65d875a8-a526-42a6-a66b-a260fd09237f

📥 Commits

Reviewing files that changed from the base of the PR and between a625f09 and c4de90e.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (4)
  • apps/desktop/package.json
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/CodeMirrorDiffViewer.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/CodeMirrorDiffViewer/index.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx

The default scanLimit (500) causes the diff algorithm to fall back
to an imprecise mode for large files, marking everything as changed.
Increase to 50000 with a 5s timeout.
@MocA-Love MocA-Love self-assigned this Mar 28, 2026
Keep context menu actions (split, move, close, edit at location)
available for large file diffs. Also conditionally skip
MarkdownSearch and DiffScrollbarDecorations for CodeMirror path
since they are @pierre/diffs specific.
@MocA-Love MocA-Love merged commit 3abd79f into main Mar 28, 2026
1 check passed
@MocA-Love MocA-Love deleted the feat/codemirror-diff-viewer branch March 29, 2026 05:05
MocA-Love added a commit that referenced this pull request Apr 23, 2026
upstream 取り込み PR #5: superset-sh#3295 + 19 procedure architecture rework
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant