pax_global_header00006660000000000000000000000064146474606350014531gustar00rootroot0000000000000052 comment=9ff45591957ea589b7e5acd5f307af2ab3c1e158 go-tty-0.0.7/000077500000000000000000000000001464746063500127605ustar00rootroot00000000000000go-tty-0.0.7/.travis.yml000066400000000000000000000000451464746063500150700ustar00rootroot00000000000000language: go sudo: false go: - tip go-tty-0.0.7/LICENSE000066400000000000000000000020751464746063500137710ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2018 Yasuhiro Matsumoto Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. go-tty-0.0.7/README.md000066400000000000000000000011621464746063500142370ustar00rootroot00000000000000# go-tty Simple tty utility ## Usage ```go tty, err := tty.Open() if err != nil { log.Fatal(err) } defer tty.Close() for { r, err := tty.ReadRune() if err != nil { log.Fatal(err) } // handle key event } ``` if you are on windows and want to display ANSI colors, use go-colorable. ```go tty, err := tty.Open() if err != nil { log.Fatal(err) } defer tty.Close() out := colorable.NewColorable(tty.Output()) fmt.Fprintln(out, "\x1b[2J") ``` ## Installation ``` $ go get github.com/mattn/go-tty ``` ## License MIT ## Author Yasuhiro Matsumoto (a.k.a mattn) go-tty-0.0.7/_example/000077500000000000000000000000001464746063500145525ustar00rootroot00000000000000go-tty-0.0.7/_example/password.go000066400000000000000000000007211464746063500167430ustar00rootroot00000000000000// +build ignore package main import ( "encoding/base64" "fmt" "github.com/mattn/go-tty" ) func main() { tty, err := tty.Open() defer tty.Close() fmt.Print("Username: ") username, err := tty.ReadString() if err != nil { println("canceled") return } fmt.Print("Password: ") password, err := tty.ReadPassword() if err != nil { println("canceled") return } fmt.Println(base64.StdEncoding.EncodeToString([]byte(username + ":" + password))) } go-tty-0.0.7/_example/readline.go000066400000000000000000000004371464746063500166700ustar00rootroot00000000000000// +build ignore package main import ( "log" "github.com/mattn/go-tty" "github.com/mattn/go-tty/ttyutil" ) func main() { t, err := tty.Open() if err != nil { log.Fatal(err) } defer t.Close() s, err := ttyutil.ReadLine(t) if err != nil { log.Fatal(err) } println(s) } go-tty-0.0.7/_example/simple.go000066400000000000000000000010441464746063500163710ustar00rootroot00000000000000// +build ignore package main import ( "fmt" "log" "github.com/mattn/go-tty" ) func main() { t, err := tty.Open() if err != nil { log.Fatal(err) } defer t.Close() go func() { for ws := range t.SIGWINCH() { fmt.Println("Resized", ws.W, ws.H) } }() clean,err := t.Raw() if err != nil { log.Fatal(err) } defer clean() fmt.Println("Hit any key") for { r, err := t.ReadRune() if err != nil { log.Fatal(err) } if r == 0 { continue } fmt.Printf("0x%X: %c\n", r, r) if !t.Buffered() { break } } } go-tty-0.0.7/go.mod000066400000000000000000000003641464746063500140710ustar00rootroot00000000000000module github.com/mattn/go-tty go 1.18 require ( github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-runewidth v0.0.16 golang.org/x/sys v0.22.0 ) require github.com/rivo/uniseg v0.4.7 // indirect go-tty-0.0.7/go.sum000066400000000000000000000022701464746063500141140ustar00rootroot00000000000000github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= go-tty-0.0.7/tty.go000066400000000000000000000042741464746063500141360ustar00rootroot00000000000000package tty import ( "os" "strings" "unicode" ) var linefeed = []byte{'\n'} func Open() (*TTY, error) { return open("/dev/tty") } func OpenDevice(path string) (*TTY, error) { return open(path) } func (tty *TTY) Raw() (func() error, error) { return tty.raw() } func (tty *TTY) MustRaw() func() error { f, err := tty.raw() if err != nil { panic(err.Error()) } return f } func (tty *TTY) Buffered() bool { return tty.buffered() } func (tty *TTY) ReadRune() (rune, error) { return tty.readRune() } func (tty *TTY) Close() error { return tty.close() } func (tty *TTY) Size() (int, int, error) { return tty.size() } func (tty *TTY) SizePixel() (int, int, int, int, error) { return tty.sizePixel() } func (tty *TTY) Input() *os.File { return tty.input() } func (tty *TTY) Output() *os.File { return tty.output() } // Display types. const ( displayNone = iota displayRune displayMask ) func (tty *TTY) readString(displayType int) (string, error) { rs := []rune{} loop: for { r, err := tty.readRune() if err != nil { return "", err } switch r { case 13: break loop case 8, 127: if len(rs) > 0 { rs = rs[:len(rs)-1] if displayType != displayNone { tty.Output().Write([]byte("\b \b")) } } default: if unicode.IsPrint(r) { rs = append(rs, r) switch displayType { case displayRune: tty.Output().Write([]byte(string(r))) case displayMask: tty.Output().Write([]byte{'*'}) } } } } return string(rs), nil } func (tty *TTY) ReadString() (string, error) { defer tty.Output().Write(linefeed) return tty.readString(displayRune) } func (tty *TTY) ReadPassword() (string, error) { defer tty.Output().Write(linefeed) return tty.readString(displayMask) } func (tty *TTY) ReadPasswordNoEcho() (string, error) { defer tty.Output().Write(linefeed) return tty.readString(displayNone) } func (tty *TTY) ReadPasswordClear() (string, error) { s, err := tty.readString(displayMask) tty.Output().Write([]byte( strings.Repeat("\b", len(s)) + strings.Repeat(" ", len(s)) + strings.Repeat("\b", len(s)))) return s, err } type WINSIZE struct { W int H int } func (tty *TTY) SIGWINCH() <-chan WINSIZE { return tty.sigwinch() } go-tty-0.0.7/tty_bsd.go000066400000000000000000000003641464746063500147620ustar00rootroot00000000000000//go:build darwin || dragonfly || freebsd || netbsd || openbsd // +build darwin dragonfly freebsd netbsd openbsd package tty import ( "golang.org/x/sys/unix" ) const ( ioctlReadTermios = unix.TIOCGETA ioctlWriteTermios = unix.TIOCSETA ) go-tty-0.0.7/tty_linux.go000066400000000000000000000002401464746063500153420ustar00rootroot00000000000000//go:build linux // +build linux package tty import ( "golang.org/x/sys/unix" ) const ( ioctlReadTermios = unix.TCGETS ioctlWriteTermios = unix.TCSETS ) go-tty-0.0.7/tty_plan9.go000066400000000000000000000021341464746063500152320ustar00rootroot00000000000000package tty import ( "bufio" "errors" "os" "syscall" ) type TTY struct { in *os.File bin *bufio.Reader out *os.File } func open(path string) (*TTY, error) { tty := new(TTY) in, err := os.Open("/dev/cons") if err != nil { return nil, err } tty.in = in tty.bin = bufio.NewReader(in) out, err := os.OpenFile("/dev/cons", syscall.O_WRONLY, 0) if err != nil { return nil, err } tty.out = out return tty, nil } func (tty *TTY) buffered() bool { return tty.bin.Buffered() > 0 } func (tty *TTY) readRune() (rune, error) { r, _, err := tty.bin.ReadRune() return r, err } func (tty *TTY) close() (err error) { if err2 := tty.in.Close(); err2 != nil { err = err2 } if err2 := tty.out.Close(); err2 != nil { err = err2 } return } func (tty *TTY) size() (int, int, error) { return 80, 24, nil } func (tty *TTY) sizePixel() (int, int, int, int, error) { x, y, _ := tty.size() return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Plan 9") } func (tty *TTY) input() *os.File { return tty.in } func (tty *TTY) output() *os.File { return tty.out } go-tty-0.0.7/tty_solaris.go000066400000000000000000000002441464746063500156630ustar00rootroot00000000000000//go:build solaris // +build solaris package tty import ( "golang.org/x/sys/unix" ) const ( ioctlReadTermios = unix.TCGETS ioctlWriteTermios = unix.TCSETS ) go-tty-0.0.7/tty_unix.go000066400000000000000000000061121464746063500151720ustar00rootroot00000000000000//go:build !windows && !plan9 // +build !windows,!plan9 package tty import ( "bufio" "os" "os/signal" "golang.org/x/sys/unix" ) type TTY struct { in *os.File bin *bufio.Reader out *os.File termios unix.Termios ss chan os.Signal } func open(path string) (*TTY, error) { tty := new(TTY) in, err := os.Open(path) if err != nil { return nil, err } tty.in = in tty.bin = bufio.NewReader(in) out, err := os.OpenFile(path, unix.O_WRONLY, 0) if err != nil { return nil, err } tty.out = out termios, err := unix.IoctlGetTermios(int(tty.in.Fd()), ioctlReadTermios) if err != nil { return nil, err } tty.termios = *termios termios.Iflag &^= unix.ISTRIP | unix.INLCR | unix.ICRNL | unix.IGNCR | unix.IXOFF termios.Lflag &^= unix.ECHO | unix.ICANON /*| unix.ISIG*/ termios.Cc[unix.VMIN] = 1 termios.Cc[unix.VTIME] = 0 if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil { return nil, err } tty.ss = make(chan os.Signal, 1) return tty, nil } func (tty *TTY) buffered() bool { return tty.bin.Buffered() > 0 } func (tty *TTY) readRune() (rune, error) { r, _, err := tty.bin.ReadRune() return r, err } func (tty *TTY) close() error { if tty.out == nil { return nil } signal.Stop(tty.ss) close(tty.ss) err1 := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, &tty.termios) err2 := tty.out.Close() tty.out = nil if err1 != nil { return err1 } return err2 } func (tty *TTY) size() (int, int, error) { x, y, _, _, err := tty.sizePixel() return x, y, err } func (tty *TTY) sizePixel() (int, int, int, int, error) { ws, err := unix.IoctlGetWinsize(int(tty.out.Fd()), unix.TIOCGWINSZ) if err != nil { return -1, -1, -1, -1, err } return int(ws.Col), int(ws.Row), int(ws.Xpixel), int(ws.Ypixel), nil } func (tty *TTY) input() *os.File { return tty.in } func (tty *TTY) output() *os.File { return tty.out } func (tty *TTY) raw() (func() error, error) { termios, err := unix.IoctlGetTermios(int(tty.in.Fd()), ioctlReadTermios) if err != nil { return nil, err } backup := *termios termios.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON termios.Oflag &^= unix.OPOST termios.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN termios.Cflag &^= unix.CSIZE | unix.PARENB termios.Cflag |= unix.CS8 termios.Cc[unix.VMIN] = 1 termios.Cc[unix.VTIME] = 0 if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, termios); err != nil { return nil, err } return func() error { if err := unix.IoctlSetTermios(int(tty.in.Fd()), ioctlWriteTermios, &backup); err != nil { return err } return nil }, nil } func (tty *TTY) sigwinch() <-chan WINSIZE { signal.Notify(tty.ss, unix.SIGWINCH) ws := make(chan WINSIZE) go func() { defer close(ws) for sig := range tty.ss { if sig != unix.SIGWINCH { continue } w, h, err := tty.size() if err != nil { continue } // send but do not block for it select { case ws <- WINSIZE{W: w, H: h}: default: } } }() return ws } go-tty-0.0.7/tty_windows.go000066400000000000000000000234201464746063500157020ustar00rootroot00000000000000//go:build windows // +build windows package tty import ( "context" "errors" "os" "syscall" "unsafe" "github.com/mattn/go-isatty" ) const ( rightAltPressed = 1 leftAltPressed = 2 rightCtrlPressed = 4 leftCtrlPressed = 8 shiftPressed = 0x0010 ctrlPressed = rightCtrlPressed | leftCtrlPressed altPressed = rightAltPressed | leftAltPressed ) const ( enableProcessedInput = 0x1 enableLineInput = 0x2 enableEchoInput = 0x4 enableWindowInput = 0x8 enableMouseInput = 0x10 enableInsertMode = 0x20 enableQuickEditMode = 0x40 enableExtendedFlag = 0x80 enableProcessedOutput = 1 enableWrapAtEolOutput = 2 keyEvent = 0x1 mouseEvent = 0x2 windowBufferSizeEvent = 0x4 ) var kernel32 = syscall.NewLazyDLL("kernel32.dll") var ( procAllocConsole = kernel32.NewProc("AllocConsole") procSetStdHandle = kernel32.NewProc("SetStdHandle") procGetStdHandle = kernel32.NewProc("GetStdHandle") procSetConsoleScreenBufferSize = kernel32.NewProc("SetConsoleScreenBufferSize") procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer") procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") procWriteConsoleOutputCharacter = kernel32.NewProc("WriteConsoleOutputCharacterW") procWriteConsoleOutputAttribute = kernel32.NewProc("WriteConsoleOutputAttribute") procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW") procGetConsoleMode = kernel32.NewProc("GetConsoleMode") procSetConsoleMode = kernel32.NewProc("SetConsoleMode") procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") procScrollConsoleScreenBuffer = kernel32.NewProc("ScrollConsoleScreenBufferW") ) type wchar uint16 type short int16 type dword uint32 type word uint16 type coord struct { x short y short } type smallRect struct { left short top short right short bottom short } type consoleScreenBufferInfo struct { size coord cursorPosition coord attributes word window smallRect maximumWindowSize coord } type consoleCursorInfo struct { size dword visible int32 } type inputRecord struct { eventType word _ [2]byte event [16]byte } type keyEventRecord struct { keyDown int32 repeatCount word virtualKeyCode word virtualScanCode word unicodeChar wchar controlKeyState dword } type windowBufferSizeRecord struct { size coord } type mouseEventRecord struct { mousePos coord buttonState dword controlKeyState dword eventFlags dword } type charInfo struct { unicodeChar wchar attributes word } type TTY struct { in *os.File out *os.File st uint32 rs []rune ws chan WINSIZE sigwinchCtx context.Context sigwinchCtxCancel context.CancelFunc readNextKeyUp bool } func readConsoleInput(fd uintptr, record *inputRecord) (err error) { var w uint32 r1, _, err := procReadConsoleInput.Call(fd, uintptr(unsafe.Pointer(record)), 1, uintptr(unsafe.Pointer(&w))) if r1 == 0 { return err } return nil } func open(path string) (*TTY, error) { tty := new(TTY) if false && isatty.IsTerminal(os.Stdin.Fd()) { tty.in = os.Stdin } else { in, err := syscall.Open("CONIN$", syscall.O_RDWR, 0) if err != nil { return nil, err } tty.in = os.NewFile(uintptr(in), "/dev/tty") } if isatty.IsTerminal(os.Stdout.Fd()) { tty.out = os.Stdout } else { procAllocConsole.Call() out, err := syscall.Open("CONOUT$", syscall.O_RDWR, 0) if err != nil { return nil, err } tty.out = os.NewFile(uintptr(out), "/dev/tty") } h := tty.in.Fd() var st uint32 r1, _, err := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&st))) if r1 == 0 { return nil, err } tty.st = st st &^= enableEchoInput st &^= enableInsertMode st &^= enableLineInput st &^= enableMouseInput st &^= enableWindowInput st &^= enableExtendedFlag st &^= enableQuickEditMode // ignore error procSetConsoleMode.Call(h, uintptr(st)) tty.ws = make(chan WINSIZE) tty.sigwinchCtx, tty.sigwinchCtxCancel = context.WithCancel(context.Background()) return tty, nil } func (tty *TTY) buffered() bool { return len(tty.rs) > 0 } func (tty *TTY) readRune() (rune, error) { if len(tty.rs) > 0 { r := tty.rs[0] tty.rs = tty.rs[1:] return r, nil } var ir inputRecord err := readConsoleInput(tty.in.Fd(), &ir) if err != nil { return 0, err } switch ir.eventType { case windowBufferSizeEvent: wr := (*windowBufferSizeRecord)(unsafe.Pointer(&ir.event)) ws := WINSIZE{ W: int(wr.size.x), H: int(wr.size.y), } if err := tty.sigwinchCtx.Err(); err != nil { // closing // the following select might panic without this guard close return 0, err } select { case tty.ws <- ws: case <-tty.sigwinchCtx.Done(): return 0, tty.sigwinchCtx.Err() default: return 0, nil // no one is currently trying to read } case keyEvent: kr := (*keyEventRecord)(unsafe.Pointer(&ir.event)) if kr.keyDown == 0 { if kr.unicodeChar != 0 && tty.readNextKeyUp { tty.readNextKeyUp = false if 0x2000 <= kr.unicodeChar && kr.unicodeChar < 0x3000 { return rune(kr.unicodeChar), nil } } } else { if kr.controlKeyState&altPressed != 0 && kr.unicodeChar > 0 { tty.rs = []rune{rune(kr.unicodeChar)} return rune(0x1b), nil } if kr.unicodeChar > 0 { if kr.controlKeyState&shiftPressed != 0 { switch kr.unicodeChar { case 0x09: tty.rs = []rune{0x5b, 0x5a} return rune(0x1b), nil } } return rune(kr.unicodeChar), nil } vk := kr.virtualKeyCode if kr.controlKeyState&ctrlPressed != 0 { switch vk { case 0x21: // ctrl-page-up tty.rs = []rune{0x5b, 0x35, 0x3B, 0x35, 0x7e} return rune(0x1b), nil case 0x22: // ctrl-page-down tty.rs = []rune{0x5b, 0x36, 0x3B, 0x35, 0x7e} return rune(0x1b), nil case 0x23: // ctrl-end tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x46} return rune(0x1b), nil case 0x24: // ctrl-home tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x48} return rune(0x1b), nil case 0x25: // ctrl-left tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x44} return rune(0x1b), nil case 0x26: // ctrl-up tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x41} return rune(0x1b), nil case 0x27: // ctrl-right tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x43} return rune(0x1b), nil case 0x28: // ctrl-down tty.rs = []rune{0x5b, 0x31, 0x3B, 0x35, 0x42} return rune(0x1b), nil case 0x2e: // ctrl-delete tty.rs = []rune{0x5b, 0x33, 0x3B, 0x35, 0x7e} return rune(0x1b), nil } } switch vk { case 0x12: // menu if kr.controlKeyState&leftAltPressed != 0 { tty.readNextKeyUp = true } return 0, nil case 0x21: // page-up tty.rs = []rune{0x5b, 0x35, 0x7e} return rune(0x1b), nil case 0x22: // page-down tty.rs = []rune{0x5b, 0x36, 0x7e} return rune(0x1b), nil case 0x23: // end tty.rs = []rune{0x5b, 0x46} return rune(0x1b), nil case 0x24: // home tty.rs = []rune{0x5b, 0x48} return rune(0x1b), nil case 0x25: // left tty.rs = []rune{0x5b, 0x44} return rune(0x1b), nil case 0x26: // up tty.rs = []rune{0x5b, 0x41} return rune(0x1b), nil case 0x27: // right tty.rs = []rune{0x5b, 0x43} return rune(0x1b), nil case 0x28: // down tty.rs = []rune{0x5b, 0x42} return rune(0x1b), nil case 0x2D: // Insert tty.rs = []rune{0x5b, 0x32, 0x7e} return rune(0x1b), nil case 0x2e: // delete tty.rs = []rune{0x5b, 0x33, 0x7e} return rune(0x1b), nil case 0x70, 0x71, 0x72, 0x73: // F1,F2,F3,F4 tty.rs = []rune{0x5b, 0x4f, rune(vk) - 0x20} return rune(0x1b), nil case 0x074, 0x75, 0x76, 0x77: // F5,F6,F7,F8 tty.rs = []rune{0x5b, 0x31, rune(vk) - 0x3f, 0x7e} return rune(0x1b), nil case 0x78, 0x79: // F9,F10 tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x48, 0x7e} return rune(0x1b), nil case 0x7a, 0x7b: // F11,F12 tty.rs = []rune{0x5b, 0x32, rune(vk) - 0x47, 0x7e} return rune(0x1b), nil } return 0, nil } } return 0, nil } func (tty *TTY) close() error { procSetConsoleMode.Call(tty.in.Fd(), uintptr(tty.st)) tty.sigwinchCtxCancel() close(tty.ws) return nil } func (tty *TTY) size() (int, int, error) { var csbi consoleScreenBufferInfo r1, _, err := procGetConsoleScreenBufferInfo.Call(tty.out.Fd(), uintptr(unsafe.Pointer(&csbi))) if r1 == 0 { return 0, 0, err } return int(csbi.window.right - csbi.window.left + 1), int(csbi.window.bottom - csbi.window.top + 1), nil } func (tty *TTY) sizePixel() (int, int, int, int, error) { x, y, err := tty.size() if err != nil { x = -1 y = -1 } return x, y, -1, -1, errors.New("no implemented method for querying size in pixels on Windows") } func (tty *TTY) input() *os.File { return tty.in } func (tty *TTY) output() *os.File { return tty.out } func (tty *TTY) raw() (func() error, error) { var st uint32 r1, _, err := procGetConsoleMode.Call(tty.in.Fd(), uintptr(unsafe.Pointer(&st))) if r1 == 0 { return nil, err } mode := st &^ (enableEchoInput | enableProcessedInput | enableLineInput | enableProcessedOutput) r1, _, err = procSetConsoleMode.Call(tty.in.Fd(), uintptr(mode)) if r1 == 0 { return nil, err } return func() error { r1, _, err := procSetConsoleMode.Call(tty.in.Fd(), uintptr(st)) if r1 == 0 { return err } return nil }, nil } func (tty *TTY) sigwinch() <-chan WINSIZE { return tty.ws } go-tty-0.0.7/ttyutil/000077500000000000000000000000001464746063500144765ustar00rootroot00000000000000go-tty-0.0.7/ttyutil/readline.go000066400000000000000000000071631464746063500166170ustar00rootroot00000000000000package ttyutil import ( "bytes" "fmt" "io" "os" "os/signal" "github.com/mattn/go-colorable" "github.com/mattn/go-runewidth" "github.com/mattn/go-tty" ) type ctx struct { w io.Writer input []rune last []rune prompt string cursor_x int old_row int old_crow int size int } func (c *ctx) redraw(dirty bool, passwordChar rune) error { var buf bytes.Buffer buf.WriteString("\x1b[5>h") buf.WriteString("\x1b[1G") if dirty { buf.WriteString("\x1b[0K") } for i := 0; i < c.old_row-c.old_crow; i++ { buf.WriteString("\x1b[B") } for i := 0; i < c.old_row; i++ { if dirty { buf.WriteString("\x1b[2K") } buf.WriteString("\x1b[A") } var rs []rune if passwordChar != 0 { for i := 0; i < len(c.input); i++ { rs = append(rs, passwordChar) } } else { rs = c.input } ccol, crow, col, row := -1, 0, 0, 0 plen := len([]rune(c.prompt)) for i, r := range []rune(c.prompt + string(rs)) { if i == plen+c.cursor_x { ccol = col crow = row } rw := runewidth.RuneWidth(r) if col+rw > c.size { col = 0 row++ if dirty { buf.WriteString("\n\r\x1b[0K") } } if dirty { buf.WriteString(string(r)) } col += rw } if dirty { buf.WriteString("\x1b[1G") for i := 0; i < row; i++ { buf.WriteString("\x1b[A") } } if ccol == -1 { ccol = col crow = row } for i := 0; i < crow; i++ { buf.WriteString("\x1b[B") } buf.WriteString(fmt.Sprintf("\x1b[%dG", ccol+1)) buf.WriteString("\x1b[5>l") io.Copy(c.w, &buf) c.old_row = row c.old_crow = crow return nil } func ReadLine(tty *tty.TTY) (string, error) { c := new(ctx) c.w = colorable.NewColorableStdout() quit := false sc := make(chan os.Signal, 1) signal.Notify(sc, os.Interrupt) go func() { <-sc c.input = nil quit = true }() c.size = 80 dirty := true loop: for !quit { err := c.redraw(dirty, 0) if err != nil { return "", err } dirty = false r, err := tty.ReadRune() if err != nil { break } switch r { case 0: case 1: // CTRL-A c.cursor_x = 0 case 2: // CTRL-B if c.cursor_x > 0 { c.cursor_x-- } case 3: // BREAK return "", nil case 4: // CTRL-D if len(c.input) > 0 { continue } return "", io.EOF case 5: // CTRL-E c.cursor_x = len(c.input) case 6: // CTRL-F if c.cursor_x < len(c.input) { c.cursor_x++ } case 8, 0x7F: // BS if c.cursor_x > 0 { c.input = append(c.input[0:c.cursor_x-1], c.input[c.cursor_x:len(c.input)]...) c.cursor_x-- dirty = true } case 27: if !tty.Buffered() { return "", io.EOF } r, err = tty.ReadRune() if err == nil && r == 0x5b { r, err = tty.ReadRune() if err != nil { panic(err) } switch r { case 'C': if c.cursor_x < len(c.input) { c.cursor_x++ } case 'D': if c.cursor_x > 0 { c.cursor_x-- } } } case 10: // LF break loop case 11: // CTRL-K c.input = c.input[:c.cursor_x] dirty = true case 12: // CTRL-L dirty = true case 13: // CR break loop case 21: // CTRL-U c.input = c.input[c.cursor_x:] c.cursor_x = 0 dirty = true case 23: // CTRL-W for i := len(c.input) - 1; i >= 0; i-- { if i == 0 || c.input[i] == ' ' || c.input[i] == '\t' { c.input = append(c.input[:i], c.input[c.cursor_x:]...) c.cursor_x = i dirty = true break } } default: tmp := []rune{} tmp = append(tmp, c.input[0:c.cursor_x]...) tmp = append(tmp, r) c.input = append(tmp, c.input[c.cursor_x:len(c.input)]...) c.cursor_x++ dirty = true } } os.Stdout.WriteString("\n") if c.input == nil { return "", io.EOF } return string(c.input), nil }