module; #include module Core.ReadLine; import Core.Array; import Core.Clock; import Core.Logger; import Core.Queue; import Core.Thread; import Core.Terminal; import Core.TerminalConstants; import Core.Types; import Core.Unicode; import Core.Std; static constexpr size_t HISTORY_LENGTH = 10; static constexpr size_t CONSOLE_BUFFER_SIZE = 256; struct ConsoleLine { Core::Array data{}; size_t length = 0; }; static std::atomic_bool running = true; static Core::Thread readThread; static Core::Queue buffer; static ConsoleLine currentBuffer; static size_t move = 0; static size_t cursorMove = 0; static Core::Mutex bufferMutex; 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) { Core::UTF8 u = Core::convertUnicodeToUTF8(static_cast(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] = static_cast(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.begin()); if(cursorMove > 0) { Core::moveCursorLeft(static_cast(cursorMove)); } fflush(stdout); Core::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 addLine() { addToHistory(); Core::MutexGuard mg(bufferMutex); buffer.add(currentBuffer); clear(); } static bool removeChar() { size_t pos = currentBuffer.length - move; if(pos > 0) { size_t l = 1; while(pos >= l) { if(!Core::isUTF8Remainder( static_cast(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 && Core::isUTF8Remainder(static_cast(getMoved()))) { move++; } cursorMove++; } } static void handleRightArrow() { if(move > 0) { move--; while(move > 0 && Core::isUTF8Remainder(static_cast(getMoved()))) { move--; } cursorMove--; } } static void handleChars() { while(true) { u64 c = Core::getRawChar(); if(c == 0) { break; } else if(c == Core::Terminal::KEY_ARROW_UP) { handleUpArrow(); } else if(c == Core::Terminal::KEY_ARROW_DOWN) { handleDownArrow(); } else if(c == Core::Terminal::KEY_ARROW_RIGHT) { handleRightArrow(); } else if(c == Core::Terminal::KEY_ARROW_LEFT) { handleLeftArrow(); } else if(c == Core::Terminal::KEY_DELETE) { if(move > 0) { handleRightArrow(); removeChar(); } } else if(iscntrl(c & 0x7FFF'FFFF)) { if(handleControlKey(c)) { addLine(); return; } } else if(!Core::isSpecialChar(c)) { addChar(c); } } } static void loop(void*) { while(running) { handleChars(); refreshLine("> "); Core::Clock::sleepMillis(1); } } bool Core::startReadLine(void) { if(enterRawTerminal()) { report(LogLevel::WARNING, "cannot set terminal attributes"); } running = true; if(readThread.start(loop, nullptr)) { report(LogLevel::ERROR, "cannot start read thread"); stopReadLine(); return true; } return false; } bool Core::readLine(char* buffer_, size_t n) { if(buffer.getLength() == 0) { return false; } Core::MutexGuard mg(bufferMutex); snprintf(buffer_, n, "%s", buffer[0].data.begin()); buffer.remove(); return true; } void Core::stopReadLine() { running = false; readThread.join(); if(Core::leaveRawTerminal()) { report(LogLevel::WARNING, "cannot restore terminal attributes"); } buffer.clear(); }