['restore']) => {
const fileContents = {
name: file.name,
contents: content,
lang: getLanguageFromExtension(file.path) || undefined,
};
const pierreFile = (key: string) => (
);
if (!isLargeFile) {
return {pierreFile(file.path)}
;
}
// Large files render through pierre's Virtualizer (viewport-only DOM) and
// the shared Shiki worker pool. The pool is created lazily: until it is
// ready the key carries a 'pending' suffix so the file remounts with the
// worker-backed highlighter instead of silently staying on the main thread.
return (
{pierreFile(`${file.path}:${shikiWorkerPool ? 'pool' : 'pending'}`)}
);
}, [currentTheme.metadata.variant, isLargeFile, largeFileCacheKey, pierreTheme, shikiWorkerPool, wrapLines]);
const renderFloatingFileControls = ({
exitFullscreenOnly = false,
layout = 'floating',
}: { exitFullscreenOnly?: boolean; layout?: 'floating' | 'docked' } = {}) => {
if (!selectedFile) {
return null;
}
const docked = layout === 'docked';
const saveShortcut = formatShortcutForDisplay(getEffectiveShortcutCombo('save_file'));
const wrapperCls = docked
? 'pointer-events-auto flex flex-wrap items-center gap-1'
: 'pointer-events-auto flex items-center gap-1 rounded-lg border border-[var(--interactive-border)] bg-[var(--surface-elevated)] p-1 shadow-sm';
const withTooltip = (label: React.ReactNode, trigger: React.ReactElement) => (
{trigger}
{label}
);
return (
{canEdit && isEditingFile && (
<>
{isSaving ? (
{t('filesView.editor.saving')}
) : autoSaveEnabled && autoSaveStatus === 'saved' && !isDirty ? (
{t('filesView.editor.saved')}
) : isDirty ? withTooltip(t(autoSaveEnabled ? 'filesView.editor.saveNowTitle' : 'filesView.editor.saveNowManualTitle', { shortcut: saveShortcut }),
) : null}
{withTooltip(autoSaveEnabled ? t('filesView.editor.autoSaveOn') : t('filesView.editor.manualSave'),
)}
>
)}
{t('filesView.editor.openInDesktopApp')}
{openInApps.map((app) => (
void handleOpenInApp(app)}
>
{app.label}
))}
{openInCacheStale ? (
void loadOpenInApps(true)}
>
{t('filesView.editor.refreshApps')}
) : null}
{!isSelectedImage && !isSelectedPdf && !isUnsupportedBinary && (
<>
{withTooltip(wrapLines ? t('filesView.editor.disableLineWrap') : t('filesView.editor.enableLineWrap'),
)}
{textViewMode === 'edit' && (
<>
{withTooltip(t('filesView.editor.findInFile'),
)}
{withTooltip(t('filesView.editor.goToLine'),
)}
>
)}
>
)}
{canUseShikiFileView && canEdit && !isJson && !isHtml && (
{
saveTextViewMode(textViewMode === 'view' ? 'edit' : 'view');
}}
/>
)}
{isMarkdown && (
withTooltip(
t(getMdViewMode() === 'preview' ? 'filesView.editor.switchToEditMode' : 'filesView.editor.switchToPreviewMode'),
)
)}
{isHtmlFile(selectedFile?.path ?? '') && (
{
saveHtmlViewMode(getHtmlViewMode() === 'preview' ? 'edit' : 'preview');
}}
/>
)}
{isMarkdown && getMdViewMode() === 'preview' && (
withTooltip(t('filesView.editor.findInFile'),
)
)}
{isMarkdown && getMdViewMode() === 'preview' && showMessageTTSButtons && (
{isTTSPlaying ? t('filesView.tts.stopSpeaking') : t('filesView.tts.readAloud')}
)}
{isDrawio && (
<>
saveDrawioViewMode(drawioViewMode === 'preview' ? 'edit' : 'preview')}
/>
{drawioViewMode === 'preview' && (
)}
>
)}
{isJson && (
withTooltip(jsonViewMode === 'tree' ? t('filesView.editor.switchToTextView') : t('filesView.editor.switchToTreeView'),
)
)}
{canCopy && (
withTooltip(t('filesView.editor.copyFileContents'),
)
)}
{canCopyPath && (
withTooltip(t('filesView.editor.copyFilePathTitle', { path: displaySelectedPath }),
)
)}
{files.downloadFile && (
withTooltip(t('filesView.editor.saveFile'),
)
)}
{exitFullscreenOnly ? (
withTooltip(t('filesView.editor.exitFullscreen'),
)
) : (!isMobile && mode === 'full' && (
withTooltip(isFullscreen ? t('filesView.editor.exitFullscreen') : t('filesView.editor.fullscreen'),
)
))}
);
};
const fileViewer = (
{/* Row 1: Tabs */}
{showEditorTabsRow ? (
{isMobile && showMobilePageContent && (
)}
{isMobile ? (
selectedFile ? (
{openFiles.map((file) => {
const isActive = selectedFile?.path === file.path;
return (
{
const target = event.target as HTMLElement;
if (target.closest('[data-close-open-file]')) {
event.preventDefault();
return;
}
if (!isActive) {
void handleSelectFile(file);
}
}}
className={cn(
'flex min-w-0 items-center justify-between gap-2 overflow-hidden',
isActive && 'bg-[var(--interactive-selection)] text-[var(--interactive-selection-foreground)]'
)}
>
);
})}
) : (
{t('filesView.editor.selectFile')}
)
) : (
openFiles.length > 0 ? (
{editorTabsOverflow.left && (
)}
{editorTabsOverflow.right && (
)}
{openFiles.map((file) => {
const isActive = selectedFile?.path === file.path;
return (
);
})}
) : (
{t('filesView.editor.selectFile')}
)
)}
) : null}
{/* Row 2: Docked editor toolbar. */}
{selectedFile ? (
{/* Mobile hosts already show the file name in their own header;
a truncated duplicate here just eats toolbar width. */}
{displaySelectedPath && !isMobile ? (
{displaySelectedPath}
) : null}
{renderFloatingFileControls({ layout: 'docked' })}
) : null}
{!selectedFile ? (
{t('filesView.editor.pickFileFromTree')}
) : (fileLoading || isPdfAssetAuthLoading) ? (
suppressFileLoadingIndicator
?
: (
{t('filesView.state.loading')}
)
) : fileError ? (
{fileError}
) : isSelectedImage ? (
) : isSelectedPdf ? (
renderPdfPreview(selectedFile)
) : isUnsupportedBinary ? (
{t('filesView.editor.cannotPreviewBinary')}
{t('filesView.editor.binaryFileDescription')}
{files.downloadFile ? (
) : null}
) : selectedFile && isDrawio && drawioViewMode === 'preview' ? (
) : selectedFile && isJson && jsonViewMode === 'tree' ? (
{t('filesView.error.jsonViewerUnavailable')}
{t('filesView.error.switchToTextMode')}
}
>
) : selectedFile && isMarkdown && getMdViewMode() === 'preview' ? (
{fileContent.length > 500 * 1024 && (
{t('filesView.warning.largeFilePreviewLimited', { sizeKb: Math.round(fileContent.length / 1024) })}
)}
{t('filesView.error.previewUnavailable')}
{t('filesView.error.switchToEditMode')}
}
>
{!isFullscreen && (
)}
) : selectedFile && isHtml && htmlViewMode === 'preview' ? (
isHtmlAssetAuthLoading ? (
{t('common.loading')}
) : (
)
) : selectedFile && canUseShikiFileView && textViewMode === 'view' ? (
renderShikiFileView(selectedFile, isLargeFile ? fileContent : draftContent, mainViewVirtualizer, restoreMainCodeScroll)
) : (
{
editorViewRef.current = view;
setEditorViewReadyNonce((value) => value + 1);
window.requestAnimationFrame(() => {
nudgeEditorSelectionAboveKeyboard(view);
});
}}
onViewDestroy={() => {
if (editorViewRef.current) {
editorViewRef.current = null;
}
setEditorViewReadyNonce((value) => value + 1);
}}
enableSearch
searchOpen={isSearchOpen}
onSearchOpenChange={setIsSearchOpen}
highlightLines={lineSelection
? {
start: Math.min(lineSelection.start, lineSelection.end),
end: Math.max(lineSelection.start, lineSelection.end),
}
: undefined}
lineNumbersConfig={{
domEventHandlers: {
mousedown: (view: EditorView, line: { from: number; to: number }, event: Event) => {
if (!(event instanceof MouseEvent)) {
return false;
}
if (event.button !== 0) {
return false;
}
event.preventDefault();
const lineNumber = view.state.doc.lineAt(line.from).number;
if (
lineSelection &&
!event.shiftKey &&
Math.min(lineSelection.start, lineSelection.end) === lineNumber &&
Math.max(lineSelection.start, lineSelection.end) === lineNumber
) {
setLineSelection(null);
cancel();
isSelectingRef.current = false;
selectionStartRef.current = null;
setIsDragging(false);
return true;
}
// Mobile: tap-to-extend selection
if (isMobile && lineSelection && !event.shiftKey) {
const start = Math.min(lineSelection.start, lineSelection.end, lineNumber);
const end = Math.max(lineSelection.start, lineSelection.end, lineNumber);
setLineSelection({ start, end });
isSelectingRef.current = false;
selectionStartRef.current = null;
setIsDragging(false);
return true;
}
isSelectingRef.current = true;
selectionStartRef.current = lineNumber;
setIsDragging(true);
if (lineSelection && event.shiftKey) {
const start = Math.min(lineSelection.start, lineNumber);
const end = Math.max(lineSelection.end, lineNumber);
setLineSelection({ start, end });
} else {
setLineSelection({ start: lineNumber, end: lineNumber });
}
return true;
},
mouseover: (view: EditorView, line: { from: number; to: number }, event: Event) => {
if (!(event instanceof MouseEvent)) {
return false;
}
if (event.buttons !== 1) {
return false;
}
if (!isSelectingRef.current || selectionStartRef.current === null) {
return false;
}
const lineNumber = view.state.doc.lineAt(line.from).number;
const start = Math.min(selectionStartRef.current, lineNumber);
const end = Math.max(selectionStartRef.current, lineNumber);
setLineSelection({ start, end });
setIsDragging(true);
return false;
},
mouseup: () => {
isSelectingRef.current = false;
selectionStartRef.current = null;
setIsDragging(false);
return false;
},
},
}}
/>
{shouldMaskEditorForPendingNavigation && (
{t('filesView.state.openingFileAtChange')}
)}
)}
);
const hasTree = Boolean(root && childrenByDir[root]);
const rootLoadError = root ? loadErrorsByDir[root] : null;
const treePanel = (
{searching ? (
-
{t('filesView.tree.search.searching')}
) : searchResults.length > 0 ? (
searchResults.map((node) => {
const isActive = selectedFile?.path === node.path;
return (
-
);
})
) : rootLoadError ? (
-
{rootLoadError}
) : hasTree ? (
renderTree(root, 0)
) : (
- {t('filesView.state.loading')}
)}
);
// Fullscreen file viewer overlay
const fullscreenViewer = mode === 'full' && isFullscreen && selectedFile && (
{/* Fullscreen content */}
{renderFloatingFileControls({ exitFullscreenOnly: true })}
{(fileLoading || isPdfAssetAuthLoading) ? (
suppressFileLoadingIndicator
?
: (
Loading…
)
) : fileError ? (
{fileError}
) : isSelectedImage ? (
) : isSelectedPdf ? (
renderPdfPreview(selectedFile)
) : isUnsupportedBinary ? (
{t('filesView.editor.cannotPreviewBinary')}
{t('filesView.editor.binaryFileDescription')}
{files.downloadFile ? (
) : null}
) : isMarkdown && getMdViewMode() === 'preview' ? (
// The find bar is a sibling of the scroll container, never a child:
// inside it, its own "1/3" and "No matches" text would be walked and
// highlighted by the search it drives.
{selectedFile ? (
) : null}
{fileContent.length > 500 * 1024 && (
{t('filesView.warning.largeFilePreviewLimited', { sizeKb: Math.round(fileContent.length / 1024) })}
)}
{t('filesView.error.previewUnavailable')}
{t('filesView.error.switchToEditMode')}
}
>
) : canUseShikiFileView && textViewMode === 'view' ? (
renderShikiFileView(selectedFile, isLargeFile ? fileContent : draftContent, fullscreenViewVirtualizer, restoreFullscreenCodeScroll)
) : (
{
editorViewRef.current = view;
window.requestAnimationFrame(() => {
nudgeEditorSelectionAboveKeyboard(view);
});
}}
onViewDestroy={() => {
if (editorViewRef.current) {
editorViewRef.current = null;
}
}}
/>
{shouldMaskEditorForPendingNavigation && (
{t('filesView.state.openingFileAtChange')}
)}
)}
);
return (
{fullscreenViewer}
{isMobile ? (
showMobilePageContent ? (
fileViewer
) : (
treePanel
)
) : mode === 'editor-only' ? (
) : (
{screenWidth >= 700 && (
{treePanel}
)}
{fileViewer}
)}
);
};