11"use client" ;
22
3- import chalk from "chalk" ;
43import { usePyodide } from "./python/pyodide" ;
5- import { clearTerminal , getRows , hideCursor , useTerminal } from "./terminal" ;
4+ import {
5+ clearTerminal ,
6+ getRows ,
7+ hideCursor ,
8+ systemMessageColor ,
9+ useTerminal ,
10+ } from "./terminal" ;
611import { useSectionCode } from "../[docs_id]/section" ;
712import { useWandbox } from "./wandbox/wandbox" ;
13+ import { ReplOutput , writeOutput } from "./repl" ;
14+ import { useState } from "react" ;
815
916export type ExecLang = "python" | "cpp" ;
1017
@@ -32,44 +39,25 @@ export function ExecFile(props: ExecProps) {
3239 const pyodide = usePyodide ( ) ;
3340 const wandbox = useWandbox ( ) ;
3441
42+ // 表示するコマンドライン文字列
3543 let commandline : string ;
36- let exec : ( ) => Promise < void > | void ;
44+ // trueの間 (初期化しています...) と表示される
3745 let runtimeInitializing : boolean ;
46+ // 初期化処理が必要な場合の関数
47+ let beforeExec : ( ( ) => Promise < void > ) | null = null ;
48+ // 実行中です... と表示される
49+ const [ isExecuting , setIsExecuting ] = useState < boolean > ( false ) ;
50+ // 実際に実行する関数
51+ let exec : ( ( ) => Promise < ReplOutput [ ] > ) | null = null ;
3852 switch ( props . language ) {
3953 case "python" :
4054 if ( props . filenames . length !== 1 ) {
4155 throw new Error ( "Pythonの実行にはファイル名が1つ必要です" ) ;
4256 }
4357 commandline = `python ${ props . filenames [ 0 ] } ` ;
4458 runtimeInitializing = pyodide . initializing ;
45- exec = async ( ) => {
46- if ( ! pyodide . ready ) {
47- clearTerminal ( terminalInstanceRef . current ! ) ;
48- terminalInstanceRef . current ! . write (
49- chalk . dim . bold . italic ( "(初期化しています...しばらくお待ちください)" )
50- ) ;
51- await pyodide . init ( ) ;
52- }
53- clearTerminal ( terminalInstanceRef . current ! ) ;
54- const outputs = await pyodide . runFile ( props . filenames [ 0 ] ) ;
55- for ( let i = 0 ; i < outputs . length ; i ++ ) {
56- const output = outputs [ i ] ;
57- if ( i > 0 ) {
58- terminalInstanceRef . current ! . writeln ( "" ) ;
59- }
60- // 出力内容に応じて色を変える
61- const message = String ( output . message ) . replace ( / \n / g, "\r\n" ) ;
62- switch ( output . type ) {
63- case "error" :
64- terminalInstanceRef . current ! . writeln ( chalk . red ( message ) ) ;
65- break ;
66- default :
67- terminalInstanceRef . current ! . writeln ( message ) ;
68- break ;
69- }
70- }
71- sectionContext ?. setExecResult ( props . filenames [ 0 ] , outputs ) ;
72- } ;
59+ beforeExec = pyodide . ready ? null : pyodide . init ;
60+ exec = ( ) => pyodide . runFile ( props . filenames [ 0 ] ) ;
7361 break ;
7462 case "cpp" :
7563 if ( ! props . filenames || props . filenames . length === 0 ) {
@@ -79,52 +67,47 @@ export function ExecFile(props: ExecProps) {
7967 ? `${ wandbox . cppOptions . commandline } ${ props . filenames . join ( " " ) } && ./a.out`
8068 : "" ;
8169 runtimeInitializing = false ;
82- exec = async ( ) => {
83- clearTerminal ( terminalInstanceRef . current ! ) ;
84- const namesSource = props . filenames ! . filter ( ( name ) =>
85- name . endsWith ( ".cpp" )
86- ) ;
87- const namesAdditional = props . filenames ! . filter (
88- ( name ) => ! name . endsWith ( ".cpp" )
89- ) ;
90- const outputs = await wandbox . runFiles (
91- "C++" ,
92- namesSource ,
93- namesAdditional
94- ) ;
95- for ( let i = 0 ; i < outputs . length ; i ++ ) {
96- const output = outputs [ i ] ;
97- if ( i > 0 ) {
98- terminalInstanceRef . current ! . writeln ( "" ) ;
99- }
100- // 出力内容に応じて色を変える
101- const message = String ( output . message ) . replace ( / \n / g, "\r\n" ) ;
102- switch ( output . type ) {
103- case "error" :
104- terminalInstanceRef . current ! . write ( chalk . red ( message ) ) ;
105- break ;
106- default :
107- terminalInstanceRef . current ! . write ( message ) ;
108- break ;
109- }
110- }
111- // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる
112- sectionContext ?. setExecResult ( props . filenames . join ( "," ) , outputs ) ;
113- } ;
70+ const namesSource = props . filenames ! . filter ( ( name ) =>
71+ name . endsWith ( ".cpp" )
72+ ) ;
73+ const namesAdditional = props . filenames ! . filter (
74+ ( name ) => ! name . endsWith ( ".cpp" )
75+ ) ;
76+ exec = ( ) => wandbox . runFiles ( "C++" , namesSource , namesAdditional ) ;
11477 break ;
11578 default :
11679 props . language satisfies never ;
11780 commandline = `エラー: 非対応の言語 ${ props . language } ` ;
11881 runtimeInitializing = false ;
119- exec = ( ) => undefined ;
12082 break ;
12183 }
84+
85+ const onClick = async ( ) => {
86+ if ( exec ) {
87+ if ( beforeExec ) {
88+ clearTerminal ( terminalInstanceRef . current ! ) ;
89+ terminalInstanceRef . current ! . write (
90+ systemMessageColor ( "(初期化しています...しばらくお待ちください)" )
91+ ) ;
92+ await beforeExec ( ) ;
93+ }
94+ clearTerminal ( terminalInstanceRef . current ! ) ;
95+ terminalInstanceRef . current ! . write ( systemMessageColor ( "実行中です..." ) ) ;
96+ setIsExecuting ( true ) ;
97+ const outputs = await exec ( ) ;
98+ setIsExecuting ( false ) ;
99+ clearTerminal ( terminalInstanceRef . current ! ) ;
100+ writeOutput ( terminalInstanceRef . current ! , outputs , false ) ;
101+ // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる
102+ sectionContext ?. setExecResult ( props . filenames . join ( "," ) , outputs ) ;
103+ }
104+ } ;
122105 return (
123106 < div className = "relative" >
124107 < div >
125108 < button
126109 className = "btn btn-soft btn-primary rounded-tl-lg rounded-none"
127- onClick = { exec }
110+ onClick = { onClick }
128111 disabled = { ! termReady || runtimeInitializing }
129112 >
130113 ▶ 実行
@@ -134,7 +117,7 @@ export function ExecFile(props: ExecProps) {
134117 < div className = "bg-base-300 p-4 pt-2 rounded-b-lg" >
135118 < div ref = { terminalRef } />
136119 </ div >
137- { runtimeInitializing && (
120+ { ( runtimeInitializing || isExecuting ) && (
138121 < div className = "absolute z-10 inset-0 cursor-wait" />
139122 ) }
140123 </ div >
0 commit comments