github.com/prattmic/llgo-embedded@v0.0.0-20150820070356-41cfecea0e1e/third_party/liner/input.go (about)

     1  // +build linux darwin openbsd freebsd netbsd
     2  
     3  package liner
     4  
     5  import (
     6  	"bufio"
     7  	"errors"
     8  	"os"
     9  	"os/signal"
    10  	"strconv"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  )
    15  
    16  type nexter struct {
    17  	r   rune
    18  	err error
    19  }
    20  
    21  // State represents an open terminal
    22  type State struct {
    23  	commonState
    24  	origMode    termios
    25  	defaultMode termios
    26  	next        <-chan nexter
    27  	winch       chan os.Signal
    28  	pending     []rune
    29  	useCHA      bool
    30  }
    31  
    32  // NewLiner initializes a new *State, and sets the terminal into raw mode. To
    33  // restore the terminal to its previous state, call State.Close().
    34  //
    35  // Note if you are still using Go 1.0: NewLiner handles SIGWINCH, so it will
    36  // leak a channel every time you call it. Therefore, it is recommened that you
    37  // upgrade to a newer release of Go, or ensure that NewLiner is only called
    38  // once.
    39  func NewLiner() *State {
    40  	var s State
    41  	s.r = bufio.NewReader(os.Stdin)
    42  
    43  	s.terminalSupported = TerminalSupported()
    44  	if m, err := TerminalMode(); err == nil {
    45  		s.origMode = *m.(*termios)
    46  	} else {
    47  		s.inputRedirected = true
    48  	}
    49  	if _, err := getMode(syscall.Stdout); err != 0 {
    50  		s.outputRedirected = true
    51  	}
    52  	if s.inputRedirected && s.outputRedirected {
    53  		s.terminalSupported = false
    54  	}
    55  	if s.terminalSupported && !s.inputRedirected && !s.outputRedirected {
    56  		mode := s.origMode
    57  		mode.Iflag &^= icrnl | inpck | istrip | ixon
    58  		mode.Cflag |= cs8
    59  		mode.Lflag &^= syscall.ECHO | icanon | iexten
    60  		mode.ApplyMode()
    61  
    62  		winch := make(chan os.Signal, 1)
    63  		signal.Notify(winch, syscall.SIGWINCH)
    64  		s.winch = winch
    65  
    66  		s.checkOutput()
    67  	}
    68  
    69  	if !s.outputRedirected {
    70  		s.getColumns()
    71  		s.outputRedirected = s.columns <= 0
    72  	}
    73  
    74  	return &s
    75  }
    76  
    77  var errTimedOut = errors.New("timeout")
    78  
    79  func (s *State) startPrompt() {
    80  	if s.terminalSupported {
    81  		if m, err := TerminalMode(); err == nil {
    82  			s.defaultMode = *m.(*termios)
    83  			mode := s.defaultMode
    84  			mode.Lflag &^= isig
    85  			mode.ApplyMode()
    86  		}
    87  	}
    88  	s.restartPrompt()
    89  }
    90  
    91  func (s *State) restartPrompt() {
    92  	next := make(chan nexter)
    93  	go func() {
    94  		for {
    95  			var n nexter
    96  			n.r, _, n.err = s.r.ReadRune()
    97  			next <- n
    98  			// Shut down nexter loop when an end condition has been reached
    99  			if n.err != nil || n.r == '\n' || n.r == '\r' || n.r == ctrlC || n.r == ctrlD {
   100  				close(next)
   101  				return
   102  			}
   103  		}
   104  	}()
   105  	s.next = next
   106  }
   107  
   108  func (s *State) stopPrompt() {
   109  	if s.terminalSupported {
   110  		s.defaultMode.ApplyMode()
   111  	}
   112  }
   113  
   114  func (s *State) nextPending(timeout <-chan time.Time) (rune, error) {
   115  	select {
   116  	case thing, ok := <-s.next:
   117  		if !ok {
   118  			return 0, errors.New("liner: internal error")
   119  		}
   120  		if thing.err != nil {
   121  			return 0, thing.err
   122  		}
   123  		s.pending = append(s.pending, thing.r)
   124  		return thing.r, nil
   125  	case <-timeout:
   126  		rv := s.pending[0]
   127  		s.pending = s.pending[1:]
   128  		return rv, errTimedOut
   129  	}
   130  	// not reached
   131  	return 0, nil
   132  }
   133  
   134  func (s *State) readNext() (interface{}, error) {
   135  	if len(s.pending) > 0 {
   136  		rv := s.pending[0]
   137  		s.pending = s.pending[1:]
   138  		return rv, nil
   139  	}
   140  	var r rune
   141  	select {
   142  	case thing, ok := <-s.next:
   143  		if !ok {
   144  			return 0, errors.New("liner: internal error")
   145  		}
   146  		if thing.err != nil {
   147  			return nil, thing.err
   148  		}
   149  		r = thing.r
   150  	case <-s.winch:
   151  		s.getColumns()
   152  		return winch, nil
   153  	}
   154  	if r != esc {
   155  		return r, nil
   156  	}
   157  	s.pending = append(s.pending, r)
   158  
   159  	// Wait at most 50 ms for the rest of the escape sequence
   160  	// If nothing else arrives, it was an actual press of the esc key
   161  	timeout := time.After(50 * time.Millisecond)
   162  	flag, err := s.nextPending(timeout)
   163  	if err != nil {
   164  		if err == errTimedOut {
   165  			return flag, nil
   166  		}
   167  		return unknown, err
   168  	}
   169  
   170  	switch flag {
   171  	case '[':
   172  		code, err := s.nextPending(timeout)
   173  		if err != nil {
   174  			if err == errTimedOut {
   175  				return code, nil
   176  			}
   177  			return unknown, err
   178  		}
   179  		switch code {
   180  		case 'A':
   181  			s.pending = s.pending[:0] // escape code complete
   182  			return up, nil
   183  		case 'B':
   184  			s.pending = s.pending[:0] // escape code complete
   185  			return down, nil
   186  		case 'C':
   187  			s.pending = s.pending[:0] // escape code complete
   188  			return right, nil
   189  		case 'D':
   190  			s.pending = s.pending[:0] // escape code complete
   191  			return left, nil
   192  		case 'F':
   193  			s.pending = s.pending[:0] // escape code complete
   194  			return end, nil
   195  		case 'H':
   196  			s.pending = s.pending[:0] // escape code complete
   197  			return home, nil
   198  		case 'Z':
   199  			s.pending = s.pending[:0] // escape code complete
   200  			return shiftTab, nil
   201  		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   202  			num := []rune{code}
   203  			for {
   204  				code, err := s.nextPending(timeout)
   205  				if err != nil {
   206  					if err == errTimedOut {
   207  						return code, nil
   208  					}
   209  					return nil, err
   210  				}
   211  				switch code {
   212  				case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   213  					num = append(num, code)
   214  				case ';':
   215  					// Modifier code to follow
   216  					// This only supports Ctrl-left and Ctrl-right for now
   217  					x, _ := strconv.ParseInt(string(num), 10, 32)
   218  					if x != 1 {
   219  						// Can't be left or right
   220  						rv := s.pending[0]
   221  						s.pending = s.pending[1:]
   222  						return rv, nil
   223  					}
   224  					num = num[:0]
   225  					for {
   226  						code, err = s.nextPending(timeout)
   227  						if err != nil {
   228  							if err == errTimedOut {
   229  								rv := s.pending[0]
   230  								s.pending = s.pending[1:]
   231  								return rv, nil
   232  							}
   233  							return nil, err
   234  						}
   235  						switch code {
   236  						case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
   237  							num = append(num, code)
   238  						case 'C', 'D':
   239  							// right, left
   240  							mod, _ := strconv.ParseInt(string(num), 10, 32)
   241  							if mod != 5 {
   242  								// Not bare Ctrl
   243  								rv := s.pending[0]
   244  								s.pending = s.pending[1:]
   245  								return rv, nil
   246  							}
   247  							s.pending = s.pending[:0] // escape code complete
   248  							if code == 'C' {
   249  								return wordRight, nil
   250  							}
   251  							return wordLeft, nil
   252  						default:
   253  							// Not left or right
   254  							rv := s.pending[0]
   255  							s.pending = s.pending[1:]
   256  							return rv, nil
   257  						}
   258  					}
   259  				case '~':
   260  					s.pending = s.pending[:0] // escape code complete
   261  					x, _ := strconv.ParseInt(string(num), 10, 32)
   262  					switch x {
   263  					case 2:
   264  						return insert, nil
   265  					case 3:
   266  						return del, nil
   267  					case 5:
   268  						return pageUp, nil
   269  					case 6:
   270  						return pageDown, nil
   271  					case 7:
   272  						return home, nil
   273  					case 8:
   274  						return end, nil
   275  					case 15:
   276  						return f5, nil
   277  					case 17:
   278  						return f6, nil
   279  					case 18:
   280  						return f7, nil
   281  					case 19:
   282  						return f8, nil
   283  					case 20:
   284  						return f9, nil
   285  					case 21:
   286  						return f10, nil
   287  					case 23:
   288  						return f11, nil
   289  					case 24:
   290  						return f12, nil
   291  					default:
   292  						return unknown, nil
   293  					}
   294  				default:
   295  					// unrecognized escape code
   296  					rv := s.pending[0]
   297  					s.pending = s.pending[1:]
   298  					return rv, nil
   299  				}
   300  			}
   301  		}
   302  
   303  	case 'O':
   304  		code, err := s.nextPending(timeout)
   305  		if err != nil {
   306  			if err == errTimedOut {
   307  				return code, nil
   308  			}
   309  			return nil, err
   310  		}
   311  		s.pending = s.pending[:0] // escape code complete
   312  		switch code {
   313  		case 'c':
   314  			return wordRight, nil
   315  		case 'd':
   316  			return wordLeft, nil
   317  		case 'H':
   318  			return home, nil
   319  		case 'F':
   320  			return end, nil
   321  		case 'P':
   322  			return f1, nil
   323  		case 'Q':
   324  			return f2, nil
   325  		case 'R':
   326  			return f3, nil
   327  		case 'S':
   328  			return f4, nil
   329  		default:
   330  			return unknown, nil
   331  		}
   332  	case 'y':
   333  		s.pending = s.pending[:0] // escape code complete
   334  		return altY, nil
   335  	default:
   336  		rv := s.pending[0]
   337  		s.pending = s.pending[1:]
   338  		return rv, nil
   339  	}
   340  
   341  	// not reached
   342  	return r, nil
   343  }
   344  
   345  // Close returns the terminal to its previous mode
   346  func (s *State) Close() error {
   347  	stopSignal(s.winch)
   348  	if !s.inputRedirected {
   349  		s.origMode.ApplyMode()
   350  	}
   351  	return nil
   352  }
   353  
   354  // TerminalSupported returns true if the current terminal supports
   355  // line editing features, and false if liner will use the 'dumb'
   356  // fallback for input.
   357  // Note that TerminalSupported does not check all factors that may
   358  // cause liner to not fully support the terminal (such as stdin redirection)
   359  func TerminalSupported() bool {
   360  	bad := map[string]bool{"": true, "dumb": true, "cons25": true}
   361  	return !bad[strings.ToLower(os.Getenv("TERM"))]
   362  }