terminal.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521
  1. // Copyright 2011 The Go Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style
  3. // license that can be found in the LICENSE file.
  4. package terminal
  5. import (
  6. "io"
  7. "sync"
  8. )
  9. // EscapeCodes contains escape sequences that can be written to the terminal in
  10. // order to achieve different styles of text.
  11. type EscapeCodes struct {
  12. // Foreground colors
  13. Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
  14. // Reset all attributes
  15. Reset []byte
  16. }
  17. var vt100EscapeCodes = EscapeCodes{
  18. Black: []byte{keyEscape, '[', '3', '0', 'm'},
  19. Red: []byte{keyEscape, '[', '3', '1', 'm'},
  20. Green: []byte{keyEscape, '[', '3', '2', 'm'},
  21. Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
  22. Blue: []byte{keyEscape, '[', '3', '4', 'm'},
  23. Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
  24. Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
  25. White: []byte{keyEscape, '[', '3', '7', 'm'},
  26. Reset: []byte{keyEscape, '[', '0', 'm'},
  27. }
  28. // Terminal contains the state for running a VT100 terminal that is capable of
  29. // reading lines of input.
  30. type Terminal struct {
  31. // AutoCompleteCallback, if non-null, is called for each keypress
  32. // with the full input line and the current position of the cursor.
  33. // If it returns a nil newLine, the key press is processed normally.
  34. // Otherwise it returns a replacement line and the new cursor position.
  35. AutoCompleteCallback func(line []byte, pos, key int) (newLine []byte, newPos int)
  36. // Escape contains a pointer to the escape codes for this terminal.
  37. // It's always a valid pointer, although the escape codes themselves
  38. // may be empty if the terminal doesn't support them.
  39. Escape *EscapeCodes
  40. // lock protects the terminal and the state in this object from
  41. // concurrent processing of a key press and a Write() call.
  42. lock sync.Mutex
  43. c io.ReadWriter
  44. prompt string
  45. // line is the current line being entered.
  46. line []byte
  47. // pos is the logical position of the cursor in line
  48. pos int
  49. // echo is true if local echo is enabled
  50. echo bool
  51. // cursorX contains the current X value of the cursor where the left
  52. // edge is 0. cursorY contains the row number where the first row of
  53. // the current line is 0.
  54. cursorX, cursorY int
  55. // maxLine is the greatest value of cursorY so far.
  56. maxLine int
  57. termWidth, termHeight int
  58. // outBuf contains the terminal data to be sent.
  59. outBuf []byte
  60. // remainder contains the remainder of any partial key sequences after
  61. // a read. It aliases into inBuf.
  62. remainder []byte
  63. inBuf [256]byte
  64. }
  65. // NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
  66. // a local terminal, that terminal must first have been put into raw mode.
  67. // prompt is a string that is written at the start of each input line (i.e.
  68. // "> ").
  69. func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
  70. return &Terminal{
  71. Escape: &vt100EscapeCodes,
  72. c: c,
  73. prompt: prompt,
  74. termWidth: 80,
  75. termHeight: 24,
  76. echo: true,
  77. }
  78. }
  79. const (
  80. keyCtrlD = 4
  81. keyEnter = '\r'
  82. keyEscape = 27
  83. keyBackspace = 127
  84. keyUnknown = 256 + iota
  85. keyUp
  86. keyDown
  87. keyLeft
  88. keyRight
  89. keyAltLeft
  90. keyAltRight
  91. )
  92. // bytesToKey tries to parse a key sequence from b. If successful, it returns
  93. // the key and the remainder of the input. Otherwise it returns -1.
  94. func bytesToKey(b []byte) (int, []byte) {
  95. if len(b) == 0 {
  96. return -1, nil
  97. }
  98. if b[0] != keyEscape {
  99. return int(b[0]), b[1:]
  100. }
  101. if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
  102. switch b[2] {
  103. case 'A':
  104. return keyUp, b[3:]
  105. case 'B':
  106. return keyDown, b[3:]
  107. case 'C':
  108. return keyRight, b[3:]
  109. case 'D':
  110. return keyLeft, b[3:]
  111. }
  112. }
  113. if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
  114. switch b[5] {
  115. case 'C':
  116. return keyAltRight, b[6:]
  117. case 'D':
  118. return keyAltLeft, b[6:]
  119. }
  120. }
  121. // If we get here then we have a key that we don't recognise, or a
  122. // partial sequence. It's not clear how one should find the end of a
  123. // sequence without knowing them all, but it seems that [a-zA-Z] only
  124. // appears at the end of a sequence.
  125. for i, c := range b[0:] {
  126. if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
  127. return keyUnknown, b[i+1:]
  128. }
  129. }
  130. return -1, b
  131. }
  132. // queue appends data to the end of t.outBuf
  133. func (t *Terminal) queue(data []byte) {
  134. t.outBuf = append(t.outBuf, data...)
  135. }
  136. var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'}
  137. var space = []byte{' '}
  138. func isPrintable(key int) bool {
  139. return key >= 32 && key < 127
  140. }
  141. // moveCursorToPos appends data to t.outBuf which will move the cursor to the
  142. // given, logical position in the text.
  143. func (t *Terminal) moveCursorToPos(pos int) {
  144. if !t.echo {
  145. return
  146. }
  147. x := len(t.prompt) + pos
  148. y := x / t.termWidth
  149. x = x % t.termWidth
  150. up := 0
  151. if y < t.cursorY {
  152. up = t.cursorY - y
  153. }
  154. down := 0
  155. if y > t.cursorY {
  156. down = y - t.cursorY
  157. }
  158. left := 0
  159. if x < t.cursorX {
  160. left = t.cursorX - x
  161. }
  162. right := 0
  163. if x > t.cursorX {
  164. right = x - t.cursorX
  165. }
  166. t.cursorX = x
  167. t.cursorY = y
  168. t.move(up, down, left, right)
  169. }
  170. func (t *Terminal) move(up, down, left, right int) {
  171. movement := make([]byte, 3*(up+down+left+right))
  172. m := movement
  173. for i := 0; i < up; i++ {
  174. m[0] = keyEscape
  175. m[1] = '['
  176. m[2] = 'A'
  177. m = m[3:]
  178. }
  179. for i := 0; i < down; i++ {
  180. m[0] = keyEscape
  181. m[1] = '['
  182. m[2] = 'B'
  183. m = m[3:]
  184. }
  185. for i := 0; i < left; i++ {
  186. m[0] = keyEscape
  187. m[1] = '['
  188. m[2] = 'D'
  189. m = m[3:]
  190. }
  191. for i := 0; i < right; i++ {
  192. m[0] = keyEscape
  193. m[1] = '['
  194. m[2] = 'C'
  195. m = m[3:]
  196. }
  197. t.queue(movement)
  198. }
  199. func (t *Terminal) clearLineToRight() {
  200. op := []byte{keyEscape, '[', 'K'}
  201. t.queue(op)
  202. }
  203. const maxLineLength = 4096
  204. // handleKey processes the given key and, optionally, returns a line of text
  205. // that the user has entered.
  206. func (t *Terminal) handleKey(key int) (line string, ok bool) {
  207. switch key {
  208. case keyBackspace:
  209. if t.pos == 0 {
  210. return
  211. }
  212. t.pos--
  213. t.moveCursorToPos(t.pos)
  214. copy(t.line[t.pos:], t.line[1+t.pos:])
  215. t.line = t.line[:len(t.line)-1]
  216. if t.echo {
  217. t.writeLine(t.line[t.pos:])
  218. }
  219. t.queue(eraseUnderCursor)
  220. t.moveCursorToPos(t.pos)
  221. case keyAltLeft:
  222. // move left by a word.
  223. if t.pos == 0 {
  224. return
  225. }
  226. t.pos--
  227. for t.pos > 0 {
  228. if t.line[t.pos] != ' ' {
  229. break
  230. }
  231. t.pos--
  232. }
  233. for t.pos > 0 {
  234. if t.line[t.pos] == ' ' {
  235. t.pos++
  236. break
  237. }
  238. t.pos--
  239. }
  240. t.moveCursorToPos(t.pos)
  241. case keyAltRight:
  242. // move right by a word.
  243. for t.pos < len(t.line) {
  244. if t.line[t.pos] == ' ' {
  245. break
  246. }
  247. t.pos++
  248. }
  249. for t.pos < len(t.line) {
  250. if t.line[t.pos] != ' ' {
  251. break
  252. }
  253. t.pos++
  254. }
  255. t.moveCursorToPos(t.pos)
  256. case keyLeft:
  257. if t.pos == 0 {
  258. return
  259. }
  260. t.pos--
  261. t.moveCursorToPos(t.pos)
  262. case keyRight:
  263. if t.pos == len(t.line) {
  264. return
  265. }
  266. t.pos++
  267. t.moveCursorToPos(t.pos)
  268. case keyEnter:
  269. t.moveCursorToPos(len(t.line))
  270. t.queue([]byte("\r\n"))
  271. line = string(t.line)
  272. ok = true
  273. t.line = t.line[:0]
  274. t.pos = 0
  275. t.cursorX = 0
  276. t.cursorY = 0
  277. t.maxLine = 0
  278. default:
  279. if t.AutoCompleteCallback != nil {
  280. t.lock.Unlock()
  281. newLine, newPos := t.AutoCompleteCallback(t.line, t.pos, key)
  282. t.lock.Lock()
  283. if newLine != nil {
  284. if t.echo {
  285. t.moveCursorToPos(0)
  286. t.writeLine(newLine)
  287. for i := len(newLine); i < len(t.line); i++ {
  288. t.writeLine(space)
  289. }
  290. t.moveCursorToPos(newPos)
  291. }
  292. t.line = newLine
  293. t.pos = newPos
  294. return
  295. }
  296. }
  297. if !isPrintable(key) {
  298. return
  299. }
  300. if len(t.line) == maxLineLength {
  301. return
  302. }
  303. if len(t.line) == cap(t.line) {
  304. newLine := make([]byte, len(t.line), 2*(1+len(t.line)))
  305. copy(newLine, t.line)
  306. t.line = newLine
  307. }
  308. t.line = t.line[:len(t.line)+1]
  309. copy(t.line[t.pos+1:], t.line[t.pos:])
  310. t.line[t.pos] = byte(key)
  311. if t.echo {
  312. t.writeLine(t.line[t.pos:])
  313. }
  314. t.pos++
  315. t.moveCursorToPos(t.pos)
  316. }
  317. return
  318. }
  319. func (t *Terminal) writeLine(line []byte) {
  320. for len(line) != 0 {
  321. remainingOnLine := t.termWidth - t.cursorX
  322. todo := len(line)
  323. if todo > remainingOnLine {
  324. todo = remainingOnLine
  325. }
  326. t.queue(line[:todo])
  327. t.cursorX += todo
  328. line = line[todo:]
  329. if t.cursorX == t.termWidth {
  330. t.cursorX = 0
  331. t.cursorY++
  332. if t.cursorY > t.maxLine {
  333. t.maxLine = t.cursorY
  334. }
  335. }
  336. }
  337. }
  338. func (t *Terminal) Write(buf []byte) (n int, err error) {
  339. t.lock.Lock()
  340. defer t.lock.Unlock()
  341. if t.cursorX == 0 && t.cursorY == 0 {
  342. // This is the easy case: there's nothing on the screen that we
  343. // have to move out of the way.
  344. return t.c.Write(buf)
  345. }
  346. // We have a prompt and possibly user input on the screen. We
  347. // have to clear it first.
  348. t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
  349. t.cursorX = 0
  350. t.clearLineToRight()
  351. for t.cursorY > 0 {
  352. t.move(1 /* up */, 0, 0, 0)
  353. t.cursorY--
  354. t.clearLineToRight()
  355. }
  356. if _, err = t.c.Write(t.outBuf); err != nil {
  357. return
  358. }
  359. t.outBuf = t.outBuf[:0]
  360. if n, err = t.c.Write(buf); err != nil {
  361. return
  362. }
  363. t.queue([]byte(t.prompt))
  364. chars := len(t.prompt)
  365. if t.echo {
  366. t.queue(t.line)
  367. chars += len(t.line)
  368. }
  369. t.cursorX = chars % t.termWidth
  370. t.cursorY = chars / t.termWidth
  371. t.moveCursorToPos(t.pos)
  372. if _, err = t.c.Write(t.outBuf); err != nil {
  373. return
  374. }
  375. t.outBuf = t.outBuf[:0]
  376. return
  377. }
  378. // ReadPassword temporarily changes the prompt and reads a password, without
  379. // echo, from the terminal.
  380. func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
  381. t.lock.Lock()
  382. defer t.lock.Unlock()
  383. oldPrompt := t.prompt
  384. t.prompt = prompt
  385. t.echo = false
  386. line, err = t.readLine()
  387. t.prompt = oldPrompt
  388. t.echo = true
  389. return
  390. }
  391. // ReadLine returns a line of input from the terminal.
  392. func (t *Terminal) ReadLine() (line string, err error) {
  393. t.lock.Lock()
  394. defer t.lock.Unlock()
  395. return t.readLine()
  396. }
  397. func (t *Terminal) readLine() (line string, err error) {
  398. // t.lock must be held at this point
  399. if t.cursorX == 0 && t.cursorY == 0 {
  400. t.writeLine([]byte(t.prompt))
  401. t.c.Write(t.outBuf)
  402. t.outBuf = t.outBuf[:0]
  403. }
  404. for {
  405. rest := t.remainder
  406. lineOk := false
  407. for !lineOk {
  408. var key int
  409. key, rest = bytesToKey(rest)
  410. if key < 0 {
  411. break
  412. }
  413. if key == keyCtrlD {
  414. return "", io.EOF
  415. }
  416. line, lineOk = t.handleKey(key)
  417. }
  418. if len(rest) > 0 {
  419. n := copy(t.inBuf[:], rest)
  420. t.remainder = t.inBuf[:n]
  421. } else {
  422. t.remainder = nil
  423. }
  424. t.c.Write(t.outBuf)
  425. t.outBuf = t.outBuf[:0]
  426. if lineOk {
  427. return
  428. }
  429. // t.remainder is a slice at the beginning of t.inBuf
  430. // containing a partial key sequence
  431. readBuf := t.inBuf[len(t.remainder):]
  432. var n int
  433. t.lock.Unlock()
  434. n, err = t.c.Read(readBuf)
  435. t.lock.Lock()
  436. if err != nil {
  437. return
  438. }
  439. t.remainder = t.inBuf[:n+len(t.remainder)]
  440. }
  441. panic("unreachable")
  442. }
  443. // SetPrompt sets the prompt to be used when reading subsequent lines.
  444. func (t *Terminal) SetPrompt(prompt string) {
  445. t.lock.Lock()
  446. defer t.lock.Unlock()
  447. t.prompt = prompt
  448. }
  449. func (t *Terminal) SetSize(width, height int) {
  450. t.lock.Lock()
  451. defer t.lock.Unlock()
  452. t.termWidth, t.termHeight = width, height
  453. }