ReadLine.c 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. #include "core/ReadLine.h"
  2. #include <ctype.h>
  3. #include <errno.h>
  4. #include <stdatomic.h>
  5. #include <stdint.h>
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include <termios.h>
  10. #include <threads.h>
  11. #include <unistd.h>
  12. #include "ErrorSimulator.h"
  13. #include "core/Logger.h"
  14. #include "core/Queue.h"
  15. #define HISTORY_LENGTH 10
  16. #define CONSOLE_BUFFER_SIZE 256
  17. typedef struct ConsoleLine {
  18. char data[CONSOLE_BUFFER_SIZE];
  19. int length;
  20. } ConsoleLine;
  21. static atomic_bool running = true;
  22. static thrd_t readThread = {0};
  23. static struct termios original;
  24. static CoreQueue buffer = {0};
  25. static ConsoleLine currentBuffer = {0};
  26. static int move = 0;
  27. static int cursorMove = 0;
  28. static mtx_t bufferMutex = {0};
  29. static ConsoleLine history[HISTORY_LENGTH];
  30. static int historyOffset = 0;
  31. static int historyIndex = 0;
  32. static int historyLength = 0;
  33. static void getAttributes(struct termios* t) {
  34. if(tcgetattr(STDIN_FILENO, t)) {
  35. CORE_LOG_WARNING("cannot get terminal attributes");
  36. }
  37. }
  38. static void restoreAttributes() {
  39. if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &original)) {
  40. CORE_LOG_WARNING("cannot restore terminal attributes: %s",
  41. strerror(errno));
  42. }
  43. }
  44. static char readChar() {
  45. char c = 0;
  46. ssize_t bytes = read(STDIN_FILENO, &c, 1);
  47. return bytes <= 0 ? '\0' : c;
  48. }
  49. static void addChar(char c) {
  50. if(currentBuffer.length >= CONSOLE_BUFFER_SIZE - 1) {
  51. return;
  52. }
  53. for(int i = 0; i < move; i++) {
  54. currentBuffer.data[currentBuffer.length - i] =
  55. currentBuffer.data[currentBuffer.length - i - 1];
  56. }
  57. currentBuffer.length++;
  58. currentBuffer.data[currentBuffer.length - move - 1] = c;
  59. currentBuffer.data[currentBuffer.length] = '\0';
  60. }
  61. static void print(const char* s) {
  62. fputs(s, stdout);
  63. }
  64. static void refreshLine(const char* prefix) {
  65. print(prefix);
  66. print(currentBuffer.data);
  67. if(cursorMove > 0) {
  68. printf("\33[%dD", cursorMove);
  69. }
  70. fflush(stdout);
  71. print("\33[2K\r");
  72. }
  73. static bool clear() {
  74. move = 0;
  75. cursorMove = 0;
  76. currentBuffer.length = 0;
  77. currentBuffer.data[0] = '\0';
  78. historyOffset = 0;
  79. return false;
  80. }
  81. static void addToHistory() {
  82. if(historyLength < HISTORY_LENGTH) {
  83. historyLength++;
  84. }
  85. history[historyIndex] = currentBuffer;
  86. historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
  87. }
  88. static void lock() {
  89. if(mtx_lock(&bufferMutex) != thrd_success || MUTEX_LOCK_FAIL) {
  90. CORE_LOG_WARNING("could not lock buffer mutex");
  91. }
  92. }
  93. static void unlock() {
  94. if(mtx_unlock(&bufferMutex) != thrd_success || MUTEX_UNLOCK_FAIL) {
  95. CORE_LOG_WARNING("could not unlock buffer mutex");
  96. }
  97. }
  98. static void addLine() {
  99. addToHistory();
  100. lock();
  101. corePushQueueData(&buffer, &currentBuffer);
  102. unlock();
  103. clear();
  104. }
  105. static char isUTF8Part(char c) {
  106. return ((uint8_t)c & 0xC0) == 0x80;
  107. }
  108. static bool removeChar() {
  109. int pos = currentBuffer.length - move;
  110. if(pos > 0) {
  111. int l = 1;
  112. while(pos - l >= 0) {
  113. if(!isUTF8Part(currentBuffer.data[pos - l])) {
  114. break;
  115. }
  116. l++;
  117. }
  118. currentBuffer.length -= l;
  119. for(int i = pos - l; i <= currentBuffer.length; i++) {
  120. currentBuffer.data[i] = currentBuffer.data[i + l];
  121. }
  122. }
  123. return false;
  124. }
  125. static bool handleControlKey(char c) {
  126. switch(c) {
  127. case 3: return clear();
  128. case 10:
  129. case 13: return true;
  130. case 127: return removeChar();
  131. }
  132. return false;
  133. }
  134. static void copyHistory() {
  135. currentBuffer = history[(historyIndex - historyOffset + HISTORY_LENGTH) %
  136. HISTORY_LENGTH];
  137. move = 0;
  138. cursorMove = 0;
  139. }
  140. static void handleUpArrow() {
  141. if(historyOffset >= historyLength) {
  142. return;
  143. }
  144. historyOffset++;
  145. copyHistory();
  146. }
  147. static void handleDownArrow() {
  148. if(historyOffset <= 1) {
  149. return;
  150. }
  151. historyOffset--;
  152. copyHistory();
  153. }
  154. static char getMoved() {
  155. return currentBuffer.data[currentBuffer.length - move];
  156. }
  157. static void handleLeftArrow() {
  158. if(move < currentBuffer.length) {
  159. move++;
  160. while(move < currentBuffer.length && isUTF8Part(getMoved())) {
  161. move++;
  162. }
  163. cursorMove++;
  164. }
  165. }
  166. static void handleRightArrow() {
  167. if(move > 0) {
  168. move--;
  169. while(move > 0 && isUTF8Part(getMoved())) {
  170. move--;
  171. }
  172. cursorMove--;
  173. }
  174. }
  175. static void readEscapeSequence() {
  176. if(readChar() != '[') {
  177. return;
  178. }
  179. switch(readChar()) {
  180. case 'A': handleUpArrow(); break;
  181. case 'B': handleDownArrow(); break;
  182. case 'C': handleRightArrow(); break;
  183. case 'D': handleLeftArrow(); break;
  184. case '3':
  185. if(readChar() == '~' && move > 0) {
  186. handleRightArrow();
  187. removeChar();
  188. }
  189. break;
  190. }
  191. }
  192. static void handleChars() {
  193. while(true) {
  194. char c = readChar();
  195. if(c == 0) {
  196. break;
  197. } else if(c == 27) {
  198. readEscapeSequence();
  199. } else if(iscntrl(c)) {
  200. if(handleControlKey(c)) {
  201. addLine();
  202. return;
  203. }
  204. } else {
  205. addChar(c);
  206. }
  207. }
  208. }
  209. static int loop(void* data) {
  210. (void)data;
  211. while(running) {
  212. handleChars();
  213. refreshLine("wusi> ");
  214. struct timespec t = {.tv_nsec = 1000000};
  215. thrd_sleep(&t, nullptr);
  216. }
  217. return 0;
  218. }
  219. bool coreStartReadLine(void) {
  220. // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
  221. getAttributes(&original);
  222. struct termios raw = original;
  223. raw.c_iflag &= ~(tcflag_t)(ICRNL | IXON);
  224. raw.c_lflag &= ~(tcflag_t)(ECHO | ICANON | IEXTEN | ISIG);
  225. raw.c_cc[VMIN] = 0;
  226. raw.c_cc[VTIME] = 0;
  227. if(tcsetattr(STDIN_FILENO, TCSANOW, &raw)) {
  228. CORE_LOG_WARNING("cannot set terminal attributes");
  229. }
  230. coreInitQueue(&buffer, 10, sizeof(ConsoleLine));
  231. atomic_store(&running, true);
  232. if(MUTEX_INIT_FAIL || mtx_init(&bufferMutex, mtx_plain) != thrd_success) {
  233. CORE_LOG_ERROR("cannot init buffer mutex");
  234. coreStopReadLine();
  235. return true;
  236. } else if(THREAD_INIT_FAIL ||
  237. thrd_create(&readThread, loop, nullptr) != thrd_success) {
  238. CORE_LOG_ERROR("cannot start read thread");
  239. coreStopReadLine();
  240. return true;
  241. }
  242. return false;
  243. }
  244. bool coreReadLine(char* buffer_, size_t n) {
  245. if(buffer.length == 0) {
  246. return false;
  247. }
  248. lock();
  249. ConsoleLine* line = coreGetQueueIndex(&buffer, 0);
  250. snprintf(buffer_, n, "%s", line->data);
  251. corePopQueueData(&buffer);
  252. unlock();
  253. return true;
  254. }
  255. void coreStopReadLine() {
  256. atomic_store(&running, false);
  257. thrd_join(readThread, nullptr);
  258. restoreAttributes();
  259. coreDestroyQueue(&buffer);
  260. mtx_destroy(&bufferMutex);
  261. }