reader_unix.go 12 KB

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