ReadLine.c 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. #include "core/ReadLine.h"
  2. #include <ctype.h>
  3. #include <stdatomic.h>
  4. #include <stdint.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. #include "ErrorSimulator.h"
  8. #include "core/Logger.h"
  9. #include "core/Queue.h"
  10. #include "core/Thread.h"
  11. #include "core/Unicode.h"
  12. static constexpr size_t HISTORY_LENGTH = 10;
  13. static constexpr size_t CONSOLE_BUFFER_SIZE = 256;
  14. typedef struct ConsoleLine {
  15. char data[CONSOLE_BUFFER_SIZE];
  16. size_t length;
  17. } ConsoleLine;
  18. QUEUE(ConsoleLine, CL)
  19. QUEUE_SOURCE(ConsoleLine, CL)
  20. static atomic_bool running = true;
  21. static thrd_t readThread = {0};
  22. static QueueCL buffer = {0};
  23. static ConsoleLine currentBuffer = {0};
  24. static size_t move = 0;
  25. static size_t cursorMove = 0;
  26. static mtx_t bufferMutex = {0};
  27. static ConsoleLine history[HISTORY_LENGTH];
  28. static size_t historyOffset = 0;
  29. static size_t historyIndex = 0;
  30. static size_t historyLength = 0;
  31. static void addChar(u64 c) {
  32. UTF8 u = convertUnicodeToUTF8((u32)c);
  33. for(u32 k = 0; k < u.length; k++) {
  34. if(currentBuffer.length >= CONSOLE_BUFFER_SIZE - 1) {
  35. return;
  36. }
  37. for(size_t i = 0; i < move; i++) {
  38. currentBuffer.data[currentBuffer.length - i] =
  39. currentBuffer.data[currentBuffer.length - i - 1];
  40. }
  41. currentBuffer.length++;
  42. currentBuffer.data[currentBuffer.length - move - 1] = (char)u.data[k];
  43. currentBuffer.data[currentBuffer.length] = '\0';
  44. }
  45. }
  46. static void print(const char* s) {
  47. fputs(s, stdout);
  48. }
  49. static void refreshLine(const char* prefix) {
  50. print(prefix);
  51. print(currentBuffer.data);
  52. if(cursorMove > 0) {
  53. moveCursorLeft((int)cursorMove);
  54. }
  55. fflush(stdout);
  56. clearTerminalLine();
  57. }
  58. static bool clear() {
  59. move = 0;
  60. cursorMove = 0;
  61. currentBuffer.length = 0;
  62. currentBuffer.data[0] = '\0';
  63. historyOffset = 0;
  64. return false;
  65. }
  66. static void addToHistory() {
  67. if(historyLength < HISTORY_LENGTH) {
  68. historyLength++;
  69. }
  70. history[historyIndex] = currentBuffer;
  71. historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
  72. }
  73. static void lock() {
  74. if(mtx_lock(&bufferMutex) != thrd_success || MUTEX_LOCK_FAIL) {
  75. REPORT(LOG_WARNING, "could not lock buffer mutex");
  76. }
  77. }
  78. static void unlock() {
  79. if(mtx_unlock(&bufferMutex) != thrd_success || MUTEX_UNLOCK_FAIL) {
  80. REPORT(LOG_WARNING, "could not unlock buffer mutex");
  81. }
  82. }
  83. static void addLine() {
  84. addToHistory();
  85. lock();
  86. pushQueueDataCL(&buffer, currentBuffer);
  87. unlock();
  88. clear();
  89. }
  90. static bool removeChar() {
  91. size_t pos = currentBuffer.length - move;
  92. if(pos > 0) {
  93. size_t l = 1;
  94. while(pos - l >= 0) {
  95. if(!isUTF8Remainder((u8)currentBuffer.data[pos - l])) {
  96. break;
  97. }
  98. l++;
  99. }
  100. currentBuffer.length -= l;
  101. for(size_t i = pos - l; i <= currentBuffer.length; i++) {
  102. currentBuffer.data[i] = currentBuffer.data[i + l];
  103. }
  104. }
  105. return false;
  106. }
  107. static bool handleControlKey(u64 c) {
  108. switch(c) {
  109. case 3: return clear();
  110. case 10:
  111. case 13: return true;
  112. case 127: return removeChar();
  113. }
  114. return false;
  115. }
  116. static void copyHistory() {
  117. currentBuffer = history
  118. [(historyIndex - historyOffset + HISTORY_LENGTH) % HISTORY_LENGTH];
  119. move = 0;
  120. cursorMove = 0;
  121. }
  122. static void handleUpArrow() {
  123. if(historyOffset >= historyLength) {
  124. return;
  125. }
  126. historyOffset++;
  127. copyHistory();
  128. }
  129. static void handleDownArrow() {
  130. if(historyOffset <= 1) {
  131. return;
  132. }
  133. historyOffset--;
  134. copyHistory();
  135. }
  136. static char getMoved() {
  137. return currentBuffer.data[currentBuffer.length - move];
  138. }
  139. static void handleLeftArrow() {
  140. if(move < currentBuffer.length) {
  141. move++;
  142. while(move < currentBuffer.length && isUTF8Remainder((u8)getMoved())) {
  143. move++;
  144. }
  145. cursorMove++;
  146. }
  147. }
  148. static void handleRightArrow() {
  149. if(move > 0) {
  150. move--;
  151. while(move > 0 && isUTF8Remainder((u8)getMoved())) {
  152. move--;
  153. }
  154. cursorMove--;
  155. }
  156. }
  157. static void handleChars() {
  158. while(true) {
  159. u64 c = getRawChar();
  160. if(c == 0) {
  161. break;
  162. } else if(c == TERMINAL_KEY_ARROW_UP) {
  163. handleUpArrow();
  164. } else if(c == TERMINAL_KEY_ARROW_DOWN) {
  165. handleDownArrow();
  166. } else if(c == TERMINAL_KEY_ARROW_RIGHT) {
  167. handleRightArrow();
  168. } else if(c == TERMINAL_KEY_ARROW_LEFT) {
  169. handleLeftArrow();
  170. } else if(c == TERMINAL_KEY_DELETE) {
  171. if(move > 0) {
  172. handleRightArrow();
  173. removeChar();
  174. }
  175. } else if(iscntrl(c)) {
  176. if(handleControlKey(c)) {
  177. addLine();
  178. return;
  179. }
  180. } else {
  181. addChar(c);
  182. }
  183. }
  184. }
  185. static int loop(void* data) {
  186. (void)data;
  187. while(running) {
  188. handleChars();
  189. refreshLine("wusi> ");
  190. struct timespec t = {.tv_nsec = 1'000'000};
  191. thrd_sleep(&t, nullptr);
  192. }
  193. return 0;
  194. }
  195. bool startReadLine(void) {
  196. if(enterRawTerminal()) {
  197. REPORT(LOG_WARNING, "cannot set terminal attributes");
  198. }
  199. initQueueCL(&buffer, 10);
  200. atomic_store(&running, true);
  201. if(MUTEX_INIT_FAIL || mtx_init(&bufferMutex, mtx_plain) != thrd_success) {
  202. REPORT(LOG_ERROR, "cannot init buffer mutex");
  203. stopReadLine();
  204. return true;
  205. } else if(
  206. THREAD_INIT_FAIL ||
  207. thrd_create(&readThread, loop, nullptr) != thrd_success) {
  208. REPORT(LOG_ERROR, "cannot start read thread");
  209. stopReadLine();
  210. return true;
  211. }
  212. return false;
  213. }
  214. bool readLine(char* buffer_, size_t n) {
  215. if(buffer.length == 0) {
  216. return false;
  217. }
  218. lock();
  219. ConsoleLine* line = getQueueIndexCL(&buffer, 0);
  220. snprintf(buffer_, n, "%s", line->data);
  221. popQueueDataCL(&buffer);
  222. unlock();
  223. return true;
  224. }
  225. void stopReadLine() {
  226. atomic_store(&running, false);
  227. joinThreadSafe(&readThread);
  228. if(leaveRawTerminal()) {
  229. REPORT(LOG_WARNING, "cannot restore terminal attributes");
  230. }
  231. destroyQueueCL(&buffer);
  232. mtx_destroy(&bufferMutex);
  233. }