diff --git a/src/App.tsx b/src/App.tsx index 70222da..7feafcc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -20,9 +20,14 @@ import { Examples, CSourceRow, StoredLogs, + StoredCLines, } from "./components"; import { getCLineId, ParsedLine, ParsedLineType } from "./parser"; +// Increment the version if there is a breaking change +// in what we store +const STORED_LOG_KEY = "logs-v2"; + function getEmptyVisualLogState(): VisualLogState { return { verifierLogState: getEmptyVerifierState(), @@ -62,6 +67,7 @@ function getVisibleLogLines( function getVisibleCLines( verifierLogState: VerifierLogState, + storedCLines: StoredCLines = {}, ): [CSourceRow[], Map] { const cLineIdToVisualIdx: Map = new Map(); const cLines: CSourceRow[] = []; @@ -74,6 +80,24 @@ function getVisibleCLines( }); ++j; + if (file in storedCLines) { + storedCLines[file].forEach((lineText, i) => { + const lineNum = i + 1; + const sourceId = getCLineId(file, lineNum); + cLines.push({ + type: "c_line", + file, + lineNum: i + 1, + lineText, + sourceId, + ignore: false, + }); + cLineIdToVisualIdx.set(sourceId, j); + ++j; + }); + continue; + } + let unknownStart = 0; for (let i = range[0]; i < range[1]; ++i) { const sourceId = getCLineId(file, i); @@ -144,6 +168,7 @@ const ContentRaw = ({ handleLogLinesOut, handleFullLogToggle, testListHeight, + addPastedCSourceFile, }: { loadError: string | null; visualLogState: VisualLogState; @@ -154,6 +179,7 @@ const ContentRaw = ({ handleLogLinesOut: (event: React.MouseEvent) => void; handleFullLogToggle: () => void; testListHeight: number | undefined; + addPastedCSourceFile: (fileName: string, pastedLines: string[]) => void; }) => { if (loadError) { return
{loadError}
; @@ -167,6 +193,7 @@ const ContentRaw = ({ handleLogLinesOut={handleLogLinesOut} handleFullLogToggle={handleFullLogToggle} testListHeight={testListHeight} + addPastedCSourceFile={addPastedCSourceFile} /> ); } else { @@ -209,7 +236,7 @@ function App({ testListHeight }: { testListHeight?: number }) { logLineIdxToVisualIdx.get(hoveredState.line) || 0; useEffect(() => { - ldb.get("logs", function (value) { + ldb.get(STORED_LOG_KEY, function (value) { try { const logs: StoredLogs = JSON.parse(value); if (logs) { @@ -245,7 +272,12 @@ function App({ testListHeight }: { testListHeight?: number }) { }); }, [verifierLogState]); - const updateStoredLogs = useCallback( + const updateStoredLogs = useCallback((nextStoredLogs: StoredLogs) => { + ldb.set(STORED_LOG_KEY, JSON.stringify(nextStoredLogs)); + setStoredLogs(nextStoredLogs); + }, []); + + const addStoredLog = useCallback( (lines: string[], name: string = "") => { const now = new Date(); const nextName = name @@ -253,11 +285,10 @@ function App({ testListHeight }: { testListHeight?: number }) { : `pasted log (${now.toDateString().toLowerCase()} - ${now.toLocaleTimeString().toLocaleLowerCase()})`; // Only keep 5 stored logs for now const nextStoredLogs: StoredLogs = [ - [nextName, lines], + [nextName, { rawLogLines: lines, pastedCLines: {} }], ...storedLogs.slice(0, 5), ]; - ldb.set("logs", JSON.stringify(nextStoredLogs)); - setStoredLogs(nextStoredLogs); + updateStoredLogs(nextStoredLogs); }, [storedLogs], ); @@ -268,13 +299,62 @@ function App({ testListHeight }: { testListHeight?: number }) { setIsLoading(false); }, []); + const addPastedCSourceFile = useCallback( + (fileName: string, pastedLines: string[]) => { + const nextStoredLogs: StoredLogs = []; + storedLogs.forEach((storedLog, idx) => { + // The first stored log is the current one + if (idx !== 0) { + nextStoredLogs.push(storedLog); + return; + } + + const nextPastedCLines: StoredCLines = {}; + let found = false; + for (const [storedFileName, lines] of Object.entries( + storedLog[1].pastedCLines, + )) { + if (storedFileName !== fileName) { + nextPastedCLines[storedFileName] = lines; + return; + } + found = true; + nextPastedCLines[storedFileName] = pastedLines; + } + if (!found) { + nextPastedCLines[fileName] = pastedLines; + } + nextStoredLogs.push([ + storedLog[0], + { + rawLogLines: storedLog[1].rawLogLines, + pastedCLines: nextPastedCLines, + }, + ]); + setVisualLogState((prevState) => { + const [cLines, cLineIdToVisualIdx] = getVisibleCLines( + verifierLogState, + nextPastedCLines, + ); + return { + ...prevState, + cLines, + cLineIdToVisualIdx, + }; + }); + }); + updateStoredLogs(nextStoredLogs); + }, + [verifierLogState, storedLogs], + ); + const loadInputText = useCallback( (text: string) => { const rawLines = text.split("\n"); loadLog(rawLines); - updateStoredLogs(rawLines); + addStoredLog(rawLines); }, - [updateStoredLogs], + [addStoredLog], ); const prepareNewLog = useCallback(() => { @@ -311,7 +391,30 @@ function App({ testListHeight }: { testListHeight?: number }) { if (!found) { console.error("Couldn't load previous log", example); } else { - loadLog(found[1]); + loadLog(found[1].rawLogLines); + // Move this to the most recent stored log + const nextStoredLogs: StoredLogs = [[found[0], found[1]]]; + storedLogs.forEach((storedLog) => { + if (storedLog[0] !== example) { + nextStoredLogs.push(storedLog); + } + }); + updateStoredLogs(nextStoredLogs); + const newVerifierLogState = processRawLines(found[1].rawLogLines); + const nextVisualLogState = getVisualLogState( + newVerifierLogState, + false, + ); + const [cLines, cLineIdToVisualIdx] = getVisibleCLines( + newVerifierLogState, + found[1].pastedCLines, + ); + setVisualLogState({ + ...nextVisualLogState, + cLines, + cLineIdToVisualIdx, + }); + setIsLoading(false); } } }, @@ -396,7 +499,7 @@ function App({ testListHeight }: { testListHeight?: number }) { } rawLines = rawLines.concat(lines); } - updateStoredLogs(rawLines, fileBlob.name); + addStoredLog(rawLines, fileBlob.name); const newVerifierLogState = processRawLines(rawLines); setVisualLogState( getVisualLogState(newVerifierLogState, visualLogState.showFullLog), @@ -452,6 +555,7 @@ function App({ testListHeight }: { testListHeight?: number }) { handleLogLinesOut={handleLogLinesOut} handleFullLogToggle={handleFullLogToggle} testListHeight={testListHeight} + addPastedCSourceFile={addPastedCSourceFile} />
cLines (N.B. not using a real Map to make it easier to json.parse/stringify) +export type StoredCLines = { [index: string]: string[] }; +export type StoredLogs = [ + string, + { rawLogLines: string[]; pastedCLines: StoredCLines }, +][]; function isVoidHelperArg(arg: HelperArg) { return arg.type === "void" && arg.name === null && arg.star === null; @@ -821,6 +827,98 @@ export function SelectedLineHint({ ); } +function CSourcePastePopup({ + verifierLogState, + fileName, + onHideCSourcePaste, + addPastedCSourceFile, +}: { + verifierLogState: VerifierLogState; + fileName: string; + onHideCSourcePaste: () => void; + addPastedCSourceFile: (fileName: string, pastedLines: string[]) => void; +}) { + const [pastedContent, setPastedContent] = useState(""); + const [mismatchError, setMismatcError] = useState(false); + + const handleChange = useCallback( + (event: ChangeEvent) => { + setMismatcError(false); + const pastedText = event.target.value; + setPastedContent(pastedText); + const pastedLines: string[] = pastedText.split("\n"); + for (const [file] of verifierLogState.cSourceMap.fileRange) { + if (file !== fileName) { + continue; + } + pastedLines.some((lineText, i) => { + const lineNum = i + 1; + const sourceId = getCLineId(file, lineNum); + const sourceLine = + verifierLogState.cSourceMap.cSourceLines.get(sourceId); + if (sourceLine && !sourceLine.ignore) { + if (!lineText.includes(sourceLine.content)) { + setMismatcError(true); + return true; + } + } + return false; + }); + } + }, + [], + ); + + const handleConfirm = useCallback(() => { + addPastedCSourceFile(fileName, pastedContent.split("\n")); + onHideCSourcePaste(); + }, [pastedContent]); + + const handleClear = useCallback(() => { + setPastedContent(""); + setMismatcError(false); + }, []); + + const onContentClick = useCallback( + (event: React.MouseEvent) => { + event.stopPropagation(); + }, + [], + ); + + return ( +
+
+

Paste contents of {fileName}

+