#include "core/ReadLine.h" #include #include #include #include #include #include "ErrorSimulator.h" #include "core/Logger.h" #include "core/Queue.h" #include "core/Thread.h" #include "core/Unicode.h" static constexpr size_t HISTORY_LENGTH = 10; static constexpr size_t CONSOLE_BUFFER_SIZE = 256; typedef struct ConsoleLine { char data[CONSOLE_BUFFER_SIZE]; size_t length; } ConsoleLine; QUEUE(ConsoleLine, CL) QUEUE_SOURCE(ConsoleLine, CL) static atomic_bool running = true; static thrd_t readThread = {0}; static QueueCL buffer = {0}; static ConsoleLine currentBuffer = {0}; static size_t move = 0; static size_t cursorMove = 0; static mtx_t bufferMutex = {0}; static ConsoleLine history[HISTORY_LENGTH]; static size_t historyOffset = 0; static size_t historyIndex = 0; static size_t historyLength = 0; static void addChar(u64 c) { UTF8 u = convertUnicodeToUTF8((u32)c); for(u32 k = 0; k < u.length; k++) { if(currentBuffer.length >= CONSOLE_BUFFER_SIZE - 1) { return; } for(size_t i = 0; i < move; i++) { currentBuffer.data[currentBuffer.length - i] = currentBuffer.data[currentBuffer.length - i - 1]; } currentBuffer.length++; currentBuffer.data[currentBuffer.length - move - 1] = (char)u.data[k]; 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) { moveCursorLeft((int)cursorMove); } fflush(stdout); clearTerminalLine(); } 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) { REPORT(LOG_WARNING, "could not lock buffer mutex"); } } static void unlock() { if(mtx_unlock(&bufferMutex) != thrd_success || MUTEX_UNLOCK_FAIL) { REPORT(LOG_WARNING, "could not unlock buffer mutex"); } } static void addLine() { addToHistory(); lock(); pushQueueDataCL(&buffer, currentBuffer); unlock(); clear(); } static bool removeChar() { size_t pos = currentBuffer.length - move; if(pos > 0) { size_t l = 1; while(pos - l >= 0) { if(!isUTF8Remainder((u8)currentBuffer.data[pos - l])) { break; } l++; } currentBuffer.length -= l; for(size_t i = pos - l; i <= currentBuffer.length; i++) { currentBuffer.data[i] = currentBuffer.data[i + l]; } } return false; } static bool handleControlKey(u64 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 && isUTF8Remainder((u8)getMoved())) { move++; } cursorMove++; } } static void handleRightArrow() { if(move > 0) { move--; while(move > 0 && isUTF8Remainder((u8)getMoved())) { move--; } cursorMove--; } } static void handleChars() { while(true) { u64 c = getRawChar(); if(c == 0) { break; } else if(c == TERMINAL_KEY_ARROW_UP) { handleUpArrow(); } else if(c == TERMINAL_KEY_ARROW_DOWN) { handleDownArrow(); } else if(c == TERMINAL_KEY_ARROW_RIGHT) { handleRightArrow(); } else if(c == TERMINAL_KEY_ARROW_LEFT) { handleLeftArrow(); } else if(c == TERMINAL_KEY_DELETE) { if(move > 0) { handleRightArrow(); removeChar(); } } 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 = 1'000'000}; thrd_sleep(&t, nullptr); } return 0; } bool startReadLine(void) { if(enterRawTerminal()) { REPORT(LOG_WARNING, "cannot set terminal attributes"); } initQueueCL(&buffer, 10); atomic_store(&running, true); if(MUTEX_INIT_FAIL || mtx_init(&bufferMutex, mtx_plain) != thrd_success) { REPORT(LOG_ERROR, "cannot init buffer mutex"); stopReadLine(); return true; } else if( THREAD_INIT_FAIL || thrd_create(&readThread, loop, nullptr) != thrd_success) { REPORT(LOG_ERROR, "cannot start read thread"); stopReadLine(); return true; } return false; } bool readLine(char* buffer_, size_t n) { if(buffer.length == 0) { return false; } lock(); ConsoleLine* line = getQueueIndexCL(&buffer, 0); snprintf(buffer_, n, "%s", line->data); popQueueDataCL(&buffer); unlock(); return true; } void stopReadLine() { atomic_store(&running, false); joinThreadSafe(&readThread); if(leaveRawTerminal()) { REPORT(LOG_WARNING, "cannot restore terminal attributes"); } destroyQueueCL(&buffer); mtx_destroy(&bufferMutex); }