Skip to content

Commit e340893

Browse files
authored
Merge pull request #149 from ut-code/copilot/fix-output-return-methods
Stream worker output immediately via callbacks instead of buffering in arrays
2 parents e415222 + ace27ff commit e340893

File tree

7 files changed

+129
-109
lines changed

7 files changed

+129
-109
lines changed

app/terminal/repl.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import { useEmbedContext } from "./embedContext";
1717
import { emptyMutex, langConstants, RuntimeLang, useRuntime } from "./runtime";
1818
import clsx from "clsx";
1919

20+
export type ReplOutputType = "stdout" | "stderr" | "error" | "return" | "trace" | "system";
2021
export interface ReplOutput {
21-
type: "stdout" | "stderr" | "error" | "return" | "trace" | "system"; // 出力の種類
22+
type: ReplOutputType; // 出力の種類
2223
message: string; // 出力メッセージ
2324
}
2425
export interface ReplCommand {

app/terminal/worker/jsEval.worker.ts

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -10,24 +10,20 @@ function format(...args: unknown[]): string {
1010
// https://nodejs.org/api/util.html#utilformatformat-args
1111
return args.map((a) => (typeof a === "string" ? a : inspect(a))).join(" ");
1212
}
13-
let jsOutput: ReplOutput[] = [];
13+
let currentOutputCallback: ((output: ReplOutput) => void) | null = null;
1414

1515
// Helper function to capture console output
1616
const originalConsole = self.console;
1717
self.console = {
1818
...originalConsole,
19-
log: (...args: unknown[]) => {
20-
jsOutput.push({ type: "stdout", message: format(...args) });
21-
},
22-
error: (...args: unknown[]) => {
23-
jsOutput.push({ type: "stderr", message: format(...args) });
24-
},
25-
warn: (...args: unknown[]) => {
26-
jsOutput.push({ type: "stderr", message: format(...args) });
27-
},
28-
info: (...args: unknown[]) => {
29-
jsOutput.push({ type: "stdout", message: format(...args) });
30-
},
19+
log: (...args: unknown[]) =>
20+
currentOutputCallback?.({ type: "stdout", message: format(...args) }),
21+
error: (...args: unknown[]) =>
22+
currentOutputCallback?.({ type: "stderr", message: format(...args) }),
23+
warn: (...args: unknown[]) =>
24+
currentOutputCallback?.({ type: "stderr", message: format(...args) }),
25+
info: (...args: unknown[]) =>
26+
currentOutputCallback?.({ type: "stdout", message: format(...args) }),
3127
};
3228

3329
async function init(/*_interruptBuffer?: Uint8Array*/): Promise<{
@@ -73,65 +69,68 @@ async function replLikeEval(code: string): Promise<unknown> {
7369
}
7470
}
7571

76-
async function runCode(code: string): Promise<{
77-
output: ReplOutput[];
72+
async function runCode(
73+
code: string,
74+
onOutput: (output: ReplOutput) => void
75+
): Promise<{
7876
updatedFiles: Record<string, string>;
7977
}> {
78+
currentOutputCallback = onOutput;
8079
try {
8180
const result = await replLikeEval(code);
82-
jsOutput.push({
81+
onOutput({
8382
type: "return",
8483
message: inspect(result),
8584
});
8685
} catch (e) {
8786
originalConsole.log(e);
8887
// TODO: stack trace?
8988
if (e instanceof Error) {
90-
jsOutput.push({
89+
onOutput({
9190
type: "error",
9291
message: `${e.name}: ${e.message}`,
9392
});
9493
} else {
95-
jsOutput.push({
94+
onOutput({
9695
type: "error",
9796
message: `${String(e)}`,
9897
});
9998
}
99+
} finally {
100+
currentOutputCallback = null;
100101
}
101102

102-
const output = [...jsOutput];
103-
jsOutput = []; // Clear output
104-
105-
return { output, updatedFiles: {} as Record<string, string> };
103+
return { updatedFiles: {} as Record<string, string> };
106104
}
107105

108106
function runFile(
109107
name: string,
110-
files: Record<string, string>
111-
): { output: ReplOutput[]; updatedFiles: Record<string, string> } {
108+
files: Record<string, string>,
109+
onOutput: (output: ReplOutput) => void
110+
): { updatedFiles: Record<string, string> } {
112111
// pyodide worker などと異なり、複数ファイルを読み込んでimportのようなことをするのには対応していません。
112+
currentOutputCallback = onOutput;
113113
try {
114114
self.eval(files[name]);
115115
} catch (e) {
116116
originalConsole.log(e);
117117
// TODO: stack trace?
118118
if (e instanceof Error) {
119-
jsOutput.push({
119+
onOutput({
120120
type: "error",
121121
message: `${e.name}: ${e.message}`,
122122
});
123123
} else {
124-
jsOutput.push({
124+
onOutput({
125125
type: "error",
126126
message: `${String(e)}`,
127127
});
128128
}
129+
} finally {
130+
currentOutputCallback = null;
129131
}
130132

131-
const output = [...jsOutput];
132-
jsOutput = []; // Clear output
133-
134-
return { output, updatedFiles: {} as Record<string, string> };
133+
return { updatedFiles: {} as Record<string, string> };
135134
}
136135

137136
async function checkSyntax(
@@ -162,8 +161,6 @@ async function checkSyntax(
162161

163162
async function restoreState(commands: string[]): Promise<object> {
164163
// Re-execute all previously successful commands to restore state
165-
jsOutput = []; // Clear output for restoration
166-
167164
for (const command of commands) {
168165
try {
169166
replLikeEval(command);
@@ -173,7 +170,6 @@ async function restoreState(commands: string[]): Promise<object> {
173170
}
174171
}
175172

176-
jsOutput = []; // Clear any output from restoration
177173
return {};
178174
}
179175

app/terminal/worker/pyodide.worker.ts

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ const PYODIDE_CDN = `https://cdn.jsdelivr.net/pyodide/v${pyodideVersion}/full/`;
1616
const HOME = `/home/pyodide/`;
1717

1818
let pyodide: PyodideInterface;
19-
let pyodideOutput: ReplOutput[] = [];
19+
let currentOutputCallback: ((output: ReplOutput) => void) | null = null;
2020

2121
// Helper function to read all files from the Pyodide file system
2222
function readAllFiles(): Record<string, string> {
@@ -47,32 +47,33 @@ async function init(
4747
pyodide = await (self as any).loadPyodide({ indexURL: PYODIDE_CDN });
4848

4949
pyodide.setStdout({
50-
batched: (str: string) => {
51-
pyodideOutput.push({ type: "stdout", message: str });
52-
},
50+
batched: (str: string) =>
51+
currentOutputCallback?.({ type: "stdout", message: str }),
5352
});
5453
pyodide.setStderr({
55-
batched: (str: string) => {
56-
pyodideOutput.push({ type: "stderr", message: str });
57-
},
54+
batched: (str: string) =>
55+
currentOutputCallback?.({ type: "stderr", message: str }),
5856
});
5957

6058
pyodide.setInterruptBuffer(interruptBuffer);
6159
}
6260
return { capabilities: { interrupt: "buffer" } };
6361
}
6462

65-
async function runCode(code: string): Promise<{
66-
output: ReplOutput[];
63+
async function runCode(
64+
code: string,
65+
onOutput: (output: ReplOutput) => void
66+
): Promise<{
6767
updatedFiles: Record<string, string>;
6868
}> {
6969
if (!pyodide) {
7070
throw new Error("Pyodide not initialized");
7171
}
72+
currentOutputCallback = onOutput;
7273
try {
7374
const result = await pyodide.runPythonAsync(code);
7475
if (result !== undefined) {
75-
pyodideOutput.push({
76+
onOutput({
7677
type: "return",
7778
message: String(result),
7879
});
@@ -86,7 +87,7 @@ async function runCode(code: string): Promise<{
8687
const execLineIndex = lines.findIndex((line) =>
8788
line.includes("<exec>")
8889
);
89-
pyodideOutput.push({
90+
onOutput({
9091
type: "error",
9192
message: lines
9293
.slice(0, 1)
@@ -95,33 +96,35 @@ async function runCode(code: string): Promise<{
9596
.trim(),
9697
});
9798
} else {
98-
pyodideOutput.push({
99+
onOutput({
99100
type: "error",
100101
message: `予期せぬエラー: ${e.message.trim()}`,
101102
});
102103
}
103104
} else {
104-
pyodideOutput.push({
105+
onOutput({
105106
type: "error",
106107
message: `予期せぬエラー: ${String(e).trim()}`,
107108
});
108109
}
110+
} finally {
111+
currentOutputCallback = null;
109112
}
110113

111114
const updatedFiles = readAllFiles();
112-
const output = [...pyodideOutput];
113-
pyodideOutput = []; // 出力をクリア
114115

115-
return { output, updatedFiles };
116+
return { updatedFiles };
116117
}
117118

118119
async function runFile(
119120
name: string,
120-
files: Record<string, string>
121-
): Promise<{ output: ReplOutput[]; updatedFiles: Record<string, string> }> {
121+
files: Record<string, string>,
122+
onOutput: (output: ReplOutput) => void
123+
): Promise<{ updatedFiles: Record<string, string> }> {
122124
if (!pyodide) {
123125
throw new Error("Pyodide not initialized");
124126
}
127+
currentOutputCallback = onOutput;
125128
try {
126129
// Use Pyodide FS API to write files to the file system
127130
for (const filename of Object.keys(files)) {
@@ -142,7 +145,7 @@ async function runFile(
142145
const execLineIndex = lines.findLastIndex((line) =>
143146
line.includes("<exec>")
144147
);
145-
pyodideOutput.push({
148+
onOutput({
146149
type: "error",
147150
message: lines
148151
.slice(0, 1)
@@ -151,23 +154,23 @@ async function runFile(
151154
.trim(),
152155
});
153156
} else {
154-
pyodideOutput.push({
157+
onOutput({
155158
type: "error",
156159
message: `予期せぬエラー: ${e.message.trim()}`,
157160
});
158161
}
159162
} else {
160-
pyodideOutput.push({
163+
onOutput({
161164
type: "error",
162165
message: `予期せぬエラー: ${String(e).trim()}`,
163166
});
164167
}
168+
} finally {
169+
currentOutputCallback = null;
165170
}
166171

167172
const updatedFiles = readAllFiles();
168-
const output = [...pyodideOutput];
169-
pyodideOutput = []; // 出力をクリア
170-
return { output, updatedFiles };
173+
return { updatedFiles };
171174
}
172175

173176
async function checkSyntax(

0 commit comments

Comments
 (0)