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
7 changes: 6 additions & 1 deletion app/terminal/exec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function ExecFile(props: ExecProps) {
}
},
});
const { files, clearExecResult, addExecOutput } = useEmbedContext();
const { files, clearExecResult, addExecOutput, writeFile } = useEmbedContext();

const { ready, runFiles, getCommandlineStr, runtimeInfo, interrupt } =
useRuntime(props.language);
Expand All @@ -52,6 +52,10 @@ export function ExecFile(props: ExecProps) {
clearExecResult(filenameKey);
let isFirstOutput = true;
await runFiles(props.filenames, files, (output) => {
if (output.type === "file") {
writeFile({ [output.filename]: output.content });
return;
}
addExecOutput(filenameKey, output);
if (isFirstOutput) {
// Clear "実行中です..." message only on first output
Expand All @@ -77,6 +81,7 @@ export function ExecFile(props: ExecProps) {
runFiles,
clearExecResult,
addExecOutput,
writeFile,
terminalInstanceRef,
props.language,
files,
Expand Down
7 changes: 1 addition & 6 deletions app/terminal/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import "mocha/mocha.css";
import { Fragment, useEffect, useRef, useState } from "react";
import { useWandbox } from "./wandbox/runtime";
import { RuntimeContext, RuntimeLang } from "./runtime";
import { useEmbedContext } from "./embedContext";
import { defineTests } from "./tests";
import { usePyodide } from "./worker/pyodide";
import { useRuby } from "./worker/ruby";
Expand Down Expand Up @@ -220,10 +219,6 @@ function MochaTest() {
const [mochaState, setMochaState] = useState<"idle" | "running" | "finished">(
"idle"
);
const { files } = useEmbedContext();
const filesRef = useRef(files);
filesRef.current = files;

const runTest = async () => {
if (typeof window !== "undefined") {
setMochaState("running");
Expand All @@ -234,7 +229,7 @@ function MochaTest() {

for (const lang of Object.keys(runtimeRef.current) as RuntimeLang[]) {
runtimeRef.current[lang].init?.();
defineTests(lang, runtimeRef, filesRef);
defineTests(lang, runtimeRef);
}

const runner = mocha.run();
Expand Down
12 changes: 11 additions & 1 deletion app/terminal/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export function ReplTerminal({
language,
initContent,
}: ReplComponentProps) {
const { addReplCommand, addReplOutput } = useEmbedContext();
const { addReplCommand, addReplOutput, writeFile } = useEmbedContext();

const [Prism, setPrism] = useState<typeof import("prismjs") | null>(null);
useEffect(() => {
Expand Down Expand Up @@ -229,6 +229,10 @@ export function ReplTerminal({
let executionDone = false;
await runtimeMutex.runExclusive(async () => {
await runCommand(command, (output) => {
if (output.type === "file") {
writeFile({ [output.filename]: output.content });
return;
}
if (executionDone) {
// すでに完了していて次のコマンドのプロンプトが出ている場合、その前に挿入
updateBuffer(null, () => {
Expand Down Expand Up @@ -285,6 +289,7 @@ export function ReplTerminal({
runtimeMutex,
runCommand,
handleOutput,
writeFile,
tabSize,
addReplCommand,
addReplOutput,
Expand Down Expand Up @@ -346,6 +351,10 @@ export function ReplTerminal({
for (const cmd of initCommand!) {
const outputs: ReplOutput[] = [];
await runCommand(cmd.command, (output) => {
if (output.type === "file") {
writeFile({ [output.filename]: output.content });
return;
}
outputs.push(output);
});
initCommandResult.push({
Expand Down Expand Up @@ -380,6 +389,7 @@ export function ReplTerminal({
runtimeMutex,
updateBuffer,
handleOutput,
writeFile,
termReady,
terminalInstanceRef,
Prism,
Expand Down
10 changes: 8 additions & 2 deletions app/terminal/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ import { WorkerProvider } from "./worker/runtime";
import { TypeScriptProvider, useTypeScript } from "./typescript/runtime";
import { MarkdownLang } from "@/[lang]/[pageId]/styledSyntaxHighlighter";

export interface UpdatedFile {
type: "file";
filename: string;
content: string;
}

/**
* Common runtime context interface for different languages
*
Expand All @@ -26,15 +32,15 @@ export interface RuntimeContext {
// repl
runCommand?: (
command: string,
onOutput: (output: ReplOutput) => void
onOutput: (output: ReplOutput | UpdatedFile) => void
) => Promise<void>;
checkSyntax?: (code: string) => Promise<SyntaxStatus>;
splitReplExamples?: (content: string) => ReplCommand[];
// file
runFiles: (
filenames: string[],
files: Readonly<Record<string, string>>,
onOutput: (output: ReplOutput) => void
onOutput: (output: ReplOutput | UpdatedFile) => void
) => Promise<void>;
getCommandlineStr?: (filenames: string[]) => string;
runtimeInfo?: RuntimeInfo;
Expand Down
45 changes: 27 additions & 18 deletions app/terminal/tests.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { expect } from "chai";
import { RefObject } from "react";
import { emptyMutex, RuntimeContext, RuntimeLang } from "./runtime";
import { emptyMutex, RuntimeContext, RuntimeLang, UpdatedFile } from "./runtime";
import { ReplOutput } from "./repl";

export function defineTests(
lang: RuntimeLang,
runtimeRef: RefObject<Record<RuntimeLang, RuntimeContext>>,
filesRef: RefObject<Readonly<Record<string, string>>>
runtimeRef: RefObject<Record<RuntimeLang, RuntimeContext>>
) {
describe(`${lang} Runtime`, function () {
this.timeout(
Expand Down Expand Up @@ -46,7 +45,7 @@ export function defineTests(
const outputs: ReplOutput[] = [];
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
runtimeRef.current[lang].runCommand!(printCode, (output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
})
);
console.log(`${lang} REPL stdout test: `, outputs);
Expand Down Expand Up @@ -79,7 +78,7 @@ export function defineTests(
await runtimeRef.current[lang].runCommand!(
printIntVarCode,
(output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
}
);
}
Expand Down Expand Up @@ -109,7 +108,7 @@ export function defineTests(
const outputs: ReplOutput[] = [];
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
runtimeRef.current[lang].runCommand!(errorCode, (output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
})
);
console.log(`${lang} REPL error capture test: `, outputs);
Expand Down Expand Up @@ -153,7 +152,7 @@ export function defineTests(
const outputs: ReplOutput[] = [];
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
runtimeRef.current[lang].runCommand!(printIntVarCode, (output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
})
);
console.log(`${lang} REPL interrupt recovery test: `, outputs);
Expand All @@ -176,12 +175,17 @@ export function defineTests(
if (!writeCode) {
this.skip();
}
const updatedFiles: UpdatedFile[] = [];
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
runtimeRef.current[lang].runCommand!(writeCode, () => {})
runtimeRef.current[lang].runCommand!(writeCode, (output) => {
if (output.type === "file") {
updatedFiles.push(output);
}
})
);
// wait for files to be updated
await new Promise((resolve) => setTimeout(resolve, 100));
expect(filesRef.current[targetFile]).to.equal(msg);
expect(
updatedFiles.find((f) => f.filename === targetFile)?.content
).to.equal(msg);
});
});

Expand Down Expand Up @@ -211,7 +215,7 @@ export function defineTests(
[filename]: code,
},
(output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
}
);
console.log(`${lang} single file stdout test: `, outputs);
Expand Down Expand Up @@ -247,7 +251,7 @@ export function defineTests(
[filename]: code,
},
(output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
}
);
console.log(`${lang} single file error capture test: `, outputs);
Expand Down Expand Up @@ -305,7 +309,7 @@ export function defineTests(
}
const outputs: ReplOutput[] = [];
await runtimeRef.current[lang].runFiles(execFiles, codes, (output) => {
outputs.push(output);
if (output.type !== "file") outputs.push(output);
});
console.log(`${lang} multifile stdout test: `, outputs);
expect(outputs).to.be.deep.include({ type: "stdout", message: msg });
Expand Down Expand Up @@ -333,16 +337,21 @@ export function defineTests(
if (!filename || !code) {
this.skip();
}
const updatedFiles: UpdatedFile[] = [];
await runtimeRef.current[lang].runFiles(
[filename],
{
[filename]: code,
},
() => {}
(output) => {
if (output.type === "file") {
updatedFiles.push(output);
}
}
);
// wait for files to be updated
await new Promise((resolve) => setTimeout(resolve, 100));
expect(filesRef.current[targetFile]).to.equal(msg);
expect(
updatedFiles.find((f) => f.filename === targetFile)?.content
).to.equal(msg);
});
});
});
Expand Down
21 changes: 10 additions & 11 deletions app/terminal/typescript/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import {
useMemo,
useState,
} from "react";
import { useEmbedContext } from "../embedContext";
import { ReplOutput } from "../repl";
import { RuntimeContext, RuntimeInfo } from "../runtime";
import { RuntimeContext, RuntimeInfo, UpdatedFile } from "../runtime";

export const compilerOptions: CompilerOptions = {
lib: ["ESNext", "WebWorker"],
Expand Down Expand Up @@ -93,12 +92,11 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
jsInit?.();
}, [tsInit, jsInit]);

const { writeFile } = useEmbedContext();
const runFiles = useCallback(
async (
filenames: string[],
files: Readonly<Record<string, string>>,
onOutput: (output: ReplOutput) => void
onOutput: (output: ReplOutput | UpdatedFile) => void
) => {
if (tsEnv === null || typeof window === "undefined") {
onOutput({ type: "error", message: "TypeScript is not ready yet." });
Expand Down Expand Up @@ -137,25 +135,26 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
}

const emitOutput = tsEnv.languageService.getEmitOutput(filenames[0]);
files = await writeFile(
Object.fromEntries(
emitOutput.outputFiles.map((of) => [of.name, of.text])
)
const emittedFiles: Record<string, string> = Object.fromEntries(
emitOutput.outputFiles.map((of) => [of.name, of.text])
);
for (const [filename, content] of Object.entries(emittedFiles)) {
onOutput({ type: "file", filename, content });
}

for (const filename of Object.keys(files)) {
for (const filename of Object.keys(emittedFiles)) {
tsEnv.deleteFile(filename);
}

console.log(emitOutput);
await jsEval.runFiles(
[emitOutput.outputFiles[0].name],
files,
{ ...files, ...emittedFiles },
onOutput
);
}
},
[tsEnv, writeFile, jsEval]
[tsEnv, jsEval]
);

const runtimeInfo = useMemo<RuntimeInfo>(
Expand Down
6 changes: 3 additions & 3 deletions app/terminal/wandbox/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
import useSWR from "swr";
import { compilerInfoFetcher, SelectedCompiler } from "./api";
import { cppRunFiles, selectCppCompiler } from "./cpp";
import { RuntimeContext, RuntimeInfo, RuntimeLang } from "../runtime";
import { RuntimeContext, RuntimeInfo, RuntimeLang, UpdatedFile } from "../runtime";
import { ReplOutput } from "../repl";
import { rustRunFiles, selectRustCompiler } from "./rust";

Expand All @@ -26,7 +26,7 @@ interface IWandboxContext {
) => (
filenames: string[],
files: Readonly<Record<string, string>>,
onOutput: (output: ReplOutput) => void
onOutput: (output: ReplOutput | UpdatedFile) => void
) => Promise<void>;
runtimeInfo: Record<WandboxLang, RuntimeInfo> | undefined,
}
Expand Down Expand Up @@ -70,7 +70,7 @@ export function WandboxProvider({ children }: { children: ReactNode }) {
async (
filenames: string[],
files: Readonly<Record<string, string>>,
onOutput: (output: ReplOutput) => void
onOutput: (output: ReplOutput | UpdatedFile) => void
) => {
if (!selectedCompiler) {
onOutput({ type: "error", message: "Wandbox is not ready yet." });
Expand Down
15 changes: 5 additions & 10 deletions app/terminal/worker/jsEval.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import { expose } from "comlink";
import type { ReplOutput } from "../repl";
import type { WorkerCapabilities } from "./runtime";
import type { UpdatedFile } from "../runtime";
import inspect from "object-inspect";
import { replLikeEval, checkSyntax } from "@my-code/js-eval";

Expand Down Expand Up @@ -37,10 +38,8 @@ async function init(/*_interruptBuffer?: Uint8Array*/): Promise<{

async function runCode(
code: string,
onOutput: (output: ReplOutput) => void
): Promise<{
updatedFiles: Record<string, string>;
}> {
onOutput: (output: ReplOutput | UpdatedFile) => void
): Promise<void> {
currentOutputCallback = onOutput;
try {
const result = await replLikeEval(code);
Expand All @@ -63,15 +62,13 @@ async function runCode(
});
}
}

return { updatedFiles: {} as Record<string, string> };
}

function runFile(
name: string,
files: Record<string, string>,
onOutput: (output: ReplOutput) => void
): { updatedFiles: Record<string, string> } {
onOutput: (output: ReplOutput | UpdatedFile) => void
): void {
// pyodide worker などと異なり、複数ファイルを読み込んでimportのようなことをするのには対応していません。
currentOutputCallback = onOutput;
try {
Expand All @@ -91,8 +88,6 @@ function runFile(
});
}
}

return { updatedFiles: {} as Record<string, string> };
}

async function restoreState(commands: string[]): Promise<object> {
Expand Down
Loading