github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/cli/term/reader_unix.go (about)

     1  //go:build !windows && !plan9
     2  // +build !windows,!plan9
     3  
     4  package term
     5  
     6  import (
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/markusbkk/elvish/pkg/ui"
    11  )
    12  
    13  // reader reads terminal escape sequences and decodes them into events.
    14  type reader struct {
    15  	fr fileReader
    16  }
    17  
    18  func newReader(f *os.File) *reader {
    19  	fr, err := newFileReader(f)
    20  	if err != nil {
    21  		// TODO(xiaq): Do not panic.
    22  		panic(err)
    23  	}
    24  	return &reader{fr}
    25  }
    26  
    27  func (rd *reader) ReadEvent() (Event, error) {
    28  	return readEvent(rd.fr)
    29  }
    30  
    31  func (rd *reader) ReadRawEvent() (Event, error) {
    32  	r, err := readRune(rd.fr, -1)
    33  	return K(r), err
    34  }
    35  
    36  func (rd *reader) Close() {
    37  	rd.fr.Stop()
    38  	rd.fr.Close()
    39  }
    40  
    41  // Used by readRune in readOne to signal end of current sequence.
    42  const runeEndOfSeq rune = -1
    43  
    44  // Timeout for bytes in escape sequences. Modern terminal emulators send escape
    45  // sequences very fast, so 10ms is more than sufficient. SSH connections on a
    46  // slow link might be problematic though.
    47  var keySeqTimeout = 10 * time.Millisecond
    48  
    49  func readEvent(rd byteReaderWithTimeout) (event Event, err error) {
    50  	var r rune
    51  	r, err = readRune(rd, -1)
    52  	if err != nil {
    53  		return
    54  	}
    55  
    56  	currentSeq := string(r)
    57  	// Attempts to read a rune within a timeout of keySeqTimeout. It returns
    58  	// runeEndOfSeq if there is any error; the caller should terminate the
    59  	// current sequence when it sees that value.
    60  	readRune :=
    61  		func() rune {
    62  			r, e := readRune(rd, keySeqTimeout)
    63  			if e != nil {
    64  				return runeEndOfSeq
    65  			}
    66  			currentSeq += string(r)
    67  			return r
    68  		}
    69  	badSeq := func(msg string) {
    70  		err = seqError{msg, currentSeq}
    71  	}
    72  
    73  	switch r {
    74  	case 0x1b: // ^[ Escape
    75  		r2 := readRune()
    76  		// According to https://unix.stackexchange.com/a/73697, rxvt and derivatives
    77  		// prepend another ESC to a CSI-style or G3-style sequence to signal Alt.
    78  		// If that happens, remember this now; it will be later picked up when parsing
    79  		// those two kinds of sequences.
    80  		//
    81  		// issue #181
    82  		hasTwoLeadingESC := false
    83  		if r2 == 0x1b {
    84  			hasTwoLeadingESC = true
    85  			r2 = readRune()
    86  		}
    87  		if r2 == runeEndOfSeq {
    88  			// TODO(xiaq): Error is swallowed.
    89  			// Nothing follows. Taken as a lone Escape.
    90  			event = KeyEvent{'[', ui.Ctrl}
    91  			break
    92  		}
    93  		switch r2 {
    94  		case '[':
    95  			// A '[' follows. CSI style function key sequence.
    96  			r = readRune()
    97  			if r == runeEndOfSeq {
    98  				event = KeyEvent{'[', ui.Alt}
    99  				return
   100  			}
   101  
   102  			nums := make([]int, 0, 2)
   103  			var starter rune
   104  
   105  			// Read an optional starter.
   106  			switch r {
   107  			case '<':
   108  				starter = r
   109  				r = readRune()
   110  			case 'M':
   111  				// Mouse event.
   112  				cb := readRune()
   113  				if cb == runeEndOfSeq {
   114  					badSeq("incomplete mouse event")
   115  					return
   116  				}
   117  				cx := readRune()
   118  				if cx == runeEndOfSeq {
   119  					badSeq("incomplete mouse event")
   120  					return
   121  				}
   122  				cy := readRune()
   123  				if cy == runeEndOfSeq {
   124  					badSeq("incomplete mouse event")
   125  					return
   126  				}
   127  				down := true
   128  				button := int(cb & 3)
   129  				if button == 3 {
   130  					down = false
   131  					button = -1
   132  				}
   133  				mod := mouseModify(int(cb))
   134  				event = MouseEvent{
   135  					Pos{int(cy) - 32, int(cx) - 32}, down, button, mod}
   136  				return
   137  			}
   138  		CSISeq:
   139  			for {
   140  				switch {
   141  				case r == ';':
   142  					nums = append(nums, 0)
   143  				case '0' <= r && r <= '9':
   144  					if len(nums) == 0 {
   145  						nums = append(nums, 0)
   146  					}
   147  					cur := len(nums) - 1
   148  					nums[cur] = nums[cur]*10 + int(r-'0')
   149  				case r == runeEndOfSeq:
   150  					// Incomplete CSI.
   151  					badSeq("incomplete CSI")
   152  					return
   153  				default: // Treat as a terminator.
   154  					break CSISeq
   155  				}
   156  
   157  				r = readRune()
   158  			}
   159  			if starter == 0 && r == 'R' {
   160  				// Cursor position report.
   161  				if len(nums) != 2 {
   162  					badSeq("bad CPR")
   163  					return
   164  				}
   165  				event = CursorPosition{nums[0], nums[1]}
   166  			} else if starter == '<' && (r == 'm' || r == 'M') {
   167  				// SGR-style mouse event.
   168  				if len(nums) != 3 {
   169  					badSeq("bad SGR mouse event")
   170  					return
   171  				}
   172  				down := r == 'M'
   173  				button := nums[0] & 3
   174  				mod := mouseModify(nums[0])
   175  				event = MouseEvent{Pos{nums[2], nums[1]}, down, button, mod}
   176  			} else if r == '~' && len(nums) == 1 && (nums[0] == 200 || nums[0] == 201) {
   177  				b := nums[0] == 200
   178  				event = PasteSetting(b)
   179  			} else {
   180  				k := parseCSI(nums, r, currentSeq)
   181  				if k == (ui.Key{}) {
   182  					badSeq("bad CSI")
   183  				} else {
   184  					if hasTwoLeadingESC {
   185  						k.Mod |= ui.Alt
   186  					}
   187  					event = KeyEvent(k)
   188  				}
   189  			}
   190  		case 'O':
   191  			// An 'O' follows. G3 style function key sequence: read one rune.
   192  			r = readRune()
   193  			if r == runeEndOfSeq {
   194  				// Nothing follows after 'O'. Taken as Alt-O.
   195  				event = KeyEvent{'O', ui.Alt}
   196  				return
   197  			}
   198  			k, ok := g3Seq[r]
   199  			if ok {
   200  				if hasTwoLeadingESC {
   201  					k.Mod |= ui.Alt
   202  				}
   203  				event = KeyEvent(k)
   204  			} else {
   205  				badSeq("bad G3")
   206  			}
   207  		default:
   208  			// Something other than '[' or 'O' follows. Taken as an
   209  			// Alt-modified key, possibly also modified by Ctrl.
   210  			k := ctrlModify(r2)
   211  			k.Mod |= ui.Alt
   212  			event = KeyEvent(k)
   213  		}
   214  	default:
   215  		event = KeyEvent(ctrlModify(r))
   216  	}
   217  	return
   218  }
   219  
   220  // Determines whether a rune corresponds to a Ctrl-modified key and returns the
   221  // ui.Key the rune represents.
   222  func ctrlModify(r rune) ui.Key {
   223  	switch r {
   224  	// TODO(xiaq): Are the following special cases universal?
   225  	case 0x0:
   226  		return ui.K('`', ui.Ctrl) // ^@
   227  	case 0x1e:
   228  		return ui.K('6', ui.Ctrl) // ^^
   229  	case 0x1f:
   230  		return ui.K('/', ui.Ctrl) // ^_
   231  	case ui.Tab, ui.Enter, ui.Backspace: // ^I ^J ^?
   232  		// Ambiguous Ctrl keys; prefer the non-Ctrl form as they are more likely.
   233  		return ui.K(r)
   234  	default:
   235  		// Regular ui.Ctrl sequences.
   236  		if 0x1 <= r && r <= 0x1d {
   237  			return ui.K(r+0x40, ui.Ctrl)
   238  		}
   239  	}
   240  	return ui.K(r)
   241  }
   242  
   243  // Tables for key sequences. Comments document which terminal emulators are
   244  // known to generate which sequences. The terminal emulators tested are
   245  // categorized into xterm (including actual xterm, libvte-based terminals,
   246  // Konsole and Terminal.app unless otherwise noted), urxvt, tmux.
   247  
   248  // G3-style key sequences: \eO followed by exactly one character. For instance,
   249  // \eOP is F1. These are pretty limited in that they cannot be extended to
   250  // support modifier keys, other than a leading \e for Alt (e.g. \e\eOP is
   251  // Alt-F1). Terminals that send G3-style key sequences typically switch to
   252  // sending a CSI-style key sequence when a non-Alt modifier key is pressed.
   253  var g3Seq = map[rune]ui.Key{
   254  	// xterm, tmux -- only in Vim, depends on termios setting?
   255  	// NOTE(xiaq): According to urxvt's manpage, \eO[ABCD] sequences are used for
   256  	// Ctrl-Shift-modified arrow keys; however, this doesn't seem to be true for
   257  	// urxvt 9.22 packaged by Debian; those keys simply send the same sequence
   258  	// as Ctrl-modified keys (\eO[abcd]).
   259  	'A': ui.K(ui.Up), 'B': ui.K(ui.Down), 'C': ui.K(ui.Right), 'D': ui.K(ui.Left),
   260  	'H': ui.K(ui.Home), 'F': ui.K(ui.End), 'M': ui.K(ui.Insert),
   261  	// urxvt
   262  	'a': ui.K(ui.Up, ui.Ctrl), 'b': ui.K(ui.Down, ui.Ctrl),
   263  	'c': ui.K(ui.Right, ui.Ctrl), 'd': ui.K(ui.Left, ui.Ctrl),
   264  	// xterm, urxvt, tmux
   265  	'P': ui.K(ui.F1), 'Q': ui.K(ui.F2), 'R': ui.K(ui.F3), 'S': ui.K(ui.F4),
   266  }
   267  
   268  // Tables for CSI-style key sequences. A CSI sequence is \e[ followed by zero or
   269  // more numerical arguments (separated by semicolons), ending in a non-numeric,
   270  // non-semicolon rune. They are used for many purposes, and CSI-style key
   271  // sequences are a subset of them.
   272  //
   273  // There are several variants of CSI-style key sequences; see comments above the
   274  // respective tables. In all variants, modifier keys are encoded in numerical
   275  // arguments; see xtermModify. Note that although the set of possible sequences
   276  // make it possible to express a very complete set of key combinations, they are
   277  // not always sent by terminals. For instance, many (if not most) terminals will
   278  // send the same sequence for Up when Shift-Up is pressed, even if Shift-Up is
   279  // expressible using the escape sequences described below.
   280  
   281  // CSI-style key sequences identified by the last rune. For instance, \e[A is
   282  // Up. When modified, two numerical arguments are added, the first always beging
   283  // 1 and the second identifying the modifier. For instance, \e[1;5A is Ctrl-Up.
   284  var csiSeqByLast = map[rune]ui.Key{
   285  	// xterm, urxvt, tmux
   286  	'A': ui.K(ui.Up), 'B': ui.K(ui.Down), 'C': ui.K(ui.Right), 'D': ui.K(ui.Left),
   287  	// urxvt
   288  	'a': ui.K(ui.Up, ui.Shift), 'b': ui.K(ui.Down, ui.Shift),
   289  	'c': ui.K(ui.Right, ui.Shift), 'd': ui.K(ui.Left, ui.Shift),
   290  	// xterm (Terminal.app only sends those in alternate screen)
   291  	'H': ui.K(ui.Home), 'F': ui.K(ui.End),
   292  	// xterm, urxvt, tmux
   293  	'Z': ui.K(ui.Tab, ui.Shift),
   294  }
   295  
   296  // CSI-style key sequences ending with '~' with by one or two numerical
   297  // arguments. The first argument identifies the key, and the optional second
   298  // argument identifies the modifier. For instance, \e[3~ is Delete, and \e[3;5~
   299  // is Ctrl-Delete.
   300  //
   301  // An alternative encoding of the modifier key, only known to be used by urxvt
   302  // (or for that matter, likely also rxvt) is to change the last rune: '$' for
   303  // Shift, '^' for Ctrl, and '@' for Ctrl+Shift. The numeric argument is kept
   304  // unchanged. For instance, \e[3^ is Ctrl-Delete.
   305  var csiSeqTilde = map[int]rune{
   306  	// tmux (NOTE: urxvt uses the pair for Find/Select)
   307  	1: ui.Home, 4: ui.End,
   308  	// xterm (Terminal.app sends ^M for Fn+Enter), urxvt, tmux
   309  	2: ui.Insert,
   310  	// xterm, urxvt, tmux
   311  	3: ui.Delete,
   312  	// xterm (Terminal.app only sends those in alternate screen), urxvt, tmux
   313  	// NOTE: called Prior/Next in urxvt manpage
   314  	5: ui.PageUp, 6: ui.PageDown,
   315  	// urxvt
   316  	7: ui.Home, 8: ui.End,
   317  	// urxvt
   318  	11: ui.F1, 12: ui.F2, 13: ui.F3, 14: ui.F4,
   319  	// xterm, urxvt, tmux
   320  	// NOTE: 16 and 22 are unused
   321  	15: ui.F5, 17: ui.F6, 18: ui.F7, 19: ui.F8,
   322  	20: ui.F9, 21: ui.F10, 23: ui.F11, 24: ui.F12,
   323  }
   324  
   325  // CSI-style key sequences ending with '~', with the first argument always 27,
   326  // the second argument identifying the modifier, and the third argument
   327  // identifying the key. For instance, \e[27;5;9~ is Ctrl-Tab.
   328  //
   329  // NOTE(xiaq): The list is taken blindly from xterm-keys.c in the tmux source
   330  // tree. I do not have a keyboard-terminal combination that generate such
   331  // sequences, but assumably they are generated by some terminals for numpad
   332  // inputs.
   333  var csiSeqTilde27 = map[int]rune{
   334  	9: '\t', 13: '\r',
   335  	33: '!', 35: '#', 39: '\'', 40: '(', 41: ')', 43: '+', 44: ',', 45: '-',
   336  	46: '.',
   337  	48: '0', 49: '1', 50: '2', 51: '3', 52: '4', 53: '5', 54: '6', 55: '7',
   338  	56: '8', 57: '9',
   339  	58: ':', 59: ';', 60: '<', 61: '=', 62: '>', 63: ';',
   340  }
   341  
   342  // parseCSI parses a CSI-style key sequence. See comments above for all the 3
   343  // variants this function handles.
   344  func parseCSI(nums []int, last rune, seq string) ui.Key {
   345  	if k, ok := csiSeqByLast[last]; ok {
   346  		if len(nums) == 0 {
   347  			// Unmodified: \e[A (Up)
   348  			return k
   349  		} else if len(nums) == 2 && nums[0] == 1 {
   350  			// Modified: \e[1;5A (Ctrl-Up)
   351  			return xtermModify(k, nums[1], seq)
   352  		} else {
   353  			return ui.Key{}
   354  		}
   355  	}
   356  
   357  	switch last {
   358  	case '~':
   359  		if len(nums) == 1 || len(nums) == 2 {
   360  			if r, ok := csiSeqTilde[nums[0]]; ok {
   361  				k := ui.K(r)
   362  				if len(nums) == 1 {
   363  					// Unmodified: \e[5~ (e.g. PageUp)
   364  					return k
   365  				}
   366  				// Modified: \e[5;5~ (e.g. Ctrl-PageUp)
   367  				return xtermModify(k, nums[1], seq)
   368  			}
   369  		} else if len(nums) == 3 && nums[0] == 27 {
   370  			if r, ok := csiSeqTilde27[nums[2]]; ok {
   371  				k := ui.K(r)
   372  				return xtermModify(k, nums[1], seq)
   373  			}
   374  		}
   375  	case '$', '^', '@':
   376  		// Modified by urxvt; see comment above csiSeqTilde.
   377  		if len(nums) == 1 {
   378  			if r, ok := csiSeqTilde[nums[0]]; ok {
   379  				var mod ui.Mod
   380  				switch last {
   381  				case '$':
   382  					mod = ui.Shift
   383  				case '^':
   384  					mod = ui.Ctrl
   385  				case '@':
   386  					mod = ui.Shift | ui.Ctrl
   387  				}
   388  				return ui.K(r, mod)
   389  			}
   390  		}
   391  	}
   392  
   393  	return ui.Key{}
   394  }
   395  
   396  func xtermModify(k ui.Key, mod int, seq string) ui.Key {
   397  	if mod < 0 || mod > 16 {
   398  		// Out of range
   399  		return ui.Key{}
   400  	}
   401  	if mod == 0 {
   402  		return k
   403  	}
   404  	modFlags := mod - 1
   405  	if modFlags&0x1 != 0 {
   406  		k.Mod |= ui.Shift
   407  	}
   408  	if modFlags&0x2 != 0 {
   409  		k.Mod |= ui.Alt
   410  	}
   411  	if modFlags&0x4 != 0 {
   412  		k.Mod |= ui.Ctrl
   413  	}
   414  	if modFlags&0x8 != 0 {
   415  		// This should be Meta, but we currently conflate Meta and Alt.
   416  		k.Mod |= ui.Alt
   417  	}
   418  	return k
   419  }
   420  
   421  func mouseModify(n int) ui.Mod {
   422  	var mod ui.Mod
   423  	if n&4 != 0 {
   424  		mod |= ui.Shift
   425  	}
   426  	if n&8 != 0 {
   427  		mod |= ui.Alt
   428  	}
   429  	if n&16 != 0 {
   430  		mod |= ui.Ctrl
   431  	}
   432  	return mod
   433  }