|
@@ -0,0 +1,303 @@
|
|
|
+#include <ctype.h>
|
|
|
+#include <errno.h>
|
|
|
+#include <stdio.h>
|
|
|
+#include <stdlib.h>
|
|
|
+#include <string.h>
|
|
|
+#include <termios.h>
|
|
|
+#include <unistd.h>
|
|
|
+
|
|
|
+#include "Console.h"
|
|
|
+
|
|
|
+#define ERROR_SIZE 256
|
|
|
+#define HISTORY_LENGTH 4
|
|
|
+#define OUTPUT_BUFFER_SIZE (1024 * 1024)
|
|
|
+
|
|
|
+static char error[ERROR_SIZE] = {'\0'};
|
|
|
+static struct termios original;
|
|
|
+static ConsoleLine buffer[2];
|
|
|
+static bool bufferIndex = 0;
|
|
|
+static int move = 0;
|
|
|
+
|
|
|
+static ConsoleLine history[HISTORY_LENGTH];
|
|
|
+static int historyOffset = 0;
|
|
|
+static int historyIndex = 0;
|
|
|
+static int historyLength = 0;
|
|
|
+
|
|
|
+static char outputBuffer[OUTPUT_BUFFER_SIZE];
|
|
|
+
|
|
|
+static void setError(const char* msg) {
|
|
|
+ snprintf(error, ERROR_SIZE, "%s: %s", msg, strerror(errno));
|
|
|
+}
|
|
|
+
|
|
|
+static bool getAttributes(struct termios* t) {
|
|
|
+ if(tcgetattr(STDIN_FILENO, t)) {
|
|
|
+ setError("cannot get terminal attributes");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void restoreAttributes() {
|
|
|
+ if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &original)) {
|
|
|
+ printf("cannot restore terminal attributes: %s", strerror(errno));
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+bool initConsole() {
|
|
|
+ buffer[0].length = 0;
|
|
|
+ buffer[1].length = 0;
|
|
|
+
|
|
|
+ if(setvbuf(stdout, outputBuffer, _IOFBF, OUTPUT_BUFFER_SIZE)) {
|
|
|
+ setError("cannot buffer output");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
|
|
|
+ if(getAttributes(&original)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ struct termios raw;
|
|
|
+ if(getAttributes(&raw)) {
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ raw.c_iflag &= ~(ICRNL | IXON);
|
|
|
+ raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
|
|
+ raw.c_cc[VMIN] = 0;
|
|
|
+ raw.c_cc[VTIME] = 0;
|
|
|
+
|
|
|
+ if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw)) {
|
|
|
+ setError("cannot set terminal attributes");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ atexit(restoreAttributes);
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+const char* getConsoleError() {
|
|
|
+ return error;
|
|
|
+}
|
|
|
+
|
|
|
+static bool readChar(uint8_t* c) {
|
|
|
+ int bytes = read(STDIN_FILENO, c, 1);
|
|
|
+ if(bytes == 0) {
|
|
|
+ setError("expected part of unicode character");
|
|
|
+ return true;
|
|
|
+ } else if(bytes < 0) {
|
|
|
+ setError("cannot read character from terminal");
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static uint32_t readUnicode2(uint8_t base) {
|
|
|
+ uint8_t c = 0;
|
|
|
+ if(readChar(&c)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return ((base & 0x1F) << 6) | (c & 0x3F);
|
|
|
+}
|
|
|
+
|
|
|
+static uint32_t readUnicode3(uint8_t base) {
|
|
|
+ uint8_t c1 = 0;
|
|
|
+ uint8_t c2 = 0;
|
|
|
+ if(readChar(&c1) || readChar(&c2)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return ((base & 0x0F) << 12) | ((c1 & 0x3F) << 6) | (c2 & 0x3F);
|
|
|
+}
|
|
|
+
|
|
|
+static uint32_t readUnicode4(uint8_t base) {
|
|
|
+ uint8_t c1 = 0;
|
|
|
+ uint8_t c2 = 0;
|
|
|
+ uint8_t c3 = 0;
|
|
|
+ if(readChar(&c1) || readChar(&c2) || readChar(&c3)) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ return ((base & 0x07) << 18) | ((c1 & 0x3F) << 12) | ((c2 & 0x3F) << 6) |
|
|
|
+ (c3 & 0x3F);
|
|
|
+}
|
|
|
+
|
|
|
+static uint32_t readUnicode() {
|
|
|
+ uint8_t c = 0;
|
|
|
+ int bytes = read(STDIN_FILENO, &c, 1);
|
|
|
+ if(bytes == 0) {
|
|
|
+ return 0;
|
|
|
+ } else if(bytes < 0) {
|
|
|
+ setError("cannot read character from terminal");
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ if((c & 0x80) == 0x00) {
|
|
|
+ return c;
|
|
|
+ } else if((c & 0xE0) == 0xC0) {
|
|
|
+ return readUnicode2(c);
|
|
|
+ } else if((c & 0xF0) == 0xE0) {
|
|
|
+ return readUnicode3(c);
|
|
|
+ } else if((c & 0xF8) == 0xF0) {
|
|
|
+ return readUnicode4(c);
|
|
|
+ }
|
|
|
+ setError("invalid UTF8 character");
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void addChar(uint32_t c) {
|
|
|
+ ConsoleLine* line = buffer + bufferIndex;
|
|
|
+ if(line->length >= CONSOLE_BUFFER_SIZE) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ for(int i = 0; i < move; i++) {
|
|
|
+ line->data[line->length - i] = line->data[line->length - i - 1];
|
|
|
+ }
|
|
|
+ line->length++;
|
|
|
+ line->data[line->length - move - 1] = c;
|
|
|
+}
|
|
|
+
|
|
|
+static void print(const char* s) {
|
|
|
+ fputs(s, stdout);
|
|
|
+}
|
|
|
+
|
|
|
+static void printBuffer() {
|
|
|
+ ConsoleLine* line = buffer + bufferIndex;
|
|
|
+ for(int i = 0; i < line->length; i++) {
|
|
|
+ uint32_t c = line->data[i];
|
|
|
+ if(c <= 0x0000007F) {
|
|
|
+ putchar(c);
|
|
|
+ } else if(c <= 0x000007FF) {
|
|
|
+ putchar(((c >> 6) & 0x1F) | 0xC0);
|
|
|
+ putchar((c & 0x3F) | 0x80);
|
|
|
+ } else if(c <= 0x0000FFFF) {
|
|
|
+ putchar(((c >> 12) & 0x0F) | 0xE0);
|
|
|
+ putchar(((c >> 6) & 0x3F) | 0x80);
|
|
|
+ putchar((c & 0x3F) | 0x80);
|
|
|
+ } else if(c <= 0x0010FFFF) {
|
|
|
+ putchar(((c >> 18) & 0x07) | 0xF0);
|
|
|
+ putchar(((c >> 12) & 0x3F) | 0x80);
|
|
|
+ putchar(((c >> 6) & 0x3F) | 0x80);
|
|
|
+ putchar((c & 0x3F) | 0x80);
|
|
|
+ } else {
|
|
|
+ putchar('?');
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void refreshLine(const char* prefix) {
|
|
|
+ print(prefix);
|
|
|
+ printBuffer();
|
|
|
+ if(move > 0) {
|
|
|
+ printf("\33[%dD", move);
|
|
|
+ }
|
|
|
+ fflush(stdout);
|
|
|
+ print("\33[2K\r");
|
|
|
+}
|
|
|
+
|
|
|
+static bool clear() {
|
|
|
+ move = 0;
|
|
|
+ buffer[bufferIndex].length = 0;
|
|
|
+ historyOffset = 0;
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void addToHistory() {
|
|
|
+ if(historyLength < HISTORY_LENGTH) {
|
|
|
+ historyLength++;
|
|
|
+ }
|
|
|
+ history[historyIndex] = buffer[bufferIndex];
|
|
|
+ historyIndex = (historyIndex + 1) % HISTORY_LENGTH;
|
|
|
+}
|
|
|
+
|
|
|
+static const ConsoleLine* swapBuffers() {
|
|
|
+ addToHistory();
|
|
|
+ bufferIndex = !bufferIndex;
|
|
|
+ clear();
|
|
|
+ return buffer + (!bufferIndex);
|
|
|
+}
|
|
|
+
|
|
|
+static bool removeChar() {
|
|
|
+ ConsoleLine* line = buffer + bufferIndex;
|
|
|
+ int pos = line->length - move - 1;
|
|
|
+ if(pos >= 0) {
|
|
|
+ line->length--;
|
|
|
+ for(int i = pos; i < line->length; i++) {
|
|
|
+ line->data[i] = line->data[i + 1];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ while(line->length - move < 0) {
|
|
|
+ move--;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static bool handleControlKey(char c) {
|
|
|
+ switch(c) {
|
|
|
+ case 3: return clear();
|
|
|
+ case 13: return true;
|
|
|
+ case 127: return removeChar();
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static void copyHistory() {
|
|
|
+ buffer[bufferIndex] =
|
|
|
+ history[(historyIndex - historyOffset + HISTORY_LENGTH) %
|
|
|
+ HISTORY_LENGTH];
|
|
|
+ move = 0;
|
|
|
+}
|
|
|
+
|
|
|
+static void handleUpArrow() {
|
|
|
+ if(historyOffset >= historyLength) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ historyOffset++;
|
|
|
+ copyHistory();
|
|
|
+}
|
|
|
+
|
|
|
+static void handleDownArrow() {
|
|
|
+ if(historyOffset <= 1) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ historyOffset--;
|
|
|
+ copyHistory();
|
|
|
+}
|
|
|
+
|
|
|
+static void handleLeftArrow() {
|
|
|
+ if(move < buffer[bufferIndex].length) {
|
|
|
+ move++;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void handleRightArrow() {
|
|
|
+ if(move > 0) {
|
|
|
+ move--;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void readEscapeSequence() {
|
|
|
+ if(readUnicode() != '[') {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ switch(readUnicode()) {
|
|
|
+ case 'A': handleUpArrow(); break;
|
|
|
+ case 'B': handleDownArrow(); break;
|
|
|
+ case 'C': handleRightArrow(); break;
|
|
|
+ case 'D': handleLeftArrow(); break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+const ConsoleLine* readConsoleLine(const char* prefix) {
|
|
|
+ while(true) {
|
|
|
+ uint32_t c = readUnicode();
|
|
|
+ if(c == 0) {
|
|
|
+ break;
|
|
|
+ } else if(c == 27) {
|
|
|
+ readEscapeSequence();
|
|
|
+ } else if(iscntrl(c)) {
|
|
|
+ if(handleControlKey(c)) {
|
|
|
+ return swapBuffers();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ addChar(c);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ refreshLine(prefix);
|
|
|
+ return NULL;
|
|
|
+}
|