RawReader.h 5.3 KB

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