runereader.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. package terminal
  2. import (
  3. "os"
  4. "unicode"
  5. )
  6. type RuneReader struct {
  7. Input *os.File
  8. state runeReaderState
  9. }
  10. func NewRuneReader(input *os.File) *RuneReader {
  11. return &RuneReader{
  12. Input: input,
  13. state: newRuneReaderState(input),
  14. }
  15. }
  16. func (rr *RuneReader) ReadLine(mask rune) ([]rune, error) {
  17. line := []rune{}
  18. // we only care about horizontal displacements from the origin so start counting at 0
  19. index := 0
  20. for {
  21. // wait for some input
  22. r, _, err := rr.ReadRune()
  23. if err != nil {
  24. return line, err
  25. }
  26. // if the user pressed enter or some other newline/termination like ctrl+d
  27. if r == '\r' || r == '\n' || r == KeyEndTransmission {
  28. // go to the beginning of the next line
  29. Print("\r\n")
  30. // we're done processing the input
  31. return line, nil
  32. }
  33. // if the user interrupts (ie with ctrl+c)
  34. if r == KeyInterrupt {
  35. // go to the beginning of the next line
  36. Print("\r\n")
  37. // we're done processing the input, and treat interrupt like an error
  38. return line, InterruptErr
  39. }
  40. // allow for backspace/delete editing of inputs
  41. if r == KeyBackspace || r == KeyDelete {
  42. // and we're not at the beginning of the line
  43. if index > 0 && len(line) > 0 {
  44. // if we are at the end of the word
  45. if index == len(line) {
  46. // just remove the last letter from the internal representation
  47. line = line[:len(line)-1]
  48. // go back one
  49. CursorBack(1)
  50. // clear the rest of the line
  51. EraseLine(ERASE_LINE_END)
  52. } else {
  53. // we need to remove a character from the middle of the word
  54. // remove the current index from the list
  55. line = append(line[:index-1], line[index:]...)
  56. // go back one space so we can clear the rest
  57. CursorBack(1)
  58. // clear the rest of the line
  59. EraseLine(ERASE_LINE_END)
  60. // print what comes after
  61. Print(string(line[index-1:]))
  62. // leave the cursor where the user left it
  63. CursorBack(len(line) - index + 1)
  64. }
  65. // decrement the index
  66. index--
  67. } else {
  68. // otherwise the user pressed backspace while at the beginning of the line
  69. soundBell()
  70. }
  71. // we're done processing this key
  72. continue
  73. }
  74. // if the left arrow is pressed
  75. if r == KeyArrowLeft {
  76. // and we have space to the left
  77. if index > 0 {
  78. // move the cursor to the left
  79. CursorBack(1)
  80. // decrement the index
  81. index--
  82. } else {
  83. // otherwise we are at the beginning of where we started reading lines
  84. // sound the bell
  85. soundBell()
  86. }
  87. // we're done processing this key press
  88. continue
  89. }
  90. // if the right arrow is pressed
  91. if r == KeyArrowRight {
  92. // and we have space to the right of the word
  93. if index < len(line) {
  94. // move the cursor to the right
  95. CursorForward(1)
  96. // increment the index
  97. index++
  98. } else {
  99. // otherwise we are at the end of the word and can't go past
  100. // sound the bell
  101. soundBell()
  102. }
  103. // we're done processing this key press
  104. continue
  105. }
  106. // if the letter is another escape sequence
  107. if unicode.IsControl(r) {
  108. // ignore it
  109. continue
  110. }
  111. // the user pressed a regular key
  112. // if we are at the end of the line
  113. if index == len(line) {
  114. // just append the character at the end of the line
  115. line = append(line, r)
  116. // increment the location counter
  117. index++
  118. // if we don't need to mask the input
  119. if mask == 0 {
  120. // just print the character the user pressed
  121. Printf("%c", r)
  122. } else {
  123. // otherwise print the mask we were given
  124. Printf("%c", mask)
  125. }
  126. } else {
  127. // we are in the middle of the word so we need to insert the character the user pressed
  128. line = append(line[:index], append([]rune{r}, line[index:]...)...)
  129. // visually insert the character by deleting the rest of the line
  130. EraseLine(ERASE_LINE_END)
  131. // print the rest of the word after
  132. for _, char := range line[index:] {
  133. // if we don't need to mask the input
  134. if mask == 0 {
  135. // just print the character the user pressed
  136. Printf("%c", char)
  137. } else {
  138. // otherwise print the mask we were given
  139. Printf("%c", mask)
  140. }
  141. }
  142. // leave the cursor where the user left it
  143. CursorBack(len(line) - index - 1)
  144. // accommodate the new letter in our counter
  145. index++
  146. }
  147. }
  148. }