Files
five/hbrtl/rawtty.go
Charles KWON OhJun 59568f3301 Five v0.9 — Harbour + Go fusion language
- Compiler: PP → Lexer → Parser → Analyzer → Gengo pipeline
- Parser: 232/236 (98%) Harbour compatibility, registry-based dispatch
- RTL: 351 Harbour-compatible functions
- RDD: DBF/NTX/CDX engines with Rushmore bitmap optimization
- Go Interop: IMPORT + pkg.Func() + obj:Method() with FastPath (15M calls/sec)
- HB_FUNC API: Full Harbour C API compatible Go bridge
- Concurrency: SPAWN/LAUNCH/GOROUTINE, <-, WATCH, PARALLEL FOR, ASYNC/AWAIT
- Extensions: Multi-return, DEFER, Slice, f-string, Nil-safe ?:, CONST
- Macro Compiler: Runtime AST parsing and evaluation
- Debugger: TUI debugger with source display, breakpoints, stepping
- FRB: Native + Pcode dual mode runtime binary
- Tests: 13 packages ALL PASS

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 09:41:50 +09:00

134 lines
3.0 KiB
Go

// Copyright (c) 2026 Charles KWON OhJun (charleskwonohjun@gmail.com)
// All rights reserved.
// Raw terminal for Five. Uses /dev/tty opened fresh each ReadKey call.
package hbrtl
import (
"os"
"syscall"
"unsafe"
)
var (
origTermios syscall.Termios
rawModeOn bool
stdinFd int
)
// InitRawTerminal sets raw mode on stdin.
func InitRawTerminal() {
if rawModeOn {
return
}
stdinFd = int(os.Stdin.Fd())
// Save original
_, _, e := syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5401, uintptr(unsafe.Pointer(&origTermios)), 0, 0, 0)
if e != 0 {
return
}
// Set raw on stdin
raw := origTermios
raw.Lflag &^= syscall.ICANON | syscall.ECHO | syscall.ISIG
raw.Oflag &^= syscall.OPOST
raw.Cc[syscall.VMIN] = 1
raw.Cc[syscall.VTIME] = 0
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5402, uintptr(unsafe.Pointer(&raw)), 0, 0, 0)
rawModeOn = true
}
// RestoreTerminal restores original terminal.
func RestoreTerminal() {
if !rawModeOn {
return
}
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(stdinFd), 0x5402, uintptr(unsafe.Pointer(&origTermios)), 0, 0, 0)
rawModeOn = false
}
// IsRawMode returns true if raw mode is active.
func IsRawMode() bool {
return rawModeOn
}
// ReadKey reads one key. Opens /dev/tty fresh each time to avoid buffered data.
func ReadKey() int {
if !rawModeOn {
InitRawTerminal()
}
os.Stdout.Sync()
// Open /dev/tty fresh — no stale data possible
fd, err := syscall.Open("/dev/tty", syscall.O_RDONLY, 0)
if err != nil {
return 27 // ESC
}
defer syscall.Close(fd)
// Set this fd to raw too
var t syscall.Termios
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5401, uintptr(unsafe.Pointer(&t)), 0, 0, 0)
t.Lflag &^= syscall.ICANON | syscall.ECHO | syscall.ISIG
t.Cc[syscall.VMIN] = 1
t.Cc[syscall.VTIME] = 0
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5402, uintptr(unsafe.Pointer(&t)), 0, 0, 0)
// Flush input buffer (TCFLSH = 0x540B, TCIFLUSH = 0)
syscall.Syscall(syscall.SYS_IOCTL, uintptr(fd), 0x540B, 0)
buf := make([]byte, 1)
n, e := syscall.Read(fd, buf)
if e != nil || n == 0 {
return 27
}
b := buf[0]
if b == 27 { // ESC — bare ESC or sequence?
// Short timeout to check for sequence
var t2 syscall.Termios
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5401, uintptr(unsafe.Pointer(&t2)), 0, 0, 0)
t2.Cc[syscall.VMIN] = 0
t2.Cc[syscall.VTIME] = 1 // 100ms
syscall.Syscall6(syscall.SYS_IOCTL, uintptr(fd), 0x5402, uintptr(unsafe.Pointer(&t2)), 0, 0, 0)
n2, _ := syscall.Read(fd, buf)
if n2 == 0 {
return 27 // bare ESC
}
if buf[0] == '[' {
n3, _ := syscall.Read(fd, buf)
if n3 == 0 {
return 27
}
switch buf[0] {
case 'A':
return 'A'
case 'B':
return 'B'
case 'C':
return 'C'
case 'D':
return 'D'
case '5':
syscall.Read(fd, buf)
return '5'
case '6':
syscall.Read(fd, buf)
return '6'
case 'H':
return 'H'
case 'F':
return 'F'
default:
return 27
}
}
return 27
}
return int(b)
}