123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296 |
- #include "core/ReadLine.h"
- #include <ctype.h>
- #include <errno.h>
- #include <stdatomic.h>
- #include <stdint.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
- #include <termios.h>
- #include <threads.h>
- #include <unistd.h>
- #include "ErrorSimulator.h"
- #include "core/Logger.h"
- #include "core/Queue.h"
- #define HISTORY_LENGTH 10
- #define CONSOLE_BUFFER_SIZE 256
- typedef struct ConsoleLine {
- char data[CONSOLE_BUFFER_SIZE];
- int length;
- } ConsoleLine;
- static atomic_bool running = true;
- static thrd_t readThread = {0};
- static struct termios original;
- static CoreQueue buffer = {0};
- static ConsoleLine currentBuffer = {0};
- static int move = 0;
- static int cursorMove = 0;
- static mtx_t bufferMutex = {0};
- static ConsoleLine history[HISTORY_LENGTH];
- static int historyOffset = 0;
- static int historyIndex = 0;
- static int historyLength = 0;
- static void getAttributes(struct termios* t) {
- if(tcgetattr(STDIN_FILENO, t)) {
- CORE_LOG_WARNING("cannot get terminal attributes");
- }
- }
- static void restoreAttributes() {
- if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &original)) {
- CORE_LOG_WARNING("cannot restore terminal attributes: %s",
- strerror(errno));
- }
- }
- static char readChar() {
- char c = 0;
- ssize_t bytes = read(STDIN_FILENO, &c, 1);
- return bytes <= 0 ? '\0' : c;
- }
- static void addChar(char c) {
- if(currentBuffer.length >= CONSOLE_BUFFER_SIZE - 1) {
- return;
- }
- for(int i = 0; i < move; i++) {
- currentBuffer.data[currentBuffer.length - i] =
- currentBuffer.data[currentBuffer.length - i - 1];
- }
- currentBuffer.length++;
- currentBuffer.data[currentBuffer.length - move - 1] = c;
- currentBuffer.data[currentBuffer.length] = '\0';
- }
- static void print(const char* s) {
- fputs(s, stdout);
- }
- static void refreshLine(const char* prefix) {
- print(prefix);
- print(currentBuffer.data);
- if(cursorMove > 0) {
- printf("\33[%dD", cursorMove);
- }
- fflush(stdout);
- print("\33[2K\r");
- }
- static bool clear() {
- move = 0;
- cursorMove = 0;
- currentBuffer.length = 0;
- currentBuffer.data[0] = '\0';
- historyOffset = 0;
- return false;
- }
- static void addToHistory() {
- if(historyLength < HISTORY_LENGTH) {
- historyLength++;
- }
- history[historyIndex] = currentBuffer;
- historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
- }
- static void lock() {
- if(mtx_lock(&bufferMutex) != thrd_success || MUTEX_LOCK_FAIL) {
- CORE_LOG_WARNING("could not lock buffer mutex");
- }
- }
- static void unlock() {
- if(mtx_unlock(&bufferMutex) != thrd_success || MUTEX_UNLOCK_FAIL) {
- CORE_LOG_WARNING("could not unlock buffer mutex");
- }
- }
- static void addLine() {
- addToHistory();
- lock();
- corePushQueueData(&buffer, ¤tBuffer);
- unlock();
- clear();
- }
- static char isUTF8Part(char c) {
- return ((uint8_t)c & 0xC0) == 0x80;
- }
- static bool removeChar() {
- int pos = currentBuffer.length - move;
- if(pos > 0) {
- int l = 1;
- while(pos - l >= 0) {
- if(!isUTF8Part(currentBuffer.data[pos - l])) {
- break;
- }
- l++;
- }
- currentBuffer.length -= l;
- for(int i = pos - l; i <= currentBuffer.length; i++) {
- currentBuffer.data[i] = currentBuffer.data[i + l];
- }
- }
- return false;
- }
- static bool handleControlKey(char c) {
- switch(c) {
- case 3: return clear();
- case 10:
- case 13: return true;
- case 127: return removeChar();
- }
- return false;
- }
- static void copyHistory() {
- currentBuffer = history[(historyIndex - historyOffset + HISTORY_LENGTH) %
- HISTORY_LENGTH];
- move = 0;
- cursorMove = 0;
- }
- static void handleUpArrow() {
- if(historyOffset >= historyLength) {
- return;
- }
- historyOffset++;
- copyHistory();
- }
- static void handleDownArrow() {
- if(historyOffset <= 1) {
- return;
- }
- historyOffset--;
- copyHistory();
- }
- static char getMoved() {
- return currentBuffer.data[currentBuffer.length - move];
- }
- static void handleLeftArrow() {
- if(move < currentBuffer.length) {
- move++;
- while(move < currentBuffer.length && isUTF8Part(getMoved())) {
- move++;
- }
- cursorMove++;
- }
- }
- static void handleRightArrow() {
- if(move > 0) {
- move--;
- while(move > 0 && isUTF8Part(getMoved())) {
- move--;
- }
- cursorMove--;
- }
- }
- static void readEscapeSequence() {
- if(readChar() != '[') {
- return;
- }
- switch(readChar()) {
- case 'A': handleUpArrow(); break;
- case 'B': handleDownArrow(); break;
- case 'C': handleRightArrow(); break;
- case 'D': handleLeftArrow(); break;
- case '3':
- if(readChar() == '~' && move > 0) {
- handleRightArrow();
- removeChar();
- }
- break;
- }
- }
- static void handleChars() {
- while(true) {
- char c = readChar();
- if(c == 0) {
- break;
- } else if(c == 27) {
- readEscapeSequence();
- } else if(iscntrl(c)) {
- if(handleControlKey(c)) {
- addLine();
- return;
- }
- } else {
- addChar(c);
- }
- }
- }
- static int loop(void* data) {
- (void)data;
- while(running) {
- handleChars();
- refreshLine("wusi> ");
- struct timespec t = {.tv_nsec = 1000000};
- thrd_sleep(&t, nullptr);
- }
- return 0;
- }
- bool coreStartReadLine(void) {
- // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
- getAttributes(&original);
- struct termios raw = original;
- raw.c_iflag &= ~(tcflag_t)(ICRNL | IXON);
- raw.c_lflag &= ~(tcflag_t)(ECHO | ICANON | IEXTEN | ISIG);
- raw.c_cc[VMIN] = 0;
- raw.c_cc[VTIME] = 0;
- if(tcsetattr(STDIN_FILENO, TCSANOW, &raw)) {
- CORE_LOG_WARNING("cannot set terminal attributes");
- }
- coreInitQueue(&buffer, 10, sizeof(ConsoleLine));
- atomic_store(&running, true);
- if(MUTEX_INIT_FAIL || mtx_init(&bufferMutex, mtx_plain) != thrd_success) {
- CORE_LOG_ERROR("cannot init buffer mutex");
- coreStopReadLine();
- return true;
- } else if(THREAD_INIT_FAIL ||
- thrd_create(&readThread, loop, nullptr) != thrd_success) {
- CORE_LOG_ERROR("cannot start read thread");
- coreStopReadLine();
- return true;
- }
- return false;
- }
- bool coreReadLine(char* buffer_, size_t n) {
- if(buffer.length == 0) {
- return false;
- }
- lock();
- ConsoleLine* line = coreGetQueueIndex(&buffer, 0);
- snprintf(buffer_, n, "%s", line->data);
- corePopQueueData(&buffer);
- unlock();
- return true;
- }
- void coreStopReadLine() {
- atomic_store(&running, false);
- thrd_join(readThread, nullptr);
- restoreAttributes();
- coreDestroyQueue(&buffer);
- mtx_destroy(&bufferMutex);
- }
|