|
@@ -0,0 +1,297 @@
|
|
|
+#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/RingBuffer.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 CoreRingBuffer 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 || CORE_MUTEX_LOCK_FAIL) {
|
|
|
+ CORE_LOG_WARNING("could not lock buffer mutex");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void unlock() {
|
|
|
+ if(mtx_unlock(&bufferMutex) != thrd_success || CORE_MUTEX_UNLOCK_FAIL) {
|
|
|
+ CORE_LOG_WARNING("could not unlock buffer mutex");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void addLine() {
|
|
|
+ addToHistory();
|
|
|
+ lock();
|
|
|
+ coreRingBufferAddPointer(&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) {
|
|
|
+
|
|
|
+ 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");
|
|
|
+ }
|
|
|
+
|
|
|
+ buffer = CORE_RING_BUFFER(10, sizeof(ConsoleLine));
|
|
|
+ atomic_store(&running, true);
|
|
|
+ if(CORE_MUTEX_INIT_FAIL ||
|
|
|
+ mtx_init(&bufferMutex, mtx_plain) != thrd_success) {
|
|
|
+ CORE_LOG_ERROR("cannot init buffer mutex");
|
|
|
+ coreStopReadLine();
|
|
|
+ return true;
|
|
|
+ } else if(CORE_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 = coreRingBufferGetVoidPointer(&buffer, 0);
|
|
|
+ snprintf(buffer_, n, "%s", line->data);
|
|
|
+ coreRingBufferRemove(&buffer);
|
|
|
+ unlock();
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void coreStopReadLine() {
|
|
|
+ atomic_store(&running, false);
|
|
|
+ thrd_join(readThread, nullptr);
|
|
|
+ restoreAttributes();
|
|
|
+ coreDestroyRingBuffer(&buffer);
|
|
|
+ mtx_destroy(&bufferMutex);
|
|
|
+}
|