RawReader.h 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235
  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 || c < 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. index++;
  137. for(int i = 0; i < move + 1; i++) {
  138. buffer[bufferIndex][index - i] = buffer[bufferIndex][index - i - 1];
  139. }
  140. buffer[bufferIndex][index - move - 1] = c;
  141. }
  142. }
  143. char readChar() {
  144. char c;
  145. return read(STDIN_FILENO, &c, 1) == 1 ? c : -1;
  146. }
  147. void readEscapeSequence() {
  148. if(readChar() != '[') {
  149. return;
  150. }
  151. switch(readChar()) {
  152. case 'A':
  153. handleUpArrow();
  154. break;
  155. case 'B':
  156. handleDownArrow();
  157. break;
  158. case 'C':
  159. handleRightArrow();
  160. break;
  161. case 'D':
  162. handleLeftArrow();
  163. break;
  164. }
  165. }
  166. void handleUpArrow() {
  167. if(historyOffset >= historyLength) {
  168. return;
  169. }
  170. historyOffset++;
  171. copyHistory();
  172. }
  173. void handleDownArrow() {
  174. if(historyOffset <= 1) {
  175. return;
  176. }
  177. historyOffset--;
  178. copyHistory();
  179. }
  180. void handleLeftArrow() {
  181. if(move < index) {
  182. move++;
  183. }
  184. }
  185. void handleRightArrow() {
  186. if(move > 0) {
  187. move--;
  188. }
  189. }
  190. void addToHistory() {
  191. if(historyLength < H) {
  192. historyLength++;
  193. }
  194. strncpy(history[historyIndex], buffer[bufferIndex], N);
  195. historyIndex = (historyIndex + 1) % H;
  196. }
  197. void copyHistory() {
  198. strncpy(buffer[bufferIndex], history[(historyIndex - historyOffset + H) % H], N);
  199. move = 0;
  200. index = strnlen(buffer[bufferIndex], N);
  201. }
  202. };
  203. #endif