# All files / src/hooks usePythonTerminal.ts

0% Statements 0/103
0% Branches 0/1
0% Functions 0/1
0% Lines 0/103

Press n or j to go to the next uncovered block, b, p or k for the previous block.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104

     import { useState, useEffect, useRef } from 'react'
    import { runPythonCodeInteractive, getPyodide, isPyodideReady } from '@/lib/pyodide-runner'
    import { toast } from 'sonner'

    interface TerminalLine {
      type: 'output' | 'error' | 'input-prompt' | 'input-value'
      content: string
      id: string
    }

    export function usePythonTerminal() {
      const [lines, setLines] = useState<TerminalLine[]>([])
      const [isRunning, setIsRunning] = useState(false)
      const [isInitializing, setIsInitializing] = useState(!isPyodideReady())
      const [inputValue, setInputValue] = useState('')
      const [waitingForInput, setWaitingForInput] = useState(false)
      const inputResolveRef = useRef<((value: string) => void) | null>(null)

      useEffect(() => {
        if (!isPyodideReady()) {
          setIsInitializing(true)
          getPyodide()
            .then(() => {
              setIsInitializing(false)
              toast.success('Python environment ready!')
            })
            .catch((err) => {
              setIsInitializing(false)
              toast.error('Failed to load Python environment')
              console.error(err)
            })
        }
      }, [])

      const addLine = (type: TerminalLine['type'], content: string) => {
        setLines((prev) => [
          ...prev,
          { type, content, id: `${Date.now()}-${Math.random()}` },
        ])
      }

      const handleInputPrompt = (prompt: string): Promise<string> => {
        return new Promise((resolve) => {
          addLine('input-prompt', prompt)
          setWaitingForInput(true)
          inputResolveRef.current = resolve
        })
      }

      const handleInputSubmit = (e: React.FormEvent) => {
        e.preventDefault()
        if (!waitingForInput || !inputResolveRef.current) return

        const value = inputValue
        addLine('input-value', value)
        setInputValue('')
        setWaitingForInput(false)

        const resolve = inputResolveRef.current
        inputResolveRef.current = null
        resolve(value)
      }

      const handleRun = async (code: string) => {
        if (isInitializing) {
          toast.info('Python environment is still loading...')
          return
        }

        setIsRunning(true)
        setLines([])
        setWaitingForInput(false)
        setInputValue('')

        try {
          await runPythonCodeInteractive(code, {
            onOutput: (text) => {
              addLine('output', text)
            },
            onError: (text) => {
              addLine('error', text)
            },
            onInputRequest: handleInputPrompt,
          })
        } catch (err) {
          addLine('error', err instanceof Error ? err.message : String(err))
        } finally {
          setIsRunning(false)
          setWaitingForInput(false)
        }
      }

      return {
        lines,
        isRunning,
        isInitializing,
        inputValue,
        waitingForInput,
        setInputValue,
        handleInputSubmit,
        handleRun,
      }
    }