github.com/hoop33/elvish@v0.0.0-20160801152013-6d25485beab4/edit/reader.go (about)

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