#include "core/ReadLine.h" #include #include #include #include #include #include #include #include #include #include #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); }