vzcode
Version:
Multiplayer code editor system
181 lines (152 loc) • 4.84 kB
text/typescript
import { shouldTriggerRun } from '../shouldTriggerRun';
import { JSONOp } from '../../ot';
import { VizFileId, VizContent } from '@vizhub/viz-types';
import { ShareDBDoc } from '../../types';
import { useEffect, useRef, useState } from 'react';
// The time in milliseconds by which auto-saving is debounced.
export const autoPrettierDebounceTimeMS = 1200;
// Computes a file extension from a file name.
// Example: 'foo.js' => '.js'
const extension = (fileName: string) => {
const match = fileName.match(/\.([^.]+)$/);
if (match) {
return match[1];
} else {
return '';
}
};
// export const shouldTriggerRun = (event: KeyboardEvent) => {
// if(event.ctrlKey && event.key === 's') {
// return true;
// }
// }
export const usePrettier = ({
shareDBDoc,
submitOperation,
prettierWorker,
}: {
shareDBDoc: ShareDBDoc<VizContent> | null;
submitOperation: (
next: (content: VizContent) => VizContent,
) => void;
prettierWorker: Worker;
}) => {
// The set of files that have been modified
// since the last Prettier run.
const dirtyFilesRef = useRef<Set<VizFileId>>(new Set());
// State to hold the error from Prettier
// `null` means no errors
// If this is a string, it's the
// error message from Prettier.
const [prettierError, setPrettierError] = useState<
string | null
>(null);
// A ref to keep track of whether an op is being applied.
// This is used to prevent Prettier from running
// when the op is coming from Prettier itself.
const isApplyingOpRef = useRef(false);
// We use a ref to keep track of the runPrettier function.
const runPrettierRef = useRef(null);
useEffect(() => {
if (!shareDBDoc) {
return;
}
// Get the message that comes back from the worker.
// This is the prettified text for a specific file.
const handleMessage = (event) => {
const { fileId, error, fileTextPrettified } =
event.data;
// Handle syntax errors
if (error) {
// If there's an error, set the error state
setPrettierError(error);
// Return early as there is no text change to apply.
return;
} else {
// Make sure the error overlay goes away
// when Prettier runs successfully with no errors.
setPrettierError(null);
}
isApplyingOpRef.current = true;
submitOperation((document) => ({
...document,
files: {
...document.files,
[fileId]: {
...document.files[fileId],
text: fileTextPrettified,
},
},
}));
isApplyingOpRef.current = false;
};
// Listen for messages from the worker.
prettierWorker.addEventListener(
'message',
handleMessage,
);
const runPrettier = async () => {
// Get the content of the document
const content = shareDBDoc.data;
// Get the files
const files = content.files;
// Get the set of dirty files
const dirtyFiles = dirtyFilesRef.current;
// Get the dirty files
const dirtyFileIds = Array.from(dirtyFiles);
// Clear the set of dirty files
dirtyFiles.clear();
// Run Prettier on each dirty file
for (const fileId of dirtyFileIds) {
// Get the file
const file = files[fileId];
// Craft the data to send to the worker
const data = {
fileText: file.text,
fileExtension: extension(file.name),
fileId,
};
// Run Prettier for this file
prettierWorker.postMessage(data);
}
};
runPrettierRef.current = runPrettier;
const handleOpBatch = (op: JSONOp) => {
// Only act on changes coming from the client
// OR changes coming from the AI Assist that we
// if (isLocal) {
// If the op is coming from Prettier itself,
// then do nothing.
if (isApplyingOpRef.current) {
// console.log('ignoring op from Prettier');
return;
}
// Check if the path of this op is the text content of a file
if (
op &&
op.length === 4 &&
op[0] === 'files' &&
typeof op[1] === 'string' &&
op[2] === 'text'
) {
// Get the file id
const fileId: VizFileId = op[1];
// Add the file id to the set of dirty files
dirtyFilesRef.current.add(fileId);
}
};
// Listen for changes
shareDBDoc.on('op batch', handleOpBatch);
// Clean up the event listeners
return () => {
prettierWorker.removeEventListener(
'message',
handleMessage,
);
shareDBDoc.removeListener('op batch', handleOpBatch);
};
}, [shareDBDoc]);
// Return the errors and run prettier function ref
// for use elsewhere.
return { prettierError, runPrettierRef };
};