Files
orca/src/renderer/src/components/editor/editor-panel-render-model.ts
T
Jinjing 008ad62d3f Add HTML preview-to-side support for single unstaged/staged diffs (#8018)
- Move the preview button before the single-diff tooltip in the header
- Extend canOpenPreviewToSide to allow single diffs (not commit diffs)
  when the modified file still exists on disk, since the preview
  renders the working-tree file rather than diff content
- Add tests covering HTML edit tabs, unstaged diffs, deleted files,
  commit diffs, and non-HTML diffs
2026-07-09 21:57:38 -07:00

178 lines
6.9 KiB
TypeScript

import { detectLanguage } from '@/lib/language-detect'
import { canPreviewLanguage } from '@/lib/file-preview'
import type { useAppStore } from '@/store'
import type { MarkdownViewMode, OpenFile } from '@/store/slices/editor'
import {
canOpenMarkdownPreview,
getDefaultMarkdownViewMode,
getEditorToggleModes,
getMarkdownViewModes
} from './markdown-preview-controls'
import { getEditorHeaderOpenFileState } from './editor-header'
import type { EditorToggleValue } from './EditorViewToggle'
import type { FileContent } from './editor-panel-content-types'
import { canUseChangesModeForFile } from './editor-panel-file-mode'
import { getMarkdownRenderMode } from './markdown-render-mode'
import { getMarkdownRichModeUnsupportedMessage } from './markdown-rich-mode'
import { exceedsMarkdownRichModeSizeLimit } from './markdown-rich-size-limit'
type StoreState = ReturnType<typeof useAppStore.getState>
type EditorPanelRenderModelParams = {
activeFile: OpenFile
fileContents: Record<string, FileContent>
editorDrafts: StoreState['editorDrafts']
gitStatusByWorktree: StoreState['gitStatusByWorktree']
gitBranchChangesByWorktree: StoreState['gitBranchChangesByWorktree']
markdownViewMode: StoreState['markdownViewMode']
isChangesMode: boolean
}
export function getEditorPanelRenderModel({
activeFile,
fileContents,
editorDrafts,
gitStatusByWorktree,
gitBranchChangesByWorktree,
markdownViewMode,
isChangesMode
}: EditorPanelRenderModelParams) {
const isSingleDiff =
activeFile.mode === 'diff' &&
activeFile.diffSource !== undefined &&
activeFile.diffSource !== 'combined-all' &&
activeFile.diffSource !== 'combined-uncommitted' &&
activeFile.diffSource !== 'combined-branch' &&
activeFile.diffSource !== 'combined-commit'
const isCombinedDiff =
activeFile.mode === 'diff' &&
(activeFile.diffSource === 'combined-all' ||
activeFile.diffSource === 'combined-uncommitted' ||
activeFile.diffSource === 'combined-branch' ||
activeFile.diffSource === 'combined-commit')
const resolvedLanguage =
activeFile.mode === 'diff'
? detectLanguage(activeFile.relativePath)
: detectLanguage(activeFile.filePath)
const worktreeEntries = gitStatusByWorktree[activeFile.worktreeId] ?? []
const branchEntries = gitBranchChangesByWorktree[activeFile.worktreeId] ?? []
const matchingWorktreeEntry =
activeFile.mode === 'diff' &&
(activeFile.diffSource === 'staged' || activeFile.diffSource === 'unstaged')
? (worktreeEntries.find(
(entry) =>
entry.path === activeFile.relativePath &&
(activeFile.diffSource === 'staged'
? entry.area === 'staged'
: entry.area === 'unstaged')
) ?? null)
: null
const matchingBranchEntry =
activeFile.mode === 'diff' && activeFile.diffSource === 'branch'
? (branchEntries.find((entry) => entry.path === activeFile.relativePath) ?? null)
: null
const openFileState = getEditorHeaderOpenFileState(
activeFile,
matchingWorktreeEntry,
matchingBranchEntry
)
const markdownViewModes = getMarkdownViewModes({
language: resolvedLanguage,
mode: activeFile.mode,
diffSource: activeFile.diffSource
})
const hasViewModeToggle = markdownViewModes.length > 0
const defaultMarkdownViewMode = getDefaultMarkdownViewMode({
language: resolvedLanguage,
mode: activeFile.mode,
diffSource: activeFile.diffSource
})
const storedMarkdownViewMode = markdownViewMode[activeFile.id]
const mdViewMode: MarkdownViewMode =
hasViewModeToggle &&
storedMarkdownViewMode !== undefined &&
markdownViewModes.includes(storedMarkdownViewMode)
? storedMarkdownViewMode
: defaultMarkdownViewMode
const editorToggleModes = getEditorToggleModes({
language: resolvedLanguage,
mode: activeFile.mode,
diffSource: activeFile.diffSource
})
const isBinaryEditSurface =
activeFile.mode === 'edit' && fileContents[activeFile.id]?.isBinary === true
const availableEditorToggleModes =
isBinaryEditSurface || !canUseChangesModeForFile(activeFile)
? editorToggleModes.filter((mode) => mode !== 'changes')
: editorToggleModes
const effectiveToggleValue: EditorToggleValue = isChangesMode
? 'changes'
: hasViewModeToggle
? mdViewMode
: 'edit'
const inlineMarkdownContent =
activeFile.mode === 'edit'
? (editorDrafts[activeFile.id] ?? fileContents[activeFile.id]?.content ?? null)
: null
const shouldShowMarkdownExportAction =
resolvedLanguage === 'markdown' &&
(activeFile.mode === 'edit' || activeFile.mode === 'markdown-preview')
const inlineMarkdownRenderMode =
activeFile.mode === 'edit' && inlineMarkdownContent !== null
? getMarkdownRenderMode({
exceedsRichModeSizeLimit: exceedsMarkdownRichModeSizeLimit(inlineMarkdownContent),
hasRichModeUnsupportedContent:
getMarkdownRichModeUnsupportedMessage(inlineMarkdownContent) !== null,
viewMode: mdViewMode
})
: null
const canExportMarkdownToPdf =
shouldShowMarkdownExportAction &&
((activeFile.mode === 'markdown-preview' &&
fileContents[activeFile.id] !== undefined &&
fileContents[activeFile.id]?.isBinary !== true &&
!fileContents[activeFile.id]?.loadError) ||
(activeFile.mode === 'edit' &&
fileContents[activeFile.id] !== undefined &&
!isChangesMode &&
inlineMarkdownRenderMode !== null &&
inlineMarkdownRenderMode !== 'source' &&
fileContents[activeFile.id]?.isBinary !== true &&
!fileContents[activeFile.id]?.loadError &&
activeFile.conflict?.conflictStatus !== 'unresolved'))
return {
isSingleDiff,
isDiffSurface: isSingleDiff || isChangesMode,
isCombinedDiff,
worktreeEntries,
resolvedLanguage,
openFileState,
isMarkdown: resolvedLanguage === 'markdown',
isMermaid: resolvedLanguage === 'mermaid',
isCsv: resolvedLanguage === 'csv' || resolvedLanguage === 'tsv',
isNotebook: resolvedLanguage === 'notebook',
// Why: the preview renders the on-disk file, so diff surfaces only get it
// when the modified side still exists on disk (canOpen excludes deleted
// files and commit diffs whose content may not match the working tree).
canOpenPreviewToSide:
canPreviewLanguage(resolvedLanguage) &&
(activeFile.mode === 'edit' || (isSingleDiff && openFileState.canOpen)),
mdViewMode,
hasViewModeToggle,
availableEditorToggleModes,
hasEditorToggle: availableEditorToggleModes.length > 1,
effectiveToggleValue,
isMarkdownTableOfContentsDisabled: hasViewModeToggle && mdViewMode === 'source',
shouldShowMarkdownExportAction,
canExportMarkdownToPdf,
canShowMarkdownTableOfContents:
resolvedLanguage === 'markdown' &&
(hasViewModeToggle || activeFile.mode === 'markdown-preview'),
canShowMarkdownPreview: canOpenMarkdownPreview({
language: resolvedLanguage,
mode: activeFile.mode,
diffSource: activeFile.diffSource
})
}
}