github.com/ladydascalie/elvish@v0.0.0-20170703214355-2964dd3ece7f/edit/tty/reader.go (about)

     1  package tty
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"time"
     7  
     8  	"github.com/elves/elvish/edit/ui"
     9  )
    10  
    11  var (
    12  	// EscSequenceTimeout is the amount of time within which runes that make up
    13  	// an escape sequence are supposed to follow each other. Modern terminal
    14  	// emulators send escape sequences very fast, so 10ms is more than
    15  	// sufficient. SSH connections on a slow link might be problematic though.
    16  	EscSequenceTimeout = 10 * time.Millisecond
    17  )
    18  
    19  // Special rune values used in the return value of (*Reader).ReadRune.
    20  const (
    21  	// No rune received before specified time.
    22  	runeTimeout rune = -1 - iota
    23  	// Error occurred in AsyncReader. The error is left at the readError field.
    24  	runeReadError
    25  )
    26  
    27  // Reader converts a stream of events on separate channels.
    28  type Reader struct {
    29  	ar  *AsyncReader
    30  	raw bool
    31  
    32  	unitChan chan ReadUnit
    33  	/*
    34  		rawRuneChan chan rune
    35  		keyChan     chan ui.Key
    36  		cprChan     chan Pos
    37  		mouseChan   chan MouseEvent
    38  		pasteChan   chan bool
    39  	*/
    40  	errChan chan error
    41  	quit    chan struct{}
    42  }
    43  
    44  type MouseEvent struct {
    45  	Pos
    46  	Down bool
    47  	// Number of the Button, 0-based. -1 for unknown.
    48  	Button int
    49  	Mod    ui.Mod
    50  }
    51  
    52  // NewReader creates a new Reader on the given terminal file.
    53  func NewReader(f *os.File) *Reader {
    54  	rd := &Reader{
    55  		NewAsyncReader(f),
    56  		false,
    57  		make(chan ReadUnit),
    58  		/*
    59  			make(chan rune),
    60  			make(chan ui.Key),
    61  			make(chan Pos),
    62  			make(chan MouseEvent),
    63  			make(chan bool),
    64  		*/
    65  		make(chan error),
    66  		nil,
    67  	}
    68  	return rd
    69  }
    70  
    71  // SetRaw turns the raw option on or off. If the reader is in the middle of
    72  // reading one event, it takes effect after this event is fully read.
    73  func (rd *Reader) SetRaw(raw bool) {
    74  	rd.raw = raw
    75  }
    76  
    77  // UnitChan returns the channel onto which the Reader writes what it has read.
    78  func (rd *Reader) UnitChan() <-chan ReadUnit {
    79  	return rd.unitChan
    80  }
    81  
    82  // ErrorChan returns the channel onto which the Reader writes errors it came
    83  // across during the reading process.
    84  func (rd *Reader) ErrorChan() <-chan error {
    85  	return rd.errChan
    86  }
    87  
    88  // Run runs the Reader. It blocks until Quit is called and should be called in
    89  // a separate goroutine.
    90  func (rd *Reader) Run() {
    91  	runes := rd.ar.Chan()
    92  	quit := make(chan struct{})
    93  	rd.quit = quit
    94  	go rd.ar.Run()
    95  
    96  	for {
    97  		select {
    98  		case r := <-runes:
    99  			if rd.raw {
   100  				rd.unitChan <- RawRune(r)
   101  			} else {
   102  				rd.readOne(r)
   103  			}
   104  		case <-quit:
   105  			return
   106  		}
   107  	}
   108  }
   109  
   110  // Quit terminates the loop of Run.
   111  func (rd *Reader) Quit() {
   112  	rd.ar.Quit()
   113  	close(rd.quit)
   114  }
   115  
   116  // Close releases files associated with the Reader. It does not close the file
   117  // used to create it.
   118  func (rd *Reader) Close() {
   119  	rd.ar.Close()
   120  }
   121  
   122  // readOne attempts to read one key or CPR, led by a rune already read.
   123  func (rd *Reader) readOne(r rune) {
   124  	var unit ReadUnit
   125  	var err error
   126  	currentSeq := string(r)
   127  
   128  	badSeq := func(msg string) {
   129  		err = fmt.Errorf("%s: %q", msg, currentSeq)
   130  	}
   131  
   132  	// readRune attempts to read a rune within EscSequenceTimeout. It writes to
   133  	// the err and currentSeq variable in the outer scope.
   134  	readRune :=
   135  		func() rune {
   136  			select {
   137  			case r := <-rd.ar.Chan():
   138  				currentSeq += string(r)
   139  				return r
   140  			case err = <-rd.ar.ErrorChan():
   141  				return runeReadError
   142  			case <-time.After(EscSequenceTimeout):
   143  				return runeTimeout
   144  			}
   145  		}
   146  
   147  	defer func() {
   148  		if unit != nil {
   149  			select {
   150  			case rd.unitChan <- unit:
   151  			case <-rd.quit:
   152  			}
   153  		}
   154  		if err != nil {
   155  			select {
   156  			case rd.errChan <- err:
   157  			case <-rd.quit:
   158  			}
   159  		}
   160  	}()
   161  
   162  	switch r {
   163  	case 0x1b: // ^[ Escape
   164  		r2 := readRune()
   165  		if r2 == runeTimeout || r2 == runeReadError {
   166  			// Nothing follows. Taken as a lone Escape.
   167  			unit = Key{'[', ui.Ctrl}
   168  			break
   169  		}
   170  		switch r2 {
   171  		case '[':
   172  			// A '[' follows. CSI style function key sequence.
   173  			r = readRune()
   174  			if r == runeTimeout || r == runeReadError {
   175  				unit = Key{'[', ui.Alt}
   176  				return
   177  			}
   178  
   179  			nums := make([]int, 0, 2)
   180  			var starter rune
   181  
   182  			// Read an optional starter.
   183  			switch r {
   184  			case '<':
   185  				starter = r
   186  				r = readRune()
   187  			case 'M':
   188  				// Mouse event.
   189  				cb := readRune()
   190  				if cb == runeTimeout || cb == runeReadError {
   191  					badSeq("Incomplete mouse event")
   192  					return
   193  				}
   194  				cx := readRune()
   195  				if cx == runeTimeout || cx == runeReadError {
   196  					badSeq("Incomplete mouse event")
   197  					return
   198  				}
   199  				cy := readRune()
   200  				if cy == runeTimeout || cy == runeReadError {
   201  					badSeq("Incomplete mouse event")
   202  					return
   203  				}
   204  				down := true
   205  				button := int(cb & 3)
   206  				if button == 3 {
   207  					down = false
   208  					button = -1
   209  				}
   210  				mod := mouseModify(int(cb))
   211  				unit = MouseEvent{
   212  					Pos{int(cy) - 32, int(cx) - 32}, down, button, mod}
   213  				return
   214  			}
   215  		CSISeq:
   216  			for {
   217  				switch {
   218  				case r == ';':
   219  					nums = append(nums, 0)
   220  				case '0' <= r && r <= '9':
   221  					if len(nums) == 0 {
   222  						nums = append(nums, 0)
   223  					}
   224  					cur := len(nums) - 1
   225  					nums[cur] = nums[cur]*10 + int(r-'0')
   226  				case r == runeTimeout:
   227  					// Incomplete CSI.
   228  					badSeq("Incomplete CSI")
   229  					return
   230  				case r == runeReadError:
   231  					// TODO Also complain about incomplte CSI.
   232  					return
   233  				default: // Treat as a terminator.
   234  					break CSISeq
   235  				}
   236  
   237  				r = readRune()
   238  			}
   239  			if starter == 0 && r == 'R' {
   240  				// Cursor position report.
   241  				if len(nums) != 2 {
   242  					badSeq("bad CPR")
   243  					return
   244  				}
   245  				unit = CursorPosition{nums[0], nums[1]}
   246  			} else if starter == '<' && (r == 'm' || r == 'M') {
   247  				// SGR-style mouse event.
   248  				if len(nums) != 3 {
   249  					badSeq("bad SGR mouse event")
   250  					return
   251  				}
   252  				down := r == 'M'
   253  				button := nums[0] & 3
   254  				mod := mouseModify(nums[0])
   255  				unit = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod}
   256  			} else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) {
   257  				b := nums[0] == 200
   258  				unit = PasteSetting(b)
   259  			} else {
   260  				k := parseCSI(nums, r, currentSeq)
   261  				if k == (ui.Key{}) {
   262  					badSeq("bad CSI")
   263  				} else {
   264  					unit = Key(k)
   265  				}
   266  			}
   267  		case 'O':
   268  			// An 'O' follows. G3 style function key sequence: read one rune.
   269  			r = readRune()
   270  			if r == runeTimeout || r == runeReadError {
   271  				// Nothing follows after 'O'. Taken as ui.Alt-o.
   272  				unit = Key{'o', ui.Alt}
   273  				return
   274  			}
   275  			r, ok := g3Seq[r]
   276  			if ok {
   277  				unit = Key{r, 0}
   278  			} else {
   279  				badSeq("bad G3")
   280  			}
   281  		default:
   282  			// Something other than '[' or 'O' follows. Taken as an
   283  			// ui.Alt-modified key, possibly also modified by ui.Ctrl.
   284  			k := ctrlModify(r2)
   285  			k.Mod |= ui.Alt
   286  			unit = Key(k)
   287  		}
   288  	default:
   289  		k := ctrlModify(r)
   290  		unit = Key(k)
   291  	}
   292  }
   293  
   294  // ctrlModify determines whether a rune corresponds to a ui.Ctrl-modified key and
   295  // returns the ui.Key the rune represents.
   296  func ctrlModify(r rune) ui.Key {
   297  	switch r {
   298  	case 0x0:
   299  		return ui.Key{'`', ui.Ctrl} // ^@
   300  	case 0x1e:
   301  		return ui.Key{'6', ui.Ctrl} // ^^
   302  	case 0x1f:
   303  		return ui.Key{'/', ui.Ctrl} // ^_
   304  	case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^?
   305  		return ui.Key{r, 0}
   306  	default:
   307  		// Regular ui.Ctrl sequences.
   308  		if 0x1 <= r && r <= 0x1d {
   309  			return ui.Key{r + 0x40, ui.Ctrl}
   310  		}
   311  	}
   312  	return ui.Key{r, 0}
   313  }
   314  
   315  // G3-style key sequences: \eO followed by exactly one character. For instance,
   316  // \eOP is ui.F1.
   317  var g3Seq = map[rune]rune{
   318  	'A': ui.Up, 'B': ui.Down, 'C': ui.Right, 'D': ui.Left,
   319  
   320  	// ui.F1-ui.F4: xterm, libvte and tmux
   321  	'P': ui.F1, 'Q': ui.F2,
   322  	'R': ui.F3, 'S': ui.F4,
   323  
   324  	// ui.Home and ui.End: libvte
   325  	'H': ui.Home, 'F': ui.End,
   326  }
   327  
   328  // Tables for CSI-style key sequences, which are \e[ followed by a list of
   329  // semicolon-delimited numeric arguments, before being concluded by a
   330  // non-numeric, non-semicolon rune.
   331  
   332  // CSI-style key sequences that can be identified based on the ending rune. For
   333  // instance, \e[A is ui.Up.
   334  var keyByLast = map[rune]ui.Key{
   335  	'A': {ui.Up, 0}, 'B': {ui.Down, 0},
   336  	'C': {ui.Right, 0}, 'D': {ui.Left, 0},
   337  	'H': {ui.Home, 0}, 'F': {ui.End, 0},
   338  	'Z': {ui.Tab, ui.Shift},
   339  }
   340  
   341  // CSI-style key sequences ending with '~' and can be identified based on the
   342  // only number argument. For instance, \e[~ is ui.Home. When they are
   343  // modified, they take two arguments, first being 1 and second identifying the
   344  // modifier (see xtermModify). For instance, \e[1;4~ is Shift-Alt-Home.
   345  var keyByNum0 = map[int]rune{
   346  	1: ui.Home, 2: ui.Insert, 3: ui.Delete, 4: ui.End,
   347  	5: ui.PageUp, 6: ui.PageDown,
   348  	11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4,
   349  	15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8,
   350  	20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12,
   351  }
   352  
   353  // CSI-style key sequences ending with '~', with 27 as the first numeric
   354  // argument. For instance, \e[27;9~ is ui.Tab.
   355  //
   356  // The list is taken blindly from tmux source xterm-keys.c. I don't have a
   357  // keyboard-terminal combination that generate such sequences, but assumably
   358  // some PC keyboard with a numpad can.
   359  var keyByNum2 = map[int]rune{
   360  	9: '\t', 13: '\r',
   361  	33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
   362  	46: '.',
   363  	48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
   364  	56: '8', 57: '9',
   365  	58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
   366  }
   367  
   368  // parseCSI parses a CSI-style key sequence.
   369  func parseCSI(nums []int, last rune, seq string) ui.Key {
   370  	if k, ok := keyByLast[last]; ok {
   371  		if len(nums) == 0 {
   372  			// Unmodified: \e[A (ui.Up)
   373  			return k
   374  		} else if len(nums) == 2 && nums[0] == 1 {
   375  			// Modified: \e[1;5A (ui.Ctrl-ui.Up)
   376  			return xtermModify(k, nums[1], seq)
   377  		} else {
   378  			return ui.Key{}
   379  		}
   380  	}
   381  
   382  	if last == '~' {
   383  		if len(nums) == 1 || len(nums) == 2 {
   384  			if r, ok := keyByNum0[nums[0]]; ok {
   385  				k := ui.Key{r, 0}
   386  				if len(nums) == 1 {
   387  					// Unmodified: \e[5~ (ui.PageUp)
   388  					return k
   389  				}
   390  				// Modified: \e[5;5~ (ui.Ctrl-ui.PageUp)
   391  				return xtermModify(k, nums[1], seq)
   392  			}
   393  		} else if len(nums) == 3 && nums[0] == 27 {
   394  			if r, ok := keyByNum2[nums[2]]; ok {
   395  				k := ui.Key{r, 0}
   396  				return xtermModify(k, nums[1], seq)
   397  			}
   398  		}
   399  	}
   400  
   401  	return ui.Key{}
   402  }
   403  
   404  func xtermModify(k ui.Key, mod int, seq string) ui.Key {
   405  	switch mod {
   406  	case 0:
   407  		// do nothing
   408  	case 2:
   409  		k.Mod |= ui.Shift
   410  	case 3:
   411  		k.Mod |= ui.Alt
   412  	case 4:
   413  		k.Mod |= ui.Shift | ui.Alt
   414  	case 5:
   415  		k.Mod |= ui.Ctrl
   416  	case 6:
   417  		k.Mod |= ui.Shift | ui.Ctrl
   418  	case 7:
   419  		k.Mod |= ui.Alt | ui.Ctrl
   420  	case 8:
   421  		k.Mod |= ui.Shift | ui.Alt | ui.Ctrl
   422  	default:
   423  		return ui.Key{}
   424  	}
   425  	return k
   426  }
   427  
   428  func mouseModify(n int) ui.Mod {
   429  	var mod ui.Mod
   430  	if n&4 != 0 {
   431  		mod |= ui.Shift
   432  	}
   433  	if n&8 != 0 {
   434  		mod |= ui.Alt
   435  	}
   436  	if n&16 != 0 {
   437  		mod |= ui.Ctrl
   438  	}
   439  	return mod
   440  }