package main import ( "bufio" "fmt" "golang.org/x/term" "os" ) func isAscii(char rune) bool { ord := int(char) return ord < 0x80 } func isCtrl(char rune) bool { if !isAscii(char) { return false } ord := int(char) return ord == 0x7f || ord < 0x20 } // Return the input code of a Ctrl-key chord. func ctrlKey(char rune) rune { ord := int(char) raw := ord & 0x1f if !isCtrl(rune(raw)) { return 0 } return rune(raw) } // Put the terminal in raw mode func rawOn() *term.State { oldState, err := term.MakeRaw(int(os.Stdin.Fd())) if err != nil { panic(err) } return oldState } // Restore the terminal to canonical mode func rawOff(oldState *term.State) { err := term.Restore(int(os.Stdin.Fd()), oldState) if err != nil { panic(err) } } // Print a formatted string to stdout, with CRLF line endings for proper terminal formatting func outLn(format string, a ...interface{}) { formatted := fmt.Sprintf(format, a...) fmt.Printf("%s\r\n", formatted) } func main() { // Go to proper raw mode, but restore canonical mode at extit oldState := rawOn() defer rawOff(oldState) reader := bufio.NewReader(os.Stdin) for { char, _, err := reader.ReadRune() if err != nil { panic(err) } // Ugliest syntax structure ever? switch { case char == ctrlKey('q'): outLn("bye!") return case isCtrl(char): outLn("%d", char) default: outLn("%d ('%c')", char, char) } } }