Skip to content

Commit ee5ab63

Browse files
Copilotna-trium-144
andcommitted
refactor: pass UpdatedFile via onOutput callback instead of writeFile
Co-authored-by: na-trium-144 <100704180+na-trium-144@users.noreply.github.com>
1 parent d5f2ffe commit ee5ab63

File tree

11 files changed

+111
-95
lines changed

11 files changed

+111
-95
lines changed

app/terminal/exec.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export function ExecFile(props: ExecProps) {
3232
}
3333
},
3434
});
35-
const { files, clearExecResult, addExecOutput } = useEmbedContext();
35+
const { files, clearExecResult, addExecOutput, writeFile } = useEmbedContext();
3636

3737
const { ready, runFiles, getCommandlineStr, runtimeInfo, interrupt } =
3838
useRuntime(props.language);
@@ -52,6 +52,10 @@ export function ExecFile(props: ExecProps) {
5252
clearExecResult(filenameKey);
5353
let isFirstOutput = true;
5454
await runFiles(props.filenames, files, (output) => {
55+
if (output.type === "file") {
56+
writeFile({ [output.filename]: output.content });
57+
return;
58+
}
5559
addExecOutput(filenameKey, output);
5660
if (isFirstOutput) {
5761
// Clear "実行中です..." message only on first output
@@ -77,6 +81,7 @@ export function ExecFile(props: ExecProps) {
7781
runFiles,
7882
clearExecResult,
7983
addExecOutput,
84+
writeFile,
8085
terminalInstanceRef,
8186
props.language,
8287
files,

app/terminal/page.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import "mocha/mocha.css";
55
import { Fragment, useEffect, useRef, useState } from "react";
66
import { useWandbox } from "./wandbox/runtime";
77
import { RuntimeContext, RuntimeLang } from "./runtime";
8-
import { useEmbedContext } from "./embedContext";
98
import { defineTests } from "./tests";
109
import { usePyodide } from "./worker/pyodide";
1110
import { useRuby } from "./worker/ruby";
@@ -220,10 +219,6 @@ function MochaTest() {
220219
const [mochaState, setMochaState] = useState<"idle" | "running" | "finished">(
221220
"idle"
222221
);
223-
const { files } = useEmbedContext();
224-
const filesRef = useRef(files);
225-
filesRef.current = files;
226-
227222
const runTest = async () => {
228223
if (typeof window !== "undefined") {
229224
setMochaState("running");
@@ -234,7 +229,7 @@ function MochaTest() {
234229

235230
for (const lang of Object.keys(runtimeRef.current) as RuntimeLang[]) {
236231
runtimeRef.current[lang].init?.();
237-
defineTests(lang, runtimeRef, filesRef);
232+
defineTests(lang, runtimeRef);
238233
}
239234

240235
const runner = mocha.run();

app/terminal/repl.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export function ReplTerminal({
8282
language,
8383
initContent,
8484
}: ReplComponentProps) {
85-
const { addReplCommand, addReplOutput } = useEmbedContext();
85+
const { addReplCommand, addReplOutput, writeFile } = useEmbedContext();
8686

8787
const [Prism, setPrism] = useState<typeof import("prismjs") | null>(null);
8888
useEffect(() => {
@@ -229,6 +229,10 @@ export function ReplTerminal({
229229
let executionDone = false;
230230
await runtimeMutex.runExclusive(async () => {
231231
await runCommand(command, (output) => {
232+
if (output.type === "file") {
233+
writeFile({ [output.filename]: output.content });
234+
return;
235+
}
232236
if (executionDone) {
233237
// すでに完了していて次のコマンドのプロンプトが出ている場合、その前に挿入
234238
updateBuffer(null, () => {
@@ -285,6 +289,7 @@ export function ReplTerminal({
285289
runtimeMutex,
286290
runCommand,
287291
handleOutput,
292+
writeFile,
288293
tabSize,
289294
addReplCommand,
290295
addReplOutput,
@@ -346,6 +351,10 @@ export function ReplTerminal({
346351
for (const cmd of initCommand!) {
347352
const outputs: ReplOutput[] = [];
348353
await runCommand(cmd.command, (output) => {
354+
if (output.type === "file") {
355+
writeFile({ [output.filename]: output.content });
356+
return;
357+
}
349358
outputs.push(output);
350359
});
351360
initCommandResult.push({
@@ -380,6 +389,7 @@ export function ReplTerminal({
380389
runtimeMutex,
381390
updateBuffer,
382391
handleOutput,
392+
writeFile,
383393
termReady,
384394
terminalInstanceRef,
385395
Prism,

app/terminal/runtime.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ import { WorkerProvider } from "./worker/runtime";
1212
import { TypeScriptProvider, useTypeScript } from "./typescript/runtime";
1313
import { MarkdownLang } from "@/[lang]/[pageId]/styledSyntaxHighlighter";
1414

15+
export interface UpdatedFile {
16+
type: "file";
17+
filename: string;
18+
content: string;
19+
}
20+
1521
/**
1622
* Common runtime context interface for different languages
1723
*
@@ -26,15 +32,15 @@ export interface RuntimeContext {
2632
// repl
2733
runCommand?: (
2834
command: string,
29-
onOutput: (output: ReplOutput) => void
35+
onOutput: (output: ReplOutput | UpdatedFile) => void
3036
) => Promise<void>;
3137
checkSyntax?: (code: string) => Promise<SyntaxStatus>;
3238
splitReplExamples?: (content: string) => ReplCommand[];
3339
// file
3440
runFiles: (
3541
filenames: string[],
3642
files: Readonly<Record<string, string>>,
37-
onOutput: (output: ReplOutput) => void
43+
onOutput: (output: ReplOutput | UpdatedFile) => void
3844
) => Promise<void>;
3945
getCommandlineStr?: (filenames: string[]) => string;
4046
runtimeInfo?: RuntimeInfo;

app/terminal/tests.ts

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import { expect } from "chai";
22
import { RefObject } from "react";
3-
import { emptyMutex, RuntimeContext, RuntimeLang } from "./runtime";
3+
import { emptyMutex, RuntimeContext, RuntimeLang, UpdatedFile } from "./runtime";
44
import { ReplOutput } from "./repl";
55

66
export function defineTests(
77
lang: RuntimeLang,
8-
runtimeRef: RefObject<Record<RuntimeLang, RuntimeContext>>,
9-
filesRef: RefObject<Readonly<Record<string, string>>>
8+
runtimeRef: RefObject<Record<RuntimeLang, RuntimeContext>>
109
) {
1110
describe(`${lang} Runtime`, function () {
1211
this.timeout(
@@ -46,7 +45,7 @@ export function defineTests(
4645
const outputs: ReplOutput[] = [];
4746
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
4847
runtimeRef.current[lang].runCommand!(printCode, (output) => {
49-
outputs.push(output);
48+
if (output.type !== "file") outputs.push(output);
5049
})
5150
);
5251
console.log(`${lang} REPL stdout test: `, outputs);
@@ -79,7 +78,7 @@ export function defineTests(
7978
await runtimeRef.current[lang].runCommand!(
8079
printIntVarCode,
8180
(output) => {
82-
outputs.push(output);
81+
if (output.type !== "file") outputs.push(output);
8382
}
8483
);
8584
}
@@ -109,7 +108,7 @@ export function defineTests(
109108
const outputs: ReplOutput[] = [];
110109
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
111110
runtimeRef.current[lang].runCommand!(errorCode, (output) => {
112-
outputs.push(output);
111+
if (output.type !== "file") outputs.push(output);
113112
})
114113
);
115114
console.log(`${lang} REPL error capture test: `, outputs);
@@ -153,7 +152,7 @@ export function defineTests(
153152
const outputs: ReplOutput[] = [];
154153
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
155154
runtimeRef.current[lang].runCommand!(printIntVarCode, (output) => {
156-
outputs.push(output);
155+
if (output.type !== "file") outputs.push(output);
157156
})
158157
);
159158
console.log(`${lang} REPL interrupt recovery test: `, outputs);
@@ -176,12 +175,17 @@ export function defineTests(
176175
if (!writeCode) {
177176
this.skip();
178177
}
178+
const updatedFiles: UpdatedFile[] = [];
179179
await (runtimeRef.current[lang].mutex || emptyMutex).runExclusive(() =>
180-
runtimeRef.current[lang].runCommand!(writeCode, () => {})
180+
runtimeRef.current[lang].runCommand!(writeCode, (output) => {
181+
if (output.type === "file") {
182+
updatedFiles.push(output);
183+
}
184+
})
181185
);
182-
// wait for files to be updated
183-
await new Promise((resolve) => setTimeout(resolve, 100));
184-
expect(filesRef.current[targetFile]).to.equal(msg);
186+
expect(
187+
updatedFiles.find((f) => f.filename === targetFile)?.content
188+
).to.equal(msg);
185189
});
186190
});
187191

@@ -211,7 +215,7 @@ export function defineTests(
211215
[filename]: code,
212216
},
213217
(output) => {
214-
outputs.push(output);
218+
if (output.type !== "file") outputs.push(output);
215219
}
216220
);
217221
console.log(`${lang} single file stdout test: `, outputs);
@@ -247,7 +251,7 @@ export function defineTests(
247251
[filename]: code,
248252
},
249253
(output) => {
250-
outputs.push(output);
254+
if (output.type !== "file") outputs.push(output);
251255
}
252256
);
253257
console.log(`${lang} single file error capture test: `, outputs);
@@ -305,7 +309,7 @@ export function defineTests(
305309
}
306310
const outputs: ReplOutput[] = [];
307311
await runtimeRef.current[lang].runFiles(execFiles, codes, (output) => {
308-
outputs.push(output);
312+
if (output.type !== "file") outputs.push(output);
309313
});
310314
console.log(`${lang} multifile stdout test: `, outputs);
311315
expect(outputs).to.be.deep.include({ type: "stdout", message: msg });
@@ -333,16 +337,21 @@ export function defineTests(
333337
if (!filename || !code) {
334338
this.skip();
335339
}
340+
const updatedFiles: UpdatedFile[] = [];
336341
await runtimeRef.current[lang].runFiles(
337342
[filename],
338343
{
339344
[filename]: code,
340345
},
341-
() => {}
346+
(output) => {
347+
if (output.type === "file") {
348+
updatedFiles.push(output);
349+
}
350+
}
342351
);
343-
// wait for files to be updated
344-
await new Promise((resolve) => setTimeout(resolve, 100));
345-
expect(filesRef.current[targetFile]).to.equal(msg);
352+
expect(
353+
updatedFiles.find((f) => f.filename === targetFile)?.content
354+
).to.equal(msg);
346355
});
347356
});
348357
});

app/terminal/typescript/runtime.tsx

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,8 @@ import {
1111
useMemo,
1212
useState,
1313
} from "react";
14-
import { useEmbedContext } from "../embedContext";
1514
import { ReplOutput } from "../repl";
16-
import { RuntimeContext, RuntimeInfo } from "../runtime";
15+
import { RuntimeContext, RuntimeInfo, UpdatedFile } from "../runtime";
1716

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

96-
const { writeFile } = useEmbedContext();
9795
const runFiles = useCallback(
9896
async (
9997
filenames: string[],
10098
files: Readonly<Record<string, string>>,
101-
onOutput: (output: ReplOutput) => void
99+
onOutput: (output: ReplOutput | UpdatedFile) => void
102100
) => {
103101
if (tsEnv === null || typeof window === "undefined") {
104102
onOutput({ type: "error", message: "TypeScript is not ready yet." });
@@ -137,25 +135,26 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
137135
}
138136

139137
const emitOutput = tsEnv.languageService.getEmitOutput(filenames[0]);
140-
files = await writeFile(
141-
Object.fromEntries(
142-
emitOutput.outputFiles.map((of) => [of.name, of.text])
143-
)
138+
const emittedFiles: Record<string, string> = Object.fromEntries(
139+
emitOutput.outputFiles.map((of) => [of.name, of.text])
144140
);
141+
for (const [filename, content] of Object.entries(emittedFiles)) {
142+
onOutput({ type: "file", filename, content });
143+
}
145144

146-
for (const filename of Object.keys(files)) {
145+
for (const filename of Object.keys(emittedFiles)) {
147146
tsEnv.deleteFile(filename);
148147
}
149148

150149
console.log(emitOutput);
151150
await jsEval.runFiles(
152151
[emitOutput.outputFiles[0].name],
153-
files,
152+
{ ...files, ...emittedFiles },
154153
onOutput
155154
);
156155
}
157156
},
158-
[tsEnv, writeFile, jsEval]
157+
[tsEnv, jsEval]
159158
);
160159

161160
const runtimeInfo = useMemo<RuntimeInfo>(

app/terminal/wandbox/runtime.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
import useSWR from "swr";
1111
import { compilerInfoFetcher, SelectedCompiler } from "./api";
1212
import { cppRunFiles, selectCppCompiler } from "./cpp";
13-
import { RuntimeContext, RuntimeInfo, RuntimeLang } from "../runtime";
13+
import { RuntimeContext, RuntimeInfo, RuntimeLang, UpdatedFile } from "../runtime";
1414
import { ReplOutput } from "../repl";
1515
import { rustRunFiles, selectRustCompiler } from "./rust";
1616

@@ -26,7 +26,7 @@ interface IWandboxContext {
2626
) => (
2727
filenames: string[],
2828
files: Readonly<Record<string, string>>,
29-
onOutput: (output: ReplOutput) => void
29+
onOutput: (output: ReplOutput | UpdatedFile) => void
3030
) => Promise<void>;
3131
runtimeInfo: Record<WandboxLang, RuntimeInfo> | undefined,
3232
}
@@ -70,7 +70,7 @@ export function WandboxProvider({ children }: { children: ReactNode }) {
7070
async (
7171
filenames: string[],
7272
files: Readonly<Record<string, string>>,
73-
onOutput: (output: ReplOutput) => void
73+
onOutput: (output: ReplOutput | UpdatedFile) => void
7474
) => {
7575
if (!selectedCompiler) {
7676
onOutput({ type: "error", message: "Wandbox is not ready yet." });

app/terminal/worker/jsEval.worker.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { expose } from "comlink";
44
import type { ReplOutput } from "../repl";
55
import type { WorkerCapabilities } from "./runtime";
6+
import type { UpdatedFile } from "../runtime";
67
import inspect from "object-inspect";
78
import { replLikeEval, checkSyntax } from "@my-code/js-eval";
89

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

3839
async function runCode(
3940
code: string,
40-
onOutput: (output: ReplOutput) => void
41-
): Promise<{
42-
updatedFiles: Record<string, string>;
43-
}> {
41+
onOutput: (output: ReplOutput | UpdatedFile) => void
42+
): Promise<void> {
4443
currentOutputCallback = onOutput;
4544
try {
4645
const result = await replLikeEval(code);
@@ -63,15 +62,13 @@ async function runCode(
6362
});
6463
}
6564
}
66-
67-
return { updatedFiles: {} as Record<string, string> };
6865
}
6966

7067
function runFile(
7168
name: string,
7269
files: Record<string, string>,
73-
onOutput: (output: ReplOutput) => void
74-
): { updatedFiles: Record<string, string> } {
70+
onOutput: (output: ReplOutput | UpdatedFile) => void
71+
): void {
7572
// pyodide worker などと異なり、複数ファイルを読み込んでimportのようなことをするのには対応していません。
7673
currentOutputCallback = onOutput;
7774
try {
@@ -91,8 +88,6 @@ function runFile(
9188
});
9289
}
9390
}
94-
95-
return { updatedFiles: {} as Record<string, string> };
9691
}
9792

9893
async function restoreState(commands: string[]): Promise<object> {

0 commit comments

Comments
 (0)