Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 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
31 changes: 20 additions & 11 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 } from "./repl";
import { writeOutput, ReplOutput } from "./repl";
import { useEffect, useState } from "react";
import { useEmbedContext } from "./embedContext";
import { RuntimeLang, useRuntime } from "./runtime";
Expand Down Expand Up @@ -46,16 +46,25 @@ export function ExecFile(props: ExecProps) {
(async () => {
clearTerminal(terminalInstanceRef.current!);
terminalInstanceRef.current!.write(systemMessageColor("実行中です..."));
const outputs = await runFiles(props.filenames, files);
clearTerminal(terminalInstanceRef.current!);
writeOutput(
terminalInstanceRef.current!,
outputs,
false,
undefined,
null, // ファイル実行で"return"メッセージが返ってくることはないはずなので、Prismを渡す必要はない
props.language
);
const outputs: ReplOutput[] = [];
let isFirstOutput = true;
await runFiles(props.filenames, files, (output) => {
outputs.push(output);
if (isFirstOutput) {
// Clear "実行中です..." message only on first output
clearTerminal(terminalInstanceRef.current!);
isFirstOutput = false;
}
// Append only the new output
writeOutput(
terminalInstanceRef.current!,
[output],
true,
undefined,
null, // ファイル実行で"return"メッセージが返ってくることはないはずなので、Prismを渡す必要はない
props.language
);
});
// TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる
setExecResult(props.filenames.join(","), outputs);
setExecutionState("idle");
Expand Down
48 changes: 34 additions & 14 deletions app/terminal/repl.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,21 +176,19 @@ export function ReplTerminal({

// ランタイムからのoutputを描画し、inputBufferをリセット
const handleOutput = useCallback(
(outputs: ReplOutput[]) => {
(output: ReplOutput) => {
if (terminalInstanceRef.current) {
writeOutput(
terminalInstanceRef.current,
outputs,
true,
[output],
false,
Comment thread
na-trium-144 marked this conversation as resolved.
Outdated
returnPrefix,
Prism,
language
);
// 出力が終わったらプロンプトを表示
updateBuffer(() => [""]);
}
},
[Prism, updateBuffer, terminalInstanceRef, returnPrefix, language]
[Prism, terminalInstanceRef, returnPrefix, language]
);

const keyHandler = useCallback(
Expand Down Expand Up @@ -220,11 +218,20 @@ export function ReplTerminal({
terminalInstanceRef.current.writeln("");
const command = inputBuffer.current.join("\n").trim();
inputBuffer.current = [];
const outputs = await runtimeMutex.runExclusive(() =>
runCommand(command)
);
handleOutput(outputs);
addReplOutput?.(terminalId, command, outputs);
const collectedOutputs: ReplOutput[] = [];
let isFirstOutput = true;
await runtimeMutex.runExclusive(async () => {
await runCommand(command, (output) => {
collectedOutputs.push(output);
handleOutput(output);
isFirstOutput = false;
});
});
if (!isFirstOutput && terminalInstanceRef.current) {
terminalInstanceRef.current.writeln("");
}
updateBuffer(() => [""]);
addReplOutput?.(terminalId, command, collectedOutputs);
}
} else if (code === 127) {
// Backspace
Expand Down Expand Up @@ -301,8 +308,14 @@ export function ReplTerminal({
updateBuffer(() => cmd.command.split("\n"));
terminalInstanceRef.current!.writeln("");
inputBuffer.current = [];
handleOutput(cmd.output);
for (const output of cmd.output) {
handleOutput(output);
}
terminalInstanceRef.current!.writeln("");
updateBuffer(() => [""]);
}
} else {
updateBuffer(() => [""]);
}
terminalInstanceRef.current!.scrollToTop();
setInitCommandState("idle");
Expand All @@ -320,7 +333,10 @@ export function ReplTerminal({
const initCommandResult: ReplCommand[] = [];
await runtimeMutex.runExclusive(async () => {
for (const cmd of initCommand!) {
const outputs = await runCommand(cmd.command);
const outputs: ReplOutput[] = [];
await runCommand(cmd.command, (output) => {
outputs.push(output);
});
initCommandResult.push({
command: cmd.command,
output: outputs,
Expand All @@ -333,7 +349,11 @@ export function ReplTerminal({
updateBuffer(() => cmd.command.split("\n"));
terminalInstanceRef.current!.writeln("");
inputBuffer.current = [];
handleOutput(cmd.output);
for (const output of cmd.output) {
handleOutput(output);
}
terminalInstanceRef.current!.writeln("");
updateBuffer(() => [""]);
}
}
updateBuffer(() => [""]);
Expand Down
10 changes: 7 additions & 3 deletions app/terminal/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ export interface RuntimeContext {
mutex?: MutexInterface;
interrupt?: () => void;
// repl
runCommand?: (command: string) => Promise<ReplOutput[]>;
runCommand?: (
command: string,
onOutput: (output: ReplOutput) => void
) => Promise<void>;
checkSyntax?: (code: string) => Promise<SyntaxStatus>;
splitReplExamples?: (content: string) => ReplCommand[];
// file
runFiles: (
filenames: string[],
files: Readonly<Record<string, string>>
) => Promise<ReplOutput[]>;
files: Readonly<Record<string, string>>,
onOutput: (output: ReplOutput) => void
) => Promise<void>;
getCommandlineStr?: (filenames: string[]) => string;
}
export interface LangConstants {
Expand Down
24 changes: 12 additions & 12 deletions app/terminal/typescript/runtime.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,24 +91,25 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {

const { writeFile } = useEmbedContext();
const runFiles = useCallback(
async (filenames: string[], files: Readonly<Record<string, string>>) => {
async (
filenames: string[],
files: Readonly<Record<string, string>>,
onOutput: (output: ReplOutput) => void
) => {
if (tsEnv === null || typeof window === "undefined") {
return [
{ type: "error" as const, message: "TypeScript is not ready yet." },
];
onOutput({ type: "error", message: "TypeScript is not ready yet." });
return;
} else {
for (const [filename, content] of Object.entries(files)) {
tsEnv.createFile(filename, content);
}

const outputs: ReplOutput[] = [];

const ts = await import("typescript");

for (const diagnostic of tsEnv.languageService.getSyntacticDiagnostics(
filenames[0]
)) {
outputs.push({
onOutput({
type: "error",
message: ts.formatDiagnosticsWithColorAndContext([diagnostic], {
getCurrentDirectory: () => "",
Expand All @@ -121,7 +122,7 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
for (const diagnostic of tsEnv.languageService.getSemanticDiagnostics(
filenames[0]
)) {
outputs.push({
onOutput({
type: "error",
message: ts.formatDiagnosticsWithColorAndContext([diagnostic], {
getCurrentDirectory: () => "",
Expand All @@ -143,12 +144,11 @@ export function useTypeScript(jsEval: RuntimeContext): RuntimeContext {
}

console.log(emitOutput);
const jsOutputs = jsEval.runFiles(
await jsEval.runFiles(
[emitOutput.outputFiles[0].name],
files
files,
onOutput
);

return outputs.concat(await jsOutputs);
}
},
[tsEnv, writeFile, jsEval]
Expand Down
104 changes: 66 additions & 38 deletions app/terminal/wandbox/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ export interface CompileNdjsonResult {
data: string;
}

/**
* Output event with original NDJSON type and converted ReplOutput
*/
export interface CompileOutputEvent {
ndjsonType: string;
output: ReplOutput;
}

export interface CompileResult {
status: string;
signal: string;
Expand Down Expand Up @@ -102,13 +110,57 @@ interface CompileProps {
codes: Record<string, string | undefined>;
// codes: Code[];
}
export interface CompileResultWithOutput extends CompileResult {
output: ReplOutput[];
}

export async function compileAndRun(
options: CompileProps
): Promise<CompileResultWithOutput> {
options: CompileProps,
onOutput: (event: CompileOutputEvent) => void
): Promise<CompileResult> {
// Helper function to process NDJSON result and call onOutput
const processNdjsonResult = (r: CompileNdjsonResult) => {
switch (r.type) {
case "CompilerMessageS":
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
onOutput({
ndjsonType: r.type,
output: { type: "stdout", message: line }
});
}
}
break;
case "CompilerMessageE":
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
onOutput({
ndjsonType: r.type,
output: { type: "error", message: line }
});
}
}
break;
case "StdOut":
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
onOutput({
ndjsonType: r.type,
output: { type: "stdout", message: line }
});
}
}
break;
case "StdErr":
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
onOutput({
ndjsonType: r.type,
output: { type: "stderr", message: line }
});
}
}
break;
}
};

// Call the ndjson API instead of json API
const response = await fetch(new URL("/api/compile.ndjson", WANDBOX), {
method: "post",
Expand Down Expand Up @@ -158,14 +210,20 @@ export async function compileAndRun(

for (const line of lines) {
if (line.trim().length > 0) {
ndjsonResults.push(JSON.parse(line) as CompileNdjsonResult);
const r = JSON.parse(line) as CompileNdjsonResult;
ndjsonResults.push(r);
// Call onOutput in real-time as we receive data
processNdjsonResult(r);
}
}
}

// Process any remaining data in the buffer
if (buffer.trim().length > 0) {
ndjsonResults.push(JSON.parse(buffer) as CompileNdjsonResult);
const r = JSON.parse(buffer) as CompileNdjsonResult;
ndjsonResults.push(r);
// Call onOutput for remaining data
processNdjsonResult(r);
}
} finally {
reader.releaseLock();
Expand All @@ -185,9 +243,6 @@ export async function compileAndRun(
url: "",
};

// Build output array in the order messages are received
const output: ReplOutput[] = [];

for (const r of ndjsonResults) {
switch (r.type) {
case "Control":
Expand All @@ -196,42 +251,18 @@ export async function compileAndRun(
case "CompilerMessageS":
result.compiler_output += r.data;
result.compiler_message += r.data;
// Add to output in order
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
output.push({ type: "stdout", message: line });
}
}
break;
case "CompilerMessageE":
result.compiler_error += r.data;
result.compiler_message += r.data;
// Add to output in order
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
output.push({ type: "error", message: line });
}
}
break;
case "StdOut":
result.program_output += r.data;
result.program_message += r.data;
// Add to output in order
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
output.push({ type: "stdout", message: line });
}
}
break;
case "StdErr":
result.program_error += r.data;
result.program_message += r.data;
// Add to output in order
if (r.data.trim()) {
for (const line of r.data.trim().split("\n")) {
output.push({ type: "stderr", message: line });
}
}
break;
case "ExitCode":
result.status += r.data;
Expand All @@ -245,8 +276,5 @@ export async function compileAndRun(
}
}

return {
...result,
output,
};
return result;
}
Loading
Loading