Skip to content

Commit 25f3c8b

Browse files
authored
Merge pull request #153 from ut-code/copilot/change-runtimecontext-output-to-stream
Change RuntimeContext runCommand/runFiles to stream output via callback
2 parents e151630 + 2912643 commit 25f3c8b

File tree

10 files changed

+417
-374
lines changed

10 files changed

+417
-374
lines changed

app/terminal/exec.tsx

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
systemMessageColor,
88
useTerminal,
99
} from "./terminal";
10-
import { writeOutput } from "./repl";
10+
import { writeOutput, ReplOutput } from "./repl";
1111
import { useEffect, useState } from "react";
1212
import { useEmbedContext } from "./embedContext";
1313
import { RuntimeLang, useRuntime } from "./runtime";
@@ -46,16 +46,24 @@ export function ExecFile(props: ExecProps) {
4646
(async () => {
4747
clearTerminal(terminalInstanceRef.current!);
4848
terminalInstanceRef.current!.write(systemMessageColor("実行中です..."));
49-
const outputs = await runFiles(props.filenames, files);
50-
clearTerminal(terminalInstanceRef.current!);
51-
writeOutput(
52-
terminalInstanceRef.current!,
53-
outputs,
54-
false,
55-
undefined,
56-
null, // ファイル実行で"return"メッセージが返ってくることはないはずなので、Prismを渡す必要はない
57-
props.language
58-
);
49+
const outputs: ReplOutput[] = [];
50+
let isFirstOutput = true;
51+
await runFiles(props.filenames, files, (output) => {
52+
outputs.push(output);
53+
if (isFirstOutput) {
54+
// Clear "実行中です..." message only on first output
55+
clearTerminal(terminalInstanceRef.current!);
56+
isFirstOutput = false;
57+
}
58+
// Append only the new output
59+
writeOutput(
60+
terminalInstanceRef.current!,
61+
output,
62+
undefined,
63+
null, // ファイル実行で"return"メッセージが返ってくることはないはずなので、Prismを渡す必要はない
64+
props.language
65+
);
66+
});
5967
// TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる
6068
setExecResult(props.filenames.join(","), outputs);
6169
setExecutionState("idle");

app/terminal/repl.tsx

Lines changed: 60 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,13 @@ 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";
20+
export type ReplOutputType =
21+
| "stdout"
22+
| "stderr"
23+
| "error"
24+
| "return"
25+
| "trace"
26+
| "system";
2127
export interface ReplOutput {
2228
type: ReplOutputType; // 出力の種類
2329
message: string; // 出力メッセージ
@@ -30,47 +36,37 @@ export type SyntaxStatus = "complete" | "incomplete" | "invalid"; // 構文チ
3036

3137
export function writeOutput(
3238
term: Terminal,
33-
outputs: ReplOutput[],
34-
endNewLine: boolean,
39+
output: ReplOutput,
3540
returnPrefix: string | undefined,
3641
Prism: typeof import("prismjs") | null,
3742
language: RuntimeLang
3843
) {
39-
for (let i = 0; i < outputs.length; i++) {
40-
const output = outputs[i];
41-
if (i > 0) {
42-
term.writeln("");
43-
}
44-
// 出力内容に応じて色を変える
45-
const message = String(output.message).replace(/\n/g, "\r\n");
46-
switch (output.type) {
47-
case "error":
48-
term.write(chalk.red(message));
49-
break;
50-
case "trace":
51-
term.write(chalk.blue.italic(message));
52-
break;
53-
case "system":
54-
term.write(systemMessageColor(message));
55-
break;
56-
case "return":
57-
if (returnPrefix) {
58-
term.write(returnPrefix);
59-
}
60-
if (Prism) {
61-
term.write(highlightCodeToAnsi(Prism, message, language));
62-
} else {
63-
console.warn("Prism is not loaded, cannot highlight return value");
64-
term.write(message);
65-
}
66-
break;
67-
default:
68-
term.write(message);
69-
break;
70-
}
71-
}
72-
if (endNewLine && outputs.length > 0) {
73-
term.writeln("");
44+
// 出力内容に応じて色を変える
45+
const message = String(output.message).replace(/\n/g, "\r\n");
46+
switch (output.type) {
47+
case "error":
48+
term.writeln(chalk.red(message));
49+
break;
50+
case "trace":
51+
term.writeln(chalk.blue.italic(message));
52+
break;
53+
case "system":
54+
term.writeln(systemMessageColor(message));
55+
break;
56+
case "return":
57+
if (returnPrefix) {
58+
term.write(returnPrefix);
59+
}
60+
if (Prism) {
61+
term.writeln(highlightCodeToAnsi(Prism, message, language));
62+
} else {
63+
console.warn("Prism is not loaded, cannot highlight return value");
64+
term.writeln(message);
65+
}
66+
break;
67+
default:
68+
term.writeln(message);
69+
break;
7470
}
7571
}
7672

@@ -176,21 +172,18 @@ export function ReplTerminal({
176172

177173
// ランタイムからのoutputを描画し、inputBufferをリセット
178174
const handleOutput = useCallback(
179-
(outputs: ReplOutput[]) => {
175+
(output: ReplOutput) => {
180176
if (terminalInstanceRef.current) {
181177
writeOutput(
182178
terminalInstanceRef.current,
183-
outputs,
184-
true,
179+
output,
185180
returnPrefix,
186181
Prism,
187182
language
188183
);
189-
// 出力が終わったらプロンプトを表示
190-
updateBuffer(() => [""]);
191184
}
192185
},
193-
[Prism, updateBuffer, terminalInstanceRef, returnPrefix, language]
186+
[Prism, terminalInstanceRef, returnPrefix, language]
194187
);
195188

196189
const keyHandler = useCallback(
@@ -220,11 +213,15 @@ export function ReplTerminal({
220213
terminalInstanceRef.current.writeln("");
221214
const command = inputBuffer.current.join("\n").trim();
222215
inputBuffer.current = [];
223-
const outputs = await runtimeMutex.runExclusive(() =>
224-
runCommand(command)
225-
);
226-
handleOutput(outputs);
227-
addReplOutput?.(terminalId, command, outputs);
216+
const collectedOutputs: ReplOutput[] = [];
217+
await runtimeMutex.runExclusive(async () => {
218+
await runCommand(command, (output) => {
219+
collectedOutputs.push(output);
220+
handleOutput(output);
221+
});
222+
});
223+
updateBuffer(() => [""]);
224+
addReplOutput?.(terminalId, command, collectedOutputs);
228225
}
229226
} else if (code === 127) {
230227
// Backspace
@@ -301,8 +298,13 @@ export function ReplTerminal({
301298
updateBuffer(() => cmd.command.split("\n"));
302299
terminalInstanceRef.current!.writeln("");
303300
inputBuffer.current = [];
304-
handleOutput(cmd.output);
301+
for (const output of cmd.output) {
302+
handleOutput(output);
303+
}
304+
updateBuffer(() => [""]);
305305
}
306+
} else {
307+
updateBuffer(() => [""]);
306308
}
307309
terminalInstanceRef.current!.scrollToTop();
308310
setInitCommandState("idle");
@@ -320,7 +322,10 @@ export function ReplTerminal({
320322
const initCommandResult: ReplCommand[] = [];
321323
await runtimeMutex.runExclusive(async () => {
322324
for (const cmd of initCommand!) {
323-
const outputs = await runCommand(cmd.command);
325+
const outputs: ReplOutput[] = [];
326+
await runCommand(cmd.command, (output) => {
327+
outputs.push(output);
328+
});
324329
initCommandResult.push({
325330
command: cmd.command,
326331
output: outputs,
@@ -333,7 +338,10 @@ export function ReplTerminal({
333338
updateBuffer(() => cmd.command.split("\n"));
334339
terminalInstanceRef.current!.writeln("");
335340
inputBuffer.current = [];
336-
handleOutput(cmd.output);
341+
for (const output of cmd.output) {
342+
handleOutput(output);
343+
}
344+
updateBuffer(() => [""]);
337345
}
338346
}
339347
updateBuffer(() => [""]);

app/terminal/runtime.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,18 @@ export interface RuntimeContext {
2424
mutex?: MutexInterface;
2525
interrupt?: () => void;
2626
// repl
27-
runCommand?: (command: string) => Promise<ReplOutput[]>;
27+
runCommand?: (
28+
command: string,
29+
onOutput: (output: ReplOutput) => void
30+
) => Promise<void>;
2831
checkSyntax?: (code: string) => Promise<SyntaxStatus>;
2932
splitReplExamples?: (content: string) => ReplCommand[];
3033
// file
3134
runFiles: (
3235
filenames: string[],
33-
files: Readonly<Record<string, string>>
34-
) => Promise<ReplOutput[]>;
36+
files: Readonly<Record<string, string>>,
37+
onOutput: (output: ReplOutput) => void
38+
) => Promise<void>;
3539
getCommandlineStr?: (filenames: string[]) => string;
3640
}
3741
export interface LangConstants {

0 commit comments

Comments
 (0)