# All files / src/hooks usePythonTerminal.ts

72.81% Statements 75/103
76.47% Branches 13/17
85.71% Functions 6/7
72.81% Lines 75/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
    1041x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    1x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    13x

    23x
    23x
    23x
    10x
    10x
    10x
    10x
    10x
    23x
    23x

    23x
    23x
    1x
    1x

    23x
    23x
    9x
    1x
    1x
    1x
    8x
    8x
    8x
    8x
    8x
    8x
    8x
    8x
    8x
    6x
    6x
    8x
    2x
    2x
    8x
    8x
    9x
    2x
    9x
    8x
    8x
    8x
    9x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
    23x
     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,
      }
    }