pax_global_header00006660000000000000000000000064126311266660014523gustar00rootroot0000000000000052 comment=d7e9ae85727d7e54d2667d1bf05eb33f25b70e71 golang-github-peterh-liner-0.0~git20151118.0.4d47685/000077500000000000000000000000001263112666600213145ustar00rootroot00000000000000golang-github-peterh-liner-0.0~git20151118.0.4d47685/COPYING000066400000000000000000000021001263112666600223400ustar00rootroot00000000000000Copyright © 2012 Peter Harris 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 (including the next paragraph) 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. golang-github-peterh-liner-0.0~git20151118.0.4d47685/README.md000066400000000000000000000051631263112666600226000ustar00rootroot00000000000000Liner ===== Liner is a command line editor with history. It was inspired by linenoise; everything Unix-like is a VT100 (or is trying very hard to be). If your terminal is not pretending to be a VT100, change it. Liner also support Windows. Liner is released under the X11 license (which is similar to the new BSD license). Line Editing ------------ The following line editing commands are supported on platforms and terminals that Liner supports: Keystroke | Action --------- | ------ Ctrl-A, Home | Move cursor to beginning of line Ctrl-E, End | Move cursor to end of line Ctrl-B, Left | Move cursor one character left Ctrl-F, Right| Move cursor one character right Ctrl-Left, Alt-B | Move cursor to previous word Ctrl-Right, Alt-F | Move cursor to next word Ctrl-D, Del | (if line is *not* empty) Delete character under cursor Ctrl-D | (if line *is* empty) End of File - usually quits application Ctrl-C | Reset input (create new empty prompt) Ctrl-L | Clear screen (line is unmodified) Ctrl-T | Transpose previous character with current character Ctrl-H, BackSpace | Delete character before cursor Ctrl-W | Delete word leading up to cursor Ctrl-K | Delete from cursor to end of line Ctrl-U | Delete from start of line to cursor Ctrl-P, Up | Previous match from history Ctrl-N, Down | Next match from history Ctrl-R | Reverse Search history (Ctrl-S forward, Ctrl-G cancel) Ctrl-Y | Paste from Yank buffer (Alt-Y to paste next yank instead) Tab | Next completion Shift-Tab | (after Tab) Previous completion Getting started ----------------- ```go package main import ( "log" "os" "path/filepath" "strings" "github.com/peterh/liner" ) var ( history_fn = filepath.Join(os.TempDir(), ".liner_example_history") names = []string{"john", "james", "mary", "nancy"} ) func main() { line := liner.NewLiner() defer line.Close() line.SetCtrlCAborts(true) line.SetCompleter(func(line string) (c []string) { for _, n := range names { if strings.HasPrefix(n, strings.ToLower(line)) { c = append(c, n) } } return }) if f, err := os.Open(history_fn); err == nil { line.ReadHistory(f) f.Close() } if name, err := line.Prompt("What is your name? "); err == nil { log.Print("Got: ", name) line.AppendHistory(name) } else if err == liner.ErrPromptAborted { log.Print("Aborted") } else { log.Print("Error reading line: ", err) } if f, err := os.Create(history_fn); err != nil { log.Print("Error writing history file: ", err) } else { line.WriteHistory(f) f.Close() } } ``` For documentation, see http://godoc.org/github.com/peterh/liner golang-github-peterh-liner-0.0~git20151118.0.4d47685/bsdinput.go000066400000000000000000000007501263112666600234750ustar00rootroot00000000000000// +build openbsd freebsd netbsd package liner import "syscall" const ( getTermios = syscall.TIOCGETA setTermios = syscall.TIOCSETA ) const ( // Input flags inpck = 0x010 istrip = 0x020 icrnl = 0x100 ixon = 0x200 // Output flags opost = 0x1 // Control flags cs8 = 0x300 // Local flags isig = 0x080 icanon = 0x100 iexten = 0x400 ) type termios struct { Iflag uint32 Oflag uint32 Cflag uint32 Lflag uint32 Cc [20]byte Ispeed int32 Ospeed int32 } golang-github-peterh-liner-0.0~git20151118.0.4d47685/common.go000066400000000000000000000152231263112666600231360ustar00rootroot00000000000000/* Package liner implements a simple command line editor, inspired by linenoise (https://github.com/antirez/linenoise/). This package supports WIN32 in addition to the xterm codes supported by everything else. */ package liner import ( "bufio" "bytes" "container/ring" "errors" "fmt" "io" "strings" "sync" "unicode/utf8" ) type commonState struct { terminalSupported bool outputRedirected bool inputRedirected bool history []string historyMutex sync.RWMutex completer WordCompleter columns int killRing *ring.Ring ctrlCAborts bool r *bufio.Reader tabStyle TabStyle multiLineMode bool cursorRows int maxRows int } // TabStyle is used to select how tab completions are displayed. type TabStyle int // Two tab styles are currently available: // // TabCircular cycles through each completion item and displays it directly on // the prompt // // TabPrints prints the list of completion items to the screen after a second // tab key is pressed. This behaves similar to GNU readline and BASH (which // uses readline) const ( TabCircular TabStyle = iota TabPrints ) // ErrPromptAborted is returned from Prompt or PasswordPrompt when the user presses Ctrl-C // if SetCtrlCAborts(true) has been called on the State var ErrPromptAborted = errors.New("prompt aborted") // ErrNotTerminalOutput is returned from Prompt or PasswordPrompt if the // platform is normally supported, but stdout has been redirected var ErrNotTerminalOutput = errors.New("standard output is not a terminal") // Max elements to save on the killring const KillRingMax = 60 // HistoryLimit is the maximum number of entries saved in the scrollback history. const HistoryLimit = 1000 // ReadHistory reads scrollback history from r. Returns the number of lines // read, and any read error (except io.EOF). func (s *State) ReadHistory(r io.Reader) (num int, err error) { s.historyMutex.Lock() defer s.historyMutex.Unlock() in := bufio.NewReader(r) num = 0 for { line, part, err := in.ReadLine() if err == io.EOF { break } if err != nil { return num, err } if part { return num, fmt.Errorf("line %d is too long", num+1) } if !utf8.Valid(line) { return num, fmt.Errorf("invalid string at line %d", num+1) } num++ s.history = append(s.history, string(line)) if len(s.history) > HistoryLimit { s.history = s.history[1:] } } return num, nil } // WriteHistory writes scrollback history to w. Returns the number of lines // successfully written, and any write error. // // Unlike the rest of liner's API, WriteHistory is safe to call // from another goroutine while Prompt is in progress. // This exception is to facilitate the saving of the history buffer // during an unexpected exit (for example, due to Ctrl-C being invoked) func (s *State) WriteHistory(w io.Writer) (num int, err error) { s.historyMutex.RLock() defer s.historyMutex.RUnlock() for _, item := range s.history { _, err := fmt.Fprintln(w, item) if err != nil { return num, err } num++ } return num, nil } // AppendHistory appends an entry to the scrollback history. AppendHistory // should be called iff Prompt returns a valid command. func (s *State) AppendHistory(item string) { s.historyMutex.Lock() defer s.historyMutex.Unlock() if len(s.history) > 0 { if item == s.history[len(s.history)-1] { return } } s.history = append(s.history, item) if len(s.history) > HistoryLimit { s.history = s.history[1:] } } // Returns the history lines starting with prefix func (s *State) getHistoryByPrefix(prefix string) (ph []string) { for _, h := range s.history { if strings.HasPrefix(h, prefix) { ph = append(ph, h) } } return } // Returns the history lines matching the inteligent search func (s *State) getHistoryByPattern(pattern string) (ph []string, pos []int) { if pattern == "" { return } for _, h := range s.history { if i := strings.Index(h, pattern); i >= 0 { ph = append(ph, h) pos = append(pos, i) } } return } // Completer takes the currently edited line content at the left of the cursor // and returns a list of completion candidates. // If the line is "Hello, wo!!!" and the cursor is before the first '!', "Hello, wo" is passed // to the completer which may return {"Hello, world", "Hello, Word"} to have "Hello, world!!!". type Completer func(line string) []string // WordCompleter takes the currently edited line with the cursor position and // returns the completion candidates for the partial word to be completed. // If the line is "Hello, wo!!!" and the cursor is before the first '!', ("Hello, wo!!!", 9) is passed // to the completer which may returns ("Hello, ", {"world", "Word"}, "!!!") to have "Hello, world!!!". type WordCompleter func(line string, pos int) (head string, completions []string, tail string) // SetCompleter sets the completion function that Liner will call to // fetch completion candidates when the user presses tab. func (s *State) SetCompleter(f Completer) { if f == nil { s.completer = nil return } s.completer = func(line string, pos int) (string, []string, string) { return "", f(line[:pos]), line[pos:] } } // SetWordCompleter sets the completion function that Liner will call to // fetch completion candidates when the user presses tab. func (s *State) SetWordCompleter(f WordCompleter) { s.completer = f } // SetTabCompletionStyle sets the behvavior when the Tab key is pressed // for auto-completion. TabCircular is the default behavior and cycles // through the list of candidates at the prompt. TabPrints will print // the available completion candidates to the screen similar to BASH // and GNU Readline func (s *State) SetTabCompletionStyle(tabStyle TabStyle) { s.tabStyle = tabStyle } // ModeApplier is the interface that wraps a representation of the terminal // mode. ApplyMode sets the terminal to this mode. type ModeApplier interface { ApplyMode() error } // SetCtrlCAborts sets whether Prompt on a supported terminal will return an // ErrPromptAborted when Ctrl-C is pressed. The default is false (will not // return when Ctrl-C is pressed). Unsupported terminals typically raise SIGINT // (and Prompt does not return) regardless of the value passed to SetCtrlCAborts. func (s *State) SetCtrlCAborts(aborts bool) { s.ctrlCAborts = aborts } // SetMultiLineMode sets whether line is auto-wrapped. The default is false (single line). func (s *State) SetMultiLineMode(mlmode bool) { s.multiLineMode = mlmode } func (s *State) promptUnsupported(p string) (string, error) { if !s.inputRedirected || !s.terminalSupported { fmt.Print(p) } linebuf, _, err := s.r.ReadLine() if err != nil { return "", err } return string(bytes.TrimSpace(linebuf)), nil } golang-github-peterh-liner-0.0~git20151118.0.4d47685/fallbackinput.go000066400000000000000000000023371263112666600244670ustar00rootroot00000000000000// +build !windows,!linux,!darwin,!openbsd,!freebsd,!netbsd package liner import ( "bufio" "errors" "os" ) // State represents an open terminal type State struct { commonState } // Prompt displays p, and then waits for user input. Prompt does not support // line editing on this operating system. func (s *State) Prompt(p string) (string, error) { return s.promptUnsupported(p) } // PasswordPrompt is not supported in this OS. func (s *State) PasswordPrompt(p string) (string, error) { return "", errors.New("liner: function not supported in this terminal") } // NewLiner initializes a new *State // // Note that this operating system uses a fallback mode without line // editing. Patches welcome. func NewLiner() *State { var s State s.r = bufio.NewReader(os.Stdin) return &s } // Close returns the terminal to its previous mode func (s *State) Close() error { return nil } // TerminalSupported returns false because line editing is not // supported on this platform. func TerminalSupported() bool { return false } type noopMode struct{} func (n noopMode) ApplyMode() error { return nil } // TerminalMode returns a noop InputModeSetter on this platform. func TerminalMode() (ModeApplier, error) { return noopMode{}, nil } golang-github-peterh-liner-0.0~git20151118.0.4d47685/input.go000066400000000000000000000176571263112666600230220ustar00rootroot00000000000000// +build linux darwin openbsd freebsd netbsd package liner import ( "bufio" "errors" "os" "os/signal" "strconv" "strings" "syscall" "time" ) type nexter struct { r rune err error } // State represents an open terminal type State struct { commonState origMode termios defaultMode termios next <-chan nexter winch chan os.Signal pending []rune useCHA bool } // NewLiner initializes a new *State, and sets the terminal into raw mode. To // restore the terminal to its previous state, call State.Close(). // // Note if you are still using Go 1.0: NewLiner handles SIGWINCH, so it will // leak a channel every time you call it. Therefore, it is recommened that you // upgrade to a newer release of Go, or ensure that NewLiner is only called // once. func NewLiner() *State { var s State s.r = bufio.NewReader(os.Stdin) s.terminalSupported = TerminalSupported() if m, err := TerminalMode(); err == nil { s.origMode = *m.(*termios) } else { s.inputRedirected = true } if _, err := getMode(syscall.Stdout); err != 0 { s.outputRedirected = true } if s.inputRedirected && s.outputRedirected { s.terminalSupported = false } if s.terminalSupported && !s.inputRedirected && !s.outputRedirected { mode := s.origMode mode.Iflag &^= icrnl | inpck | istrip | ixon mode.Cflag |= cs8 mode.Lflag &^= syscall.ECHO | icanon | iexten mode.ApplyMode() winch := make(chan os.Signal, 1) signal.Notify(winch, syscall.SIGWINCH) s.winch = winch s.checkOutput() } if !s.outputRedirected { s.getColumns() s.outputRedirected = s.columns <= 0 } return &s } var errTimedOut = errors.New("timeout") func (s *State) startPrompt() { if s.terminalSupported { if m, err := TerminalMode(); err == nil { s.defaultMode = *m.(*termios) mode := s.defaultMode mode.Lflag &^= isig mode.ApplyMode() } } s.restartPrompt() } func (s *State) restartPrompt() { next := make(chan nexter) go func() { for { var n nexter n.r, _, n.err = s.r.ReadRune() next <- n // Shut down nexter loop when an end condition has been reached if n.err != nil || n.r == '\n' || n.r == '\r' || n.r == ctrlC || n.r == ctrlD { close(next) return } } }() s.next = next } func (s *State) stopPrompt() { if s.terminalSupported { s.defaultMode.ApplyMode() } } func (s *State) nextPending(timeout <-chan time.Time) (rune, error) { select { case thing, ok := <-s.next: if !ok { return 0, errors.New("liner: internal error") } if thing.err != nil { return 0, thing.err } s.pending = append(s.pending, thing.r) return thing.r, nil case <-timeout: rv := s.pending[0] s.pending = s.pending[1:] return rv, errTimedOut } // not reached return 0, nil } func (s *State) readNext() (interface{}, error) { if len(s.pending) > 0 { rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } var r rune select { case thing, ok := <-s.next: if !ok { return 0, errors.New("liner: internal error") } if thing.err != nil { return nil, thing.err } r = thing.r case <-s.winch: s.getColumns() return winch, nil } if r != esc { return r, nil } s.pending = append(s.pending, r) // Wait at most 50 ms for the rest of the escape sequence // If nothing else arrives, it was an actual press of the esc key timeout := time.After(50 * time.Millisecond) flag, err := s.nextPending(timeout) if err != nil { if err == errTimedOut { return flag, nil } return unknown, err } switch flag { case '[': code, err := s.nextPending(timeout) if err != nil { if err == errTimedOut { return code, nil } return unknown, err } switch code { case 'A': s.pending = s.pending[:0] // escape code complete return up, nil case 'B': s.pending = s.pending[:0] // escape code complete return down, nil case 'C': s.pending = s.pending[:0] // escape code complete return right, nil case 'D': s.pending = s.pending[:0] // escape code complete return left, nil case 'F': s.pending = s.pending[:0] // escape code complete return end, nil case 'H': s.pending = s.pending[:0] // escape code complete return home, nil case 'Z': s.pending = s.pending[:0] // escape code complete return shiftTab, nil case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': num := []rune{code} for { code, err := s.nextPending(timeout) if err != nil { if err == errTimedOut { return code, nil } return nil, err } switch code { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': num = append(num, code) case ';': // Modifier code to follow // This only supports Ctrl-left and Ctrl-right for now x, _ := strconv.ParseInt(string(num), 10, 32) if x != 1 { // Can't be left or right rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } num = num[:0] for { code, err = s.nextPending(timeout) if err != nil { if err == errTimedOut { rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } return nil, err } switch code { case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': num = append(num, code) case 'C', 'D': // right, left mod, _ := strconv.ParseInt(string(num), 10, 32) if mod != 5 { // Not bare Ctrl rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } s.pending = s.pending[:0] // escape code complete if code == 'C' { return wordRight, nil } return wordLeft, nil default: // Not left or right rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } } case '~': s.pending = s.pending[:0] // escape code complete x, _ := strconv.ParseInt(string(num), 10, 32) switch x { case 2: return insert, nil case 3: return del, nil case 5: return pageUp, nil case 6: return pageDown, nil case 7: return home, nil case 8: return end, nil case 15: return f5, nil case 17: return f6, nil case 18: return f7, nil case 19: return f8, nil case 20: return f9, nil case 21: return f10, nil case 23: return f11, nil case 24: return f12, nil default: return unknown, nil } default: // unrecognized escape code rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } } } case 'O': code, err := s.nextPending(timeout) if err != nil { if err == errTimedOut { return code, nil } return nil, err } s.pending = s.pending[:0] // escape code complete switch code { case 'c': return wordRight, nil case 'd': return wordLeft, nil case 'H': return home, nil case 'F': return end, nil case 'P': return f1, nil case 'Q': return f2, nil case 'R': return f3, nil case 'S': return f4, nil default: return unknown, nil } case 'b': s.pending = s.pending[:0] // escape code complete return altB, nil case 'f': s.pending = s.pending[:0] // escape code complete return altF, nil case 'y': s.pending = s.pending[:0] // escape code complete return altY, nil default: rv := s.pending[0] s.pending = s.pending[1:] return rv, nil } // not reached return r, nil } // Close returns the terminal to its previous mode func (s *State) Close() error { stopSignal(s.winch) if !s.inputRedirected { s.origMode.ApplyMode() } return nil } // TerminalSupported returns true if the current terminal supports // line editing features, and false if liner will use the 'dumb' // fallback for input. // Note that TerminalSupported does not check all factors that may // cause liner to not fully support the terminal (such as stdin redirection) func TerminalSupported() bool { bad := map[string]bool{"": true, "dumb": true, "cons25": true} return !bad[strings.ToLower(os.Getenv("TERM"))] } golang-github-peterh-liner-0.0~git20151118.0.4d47685/input_darwin.go000066400000000000000000000007401263112666600243470ustar00rootroot00000000000000// +build darwin package liner import "syscall" const ( getTermios = syscall.TIOCGETA setTermios = syscall.TIOCSETA ) const ( // Input flags inpck = 0x010 istrip = 0x020 icrnl = 0x100 ixon = 0x200 // Output flags opost = 0x1 // Control flags cs8 = 0x300 // Local flags isig = 0x080 icanon = 0x100 iexten = 0x400 ) type termios struct { Iflag uintptr Oflag uintptr Cflag uintptr Lflag uintptr Cc [20]byte Ispeed uintptr Ospeed uintptr } golang-github-peterh-liner-0.0~git20151118.0.4d47685/input_linux.go000066400000000000000000000006021263112666600242170ustar00rootroot00000000000000// +build linux package liner import "syscall" const ( getTermios = syscall.TCGETS setTermios = syscall.TCSETS ) const ( icrnl = syscall.ICRNL inpck = syscall.INPCK istrip = syscall.ISTRIP ixon = syscall.IXON opost = syscall.OPOST cs8 = syscall.CS8 isig = syscall.ISIG icanon = syscall.ICANON iexten = syscall.IEXTEN ) type termios struct { syscall.Termios } golang-github-peterh-liner-0.0~git20151118.0.4d47685/input_test.go000066400000000000000000000022651263112666600240460ustar00rootroot00000000000000// +build !windows package liner import ( "bufio" "bytes" "testing" ) func (s *State) expectRune(t *testing.T, r rune) { item, err := s.readNext() if err != nil { t.Fatalf("Expected rune '%c', got error %s\n", r, err) } if v, ok := item.(rune); !ok { t.Fatalf("Expected rune '%c', got non-rune %v\n", r, v) } else { if v != r { t.Fatalf("Expected rune '%c', got rune '%c'\n", r, v) } } } func (s *State) expectAction(t *testing.T, a action) { item, err := s.readNext() if err != nil { t.Fatalf("Expected Action %d, got error %s\n", a, err) } if v, ok := item.(action); !ok { t.Fatalf("Expected Action %d, got non-Action %v\n", a, v) } else { if v != a { t.Fatalf("Expected Action %d, got Action %d\n", a, v) } } } func TestTypes(t *testing.T) { input := []byte{'A', 27, 'B', 27, 91, 68, 27, '[', '1', ';', '5', 'D', 'e'} var s State s.r = bufio.NewReader(bytes.NewBuffer(input)) next := make(chan nexter) go func() { for { var n nexter n.r, _, n.err = s.r.ReadRune() next <- n } }() s.next = next s.expectRune(t, 'A') s.expectRune(t, 27) s.expectRune(t, 'B') s.expectAction(t, left) s.expectAction(t, wordLeft) s.expectRune(t, 'e') } golang-github-peterh-liner-0.0~git20151118.0.4d47685/input_windows.go000066400000000000000000000165721263112666600245670ustar00rootroot00000000000000package 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 } golang-github-peterh-liner-0.0~git20151118.0.4d47685/line.go000066400000000000000000000553231263112666600226020ustar00rootroot00000000000000// +build windows linux darwin openbsd freebsd netbsd package liner import ( "container/ring" "errors" "fmt" "io" "strings" "unicode" "unicode/utf8" ) type action int const ( left action = iota right up down home end insert del pageUp pageDown f1 f2 f3 f4 f5 f6 f7 f8 f9 f10 f11 f12 altB altF altY shiftTab wordLeft wordRight winch unknown ) const ( ctrlA = 1 ctrlB = 2 ctrlC = 3 ctrlD = 4 ctrlE = 5 ctrlF = 6 ctrlG = 7 ctrlH = 8 tab = 9 lf = 10 ctrlK = 11 ctrlL = 12 cr = 13 ctrlN = 14 ctrlO = 15 ctrlP = 16 ctrlQ = 17 ctrlR = 18 ctrlS = 19 ctrlT = 20 ctrlU = 21 ctrlV = 22 ctrlW = 23 ctrlX = 24 ctrlY = 25 ctrlZ = 26 esc = 27 bs = 127 ) const ( beep = "\a" ) type tabDirection int const ( tabForward tabDirection = iota tabReverse ) func (s *State) refresh(prompt []rune, buf []rune, pos int) error { if s.multiLineMode { return s.refreshMultiLine(prompt, buf, pos) } else { return s.refreshSingleLine(prompt, buf, pos) } } func (s *State) refreshSingleLine(prompt []rune, buf []rune, pos int) error { s.cursorPos(0) _, err := fmt.Print(string(prompt)) if err != nil { return err } pLen := countGlyphs(prompt) bLen := countGlyphs(buf) pos = countGlyphs(buf[:pos]) if pLen+bLen < s.columns { _, err = fmt.Print(string(buf)) s.eraseLine() s.cursorPos(pLen + pos) } else { // Find space available space := s.columns - pLen space-- // space for cursor start := pos - space/2 end := start + space if end > bLen { end = bLen start = end - space } if start < 0 { start = 0 end = space } pos -= start // Leave space for markers if start > 0 { start++ } if end < bLen { end-- } startRune := len(getPrefixGlyphs(buf, start)) line := getPrefixGlyphs(buf[startRune:], end-start) // Output if start > 0 { fmt.Print("{") } fmt.Print(string(line)) if end < bLen { fmt.Print("}") } // Set cursor position s.eraseLine() s.cursorPos(pLen + pos) } return err } func (s *State) refreshMultiLine(prompt []rune, buf []rune, pos int) error { promptColumns := countMultiLineGlyphs(prompt, s.columns, 0) totalColumns := countMultiLineGlyphs(buf, s.columns, promptColumns) totalRows := (totalColumns + s.columns - 1) / s.columns maxRows := s.maxRows if totalRows > s.maxRows { s.maxRows = totalRows } cursorRows := s.cursorRows if cursorRows == 0 { cursorRows = 1 } /* First step: clear all the lines used before. To do so start by * going to the last row. */ if maxRows-cursorRows > 0 { s.moveDown(maxRows - cursorRows) } /* Now for every row clear it, go up. */ for i := 0; i < maxRows-1; i++ { s.cursorPos(0) s.eraseLine() s.moveUp(1) } /* Clean the top line. */ s.cursorPos(0) s.eraseLine() /* Write the prompt and the current buffer content */ if _, err := fmt.Print(string(prompt)); err != nil { return err } if _, err := fmt.Print(string(buf)); err != nil { return err } /* If we are at the very end of the screen with our prompt, we need to * emit a newline and move the prompt to the first column. */ cursorColumns := countMultiLineGlyphs(buf[:pos], s.columns, promptColumns) if cursorColumns == totalColumns && totalColumns%s.columns == 0 { s.emitNewLine() s.cursorPos(0) totalRows++ if totalRows > s.maxRows { s.maxRows = totalRows } } /* Move cursor to right position. */ cursorRows = (cursorColumns + s.columns) / s.columns if s.cursorRows > 0 && totalRows-cursorRows > 0 { s.moveUp(totalRows - cursorRows) } /* Set column. */ s.cursorPos(cursorColumns % s.columns) s.cursorRows = cursorRows return nil } func (s *State) resetMultiLine(prompt []rune, buf []rune, pos int) { columns := countMultiLineGlyphs(prompt, s.columns, 0) columns = countMultiLineGlyphs(buf[:pos], s.columns, columns) columns += 2 // ^C cursorRows := (columns + s.columns) / s.columns if s.maxRows-cursorRows > 0 { for i := 0; i < s.maxRows-cursorRows; i++ { fmt.Println() // always moves the cursor down or scrolls the window up as needed } } s.maxRows = 1 s.cursorRows = 0 } func longestCommonPrefix(strs []string) string { if len(strs) == 0 { return "" } longest := strs[0] for _, str := range strs[1:] { for !strings.HasPrefix(str, longest) { longest = longest[:len(longest)-1] } } // Remove trailing partial runes longest = strings.TrimRight(longest, "\uFFFD") return longest } func (s *State) circularTabs(items []string) func(tabDirection) (string, error) { item := -1 return func(direction tabDirection) (string, error) { if direction == tabForward { if item < len(items)-1 { item++ } else { item = 0 } } else if direction == tabReverse { if item > 0 { item-- } else { item = len(items) - 1 } } return items[item], nil } } func calculateColumns(screenWidth int, items []string) (numColumns, numRows, maxWidth int) { for _, item := range items { if len(item) >= screenWidth { return 1, len(items), screenWidth - 1 } if len(item) >= maxWidth { maxWidth = len(item) + 1 } } numColumns = screenWidth / maxWidth numRows = len(items) / numColumns if len(items)%numColumns > 0 { numRows++ } if len(items) <= numColumns { maxWidth = 0 } return } func (s *State) printedTabs(items []string) func(tabDirection) (string, error) { numTabs := 1 prefix := longestCommonPrefix(items) return func(direction tabDirection) (string, error) { if len(items) == 1 { return items[0], nil } if numTabs == 2 { if len(items) > 100 { fmt.Printf("\nDisplay all %d possibilities? (y or n) ", len(items)) for { next, err := s.readNext() if err != nil { return prefix, err } if key, ok := next.(rune); ok { if unicode.ToLower(key) == 'n' { return prefix, nil } else if unicode.ToLower(key) == 'y' { break } } } } fmt.Println("") numColumns, numRows, maxWidth := calculateColumns(s.columns, items) for i := 0; i < numRows; i++ { for j := 0; j < numColumns*numRows; j += numRows { if i+j < len(items) { if maxWidth > 0 { fmt.Printf("%-*.[1]*s", maxWidth, items[i+j]) } else { fmt.Printf("%v ", items[i+j]) } } } fmt.Println("") } } else { numTabs++ } return prefix, nil } } func (s *State) tabComplete(p []rune, line []rune, pos int) ([]rune, int, interface{}, error) { if s.completer == nil { return line, pos, rune(esc), nil } head, list, tail := s.completer(string(line), pos) if len(list) <= 0 { return line, pos, rune(esc), nil } hl := utf8.RuneCountInString(head) if len(list) == 1 { s.refresh(p, []rune(head+list[0]+tail), hl+utf8.RuneCountInString(list[0])) return []rune(head + list[0] + tail), hl + utf8.RuneCountInString(list[0]), rune(esc), nil } direction := tabForward tabPrinter := s.circularTabs(list) if s.tabStyle == TabPrints { tabPrinter = s.printedTabs(list) } for { pick, err := tabPrinter(direction) if err != nil { return line, pos, rune(esc), err } s.refresh(p, []rune(head+pick+tail), hl+utf8.RuneCountInString(pick)) next, err := s.readNext() if err != nil { return line, pos, rune(esc), err } if key, ok := next.(rune); ok { if key == tab { direction = tabForward continue } if key == esc { return line, pos, rune(esc), nil } } if a, ok := next.(action); ok && a == shiftTab { direction = tabReverse continue } return []rune(head + pick + tail), hl + utf8.RuneCountInString(pick), next, nil } // Not reached return line, pos, rune(esc), nil } // reverse intelligent search, implements a bash-like history search. func (s *State) reverseISearch(origLine []rune, origPos int) ([]rune, int, interface{}, error) { p := "(reverse-i-search)`': " s.refresh([]rune(p), origLine, origPos) line := []rune{} pos := 0 foundLine := string(origLine) foundPos := origPos getLine := func() ([]rune, []rune, int) { search := string(line) prompt := "(reverse-i-search)`%s': " return []rune(fmt.Sprintf(prompt, search)), []rune(foundLine), foundPos } history, positions := s.getHistoryByPattern(string(line)) historyPos := len(history) - 1 for { next, err := s.readNext() if err != nil { return []rune(foundLine), foundPos, rune(esc), err } switch v := next.(type) { case rune: switch v { case ctrlR: // Search backwards if historyPos > 0 && historyPos < len(history) { historyPos-- foundLine = history[historyPos] foundPos = positions[historyPos] } else { fmt.Print(beep) } case ctrlS: // Search forward if historyPos < len(history)-1 && historyPos >= 0 { historyPos++ foundLine = history[historyPos] foundPos = positions[historyPos] } else { fmt.Print(beep) } case ctrlH, bs: // Backspace if pos <= 0 { fmt.Print(beep) } else { n := len(getSuffixGlyphs(line[:pos], 1)) line = append(line[:pos-n], line[pos:]...) pos -= n // For each char deleted, display the last matching line of history history, positions := s.getHistoryByPattern(string(line)) historyPos = len(history) - 1 if len(history) > 0 { foundLine = history[historyPos] foundPos = positions[historyPos] } else { foundLine = "" foundPos = 0 } } case ctrlG: // Cancel return origLine, origPos, rune(esc), err case tab, cr, lf, ctrlA, ctrlB, ctrlD, ctrlE, ctrlF, ctrlK, ctrlL, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: fallthrough case 0, ctrlC, esc, 28, 29, 30, 31: return []rune(foundLine), foundPos, next, err default: line = append(line[:pos], append([]rune{v}, line[pos:]...)...) pos++ // For each keystroke typed, display the last matching line of history history, positions = s.getHistoryByPattern(string(line)) historyPos = len(history) - 1 if len(history) > 0 { foundLine = history[historyPos] foundPos = positions[historyPos] } else { foundLine = "" foundPos = 0 } } case action: return []rune(foundLine), foundPos, next, err } s.refresh(getLine()) } } // addToKillRing adds some text to the kill ring. If mode is 0 it adds it to a // new node in the end of the kill ring, and move the current pointer to the new // node. If mode is 1 or 2 it appends or prepends the text to the current entry // of the killRing. func (s *State) addToKillRing(text []rune, mode int) { // Don't use the same underlying array as text killLine := make([]rune, len(text)) copy(killLine, text) // Point killRing to a newNode, procedure depends on the killring state and // append mode. if mode == 0 { // Add new node to killRing if s.killRing == nil { // if killring is empty, create a new one s.killRing = ring.New(1) } else if s.killRing.Len() >= KillRingMax { // if killring is "full" s.killRing = s.killRing.Next() } else { // Normal case s.killRing.Link(ring.New(1)) s.killRing = s.killRing.Next() } } else { if s.killRing == nil { // if killring is empty, create a new one s.killRing = ring.New(1) s.killRing.Value = []rune{} } if mode == 1 { // Append to last entry killLine = append(s.killRing.Value.([]rune), killLine...) } else if mode == 2 { // Prepend to last entry killLine = append(killLine, s.killRing.Value.([]rune)...) } } // Save text in the current killring node s.killRing.Value = killLine } func (s *State) yank(p []rune, text []rune, pos int) ([]rune, int, interface{}, error) { if s.killRing == nil { return text, pos, rune(esc), nil } lineStart := text[:pos] lineEnd := text[pos:] var line []rune for { value := s.killRing.Value.([]rune) line = make([]rune, 0) line = append(line, lineStart...) line = append(line, value...) line = append(line, lineEnd...) pos = len(lineStart) + len(value) s.refresh(p, line, pos) next, err := s.readNext() if err != nil { return line, pos, next, err } switch v := next.(type) { case rune: return line, pos, next, nil case action: switch v { case altY: s.killRing = s.killRing.Prev() default: return line, pos, next, nil } } } return line, pos, esc, nil } // Prompt displays p and returns a line of user input, not including a trailing // newline character. An io.EOF error is returned if the user signals end-of-file // by pressing Ctrl-D. Prompt allows line editing if the terminal supports it. func (s *State) Prompt(prompt string) (string, error) { if s.inputRedirected || !s.terminalSupported { return s.promptUnsupported(prompt) } if s.outputRedirected { return "", ErrNotTerminalOutput } s.historyMutex.RLock() defer s.historyMutex.RUnlock() s.startPrompt() defer s.stopPrompt() s.getColumns() fmt.Print(prompt) p := []rune(prompt) var line []rune pos := 0 historyEnd := "" prefixHistory := s.getHistoryByPrefix(string(line)) historyPos := len(prefixHistory) historyAction := false // used to mark history related actions killAction := 0 // used to mark kill related actions mainLoop: for { next, err := s.readNext() haveNext: if err != nil { return "", err } historyAction = false switch v := next.(type) { case rune: switch v { case cr, lf: if s.multiLineMode { s.resetMultiLine(p, line, pos) } fmt.Println() break mainLoop case ctrlA: // Start of line pos = 0 s.refresh(p, line, pos) case ctrlE: // End of line pos = len(line) s.refresh(p, line, pos) case ctrlB: // left if pos > 0 { pos -= len(getSuffixGlyphs(line[:pos], 1)) s.refresh(p, line, pos) } else { fmt.Print(beep) } case ctrlF: // right if pos < len(line) { pos += len(getPrefixGlyphs(line[pos:], 1)) s.refresh(p, line, pos) } else { fmt.Print(beep) } case ctrlD: // del if pos == 0 && len(line) == 0 { // exit return "", io.EOF } // ctrlD is a potential EOF, so the rune reader shuts down. // Therefore, if it isn't actually an EOF, we must re-startPrompt. s.restartPrompt() if pos >= len(line) { fmt.Print(beep) } else { n := len(getPrefixGlyphs(line[pos:], 1)) line = append(line[:pos], line[pos+n:]...) s.refresh(p, line, pos) } case ctrlK: // delete remainder of line if pos >= len(line) { fmt.Print(beep) } else { if killAction > 0 { s.addToKillRing(line[pos:], 1) // Add in apend mode } else { s.addToKillRing(line[pos:], 0) // Add in normal mode } killAction = 2 // Mark that there was a kill action line = line[:pos] s.refresh(p, line, pos) } case ctrlP: // up historyAction = true if historyPos > 0 { if historyPos == len(prefixHistory) { historyEnd = string(line) } historyPos-- line = []rune(prefixHistory[historyPos]) pos = len(line) s.refresh(p, line, pos) } else { fmt.Print(beep) } case ctrlN: // down historyAction = true if historyPos < len(prefixHistory) { historyPos++ if historyPos == len(prefixHistory) { line = []rune(historyEnd) } else { line = []rune(prefixHistory[historyPos]) } pos = len(line) s.refresh(p, line, pos) } else { fmt.Print(beep) } case ctrlT: // transpose prev glyph with glyph under cursor if len(line) < 2 || pos < 1 { fmt.Print(beep) } else { if pos == len(line) { pos -= len(getSuffixGlyphs(line, 1)) } prev := getSuffixGlyphs(line[:pos], 1) next := getPrefixGlyphs(line[pos:], 1) scratch := make([]rune, len(prev)) copy(scratch, prev) copy(line[pos-len(prev):], next) copy(line[pos-len(prev)+len(next):], scratch) pos += len(next) s.refresh(p, line, pos) } case ctrlL: // clear screen s.eraseScreen() s.refresh(p, line, pos) case ctrlC: // reset fmt.Println("^C") if s.multiLineMode { s.resetMultiLine(p, line, pos) } if s.ctrlCAborts { return "", ErrPromptAborted } line = line[:0] pos = 0 fmt.Print(prompt) s.restartPrompt() case ctrlH, bs: // Backspace if pos <= 0 { fmt.Print(beep) } else { n := len(getSuffixGlyphs(line[:pos], 1)) line = append(line[:pos-n], line[pos:]...) pos -= n s.refresh(p, line, pos) } case ctrlU: // Erase line before cursor if killAction > 0 { s.addToKillRing(line[:pos], 2) // Add in prepend mode } else { s.addToKillRing(line[:pos], 0) // Add in normal mode } killAction = 2 // Mark that there was some killing line = line[pos:] pos = 0 s.refresh(p, line, pos) case ctrlW: // Erase word if pos == 0 { fmt.Print(beep) break } // Remove whitespace to the left var buf []rune // Store the deleted chars in a buffer for { if pos == 0 || !unicode.IsSpace(line[pos-1]) { break } buf = append(buf, line[pos-1]) line = append(line[:pos-1], line[pos:]...) pos-- } // Remove non-whitespace to the left for { if pos == 0 || unicode.IsSpace(line[pos-1]) { break } buf = append(buf, line[pos-1]) line = append(line[:pos-1], line[pos:]...) pos-- } // Invert the buffer and save the result on the killRing var newBuf []rune for i := len(buf) - 1; i >= 0; i-- { newBuf = append(newBuf, buf[i]) } if killAction > 0 { s.addToKillRing(newBuf, 2) // Add in prepend mode } else { s.addToKillRing(newBuf, 0) // Add in normal mode } killAction = 2 // Mark that there was some killing s.refresh(p, line, pos) case ctrlY: // Paste from Yank buffer line, pos, next, err = s.yank(p, line, pos) goto haveNext case ctrlR: // Reverse Search line, pos, next, err = s.reverseISearch(line, pos) s.refresh(p, line, pos) goto haveNext case tab: // Tab completion line, pos, next, err = s.tabComplete(p, line, pos) goto haveNext // Catch keys that do nothing, but you don't want them to beep case esc: // DO NOTHING // Unused keys case ctrlG, ctrlO, ctrlQ, ctrlS, ctrlV, ctrlX, ctrlZ: fallthrough // Catch unhandled control codes (anything <= 31) case 0, 28, 29, 30, 31: fmt.Print(beep) default: if pos == len(line) && !s.multiLineMode && countGlyphs(p)+countGlyphs(line) < s.columns-1 { line = append(line, v) fmt.Printf("%c", v) pos++ } else { line = append(line[:pos], append([]rune{v}, line[pos:]...)...) pos++ s.refresh(p, line, pos) } } case action: switch v { case del: if pos >= len(line) { fmt.Print(beep) } else { n := len(getPrefixGlyphs(line[pos:], 1)) line = append(line[:pos], line[pos+n:]...) } case left: if pos > 0 { pos -= len(getSuffixGlyphs(line[:pos], 1)) } else { fmt.Print(beep) } case wordLeft, altB: if pos > 0 { var spaceHere, spaceLeft, leftKnown bool for { pos-- if pos == 0 { break } if leftKnown { spaceHere = spaceLeft } else { spaceHere = unicode.IsSpace(line[pos]) } spaceLeft, leftKnown = unicode.IsSpace(line[pos-1]), true if !spaceHere && spaceLeft { break } } } else { fmt.Print(beep) } case right: if pos < len(line) { pos += len(getPrefixGlyphs(line[pos:], 1)) } else { fmt.Print(beep) } case wordRight, altF: if pos < len(line) { var spaceHere, spaceLeft, hereKnown bool for { pos++ if pos == len(line) { break } if hereKnown { spaceLeft = spaceHere } else { spaceLeft = unicode.IsSpace(line[pos-1]) } spaceHere, hereKnown = unicode.IsSpace(line[pos]), true if spaceHere && !spaceLeft { break } } } else { fmt.Print(beep) } case up: historyAction = true if historyPos > 0 { if historyPos == len(prefixHistory) { historyEnd = string(line) } historyPos-- line = []rune(prefixHistory[historyPos]) pos = len(line) } else { fmt.Print(beep) } case down: historyAction = true if historyPos < len(prefixHistory) { historyPos++ if historyPos == len(prefixHistory) { line = []rune(historyEnd) } else { line = []rune(prefixHistory[historyPos]) } pos = len(line) } else { fmt.Print(beep) } case home: // Start of line pos = 0 case end: // End of line pos = len(line) case winch: // Window change if s.multiLineMode { if s.maxRows-s.cursorRows > 0 { s.moveDown(s.maxRows - s.cursorRows) } for i := 0; i < s.maxRows-1; i++ { s.cursorPos(0) s.eraseLine() s.moveUp(1) } s.maxRows = 1 s.cursorRows = 1 } } s.refresh(p, line, pos) } if !historyAction { prefixHistory = s.getHistoryByPrefix(string(line)) historyPos = len(prefixHistory) } if killAction > 0 { killAction-- } } return string(line), nil } // PasswordPrompt displays p, and then waits for user input. The input typed by // the user is not displayed in the terminal. func (s *State) PasswordPrompt(prompt string) (string, error) { if !s.terminalSupported { return "", errors.New("liner: function not supported in this terminal") } if s.inputRedirected { return s.promptUnsupported(prompt) } if s.outputRedirected { return "", ErrNotTerminalOutput } s.startPrompt() defer s.stopPrompt() s.getColumns() fmt.Print(prompt) p := []rune(prompt) var line []rune pos := 0 mainLoop: for { next, err := s.readNext() if err != nil { return "", err } switch v := next.(type) { case rune: switch v { case cr, lf: if s.multiLineMode { s.resetMultiLine(p, line, pos) } fmt.Println() break mainLoop case ctrlD: // del if pos == 0 && len(line) == 0 { // exit return "", io.EOF } // ctrlD is a potential EOF, so the rune reader shuts down. // Therefore, if it isn't actually an EOF, we must re-startPrompt. s.restartPrompt() case ctrlL: // clear screen s.eraseScreen() s.refresh(p, []rune{}, 0) case ctrlH, bs: // Backspace if pos <= 0 { fmt.Print(beep) } else { n := len(getSuffixGlyphs(line[:pos], 1)) line = append(line[:pos-n], line[pos:]...) pos -= n } case ctrlC: fmt.Println("^C") if s.multiLineMode { s.resetMultiLine(p, line, pos) } if s.ctrlCAborts { return "", ErrPromptAborted } line = line[:0] pos = 0 fmt.Print(prompt) s.restartPrompt() // Unused keys case esc, tab, ctrlA, ctrlB, ctrlE, ctrlF, ctrlG, ctrlK, ctrlN, ctrlO, ctrlP, ctrlQ, ctrlR, ctrlS, ctrlT, ctrlU, ctrlV, ctrlW, ctrlX, ctrlY, ctrlZ: fallthrough // Catch unhandled control codes (anything <= 31) case 0, 28, 29, 30, 31: fmt.Print(beep) default: line = append(line[:pos], append([]rune{v}, line[pos:]...)...) pos++ } } } return string(line), nil } golang-github-peterh-liner-0.0~git20151118.0.4d47685/line_test.go000066400000000000000000000057711263112666600236430ustar00rootroot00000000000000package liner import ( "bytes" "fmt" "strings" "testing" ) func TestAppend(t *testing.T) { var s State s.AppendHistory("foo") s.AppendHistory("bar") var out bytes.Buffer num, err := s.WriteHistory(&out) if err != nil { t.Fatal("Unexpected error writing history", err) } if num != 2 { t.Fatalf("Expected 2 history entries, got %d", num) } s.AppendHistory("baz") num, err = s.WriteHistory(&out) if err != nil { t.Fatal("Unexpected error writing history", err) } if num != 3 { t.Fatalf("Expected 3 history entries, got %d", num) } s.AppendHistory("baz") num, err = s.WriteHistory(&out) if err != nil { t.Fatal("Unexpected error writing history", err) } if num != 3 { t.Fatalf("Expected 3 history entries after duplicate append, got %d", num) } s.AppendHistory("baz") } func TestHistory(t *testing.T) { input := `foo bar baz quux dingle` var s State num, err := s.ReadHistory(strings.NewReader(input)) if err != nil { t.Fatal("Unexpected error reading history", err) } if num != 5 { t.Fatal("Wrong number of history entries read") } var out bytes.Buffer num, err = s.WriteHistory(&out) if err != nil { t.Fatal("Unexpected error writing history", err) } if num != 5 { t.Fatal("Wrong number of history entries written") } if strings.TrimSpace(out.String()) != input { t.Fatal("Round-trip failure") } // Test reading with a trailing newline present var s2 State num, err = s2.ReadHistory(&out) if err != nil { t.Fatal("Unexpected error reading history the 2nd time", err) } if num != 5 { t.Fatal("Wrong number of history entries read the 2nd time") } num, err = s.ReadHistory(strings.NewReader(input + "\n\xff")) if err == nil { t.Fatal("Unexpected success reading corrupted history", err) } if num != 5 { t.Fatal("Wrong number of history entries read the 3rd time") } } func TestColumns(t *testing.T) { list := []string{"foo", "food", "This entry is quite a bit longer than the typical entry"} output := []struct { width, columns, rows, maxWidth int }{ {80, 1, 3, len(list[2]) + 1}, {120, 2, 2, len(list[2]) + 1}, {800, 14, 1, 0}, {8, 1, 3, 7}, } for i, o := range output { col, row, max := calculateColumns(o.width, list) if col != o.columns { t.Fatalf("Wrong number of columns, %d != %d, in TestColumns %d\n", col, o.columns, i) } if row != o.rows { t.Fatalf("Wrong number of rows, %d != %d, in TestColumns %d\n", row, o.rows, i) } if max != o.maxWidth { t.Fatalf("Wrong column width, %d != %d, in TestColumns %d\n", max, o.maxWidth, i) } } } // This example demonstrates a way to retrieve the current // history buffer without using a file. func ExampleState_WriteHistory() { var s State s.AppendHistory("foo") s.AppendHistory("bar") buf := new(bytes.Buffer) _, err := s.WriteHistory(buf) if err == nil { history := strings.Split(strings.TrimSpace(buf.String()), "\n") for i, line := range history { fmt.Println("History entry", i, ":", line) } } // Output: // History entry 0 : foo // History entry 1 : bar } golang-github-peterh-liner-0.0~git20151118.0.4d47685/output.go000066400000000000000000000026751263112666600232150ustar00rootroot00000000000000// +build linux darwin openbsd freebsd netbsd package liner import ( "fmt" "os" "strings" "syscall" "unsafe" ) func (s *State) cursorPos(x int) { if s.useCHA { // 'G' is "Cursor Character Absolute (CHA)" fmt.Printf("\x1b[%dG", x+1) } else { // 'C' is "Cursor Forward (CUF)" fmt.Print("\r") if x > 0 { fmt.Printf("\x1b[%dC", x) } } } func (s *State) eraseLine() { fmt.Print("\x1b[0K") } func (s *State) eraseScreen() { fmt.Print("\x1b[H\x1b[2J") } func (s *State) moveUp(lines int) { fmt.Printf("\x1b[%dA", lines) } func (s *State) moveDown(lines int) { fmt.Printf("\x1b[%dB", lines) } func (s *State) emitNewLine() { fmt.Print("\n") } type winSize struct { row, col uint16 xpixel, ypixel uint16 } func (s *State) getColumns() { var ws winSize ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws))) if ok < 0 { s.columns = 80 } s.columns = int(ws.col) } func (s *State) checkOutput() { // xterm is known to support CHA if strings.Contains(strings.ToLower(os.Getenv("TERM")), "xterm") { s.useCHA = true return } // The test for functional ANSI CHA is unreliable (eg the Windows // telnet command does not support reading the cursor position with // an ANSI DSR request, despite setting TERM=ansi) // Assume CHA isn't supported (which should be safe, although it // does result in occasional visible cursor jitter) s.useCHA = false } golang-github-peterh-liner-0.0~git20151118.0.4d47685/output_windows.go000066400000000000000000000042671263112666600247660ustar00rootroot00000000000000package liner import ( "unsafe" ) type coord struct { x, y int16 } type smallRect struct { left, top, right, bottom int16 } type consoleScreenBufferInfo struct { dwSize coord dwCursorPosition coord wAttributes int16 srWindow smallRect dwMaximumWindowSize coord } func (s *State) cursorPos(x int) { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) procSetConsoleCursorPosition.Call(uintptr(s.hOut), uintptr(int(x)&0xFFFF|int(sbi.dwCursorPosition.y)<<16)) } func (s *State) eraseLine() { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) var numWritten uint32 procFillConsoleOutputCharacter.Call(uintptr(s.hOut), uintptr(' '), uintptr(sbi.dwSize.x-sbi.dwCursorPosition.x), uintptr(int(sbi.dwCursorPosition.x)&0xFFFF|int(sbi.dwCursorPosition.y)<<16), uintptr(unsafe.Pointer(&numWritten))) } func (s *State) eraseScreen() { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) var numWritten uint32 procFillConsoleOutputCharacter.Call(uintptr(s.hOut), uintptr(' '), uintptr(sbi.dwSize.x)*uintptr(sbi.dwSize.y), 0, uintptr(unsafe.Pointer(&numWritten))) procSetConsoleCursorPosition.Call(uintptr(s.hOut), 0) } func (s *State) moveUp(lines int) { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) procSetConsoleCursorPosition.Call(uintptr(s.hOut), uintptr(int(sbi.dwCursorPosition.x)&0xFFFF|(int(sbi.dwCursorPosition.y)-lines)<<16)) } func (s *State) moveDown(lines int) { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) procSetConsoleCursorPosition.Call(uintptr(s.hOut), uintptr(int(sbi.dwCursorPosition.x)&0xFFFF|(int(sbi.dwCursorPosition.y)+lines)<<16)) } func (s *State) emitNewLine() { // windows doesn't need to omit a new line } func (s *State) getColumns() { var sbi consoleScreenBufferInfo procGetConsoleScreenBufferInfo.Call(uintptr(s.hOut), uintptr(unsafe.Pointer(&sbi))) s.columns = int(sbi.dwSize.x) } golang-github-peterh-liner-0.0~git20151118.0.4d47685/prefix_test.go000066400000000000000000000020371263112666600242010ustar00rootroot00000000000000// +build windows linux darwin openbsd freebsd netbsd package liner import "testing" type testItem struct { list []string prefix string } func TestPrefix(t *testing.T) { list := []testItem{ {[]string{"food", "foot"}, "foo"}, {[]string{"foo", "foot"}, "foo"}, {[]string{"food", "foo"}, "foo"}, {[]string{"food", "foe", "foot"}, "fo"}, {[]string{"food", "foot", "barbeque"}, ""}, {[]string{"cafeteria", "café"}, "caf"}, {[]string{"cafe", "café"}, "caf"}, {[]string{"cafè", "café"}, "caf"}, {[]string{"cafés", "café"}, "café"}, {[]string{"áéíóú", "áéíóú"}, "áéíóú"}, {[]string{"éclairs", "éclairs"}, "éclairs"}, {[]string{"éclairs are the best", "éclairs are great", "éclairs"}, "éclairs"}, {[]string{"éclair", "éclairs"}, "éclair"}, {[]string{"éclairs", "éclair"}, "éclair"}, {[]string{"éclair", "élan"}, "é"}, } for _, test := range list { lcp := longestCommonPrefix(test.list) if lcp != test.prefix { t.Errorf("%s != %s for %+v", lcp, test.prefix, test.list) } } } golang-github-peterh-liner-0.0~git20151118.0.4d47685/race_test.go000066400000000000000000000012221263112666600236110ustar00rootroot00000000000000// +build race package liner import ( "io/ioutil" "os" "sync" "testing" ) func TestWriteHistory(t *testing.T) { oldout := os.Stdout defer func() { os.Stdout = oldout }() oldin := os.Stdout defer func() { os.Stdin = oldin }() newinr, newinw, err := os.Pipe() if err != nil { t.Fatal(err) } os.Stdin = newinr newoutr, newoutw, err := os.Pipe() if err != nil { t.Fatal(err) } defer newoutr.Close() os.Stdout = newoutw var wait sync.WaitGroup wait.Add(1) s := NewLiner() go func() { s.AppendHistory("foo") s.AppendHistory("bar") s.Prompt("") wait.Done() }() s.WriteHistory(ioutil.Discard) newinw.Close() wait.Wait() } golang-github-peterh-liner-0.0~git20151118.0.4d47685/signal.go000066400000000000000000000002001263112666600231100ustar00rootroot00000000000000// +build go1.1,!windows package liner import ( "os" "os/signal" ) func stopSignal(c chan<- os.Signal) { signal.Stop(c) } golang-github-peterh-liner-0.0~git20151118.0.4d47685/signal_legacy.go000066400000000000000000000002211263112666600244370ustar00rootroot00000000000000// +build !go1.1,!windows package liner import ( "os" ) func stopSignal(c chan<- os.Signal) { // signal.Stop does not exist before Go 1.1 } golang-github-peterh-liner-0.0~git20151118.0.4d47685/unixmode.go000066400000000000000000000015101263112666600234700ustar00rootroot00000000000000// +build linux darwin freebsd openbsd netbsd package liner import ( "syscall" "unsafe" ) func (mode *termios) ApplyMode() error { _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdin), setTermios, uintptr(unsafe.Pointer(mode))) if errno != 0 { return errno } return nil } // 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) { mode, errno := getMode(syscall.Stdin) if errno != 0 { return nil, errno } return mode, nil } func getMode(handle int) (*termios, syscall.Errno) { var mode termios _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, uintptr(handle), getTermios, uintptr(unsafe.Pointer(&mode))) return &mode, errno } golang-github-peterh-liner-0.0~git20151118.0.4d47685/width.go000066400000000000000000000030571263112666600227670ustar00rootroot00000000000000package liner import "unicode" // These character classes are mostly zero width (when combined). // A few might not be, depending on the user's font. Fixing this // is non-trivial, given that some terminals don't support // ANSI DSR/CPR var zeroWidth = []*unicode.RangeTable{ unicode.Mn, unicode.Me, unicode.Cc, unicode.Cf, } var doubleWidth = []*unicode.RangeTable{ unicode.Han, unicode.Hangul, unicode.Hiragana, unicode.Katakana, } // countGlyphs considers zero-width characters to be zero glyphs wide, // and members of Chinese, Japanese, and Korean scripts to be 2 glyphs wide. func countGlyphs(s []rune) int { n := 0 for _, r := range s { switch { case unicode.IsOneOf(zeroWidth, r): case unicode.IsOneOf(doubleWidth, r): n += 2 default: n++ } } return n } func countMultiLineGlyphs(s []rune, columns int, start int) int { n := start for _, r := range s { switch { case unicode.IsOneOf(zeroWidth, r): case unicode.IsOneOf(doubleWidth, r): n += 2 // no room for a 2-glyphs-wide char in the ending // so skip a column and display it at the beginning if n%columns == 1 { n++ } default: n++ } } return n } func getPrefixGlyphs(s []rune, num int) []rune { p := 0 for n := 0; n < num && p < len(s); p++ { if !unicode.IsOneOf(zeroWidth, s[p]) { n++ } } for p < len(s) && unicode.IsOneOf(zeroWidth, s[p]) { p++ } return s[:p] } func getSuffixGlyphs(s []rune, num int) []rune { p := len(s) for n := 0; n < num && p > 0; p-- { if !unicode.IsOneOf(zeroWidth, s[p-1]) { n++ } } return s[p:] } golang-github-peterh-liner-0.0~git20151118.0.4d47685/width_test.go000066400000000000000000000050561263112666600240270ustar00rootroot00000000000000package liner import ( "strconv" "testing" ) func accent(in []rune) []rune { var out []rune for _, r := range in { out = append(out, r) out = append(out, '\u0301') } return out } type testCase struct { s []rune glyphs int } var testCases = []testCase{ {[]rune("query"), 5}, {[]rune("私"), 2}, {[]rune("hello世界"), 9}, } func TestCountGlyphs(t *testing.T) { for _, testCase := range testCases { count := countGlyphs(testCase.s) if count != testCase.glyphs { t.Errorf("ASCII count incorrect. %d != %d", count, testCase.glyphs) } count = countGlyphs(accent(testCase.s)) if count != testCase.glyphs { t.Errorf("Accent count incorrect. %d != %d", count, testCase.glyphs) } } } func compare(a, b []rune, name string, t *testing.T) { if len(a) != len(b) { t.Errorf(`"%s" != "%s" in %s"`, string(a), string(b), name) return } for i := range a { if a[i] != b[i] { t.Errorf(`"%s" != "%s" in %s"`, string(a), string(b), name) return } } } func TestPrefixGlyphs(t *testing.T) { for _, testCase := range testCases { for i := 0; i <= len(testCase.s); i++ { iter := strconv.Itoa(i) out := getPrefixGlyphs(testCase.s, i) compare(out, testCase.s[:i], "ascii prefix "+iter, t) out = getPrefixGlyphs(accent(testCase.s), i) compare(out, accent(testCase.s[:i]), "accent prefix "+iter, t) } out := getPrefixGlyphs(testCase.s, 999) compare(out, testCase.s, "ascii prefix overflow", t) out = getPrefixGlyphs(accent(testCase.s), 999) compare(out, accent(testCase.s), "accent prefix overflow", t) out = getPrefixGlyphs(testCase.s, -3) if len(out) != 0 { t.Error("ascii prefix negative") } out = getPrefixGlyphs(accent(testCase.s), -3) if len(out) != 0 { t.Error("accent prefix negative") } } } func TestSuffixGlyphs(t *testing.T) { for _, testCase := range testCases { for i := 0; i <= len(testCase.s); i++ { iter := strconv.Itoa(i) out := getSuffixGlyphs(testCase.s, i) compare(out, testCase.s[len(testCase.s)-i:], "ascii suffix "+iter, t) out = getSuffixGlyphs(accent(testCase.s), i) compare(out, accent(testCase.s[len(testCase.s)-i:]), "accent suffix "+iter, t) } out := getSuffixGlyphs(testCase.s, 999) compare(out, testCase.s, "ascii suffix overflow", t) out = getSuffixGlyphs(accent(testCase.s), 999) compare(out, accent(testCase.s), "accent suffix overflow", t) out = getSuffixGlyphs(testCase.s, -3) if len(out) != 0 { t.Error("ascii suffix negative") } out = getSuffixGlyphs(accent(testCase.s), -3) if len(out) != 0 { t.Error("accent suffix negative") } } }