| package liner |
| |
| import ( |
| "bufio" |
| "os" |
| "syscall" |
| "unsafe" |
| ) |
| |
| var ( |
| kernel32 = syscall.NewLazyDLL("kernel32.dll") |
| |
| procGetStdHandle = kernel32.NewProc("GetStdHandle") |
| procReadConsoleInput = kernel32.NewProc("ReadConsoleInputW") |
| 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 int16 |
| ControlKeyState uint32 |
| } |
| |
| // These names are from the Win32 api, so they use underscores (contrary to |
| // what golint suggests) |
| const ( |
| vk_tab = 0x09 |
| 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 |
| fKey = 0x46 |
| yKey = 0x59 |
| ) |
| |
| const ( |
| shiftPressed = 0x0010 |
| leftAltPressed = 0x0002 |
| leftCtrlPressed = 0x0008 |
| rightAltPressed = 0x0001 |
| rightCtrlPressed = 0x0004 |
| |
| modKeys = shiftPressed | leftAltPressed | rightAltPressed | leftCtrlPressed | rightCtrlPressed |
| ) |
| |
| 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)) |
| |
| 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 { |
| continue |
| } |
| |
| if ke.VirtualKeyCode == vk_tab && ke.ControlKeyState&modKeys == shiftPressed { |
| s.key = shiftTab |
| } else if ke.VirtualKeyCode == bKey && (ke.ControlKeyState&modKeys == leftAltPressed || |
| ke.ControlKeyState&modKeys == rightAltPressed) { |
| s.key = altB |
| } 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 { |
| 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 |
| } |
| return unknown, 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 |
| } |