@@ -9,31 +9,43 @@ export interface TerminalOutput {
99 type : "stdout" | "stderr" | "error" | "return" ; // 出力の種類
1010 message : string ; // 出力メッセージ
1111}
12+ export type SyntaxStatus = "complete" | "incomplete" | "invalid" ; // 構文チェックの結果
13+
1214interface TerminalComponentProps {
1315 ready : boolean ;
1416 initMessage : string ; // ターミナル初期化時のメッセージ
1517 prompt : string ; // プロンプト文字列
16- sendCommand : ( command : string ) => Promise < TerminalOutput [ ] > ; // コマンド実行時のコールバック関数
18+ promptMore ?: string ;
19+ // コマンド実行時のコールバック関数
20+ sendCommand : ( command : string ) => Promise < TerminalOutput [ ] > ;
21+ // 構文チェックのコールバック関数
22+ // incompleteの場合は次の行に続くことを示す
23+ checkSyntax ?: ( code : string ) => Promise < SyntaxStatus > ;
1724}
1825export function TerminalComponent ( props : TerminalComponentProps ) {
1926 const terminalRef = useRef < HTMLDivElement > ( null ! ) ;
2027 const terminalInstanceRef = useRef < Terminal | null > ( null ) ;
2128 const [ termReady , setTermReady ] = useState < boolean > ( false ) ;
22- const inputBuffer = useRef < string > ( "" ) ;
29+ const inputBuffer = useRef < string [ ] > ( [ "" ] ) ;
2330
24- const [ initMessage ] = useState < string > ( props . initMessage ) ;
25- const [ prompt ] = useState < string > ( props . prompt ) ;
26- const sendCommand = useRef < ( command : string ) => Promise < TerminalOutput [ ] > > (
27- props . sendCommand
28- ) ;
31+ const initMessage = useRef < string > ( null ! ) ;
32+ initMessage . current = props . initMessage ;
33+ const prompt = useRef < string > ( null ! ) ;
34+ prompt . current = props . prompt ;
35+ const promptMore = useRef < string > ( null ! ) ;
36+ promptMore . current = props . promptMore || props . prompt ;
37+ const sendCommand = useRef < ( command : string ) => Promise < TerminalOutput [ ] > > ( null ! ) ;
38+ sendCommand . current = props . sendCommand ;
39+ const checkSyntax = useRef < ( code : string ) => Promise < SyntaxStatus > > ( null ! ) ;
40+ checkSyntax . current = props . checkSyntax || ( async ( ) => "complete" ) ;
2941
3042 useEffect ( ( ) => {
3143 if ( terminalInstanceRef . current && termReady && props . ready ) {
3244 // 初期メッセージとプロンプトを表示
33- terminalInstanceRef . current . writeln ( initMessage ) ;
34- terminalInstanceRef . current . write ( prompt ) ;
45+ terminalInstanceRef . current . writeln ( initMessage . current ) ;
46+ terminalInstanceRef . current . write ( prompt . current ) ;
3547 }
36- } , [ initMessage , prompt , props . ready , termReady ] ) ;
48+ } , [ props . ready , termReady ] ) ;
3749
3850 // ターミナルの初期化処理
3951 useEffect ( ( ) => {
@@ -67,29 +79,38 @@ export function TerminalComponent(props: TerminalComponentProps) {
6779 }
6880 }
6981 // 出力が終わったらプロンプトを表示
70- term . write ( prompt ) ;
82+ term . write ( prompt . current ) ;
7183 } ;
7284
7385 // キー入力のハンドリング
74- const onDataHandler = term . onData ( ( key ) => {
86+ const onDataHandler = term . onData ( async ( key ) => {
7587 const code = key . charCodeAt ( 0 ) ;
7688
89+ // inputBufferは必ず1行以上ある状態にする
7790 if ( code === 13 ) {
7891 // Enter
79- term . writeln ( "" ) ;
80- if ( inputBuffer . current . trim ( ) . length > 0 ) {
81- sendCommand . current ( inputBuffer . current ) . then ( onOutput ) ;
82- inputBuffer . current = "" ;
92+ const hasContent = inputBuffer . current [ inputBuffer . current . length - 1 ] . trim ( ) . length > 0 ;
93+ const status = await checkSyntax . current ( inputBuffer . current . join ( "\n" ) ) ;
94+ if ( ( inputBuffer . current . length === 1 && status === "incomplete" ) || ( inputBuffer . current . length >= 2 && hasContent ) ) {
95+ // 次の行に続く
96+ term . writeln ( "" ) ;
97+ term . write ( promptMore . current ) ;
98+ inputBuffer . current . push ( "" ) ;
99+ } else {
100+ // 実行
101+ term . writeln ( "" ) ;
102+ const outputs = await sendCommand . current ( inputBuffer . current . join ( "\n" ) . trim ( ) ) ;
103+ onOutput ( outputs ) ;
104+ inputBuffer . current = [ "" ] ;
83105 }
84- // 新しいプロンプトは外部からのoutputを待ってから表示する
85106 } else if ( code === 127 ) {
86107 // Backspace
87- if ( inputBuffer . current . length > 0 ) {
108+ if ( inputBuffer . current [ inputBuffer . current . length - 1 ] . length > 0 ) {
88109 term . write ( "\b \b" ) ;
89- inputBuffer . current = inputBuffer . current . slice ( 0 , - 1 ) ;
110+ inputBuffer . current [ inputBuffer . current . length - 1 ] = inputBuffer . current [ inputBuffer . current . length - 1 ] . slice ( 0 , - 1 ) ;
90111 }
91112 } else if ( code >= 32 ) {
92- inputBuffer . current += key ;
113+ inputBuffer . current [ inputBuffer . current . length - 1 ] += key ;
93114 term . write ( key ) ;
94115 }
95116 } ) ;
@@ -99,7 +120,7 @@ export function TerminalComponent(props: TerminalComponentProps) {
99120 onDataHandler . dispose ( ) ;
100121 term . dispose ( ) ;
101122 } ;
102- } , [ initMessage , prompt ] ) ;
123+ } , [ initMessage , prompt , promptMore ] ) ;
103124
104125 return < div ref = { terminalRef } style = { { width : "100%" , height : "400px" } } /> ;
105126}
0 commit comments