• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

atomic14 / web-serial-plotter / 18176482893

01 Oct 2025 09:42PM UTC coverage: 61.184% (-0.6%) from 61.765%
18176482893

push

github

web-flow
Merge pull request #19 from atomic14/feature/ctrl-c-ctrl-d

Allow sending of CTRL-C and CTRL-D

421 of 537 branches covered (78.4%)

Branch coverage included in aggregate %.

0 of 42 new or added lines in 2 files covered. (0.0%)

2049 of 3500 relevant lines covered (58.54%)

34.04 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/components/ConsoleInput.tsx
1
import { useState, useRef, useCallback, type KeyboardEvent } from 'react'
×
2

3
interface ConsoleInputProps {
4
  onSend: (message: string) => void
5
  disabled?: boolean
6
}
7

8
export default function ConsoleInput({ onSend, disabled = false }: ConsoleInputProps) {
×
9
  const [input, setInput] = useState('')
×
10
  const [history, setHistory] = useState<string[]>([])
×
11
  const [historyIndex, setHistoryIndex] = useState(-1)
×
12
  const inputRef = useRef<HTMLInputElement>(null)
×
13

14
  const handleSend = useCallback(() => {
×
15
    if (!input.trim() || disabled) return
×
16

17
    onSend(input)
×
18
    
19
    // Add to history if it's different from the last command
20
    if (history[history.length - 1] !== input) {
×
21
      setHistory(prev => [...prev.slice(-19), input]) // Keep last 20 commands
×
22
    }
×
23
    
24
    setInput('')
×
25
    setHistoryIndex(-1)
×
26
  }, [input, disabled, onSend, history])
×
27

NEW
28
  const sendControlChar = useCallback((char: 'C' | 'D') => {
×
NEW
29
    if (disabled) return
×
30
    
31
    // Send control character (Ctrl-C = 0x03, Ctrl-D = 0x04)
NEW
32
    const code = char === 'C' ? 3 : 4
×
NEW
33
    const controlChar = String.fromCharCode(code)
×
NEW
34
    onSend(controlChar)
×
NEW
35
  }, [disabled, onSend])
×
36

37
  const handleKeyDown = useCallback((e: KeyboardEvent<HTMLInputElement>) => {
×
38
    // Handle Ctrl-C and Ctrl-D
NEW
39
    if (e.ctrlKey || e.metaKey) {
×
NEW
40
      if (e.key === 'c' || e.key === 'C') {
×
NEW
41
        e.preventDefault()
×
NEW
42
        sendControlChar('C')
×
NEW
43
        return
×
NEW
44
      } else if (e.key === 'd' || e.key === 'D') {
×
NEW
45
        e.preventDefault()
×
NEW
46
        sendControlChar('D')
×
NEW
47
        return
×
NEW
48
      }
×
NEW
49
    }
×
50
    
51
    if (e.key === 'Enter') {
×
52
      e.preventDefault()
×
53
      handleSend()
×
54
    } else if (e.key === 'ArrowUp') {
×
55
      e.preventDefault()
×
56
      if (history.length > 0) {
×
57
        const newIndex = historyIndex === -1 
×
58
          ? history.length - 1 
×
59
          : Math.max(0, historyIndex - 1)
×
60
        setHistoryIndex(newIndex)
×
61
        setInput(history[newIndex])
×
62
      }
×
63
    } else if (e.key === 'ArrowDown') {
×
64
      e.preventDefault()
×
65
      if (historyIndex >= 0) {
×
66
        if (historyIndex === history.length - 1) {
×
67
          setHistoryIndex(-1)
×
68
          setInput('')
×
69
        } else {
×
70
          const newIndex = historyIndex + 1
×
71
          setHistoryIndex(newIndex)
×
72
          setInput(history[newIndex])
×
73
        }
×
74
      }
×
75
    }
×
NEW
76
  }, [handleSend, history, historyIndex, sendControlChar])
×
77

78
  return (
×
79
    <div className="flex gap-2 p-3 border-t border-gray-200 dark:border-neutral-800 bg-white dark:bg-neutral-950">
×
80
      <input
×
81
        ref={inputRef}
×
82
        type="text"
×
83
        value={input}
×
84
        onChange={(e) => setInput(e.target.value)}
×
85
        onKeyDown={handleKeyDown}
×
NEW
86
        placeholder={disabled ? "Not connected" : "Type command and press Enter (Ctrl-C, Ctrl-D supported)..."}
×
87
        disabled={disabled}
×
88
        className="flex-1 px-3 py-2 text-sm border border-gray-300 dark:border-neutral-700 rounded-md bg-white dark:bg-neutral-900 text-gray-900 dark:text-neutral-100 placeholder-gray-400 dark:placeholder-neutral-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 disabled:opacity-50 disabled:cursor-not-allowed"
×
89
      />
×
NEW
90
      <button
×
NEW
91
        onClick={() => sendControlChar('C')}
×
NEW
92
        disabled={disabled}
×
NEW
93
        title="Send Ctrl-C (interrupt)"
×
NEW
94
        className="px-3 py-2 text-sm font-medium text-white bg-orange-600 hover:bg-orange-700 dark:bg-orange-500 dark:hover:bg-orange-600 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-orange-600 dark:disabled:hover:bg-orange-500"
×
NEW
95
      >
×
96
        ^C
NEW
97
      </button>
×
NEW
98
      <button
×
NEW
99
        onClick={() => sendControlChar('D')}
×
NEW
100
        disabled={disabled}
×
NEW
101
        title="Send Ctrl-D (EOF)"
×
NEW
102
        className="px-3 py-2 text-sm font-medium text-white bg-purple-600 hover:bg-purple-700 dark:bg-purple-500 dark:hover:bg-purple-600 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-purple-600 dark:disabled:hover:bg-purple-500"
×
NEW
103
      >
×
104
        ^D
NEW
105
      </button>
×
106
      <button
×
107
        onClick={handleSend}
×
108
        disabled={!input.trim() || disabled}
×
109
        className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 rounded-md transition-colors disabled:opacity-50 disabled:cursor-not-allowed disabled:hover:bg-blue-600 dark:disabled:hover:bg-blue-500"
×
110
      >
×
111
        Send
112
      </button>
×
113
    </div>
×
114
  )
115
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc