RawReader.h 5.2 KB

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