diff --git a/app/terminal/embedContext.tsx b/app/terminal/embedContext.tsx index 53138d3..15196c6 100644 --- a/app/terminal/embedContext.tsx +++ b/app/terminal/embedContext.tsx @@ -32,14 +32,16 @@ interface IEmbedContext { ) => Promise>>; replOutputs: Readonly>; + addReplCommand: (terminalId: TerminalId, command: string) => string; addReplOutput: ( terminalId: TerminalId, - command: string, - output: ReplOutput[] + commandId: string, + output: ReplOutput ) => void; execResults: Readonly>; - setExecResult: (filename: Filename, output: ReplOutput[]) => void; + clearExecResult: (filename: Filename) => void; + addExecOutput: (filename: Filename, output: ReplOutput) => void; } const EmbedContext = createContext(null!); @@ -63,6 +65,9 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) { const [replOutputs, setReplOutputs] = useState< Record >({}); + const [commandIdCounters, setCommandIdCounters] = useState< + Record + >({}); const [execResults, setExecResults] = useState< Record >({}); @@ -71,6 +76,7 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) { if (pathname && pathname !== currentPathname) { setCurrentPathname(pathname); setReplOutputs({}); + setCommandIdCounters({}); setExecResults({}); } }, [pathname, currentPathname]); @@ -100,8 +106,16 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) { }, [pathname] ); - const addReplOutput = useCallback( - (terminalId: TerminalId, command: string, output: ReplOutput[]) => + const addReplCommand = useCallback( + (terminalId: TerminalId, command: string): string => { + let commandId = ""; + setCommandIdCounters((counters) => { + const newCounters = { ...counters }; + const currentCount = newCounters[terminalId] ?? 0; + commandId = String(currentCount); + newCounters[terminalId] = currentCount + 1; + return newCounters; + }); setReplOutputs((outs) => { outs = { ...outs }; if (!(terminalId in outs)) { @@ -109,17 +123,53 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) { } outs[terminalId] = [ ...outs[terminalId], - { command: command, output: output }, + { command: command, output: [], commandId }, ]; return outs; + }); + return commandId; + }, + [] + ); + const addReplOutput = useCallback( + (terminalId: TerminalId, commandId: string, output: ReplOutput) => + setReplOutputs((outs) => { + outs = { ...outs }; + if (terminalId in outs) { + outs[terminalId] = [...outs[terminalId]]; + // Find the command by commandId + const commandIndex = outs[terminalId].findIndex( + (cmd) => cmd.commandId === commandId + ); + if (commandIndex >= 0) { + const command = outs[terminalId][commandIndex]; + outs[terminalId][commandIndex] = { + ...command, + output: [...command.output, output], + }; + } + } + return outs; + }), + [] + ); + const clearExecResult = useCallback( + (filename: Filename) => + setExecResults((results) => { + results = { ...results }; + results[filename] = []; + return results; }), [] ); - const setExecResult = useCallback( - (filename: Filename, output: ReplOutput[]) => + const addExecOutput = useCallback( + (filename: Filename, output: ReplOutput) => setExecResults((results) => { results = { ...results }; - results[filename] = output; + if (!(filename in results)) { + results[filename] = []; + } + results[filename] = [...results[filename], output]; return results; }), [] @@ -131,9 +181,11 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) { files: files[pathname] || {}, writeFile, replOutputs, + addReplCommand, addReplOutput, execResults, - setExecResult, + clearExecResult, + addExecOutput, }} > {children} diff --git a/app/terminal/exec.tsx b/app/terminal/exec.tsx index 46df421..566d1f0 100644 --- a/app/terminal/exec.tsx +++ b/app/terminal/exec.tsx @@ -32,7 +32,7 @@ export function ExecFile(props: ExecProps) { } }, }); - const { files, setExecResult } = useEmbedContext(); + const { files, clearExecResult, addExecOutput } = useEmbedContext(); const { ready, runFiles, getCommandlineStr } = useRuntime(props.language); @@ -46,10 +46,12 @@ export function ExecFile(props: ExecProps) { (async () => { clearTerminal(terminalInstanceRef.current!); terminalInstanceRef.current!.write(systemMessageColor("実行中です...")); - const outputs: ReplOutput[] = []; + // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる + const filenameKey = props.filenames.join(","); + clearExecResult(filenameKey); let isFirstOutput = true; await runFiles(props.filenames, files, (output) => { - outputs.push(output); + addExecOutput(filenameKey, output); if (isFirstOutput) { // Clear "実行中です..." message only on first output clearTerminal(terminalInstanceRef.current!); @@ -63,10 +65,7 @@ export function ExecFile(props: ExecProps) { null, // ファイル実行で"return"メッセージが返ってくることはないはずなので、Prismを渡す必要はない props.language ); - // TODO: 実行が完了したあとに出力された場合、embedContextのsetExecResultにも出力を追加する必要があるが、それに対応したAPIになっていない }); - // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる - setExecResult(props.filenames.join(","), outputs); setExecutionState("idle"); })(); } @@ -75,7 +74,8 @@ export function ExecFile(props: ExecProps) { ready, props.filenames, runFiles, - setExecResult, + clearExecResult, + addExecOutput, terminalInstanceRef, props.language, files, diff --git a/app/terminal/repl.tsx b/app/terminal/repl.tsx index 5b839de..6c5eca7 100644 --- a/app/terminal/repl.tsx +++ b/app/terminal/repl.tsx @@ -31,6 +31,7 @@ export interface ReplOutput { export interface ReplCommand { command: string; output: ReplOutput[]; + commandId?: string; // Optional for backward compatibility } export type SyntaxStatus = "complete" | "incomplete" | "invalid"; // 構文チェックの結果 @@ -80,7 +81,7 @@ export function ReplTerminal({ language, initContent, }: ReplComponentProps) { - const { addReplOutput } = useEmbedContext(); + const { addReplCommand, addReplOutput } = useEmbedContext(); const [Prism, setPrism] = useState(null); useEffect(() => { @@ -217,7 +218,7 @@ export function ReplTerminal({ terminalInstanceRef.current.writeln(""); const command = inputBuffer.current.join("\n").trim(); inputBuffer.current = []; - const collectedOutputs: ReplOutput[] = []; + const commandId = addReplCommand(terminalId, command); let executionDone = false; await runtimeMutex.runExclusive(async () => { await runCommand(command, (output) => { @@ -226,16 +227,14 @@ export function ReplTerminal({ updateBuffer(null, () => { handleOutput(output); }); - // TODO: embedContextのaddReplOutputにも出力を追加する必要があるが、それに対応したAPIになっていない } else { - collectedOutputs.push(output); handleOutput(output); } + addReplOutput(terminalId, commandId, output); }); }); executionDone = true; updateBuffer(() => [""]); - addReplOutput?.(terminalId, command, collectedOutputs); } } else if (code === 127) { // Backspace @@ -279,6 +278,7 @@ export function ReplTerminal({ runCommand, handleOutput, tabSize, + addReplCommand, addReplOutput, terminalId, terminalInstanceRef,