Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 63 additions & 10 deletions app/terminal/embedContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,16 @@ interface IEmbedContext {
) => Promise<Readonly<Record<Filename, string>>>;

replOutputs: Readonly<Record<TerminalId, ReplCommand[]>>;
addReplCommand: (terminalId: TerminalId, command: string) => string;
addReplOutput: (
terminalId: TerminalId,
command: string,
output: ReplOutput[]
commandId: string,
output: ReplOutput
) => void;

execResults: Readonly<Record<Filename, ReplOutput[]>>;
setExecResult: (filename: Filename, output: ReplOutput[]) => void;
clearExecResult: (filename: Filename) => void;
addExecOutput: (filename: Filename, output: ReplOutput) => void;
}
const EmbedContext = createContext<IEmbedContext>(null!);

Expand All @@ -63,6 +65,10 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) {
const [replOutputs, setReplOutputs] = useState<
Record<TerminalId, ReplCommand[]>
>({});
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [commandIdCounters, setCommandIdCounters] = useState<
Record<TerminalId, number>
>({});
const [execResults, setExecResults] = useState<
Record<Filename, ReplOutput[]>
>({});
Expand All @@ -71,6 +77,7 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) {
if (pathname && pathname !== currentPathname) {
setCurrentPathname(pathname);
setReplOutputs({});
setCommandIdCounters({});
setExecResults({});
}
}, [pathname, currentPathname]);
Expand Down Expand Up @@ -100,26 +107,70 @@ 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)) {
outs[terminalId] = [];
}
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;
}),
[]
Expand All @@ -131,9 +182,11 @@ export function EmbedContextProvider({ children }: { children: ReactNode }) {
files: files[pathname] || {},
writeFile,
replOutputs,
addReplCommand,
addReplOutput,
execResults,
setExecResult,
clearExecResult,
addExecOutput,
}}
>
{children}
Expand Down
15 changes: 8 additions & 7 deletions app/terminal/exec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
systemMessageColor,
useTerminal,
} from "./terminal";
import { writeOutput, ReplOutput } from "./repl";
import { writeOutput } from "./repl";
import { useEffect, useState } from "react";
import { useEmbedContext } from "./embedContext";
import { RuntimeLang, useRuntime } from "./runtime";
Expand All @@ -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);

Expand All @@ -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!);
Expand All @@ -64,8 +66,6 @@ export function ExecFile(props: ExecProps) {
props.language
);
});
// TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる
setExecResult(props.filenames.join(","), outputs);
setExecutionState("idle");
})();
}
Expand All @@ -74,7 +74,8 @@ export function ExecFile(props: ExecProps) {
ready,
props.filenames,
runFiles,
setExecResult,
clearExecResult,
addExecOutput,
terminalInstanceRef,
props.language,
files,
Expand Down
30 changes: 22 additions & 8 deletions app/terminal/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"; // 構文チェックの結果

Expand Down Expand Up @@ -80,7 +81,7 @@ export function ReplTerminal({
language,
initContent,
}: ReplComponentProps) {
const { addReplOutput } = useEmbedContext();
const { addReplCommand, addReplOutput } = useEmbedContext();

const [Prism, setPrism] = useState<typeof import("prismjs") | null>(null);
useEffect(() => {
Expand Down Expand Up @@ -130,7 +131,7 @@ export function ReplTerminal({

// inputBufferを更新し、画面に描画する
const updateBuffer = useCallback(
(newBuffer: () => string[]) => {
(newBuffer: (() => string[]) | null, insertBefore?: () => void) => {
if (terminalInstanceRef.current) {
hideCursor(terminalInstanceRef.current);
// バッファの行数分カーソルを戻す
Expand All @@ -142,8 +143,12 @@ export function ReplTerminal({
terminalInstanceRef.current.write("\r");
// バッファの内容をクリア
terminalInstanceRef.current.write("\x1b[0J");
// 新しいバッファの内容を表示
inputBuffer.current = newBuffer();
// バッファの前に追加で出力する内容(前のコマンドの出力)があればここで書き込む
insertBefore?.();
// 新しいバッファの内容を表示、nullなら現状維持
if (newBuffer) {
inputBuffer.current = newBuffer();
}
for (let i = 0; i < inputBuffer.current.length; i++) {
terminalInstanceRef.current.write(
(i === 0 ? prompt : (promptMore ?? prompt)) ?? "> "
Expand Down Expand Up @@ -213,15 +218,23 @@ 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) => {
collectedOutputs.push(output);
handleOutput(output);
if (executionDone) {
// すでに完了していて次のコマンドのプロンプトが出ている場合、その前に挿入
updateBuffer(null, () => {
handleOutput(output);
});
} else {
handleOutput(output);
}
addReplOutput(terminalId, commandId, output);
});
});
executionDone = true;
updateBuffer(() => [""]);
addReplOutput?.(terminalId, command, collectedOutputs);
}
} else if (code === 127) {
// Backspace
Expand Down Expand Up @@ -265,6 +278,7 @@ export function ReplTerminal({
runCommand,
handleOutput,
tabSize,
addReplCommand,
addReplOutput,
terminalId,
terminalInstanceRef,
Expand Down
4 changes: 0 additions & 4 deletions app/terminal/worker/jsEval.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ async function runCode(
message: `${String(e)}`,
});
}
} finally {
currentOutputCallback = null;
}

return { updatedFiles: {} as Record<string, string> };
Expand Down Expand Up @@ -126,8 +124,6 @@ function runFile(
message: `${String(e)}`,
});
}
} finally {
currentOutputCallback = null;
}

return { updatedFiles: {} as Record<string, string> };
Expand Down
4 changes: 0 additions & 4 deletions app/terminal/worker/pyodide.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,6 @@ async function runCode(
message: `予期せぬエラー: ${String(e).trim()}`,
});
}
} finally {
currentOutputCallback = null;
}

const updatedFiles = readAllFiles();
Expand Down Expand Up @@ -165,8 +163,6 @@ async function runFile(
message: `予期せぬエラー: ${String(e).trim()}`,
});
}
} finally {
currentOutputCallback = null;
}

const updatedFiles = readAllFiles();
Expand Down
4 changes: 0 additions & 4 deletions app/terminal/worker/ruby.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,6 @@ async function runCode(
type: "error",
message: formatRubyError(e, false),
});
} finally {
currentOutputCallback = null;
}

const updatedFiles = readAllFiles();
Expand Down Expand Up @@ -189,8 +187,6 @@ async function runFile(
type: "error",
message: formatRubyError(e, true),
});
} finally {
currentOutputCallback = null;
}

const updatedFiles = readAllFiles();
Expand Down