RawReader.h 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. #ifndef RAWREADER_H
  2. #define RAWREADER_H
  3. #include <cstdio>
  4. #include <cstring>
  5. #include <termios.h>
  6. #include <unistd.h>
  7. template<int N, int H>
  8. class RawReader final {
  9. struct termios original;
  10. int index;
  11. char buffer[2][N];
  12. bool bufferIndex;
  13. bool noError;
  14. int move;
  15. char history[H][N];
  16. int historyOffset;
  17. int historyIndex;
  18. int historyLength;
  19. public:
  20. typedef int DeciSeconds;
  21. RawReader(DeciSeconds timeout) : index(0), bufferIndex(false), noError(true), move(0),
  22. historyOffset(0), historyIndex(0), historyLength(0) {
  23. // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
  24. if(tcgetattr(STDIN_FILENO, &original) == -1) {
  25. markError();
  26. return;
  27. }
  28. struct termios raw;
  29. if(tcgetattr(STDIN_FILENO, &raw) == -1) {
  30. markError();
  31. return;
  32. };
  33. raw.c_iflag &= ~(ICRNL | IXON);
  34. raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  35. raw.c_cc[VMIN] = 0;
  36. raw.c_cc[VTIME] = timeout;
  37. if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw)) {
  38. markError();
  39. return;
  40. }
  41. buffer[0][0] = '\0';
  42. buffer[1][0] = '\0';
  43. }
  44. ~RawReader() {
  45. tcsetattr(STDIN_FILENO, TCSAFLUSH, &original);
  46. }
  47. RawReader(const RawReader&) = delete;
  48. RawReader(RawReader&&) = delete;
  49. RawReader& operator=(const RawReader&) = delete;
  50. RawReader& operator=(RawReader&&) = delete;
  51. bool canRead() const {
  52. return noError;
  53. }
  54. const char* readLine(const char* prefix) {
  55. refreshLine(prefix);
  56. while(true) {
  57. char c = 0;
  58. int bytes = read(STDIN_FILENO, &c, 1);
  59. if(bytes < 0) {
  60. markError();
  61. break;
  62. } else if(bytes == 0) {
  63. break;
  64. }
  65. if(c == 27) {
  66. readEscapeSequence();
  67. } else if(iscntrl(c)) {
  68. if(handleControlKey(c)) {
  69. clearLine();
  70. return swapBuffers();
  71. }
  72. } else {
  73. addChar(c);
  74. }
  75. refreshLine(prefix);
  76. }
  77. clearLine();
  78. return nullptr;
  79. }
  80. private:
  81. void print(const char* s) {
  82. fputs(s, stdout);
  83. }
  84. void clearLine() {
  85. print("\33[2K\r");
  86. }
  87. void markError() {
  88. noError = false;
  89. }
  90. const char* swapBuffers() {
  91. addToHistory();
  92. bufferIndex = !bufferIndex;
  93. clear();
  94. return buffer[!bufferIndex];
  95. }
  96. void refreshLine(const char* prefix) {
  97. clearLine();
  98. print(prefix);
  99. print(buffer[bufferIndex]);
  100. if(move > 0) {
  101. printf("\33[%dD", move);
  102. }
  103. fflush(stdout);
  104. }
  105. bool handleControlKey(char c) {
  106. switch(c) {
  107. case 3: return clear();
  108. case 13: return true;
  109. case 127: return removeChar();
  110. }
  111. return false;
  112. }
  113. bool clear() {
  114. index = 0;
  115. move = 0;
  116. buffer[bufferIndex][0] = '\0';
  117. historyOffset = 0;
  118. return false;
  119. }
  120. bool removeChar() {
  121. int pos = index - move - 1;
  122. if(pos >= 0) {
  123. index--;
  124. while(buffer[bufferIndex][pos] != '\0') {
  125. buffer[bufferIndex][pos] = buffer[bufferIndex][pos + 1];
  126. pos++;
  127. }
  128. }
  129. if(index - move < 0) {
  130. move--;
  131. }
  132. return false;
  133. }
  134. void addChar(char c) {
  135. if(index + 1 < N) {
  136. buffer[bufferIndex][index++] = c;
  137. buffer[bufferIndex][index] = '\0';
  138. }
  139. }
  140. char readChar() {
  141. char c;
  142. return read(STDIN_FILENO, &c, 1) == 1 ? c : -1;
  143. }
  144. void readEscapeSequence() {
  145. if(readChar() != '[') {
  146. return;
  147. }
  148. switch(readChar()) {
  149. case 'A':
  150. handleUpArrow();
  151. break;
  152. case 'B':
  153. handleDownArrow();
  154. break;
  155. case 'C':
  156. handleRightArrow();
  157. break;
  158. case 'D':
  159. handleLeftArrow();
  160. break;
  161. }
  162. }
  163. void handleUpArrow() {
  164. if(historyOffset >= historyLength) {
  165. return;
  166. }
  167. historyOffset++;
  168. copyHistory();
  169. }
  170. void handleDownArrow() {
  171. if(historyOffset <= 1) {
  172. return;
  173. }
  174. historyOffset--;
  175. copyHistory();
  176. }
  177. void handleLeftArrow() {
  178. if(move < index) {
  179. move++;
  180. }
  181. }
  182. void handleRightArrow() {
  183. if(move > 0) {
  184. move--;
  185. }
  186. }
  187. void addToHistory() {
  188. if(historyLength < H) {
  189. historyLength++;
  190. }
  191. strncpy(history[historyIndex], buffer[bufferIndex], N);
  192. historyIndex = (historyIndex + 1) % H;
  193. }
  194. void copyHistory() {
  195. strncpy(buffer[bufferIndex], history[(historyIndex - historyOffset + H) % H], N);
  196. move = 0;
  197. index = strnlen(buffer[bufferIndex], N);
  198. }
  199. };
  200. #endif