@@ -16,6 +16,7 @@ import type { Terminal } from "@xterm/xterm";
1616import { useEmbedContext } from "./embedContext" ;
1717import { emptyMutex , langConstants , RuntimeLang , useRuntime } from "./runtime" ;
1818import clsx from "clsx" ;
19+ import { InlineCode } from "@/[docs_id]/markdown" ;
1920
2021export type ReplOutputType =
2122 | "stdout"
@@ -97,6 +98,7 @@ export function ReplTerminal({
9798 runCommand,
9899 checkSyntax,
99100 splitReplExamples,
101+ runtimeInfo,
100102 } = useRuntime ( language ) ;
101103 const { tabSize, prompt, promptMore, returnPrefix } = langConstants ( language ) ;
102104 if ( ! prompt ) {
@@ -129,6 +131,10 @@ export function ReplTerminal({
129131 // REPLのユーザー入力
130132 const inputBuffer = useRef < string [ ] > ( [ ] ) ;
131133
134+ const [ executionState , setExecutionState ] = useState < "idle" | "executing" > (
135+ "idle"
136+ ) ;
137+
132138 // inputBufferを更新し、画面に描画する
133139 const updateBuffer = useCallback (
134140 ( newBuffer : ( ( ) => string [ ] ) | null , insertBefore ?: ( ) => void ) => {
@@ -219,6 +225,7 @@ export function ReplTerminal({
219225 const command = inputBuffer . current . join ( "\n" ) . trim ( ) ;
220226 inputBuffer . current = [ ] ;
221227 const commandId = addReplCommand ( terminalId , command ) ;
228+ setExecutionState ( "executing" ) ;
222229 let executionDone = false ;
223230 await runtimeMutex . runExclusive ( async ( ) => {
224231 await runCommand ( command , ( output ) => {
@@ -233,6 +240,7 @@ export function ReplTerminal({
233240 addReplOutput ( terminalId , commandId , output ) ;
234241 } ) ;
235242 } ) ;
243+ setExecutionState ( "idle" ) ;
236244 executionDone = true ;
237245 updateBuffer ( ( ) => [ "" ] ) ;
238246 }
@@ -378,51 +386,109 @@ export function ReplTerminal({
378386 ] ) ;
379387
380388 return (
381- < div className = "bg-base-300 border border-accent border-2 shadow-md m-2 p-4 pr-1 rounded-box relative h-max" >
389+ < div className = "bg-base-300 border border-accent border-2 shadow-md m-2 rounded-box h-max" >
390+ < div className = "bg-base-200 flex items-center rounded-t-box" >
391+ < button
392+ /* daisyuiのbtnはheightがvar(--size)で固定。
393+ ここでは最小でそのサイズ、ただし親コンテナがそれより大きい場合に大きくしたい
394+ → heightを解除し、min-heightをデフォルトのサイズと同じにする */
395+ className = { clsx (
396+ "btn btn-soft btn-accent h-[unset]! min-h-(--size) self-stretch" ,
397+ "rounded-none rounded-tl-[calc(var(--radius-box)-2px)]"
398+ ) }
399+ onClick = { ( ) => {
400+ // Ctrl+C
401+ if ( terminalInstanceRef . current && runtimeInterrupt ) {
402+ runtimeInterrupt ( ) ;
403+ terminalInstanceRef . current . write ( "^C" ) ;
404+ }
405+ } }
406+ disabled = {
407+ ! termReady ||
408+ initCommandState !== "done" ||
409+ executionState !== "executing"
410+ }
411+ >
412+ ■ 停止
413+ </ button >
414+ < span className = "text-sm my-1 ml-3 text-left" >
415+ { runtimeInfo ?. prettyLangName || language } 実行環境
416+ </ span >
417+ < div className = "ml-1 tooltip tooltip-secondary tooltip-bottom" >
418+ < div className = "tooltip-content bg-secondary/60 backdrop-blur-xs" >
419+ ブラウザ上で動作する
420+ < span className = "mx-0.5" >
421+ { runtimeInfo ?. prettyLangName || language }
422+ </ span >
423+ { runtimeInfo ?. version && (
424+ < span className = "mr-0.5" > { runtimeInfo ?. version } </ span >
425+ ) }
426+ のREPL実行環境です。
427+ < br />
428+ プロンプト (< InlineCode > { prompt ?. trimEnd ( ) } </ InlineCode > )
429+ の後にコマンドを入力し、
430+ < kbd className = "kbd kbd-sm text-base-content" > Enter</ kbd >
431+ キーで実行します。
432+ < br />
433+ < kbd className = "kbd kbd-sm text-base-content" > Ctrl</ kbd > +
434+ < kbd className = "kbd kbd-sm text-base-content" > C</ kbd >
435+ または左上の停止ボタンで実行中のコマンドを中断できます。
436+ </ div >
437+ < button
438+ className = { clsx (
439+ "btn btn-xs btn-soft btn-secondary rounded-full cursor-help"
440+ ) }
441+ >
442+ ?
443+ </ button >
444+ </ div >
445+ </ div >
382446 { /*
383447 ターミナル表示の初期化が完了するまでの間、ターミナルは隠し、内容をそのまま表示する。
384448 可能な限りレイアウトが崩れないようにするため & SSRでも内容が読めるように(SEO?)という意味もある
385449 */ }
386- < pre
387- className = { clsx (
388- "font-mono overflow-auto cursor-wait" ,
389- "min-h-26" , // xterm.jsで5行分の高さ
390- initCommandState !== "initializing" && "hidden"
391- ) }
392- >
393- { initContent + "\n\n" }
394- </ pre >
395- { terminalInstanceRef . current &&
396- termReady &&
397- initCommandState === "idle" && (
398- < div
399- className = "absolute z-10 inset-0 cursor-pointer"
400- onClick = { ( ) => {
401- if ( ! runtimeReady ) {
402- hideCursor ( terminalInstanceRef . current ! ) ;
403- terminalInstanceRef . current ! . write (
404- systemMessageColor (
405- "(初期化しています...しばらくお待ちください)"
406- )
407- ) ;
408- terminalInstanceRef . current ! . focus ( ) ;
409- }
410- setInitCommandState ( "triggered" ) ;
411- } }
412- />
413- ) }
414- { ( initCommandState === "triggered" ||
415- initCommandState === "executing" ) && (
416- < div className = "absolute z-10 inset-0 cursor-wait" />
417- ) }
418- < div
419- className = { clsx (
420- initCommandState === "initializing" &&
421- /* "hidden" だとterminalがdivのサイズを取得しようとしたときにバグる*/
422- "absolute invisible"
450+ < div className = "relative p-4 pr-1 pt-2" >
451+ < pre
452+ className = { clsx (
453+ "font-mono overflow-auto cursor-wait" ,
454+ "min-h-26" , // xterm.jsで5行分の高さ
455+ initCommandState !== "initializing" && "hidden"
456+ ) }
457+ >
458+ { initContent + "\n\n" }
459+ </ pre >
460+ { terminalInstanceRef . current &&
461+ termReady &&
462+ initCommandState === "idle" && (
463+ < div
464+ className = "absolute z-10 inset-0 cursor-pointer"
465+ onClick = { ( ) => {
466+ if ( ! runtimeReady ) {
467+ hideCursor ( terminalInstanceRef . current ! ) ;
468+ terminalInstanceRef . current ! . write (
469+ systemMessageColor (
470+ "(初期化しています...しばらくお待ちください)"
471+ )
472+ ) ;
473+ terminalInstanceRef . current ! . focus ( ) ;
474+ }
475+ setInitCommandState ( "triggered" ) ;
476+ } }
477+ />
478+ ) }
479+ { ( initCommandState === "triggered" ||
480+ initCommandState === "executing" ) && (
481+ < div className = "absolute z-10 inset-0 cursor-wait" />
423482 ) }
424- ref = { terminalRef }
425- />
483+ < div
484+ className = { clsx (
485+ initCommandState === "initializing" &&
486+ /* "hidden" だとterminalがdivのサイズを取得しようとしたときにバグる*/
487+ "absolute invisible"
488+ ) }
489+ ref = { terminalRef }
490+ />
491+ </ div >
426492 </ div >
427493 ) ;
428494}
0 commit comments