123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368 |
- package liner
- import (
- "bufio"
- "os"
- "syscall"
- "unicode/utf16"
- "unsafe"
- )
- var (
- kernel32 = syscall.NewLazyDLL("kernel32.dll")
- procGetStdHandle = kernel32.NewProc("GetStdHandle")
- procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW")
- procGetNumberOfConsoleInputEvents = kernel32.NewProc("GetNumberOfConsoleInputEvents")
- procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
- procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
- procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
- procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
- procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
- )
- // These names are from the Win32 api, so they use underscores (contrary to
- // what golint suggests)
- const (
- std_input_handle = uint32(-10 & 0xFFFFFFFF)
- std_output_handle = uint32(-11 & 0xFFFFFFFF)
- std_error_handle = uint32(-12 & 0xFFFFFFFF)
- invalid_handle_value = ^uintptr(0)
- )
- type inputMode uint32
- // State represents an open terminal
- type State struct {
- commonState
- handle syscall.Handle
- hOut syscall.Handle
- origMode inputMode
- defaultMode inputMode
- key interface{}
- repeat uint16
- }
- const (
- enableEchoInput = 0x4
- enableInsertMode = 0x20
- enableLineInput = 0x2
- enableMouseInput = 0x10
- enableProcessedInput = 0x1
- enableQuickEditMode = 0x40
- enableWindowInput = 0x8
- )
- // NewLiner initializes a new *State, and sets the terminal into raw mode. To
- // restore the terminal to its previous state, call State.Close().
- func NewLiner() *State {
- var s State
- hIn, _, _ := procGetStdHandle.Call(uintptr(std_input_handle))
- s.handle = syscall.Handle(hIn)
- hOut, _, _ := procGetStdHandle.Call(uintptr(std_output_handle))
- s.hOut = syscall.Handle(hOut)
- s.terminalSupported = true
- if m, err := TerminalMode(); err == nil {
- s.origMode = m.(inputMode)
- mode := s.origMode
- mode &^= enableEchoInput
- mode &^= enableInsertMode
- mode &^= enableLineInput
- mode &^= enableMouseInput
- mode |= enableWindowInput
- mode.ApplyMode()
- } else {
- s.inputRedirected = true
- s.r = bufio.NewReader(os.Stdin)
- }
- s.getColumns()
- s.outputRedirected = s.columns <= 0
- return &s
- }
- // These names are from the Win32 api, so they use underscores (contrary to
- // what golint suggests)
- const (
- focus_event = 0x0010
- key_event = 0x0001
- menu_event = 0x0008
- mouse_event = 0x0002
- window_buffer_size_event = 0x0004
- )
- type input_record struct {
- eventType uint16
- pad uint16
- blob [16]byte
- }
- type key_event_record struct {
- KeyDown int32
- RepeatCount uint16
- VirtualKeyCode uint16
- VirtualScanCode uint16
- Char uint16
- ControlKeyState uint32
- }
- // These names are from the Win32 api, so they use underscores (contrary to
- // what golint suggests)
- const (
- vk_back = 0x08
- vk_tab = 0x09
- vk_menu = 0x12 // ALT key
- vk_prior = 0x21
- vk_next = 0x22
- vk_end = 0x23
- vk_home = 0x24
- vk_left = 0x25
- vk_up = 0x26
- vk_right = 0x27
- vk_down = 0x28
- vk_insert = 0x2d
- vk_delete = 0x2e
- vk_f1 = 0x70
- vk_f2 = 0x71
- vk_f3 = 0x72
- vk_f4 = 0x73
- vk_f5 = 0x74
- vk_f6 = 0x75
- vk_f7 = 0x76
- vk_f8 = 0x77
- vk_f9 = 0x78
- vk_f10 = 0x79
- vk_f11 = 0x7a
- vk_f12 = 0x7b
- bKey = 0x42
- dKey = 0x44
- fKey = 0x46
- yKey = 0x59
- )
- const (
- shiftPressed = 0x0010
- leftAltPressed = 0x0002
- leftCtrlPressed = 0x0008
- rightAltPressed = 0x0001
- rightCtrlPressed = 0x0004
- modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed
- )
- // inputWaiting only returns true if the next call to readNext will return immediately.
- func (s *State) inputWaiting() bool {
- var num uint32
- ok, _, _ := procGetNumberOfConsoleInputEvents.Call(uintptr(s.handle), uintptr(unsafe.Pointer(&num)))
- if ok == 0 {
- // call failed, so we cannot guarantee a non-blocking readNext
- return false
- }
- // during a "paste" input events are always an odd number, and
- // the last one results in a blocking readNext, so return false
- // when num is 1 or 0.
- return num > 1
- }
- func (s *State) readNext() (interface{}, error) {
- if s.repeat > 0 {
- s.repeat--
- return s.key, nil
- }
- var input input_record
- pbuf := uintptr(unsafe.Pointer(&input))
- var rv uint32
- prv := uintptr(unsafe.Pointer(&rv))
- var surrogate uint16
- for {
- ok, _, err := procReadConsoleInput.Call(uintptr(s.handle), pbuf, 1, prv)
- if ok == 0 {
- return nil, err
- }
- if input.eventType == window_buffer_size_event {
- xy := (*coord)(unsafe.Pointer(&input.blob[0]))
- s.columns = int(xy.x)
- return winch, nil
- }
- if input.eventType != key_event {
- continue
- }
- ke := (*key_event_record)(unsafe.Pointer(&input.blob[0]))
- if ke.KeyDown == 0 {
- if ke.VirtualKeyCode == vk_menu && ke.Char > 0 {
- // paste of unicode (eg. via ALT-numpad)
- if surrogate > 0 {
- return utf16.DecodeRune(rune(surrogate), rune(ke.Char)), nil
- } else if utf16.IsSurrogate(rune(ke.Char)) {
- surrogate = ke.Char
- continue
- } else {
- return rune(ke.Char), nil
- }
- }
- continue
- }
- if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed {
- s.key = shiftTab
- } else if ke.VirtualKeyCode == vk_back && (ke.ControlKeyState&modKeys == leftAltPressed ||
- ke.ControlKeyState&modKeys == rightAltPressed) {
- s.key = altBs
- } else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
- ke.ControlKeyState&modKeys == rightAltPressed) {
- s.key = altB
- } else if ke.VirtualKeyCode == dKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
- ke.ControlKeyState&modKeys == rightAltPressed) {
- s.key = altD
- } else if ke.VirtualKeyCode == fKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
- ke.ControlKeyState&modKeys == rightAltPressed) {
- s.key = altF
- } else if ke.VirtualKeyCode == yKey && (ke.ControlKeyState&modKeys == leftAltPressed ||
- ke.ControlKeyState&modKeys == rightAltPressed) {
- s.key = altY
- } else if ke.Char > 0 {
- if surrogate > 0 {
- s.key = utf16.DecodeRune(rune(surrogate), rune(ke.Char))
- } else if utf16.IsSurrogate(rune(ke.Char)) {
- surrogate = ke.Char
- continue
- } else {
- s.key = rune(ke.Char)
- }
- } else {
- switch ke.VirtualKeyCode {
- case vk_prior:
- s.key = pageUp
- case vk_next:
- s.key = pageDown
- case vk_end:
- s.key = end
- case vk_home:
- s.key = home
- case vk_left:
- s.key = left
- if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
- if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
- s.key = wordLeft
- }
- }
- case vk_right:
- s.key = right
- if ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) != 0 {
- if ke.ControlKeyState&modKeys == ke.ControlKeyState&(leftCtrlPressed|rightCtrlPressed) {
- s.key = wordRight
- }
- }
- case vk_up:
- s.key = up
- case vk_down:
- s.key = down
- case vk_insert:
- s.key = insert
- case vk_delete:
- s.key = del
- case vk_f1:
- s.key = f1
- case vk_f2:
- s.key = f2
- case vk_f3:
- s.key = f3
- case vk_f4:
- s.key = f4
- case vk_f5:
- s.key = f5
- case vk_f6:
- s.key = f6
- case vk_f7:
- s.key = f7
- case vk_f8:
- s.key = f8
- case vk_f9:
- s.key = f9
- case vk_f10:
- s.key = f10
- case vk_f11:
- s.key = f11
- case vk_f12:
- s.key = f12
- default:
- // Eat modifier keys
- // TODO: return Action(Unknown) if the key isn't a
- // modifier.
- continue
- }
- }
- if ke.RepeatCount > 1 {
- s.repeat = ke.RepeatCount - 1
- }
- return s.key, nil
- }
- }
- // Close returns the terminal to its previous mode
- func (s *State) Close() error {
- s.origMode.ApplyMode()
- return nil
- }
- func (s *State) startPrompt() {
- if m, err := TerminalMode(); err == nil {
- s.defaultMode = m.(inputMode)
- mode := s.defaultMode
- mode &^= enableProcessedInput
- mode.ApplyMode()
- }
- }
- func (s *State) restartPrompt() {
- }
- func (s *State) stopPrompt() {
- s.defaultMode.ApplyMode()
- }
- // TerminalSupported returns true because line editing is always
- // supported on Windows.
- func TerminalSupported() bool {
- return true
- }
- func (mode inputMode) ApplyMode() error {
- hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
- if hIn == invalid_handle_value || hIn == 0 {
- return err
- }
- ok, _, err := procSetConsoleMode.Call(hIn, uintptr(mode))
- if ok != 0 {
- err = nil
- }
- return err
- }
- // TerminalMode returns the current terminal input mode as an InputModeSetter.
- //
- // This function is provided for convenience, and should
- // not be necessary for most users of liner.
- func TerminalMode() (ModeApplier, error) {
- var mode inputMode
- hIn, _, err := procGetStdHandle.Call(uintptr(std_input_handle))
- if hIn == invalid_handle_value || hIn == 0 {
- return nil, err
- }
- ok, _, err := procGetConsoleMode.Call(hIn, uintptr(unsafe.Pointer(&mode)))
- if ok != 0 {
- err = nil
- }
- return mode, err
- }
- const cursorColumn = true
|