func main() { flag.Parse() log.SetFlags(0) log.SetPrefix("--- FAIL: ") if *DebugKey { Lookup() return } if *DebugWinSize { win := term.DetectWinSize() defer win.Close() fmt.Println("[Resize the window: should print a number every time]") for i := 0; i < 7; i++ { select { case <-win.Change: fmt.Printf("%d ", i) case <-time.After(13 * time.Second): fmt.Print("\ntimed out\n") return } } return } if !*IsInteractive { pr, pw = io.Pipe() term.Input = pr } TestCharMode() TestEchoMode() if *IsInteractive { TestPassword() } TestEditLine() if *IsInteractive { TestDetectSize() } }
func TestDetectSize() { fmt.Print("\n=== RUN TestDetectSize\n") ter, _ := term.New() defer func() { if err := ter.Restore(); err != nil { log.Print(err) } }() row, col, err := ter.GetSize() if err != nil { panic(err) } winSize := term.DetectWinSize() fmt.Println("[Change the size of the terminal]") // I want to finish the test. go func() { time.Sleep(10 * time.Second) winSize.Change <- true }() <-winSize.Change winSize.Close() row2, col2, err := ter.GetSize() if err != nil { panic(err) } if row == row2 && col == col2 { log.Print("the terminal size got the same value") return } }
// Read reads charactes from input to write them to output, enabling line editing. // The errors that could return are to indicate if Ctrl+D was pressed, and for // both input/output errors. func (ln *Line) Read() (line string, err error) { var anotherLine []rune // For lines got from history. var isHistoryUsed bool // If the history has been accessed. var action keyAction in := bufio.NewReader(term.Input) // Read input. esc := make([]byte, 2) // For escape sequences. extEsc := make([]byte, 3) // Extended escape sequences. // Print the primary prompt. if err = ln.Prompt(); err != nil { return "", err } // == Detect change of window size. winSize := term.DetectWinSize() go func() { for { select { case <-winSize.Change: // Wait for. _, col, err := ln.ter.GetSize() if err != nil { ln.buf.columns = col ln.buf.refresh() } } } }() defer winSize.Close() for ; ; action = 0 { char, _, err := in.ReadRune() if err != nil { return "", inputError(err.Error()) } _S: switch char { default: if err = ln.buf.insertRune(char); err != nil { return "", err } continue case sys.K_RETURN: line = ln.buf.toString() if ln.useHistory { ln.hist.Add(line) } if _, err = term.Output.Write(CRLF); err != nil { return "", outputError(err.Error()) } return strings.TrimSpace(line), nil case sys.K_TAB: // TODO: disabled by now continue case sys.K_BACK, sys.K_CTRL_H: if err = ln.buf.deleteCharPrev(); err != nil { return "", err } continue case sys.K_CTRL_C: if err = ln.buf.insertRunes(ctrlC); err != nil { return "", err } if _, err = term.Output.Write(CRLF); err != nil { return "", outputError(err.Error()) } //ChanCtrlC <- 1 TODO: is really necessary? if err = ln.Prompt(); err != nil { return "", err } continue case sys.K_CTRL_D: if err = ln.buf.insertRunes(ctrlD); err != nil { return "", err } if _, err = term.Output.Write(CRLF); err != nil { return "", outputError(err.Error()) } ln.Restore() return "", ErrCtrlD // Escape sequence case sys.K_ESCAPE: // Ctrl+[ ("\x1b" in hexadecimal, "033" in octal) if _, err = in.Read(esc); err != nil { return "", inputError(err.Error()) } if esc[0] == 79 { // 'O' switch esc[1] { case 72: // Home: "\x1b O H" action = _HOME break _S case 70: // End: "\x1b O F" action = _END break _S } } if esc[0] == 91 { // '[' switch esc[1] { case 65: // Up: "\x1b [ A" if !ln.useHistory { continue } action = _UP break _S case 66: // Down: "\x1b [ B" if !ln.useHistory { continue } action = _DOWN break _S case 68: // "\x1b [ D" action = _LEFT break _S case 67: // "\x1b [ C" action = _RIGHT break _S } // Extended escape. if esc[1] > 48 && esc[1] < 55 { if _, err = in.Read(extEsc); err != nil { return "", inputError(err.Error()) } if extEsc[0] == 126 { // '~' switch esc[1] { //case 50: // Insert: "\x1b [ 2 ~" case 51: // Delete: "\x1b [ 3 ~" if err = ln.buf.deleteChar(); err != nil { return "", err } continue //case 53: // RePag: "\x1b [ 5 ~" //case 54: // AvPag: "\x1b [ 6 ~" } } if esc[1] == 49 && extEsc[0] == 59 && extEsc[1] == 53 { // "1;5" switch extEsc[2] { case 68: // Ctrl+left arrow: "\x1b [ 1 ; 5 D" // move to last word if err = ln.buf.wordBackward(); err != nil { return "", err } continue case 67: // Ctrl+right arrow: "\x1b [ 1 ; 5 C" // move to next word if err = ln.buf.wordForward(); err != nil { return "", err } continue } } } } continue case sys.K_CTRL_T: // Swap actual character by the previous one. if err = ln.buf.swap(); err != nil { return "", err } continue case sys.K_CTRL_L: // Clear screen. if _, err = term.Output.Write(delScreenToUpper); err != nil { return "", err } if err = ln.Prompt(); err != nil { return "", err } continue case sys.K_CTRL_U: // Delete the whole line. if err = ln.buf.deleteLine(); err != nil { return "", err } if err = ln.Prompt(); err != nil { return "", err } continue case sys.K_CTRL_K: // Delete from current to end of line. if err = ln.buf.deleteToRight(); err != nil { return "", err } continue case sys.K_CTRL_P: // Up if !ln.useHistory { continue } action = _UP case sys.K_CTRL_N: // Down if !ln.useHistory { continue } action = _DOWN case sys.K_CTRL_B: // Left action = _LEFT case sys.K_CTRL_F: // Right action = _RIGHT case sys.K_CTRL_A: // Start of line. action = _HOME case sys.K_CTRL_E: // End of line. action = _END } switch action { case _UP, _DOWN: // Up and down arrow: history if action == _UP { anotherLine, err = ln.hist.Prev() } else { anotherLine, err = ln.hist.Next() } if err != nil { continue } // Update the current history entry before to overwrite it with // the next one. // TODO: it has to be removed before of to be saved the history if !isHistoryUsed { ln.hist.Add(ln.buf.toString()) } isHistoryUsed = true ln.buf.grow(len(anotherLine)) ln.buf.size = len(anotherLine) + ln.buf.promptLen copy(ln.buf.data[ln.lenPS1:], anotherLine) if err = ln.buf.refresh(); err != nil { return "", err } continue case _LEFT: if _, err = ln.buf.backward(); err != nil { return "", err } continue case _RIGHT: if _, err = ln.buf.forward(); err != nil { return "", err } continue case _HOME: if err = ln.buf.start(); err != nil { return "", err } continue case _END: if _, err = ln.buf.end(); err != nil { return "", err } continue } } }
// Reads charactes from input to write them to output, allowing line editing. // The errors that could return are to indicate if Ctrl-D was pressed, and for // both input / output errors. func (ln *Line) Read() (line string, err error) { var anotherLine []rune // For lines got from history. var isHistoryUsed bool // If the history has been accessed. in := bufio.NewReader(input) // Read input. seq := make([]byte, 2) // For escape sequences. seq2 := make([]byte, 1) // Extended escape sequences. // Print the primary prompt. if err = ln.prompt(); err != nil { return "", err } // === Detect change of window size. wSize := term.DetectWinSize() go func() { for { <-wSize.Change // Wait for. // TODO(jwall): Check errors? _, ln.buf.winColumns, _ = tty.GetSize() ln.buf.refresh() } }() for { rune, _, err := in.ReadRune() if err != nil { return "", inputError(err.Error()) } switch rune { default: if err = ln.buf.insertRune(rune); err != nil { return "", err } continue case 13: // enter line = ln.buf.toString() if ln.useHistory { ln.hist.Add(line) } if _, err = output.Write(_CR_LF); err != nil { return "", outputError(err.Error()) } return strings.TrimSpace(line), nil case 127, 8: // backspace, Ctrl-h if err = ln.buf.deletePrev(); err != nil { return "", err } continue case 9: // horizontal tab // TODO: disabled by now continue case 3: // Ctrl-c if err = ln.buf.insertRunes(ctrlC); err != nil { return "", err } if _, err = output.Write(_CR_LF); err != nil { return "", outputError(err.Error()) } if err = ln.prompt(); err != nil { return "", err } continue case 4: // Ctrl-d if err = ln.buf.insertRunes(ctrlD); err != nil { return "", err } if _, err = output.Write(_CR_LF); err != nil { return "", outputError(err.Error()) } return "", ErrCtrlD // Escape sequence case 27: // Escape: Ctrl-[ ("033" in octal, "\x1b" in hexadecimal) if _, err = in.Read(seq); err != nil { return "", inputError(err.Error()) } if seq[0] == 79 { // 'O' switch seq[1] { case 72: // Home: "\x1bOH" goto _start case 70: // End: "\x1bOF" goto _end } } if seq[0] == 91 { // Left square bracket: "[" switch seq[1] { case 68: // "\x1b[D" goto _leftArrow case 67: // "\x1b[C" goto _rightArrow case 65, 66: // Up: "\x1b[A"; Down: "\x1b[B" goto _upDownArrow } // Extended escape. if seq[1] > 48 && seq[1] < 55 { if _, err = in.Read(seq2); err != nil { return "", inputError(err.Error()) } if seq2[0] == 126 { // '~' switch seq[1] { //case 50: // Insert: "\x1b[2~" case 51: // Delete: "\x1b[3~" if err = ln.buf.delete(); err != nil { return "", err } //case 53: // RePag: "\x1b[5~" //case 54: // AvPag: "\x1b[6~" } } } } continue case 20: // Ctrl-t, swap actual character by the previous one. if err = ln.buf.swap(); err != nil { return "", err } continue case 21: // Ctrl+u, delete the whole line. if err = ln.buf.deleteLine(); err != nil { return "", err } if err = ln.prompt(); err != nil { return "", err } continue case 11: // Ctrl+k, delete from current to end of line. if err = ln.buf.deleteRight(); err != nil { return "", err } continue case 1: // Ctrl+a, go to the start of the line. goto _start case 5: // Ctrl+e, go to the end of the line. goto _end case 2: // Ctrl-b goto _leftArrow case 6: // Ctrl-f goto _rightArrow case 16: // Ctrl-p seq[1] = 65 goto _upDownArrow case 14: // Ctrl-n seq[1] = 66 goto _upDownArrow } _upDownArrow: // Up and down arrow: history if !ln.useHistory { continue } // Up if seq[1] == 65 { anotherLine, err = ln.hist.Prev() // Down } else { anotherLine, err = ln.hist.Next() } if err != nil { continue } // Update the current history entry before to overwrite it with // the next one. // TODO: it has to be removed before of to be saved the history if !isHistoryUsed { ln.hist.Add(ln.buf.toString()) } isHistoryUsed = true ln.buf.grow(len(anotherLine)) ln.buf.size = len(anotherLine) + ln.buf.promptLen copy(ln.buf.data[ln.ps1Len:], anotherLine) if err = ln.buf.refresh(); err != nil { return "", err } continue _leftArrow: if err = ln.buf.backward(); err != nil { return "", err } continue _rightArrow: if err = ln.buf.forward(); err != nil { return "", err } continue _start: if err = ln.buf.start(); err != nil { return "", err } continue _end: if _, err = ln.buf.end(); err != nil { return "", err } continue } return }