Console.c 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. #include <ctype.h>
  2. #include <errno.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <termios.h>
  7. #include <unistd.h>
  8. #include "Console.h"
  9. #define ERROR_SIZE 256
  10. #define HISTORY_LENGTH 4
  11. #define OUTPUT_BUFFER_SIZE (1024 * 1024)
  12. static char error[ERROR_SIZE] = {'\0'};
  13. static struct termios original;
  14. static ConsoleLine buffer[2];
  15. static bool bufferIndex = 0;
  16. static int move = 0;
  17. static ConsoleLine history[HISTORY_LENGTH];
  18. static int historyOffset = 0;
  19. static int historyIndex = 0;
  20. static int historyLength = 0;
  21. static char outputBuffer[OUTPUT_BUFFER_SIZE];
  22. static void setError(const char* msg) {
  23. snprintf(error, ERROR_SIZE, "%s: %s", msg, strerror(errno));
  24. }
  25. static bool getAttributes(struct termios* t) {
  26. if(tcgetattr(STDIN_FILENO, t)) {
  27. setError("cannot get terminal attributes");
  28. return true;
  29. }
  30. return false;
  31. }
  32. static void restoreAttributes() {
  33. if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &original)) {
  34. printf("cannot restore terminal attributes: %s", strerror(errno));
  35. }
  36. }
  37. bool initConsole() {
  38. buffer[0].length = 0;
  39. buffer[1].length = 0;
  40. if(setvbuf(stdout, outputBuffer, _IOFBF, OUTPUT_BUFFER_SIZE)) {
  41. setError("cannot buffer output");
  42. return true;
  43. }
  44. // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
  45. if(getAttributes(&original)) {
  46. return true;
  47. }
  48. struct termios raw;
  49. if(getAttributes(&raw)) {
  50. return true;
  51. }
  52. raw.c_iflag &= ~(ICRNL | IXON);
  53. raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
  54. raw.c_cc[VMIN] = 0;
  55. raw.c_cc[VTIME] = 0;
  56. if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw)) {
  57. setError("cannot set terminal attributes");
  58. return true;
  59. }
  60. atexit(restoreAttributes);
  61. return false;
  62. }
  63. const char* getConsoleError() {
  64. return error;
  65. }
  66. static bool readChar(uint8_t* c) {
  67. int bytes = read(STDIN_FILENO, c, 1);
  68. if(bytes == 0) {
  69. setError("expected part of unicode character");
  70. return true;
  71. } else if(bytes < 0) {
  72. setError("cannot read character from terminal");
  73. return true;
  74. }
  75. return false;
  76. }
  77. static uint32_t readUnicode2(uint8_t base) {
  78. uint8_t c = 0;
  79. if(readChar(&c)) {
  80. return 0;
  81. }
  82. return ((base & 0x1F) << 6) | (c & 0x3F);
  83. }
  84. static uint32_t readUnicode3(uint8_t base) {
  85. uint8_t c1 = 0;
  86. uint8_t c2 = 0;
  87. if(readChar(&c1) || readChar(&c2)) {
  88. return 0;
  89. }
  90. return ((base & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F);
  91. }
  92. static uint32_t readUnicode4(uint8_t base) {
  93. uint8_t c1 = 0;
  94. uint8_t c2 = 0;
  95. uint8_t c3 = 0;
  96. if(readChar(&c1) || readChar(&c2) || readChar(&c3)) {
  97. return 0;
  98. }
  99. return ((base & 0x07) << 18) | ((c1 & 0x3F) << 12) | ((c2 & 0x3F) << 6) |
  100. (c3 & 0x3F);
  101. }
  102. static uint32_t readUnicode() {
  103. uint8_t c = 0;
  104. int bytes = read(STDIN_FILENO, &c, 1);
  105. if(bytes == 0) {
  106. return 0;
  107. } else if(bytes < 0) {
  108. setError("cannot read character from terminal");
  109. return 0;
  110. }
  111. if((c & 0x80) == 0x00) {
  112. return c;
  113. } else if((c & 0xE0) == 0xC0) {
  114. return readUnicode2(c);
  115. } else if((c & 0xF0) == 0xE0) {
  116. return readUnicode3(c);
  117. } else if((c & 0xF8) == 0xF0) {
  118. return readUnicode4(c);
  119. }
  120. setError("invalid UTF8 character");
  121. return 0;
  122. }
  123. static void addChar(uint32_t c) {
  124. ConsoleLine* line = buffer + bufferIndex;
  125. if(line->length >= CONSOLE_BUFFER_SIZE) {
  126. return;
  127. }
  128. for(int i = 0; i < move; i++) {
  129. line->data[line->length - i] = line->data[line->length - i - 1];
  130. }
  131. line->length++;
  132. line->data[line->length - move - 1] = c;
  133. }
  134. static void print(const char* s) {
  135. fputs(s, stdout);
  136. }
  137. static void printBuffer() {
  138. ConsoleLine* line = buffer + bufferIndex;
  139. for(int i = 0; i < line->length; i++) {
  140. uint32_t c = line->data[i];
  141. if(c <= 0x0000007F) {
  142. putchar(c);
  143. } else if(c <= 0x000007FF) {
  144. putchar(((c >> 6) & 0x1F) | 0xC0);
  145. putchar((c & 0x3F) | 0x80);
  146. } else if(c <= 0x0000FFFF) {
  147. putchar(((c >> 12) & 0x0F) | 0xE0);
  148. putchar(((c >> 6) & 0x3F) | 0x80);
  149. putchar((c & 0x3F) | 0x80);
  150. } else if(c <= 0x0010FFFF) {
  151. putchar(((c >> 18) & 0x07) | 0xF0);
  152. putchar(((c >> 12) & 0x3F) | 0x80);
  153. putchar(((c >> 6) & 0x3F) | 0x80);
  154. putchar((c & 0x3F) | 0x80);
  155. } else {
  156. putchar('?');
  157. }
  158. }
  159. }
  160. static void refreshLine(const char* prefix) {
  161. print(prefix);
  162. printBuffer();
  163. if(move > 0) {
  164. printf("\33[%dD", move);
  165. }
  166. fflush(stdout);
  167. print("\33[2K\r");
  168. }
  169. static bool clear() {
  170. move = 0;
  171. buffer[bufferIndex].length = 0;
  172. historyOffset = 0;
  173. return false;
  174. }
  175. static void addToHistory() {
  176. if(historyLength < HISTORY_LENGTH) {
  177. historyLength++;
  178. }
  179. history[historyIndex] = buffer[bufferIndex];
  180. historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
  181. }
  182. static const ConsoleLine* swapBuffers() {
  183. addToHistory();
  184. bufferIndex = !bufferIndex;
  185. clear();
  186. return buffer + (!bufferIndex);
  187. }
  188. static bool removeChar() {
  189. ConsoleLine* line = buffer + bufferIndex;
  190. int pos = line->length - move - 1;
  191. if(pos >= 0) {
  192. line->length--;
  193. for(int i = pos; i < line->length; i++) {
  194. line->data[i] = line->data[i + 1];
  195. }
  196. }
  197. while(line->length - move < 0) {
  198. move--;
  199. }
  200. return false;
  201. }
  202. static bool handleControlKey(char c) {
  203. switch(c) {
  204. case 3: return clear();
  205. case 13: return true;
  206. case 127: return removeChar();
  207. }
  208. return false;
  209. }
  210. static void copyHistory() {
  211. buffer[bufferIndex] =
  212. history[(historyIndex - historyOffset + HISTORY_LENGTH) %
  213. HISTORY_LENGTH];
  214. move = 0;
  215. }
  216. static void handleUpArrow() {
  217. if(historyOffset >= historyLength) {
  218. return;
  219. }
  220. historyOffset++;
  221. copyHistory();
  222. }
  223. static void handleDownArrow() {
  224. if(historyOffset <= 1) {
  225. return;
  226. }
  227. historyOffset--;
  228. copyHistory();
  229. }
  230. static void handleLeftArrow() {
  231. if(move < buffer[bufferIndex].length) {
  232. move++;
  233. }
  234. }
  235. static void handleRightArrow() {
  236. if(move > 0) {
  237. move--;
  238. }
  239. }
  240. static void readEscapeSequence() {
  241. if(readUnicode() != '[') {
  242. return;
  243. }
  244. switch(readUnicode()) {
  245. case 'A': handleUpArrow(); break;
  246. case 'B': handleDownArrow(); break;
  247. case 'C': handleRightArrow(); break;
  248. case 'D': handleLeftArrow(); break;
  249. }
  250. }
  251. const ConsoleLine* readConsoleLine(const char* prefix) {
  252. while(true) {
  253. uint32_t c = readUnicode();
  254. if(c == 0) {
  255. break;
  256. } else if(c == 27) {
  257. readEscapeSequence();
  258. } else if(iscntrl(c)) {
  259. if(handleControlKey(c)) {
  260. return swapBuffers();
  261. }
  262. } else {
  263. addChar(c);
  264. }
  265. }
  266. refreshLine(prefix);
  267. return NULL;
  268. }