refactor(files): simplify file event processing logic

Remove redundant checks for deleted paths and streamline binary file handling. This fixes the browser using excessive memory and freezing.

Improve DiffView to use a singleton instance of Shiki
This commit is contained in:
KevIsDev 2025-05-20 00:57:52 +01:00
parent 0ec30e2358
commit cfc2fc75d8
2 changed files with 78 additions and 70 deletions

View File

@ -542,8 +542,53 @@ const FileInfo = memo(
},
);
// Create and manage a single highlighter instance at the module level
let highlighterInstance: any = null;
let highlighterPromise: Promise<any> | null = null;
const getSharedHighlighter = async () => {
if (highlighterInstance) {
return highlighterInstance;
}
if (highlighterPromise) {
return highlighterPromise;
}
highlighterPromise = getHighlighter({
themes: ['github-dark', 'github-light'],
langs: [
'typescript',
'javascript',
'json',
'html',
'css',
'jsx',
'tsx',
'python',
'php',
'java',
'c',
'cpp',
'csharp',
'go',
'ruby',
'rust',
'plaintext',
],
});
highlighterInstance = await highlighterPromise;
highlighterPromise = null;
// Clear the promise once resolved
return highlighterInstance;
};
const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }: CodeComparisonProps) => {
const [isFullscreen, setIsFullscreen] = useState(false);
// Use state to hold the shared highlighter instance
const [highlighter, setHighlighter] = useState<any>(null);
const theme = useStore(themeStore);
@ -554,34 +599,32 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
const { unifiedBlocks, hasChanges, isBinary, error } = useProcessChanges(beforeCode, afterCode);
useEffect(() => {
getHighlighter({
themes: ['github-dark', 'github-light'],
langs: [
'typescript',
'javascript',
'json',
'html',
'css',
'jsx',
'tsx',
'python',
'php',
'java',
'c',
'cpp',
'csharp',
'go',
'ruby',
'rust',
'plaintext',
],
}).then(setHighlighter);
}, []);
// Fetch the shared highlighter instance
getSharedHighlighter().then(setHighlighter);
/*
* No cleanup needed here for the highlighter instance itself,
* as it's managed globally. Shiki instances don't typically
* need disposal unless you are dynamically loading/unloading themes/languages.
* If you were dynamically loading, you might need a more complex
* shared instance manager with reference counting or similar.
* For static themes/langs, a single instance is sufficient.
*/
}, []); // Empty dependency array ensures this runs only once on mount
if (isBinary || error) {
return renderContentWarning(isBinary ? 'binary' : 'error');
}
// Render a loading state or null while highlighter is not ready
if (!highlighter) {
return (
<div className="h-full flex items-center justify-center">
<div className="text-bolt-elements-textTertiary">Loading diff...</div>
</div>
);
}
return (
<FullscreenOverlay isFullscreen={isFullscreen}>
<div className="w-full h-full flex flex-col">
@ -602,7 +645,7 @@ const InlineDiffComparison = memo(({ beforeCode, afterCode, filename, language }
lineNumber={block.lineNumber}
content={block.content}
type={block.type}
highlighter={highlighter}
highlighter={highlighter} // Pass the shared instance
language={language}
block={block}
theme={theme}

View File

@ -692,27 +692,9 @@ export class FilesStore {
#processEventBuffer(events: Array<[events: PathWatcherEvent[]]>) {
const watchEvents = events.flat(2);
for (const { type, path: eventPath, buffer } of watchEvents) {
for (const { type, path, buffer } of watchEvents) {
// remove any trailing slashes
const sanitizedPath = eventPath.replace(/\/+$/g, '');
// Skip processing if this file/folder was explicitly deleted
if (this.#deletedPaths.has(sanitizedPath)) {
continue;
}
let isInDeletedFolder = false;
for (const deletedPath of this.#deletedPaths) {
if (sanitizedPath.startsWith(deletedPath + '/')) {
isInDeletedFolder = true;
break;
}
}
if (isInDeletedFolder) {
continue;
}
const sanitizedPath = path.replace(/\/+$/g, '');
switch (type) {
case 'add_dir': {
@ -738,38 +720,21 @@ export class FilesStore {
}
let content = '';
/**
* @note This check is purely for the editor. The way we detect this is not
* bullet-proof and it's a best guess so there might be false-positives.
* The reason we do this is because we don't want to display binary files
* in the editor nor allow to edit them.
*/
const isBinary = isBinaryFile(buffer);
if (isBinary && buffer) {
// For binary files, we need to preserve the content as base64
content = Buffer.from(buffer).toString('base64');
} else if (!isBinary) {
if (!isBinary) {
content = this.#decodeFileContent(buffer);
/*
* If the content is a single space and this is from our empty file workaround,
* convert it back to an actual empty string
*/
if (content === ' ' && type === 'add_file') {
content = '';
}
}
const existingFile = this.files.get()[sanitizedPath];
this.files.setKey(sanitizedPath, { type: 'file', content, isBinary });
if (existingFile?.type === 'file' && existingFile.isBinary && existingFile.content && !content) {
content = existingFile.content;
}
// Preserve lock state if the file already exists
const isLocked = existingFile?.type === 'file' ? existingFile.isLocked : false;
this.files.setKey(sanitizedPath, {
type: 'file',
content,
isBinary,
isLocked,
});
break;
}
case 'remove_file': {