Browse Source

now a global c library

Kajetan Johannes Hammerle 2 years ago
parent
commit
b247c828e2
6 changed files with 345 additions and 263 deletions
  1. 303 0
      Console.c
  2. 18 0
      Console.h
  3. 22 0
      Main.c
  4. 0 25
      Main.cpp
  5. 2 2
      Makefile
  6. 0 236
      RawReader.h

+ 303 - 0
Console.c

@@ -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;
+}

+ 18 - 0
Console.h

@@ -0,0 +1,18 @@
+#ifndef CONSOLE_H
+#define CONSOLE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#define CONSOLE_BUFFER_SIZE 1024
+
+typedef struct ConsoleLine {
+    uint32_t data[CONSOLE_BUFFER_SIZE];
+    int length;
+} ConsoleLine;
+
+bool initConsole();
+const char* getConsoleError();
+const ConsoleLine* readConsoleLine(const char* prefix);
+
+#endif

+ 22 - 0
Main.c

@@ -0,0 +1,22 @@
+#include <stdio.h>
+#include <unistd.h>
+
+#include "Console.h"
+
+int main() {
+    if(initConsole()) {
+        puts(getConsoleError());
+        return 0;
+    }
+    while(true) {
+        puts("Work 1");
+        usleep(50000);
+        puts("Work 2");
+
+        const ConsoleLine* s = readConsoleLine("> ");
+        if(s != NULL && s->length > 0 && s->data[0] == 'q') {
+            break;
+        }
+    }
+    return 0;
+}

+ 0 - 25
Main.cpp

@@ -1,25 +0,0 @@
-#include <cstring>
-#include <iostream>
-
-#include "RawReader.h"
-
-int main() {
-    RawReader<10, 5> reader(0, "> ");
-
-    while(reader.canRead()) {
-        // simulate work between reads
-        std::cout << "Work 1\n";
-        reader.updateLine();
-        usleep(50000);
-        std::cout << "Work 2\n";
-
-        const char* s = reader.readLine();
-        if(s != nullptr) {
-            std::cout << s << "\n";
-            if(strcmp(s, "q") == 0) {
-                break;
-            }
-        }
-    }
-    return 0;
-}

+ 2 - 2
Makefile

@@ -3,8 +3,8 @@ all: raw_terminal
 run: all
 	./raw_terminal
 
-raw_terminal: Main.cpp RawReader.h
-	g++ -o $@ Main.cpp -Wall -Wextra -Werror -pedantic
+raw_terminal: Main.c Console.h Console.c
+	gcc -o $@ Main.c Console.c -Wall -Wextra -Werror -pedantic
 
 clean:
 	rm -f raw_terminal

+ 0 - 236
RawReader.h

@@ -1,236 +0,0 @@
-#ifndef RAWREADER_H
-#define RAWREADER_H
-
-#include <cstdio>
-#include <cstring>
-
-#include <termios.h>
-#include <unistd.h>
-
-template<int N, int H>
-class RawReader final {
-    struct termios original;
-
-    int index;
-    char buffer[2][N];
-    bool bufferIndex;
-    bool noError;
-    int move;
-
-    char history[H][N];
-    int historyOffset;
-    int historyIndex;
-    int historyLength;
-
-    const char* prefix;
-
-public:
-    typedef int DeciSeconds;
-
-    RawReader(DeciSeconds timeout, const char* prefix)
-        : index(0), bufferIndex(false), noError(true), move(0), historyOffset(0), historyIndex(0), historyLength(0),
-          prefix(prefix) {
-        // https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
-        if(tcgetattr(STDIN_FILENO, &original) == -1) {
-            markError();
-            return;
-        }
-
-        struct termios raw;
-        if(tcgetattr(STDIN_FILENO, &raw) == -1) {
-            markError();
-            return;
-        };
-        raw.c_iflag &= ~(ICRNL | IXON);
-        raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
-        raw.c_cc[VMIN] = 0;
-        raw.c_cc[VTIME] = timeout;
-
-        if(tcsetattr(STDIN_FILENO, TCSAFLUSH, &raw)) {
-            markError();
-            return;
-        }
-
-        buffer[0][0] = '\0';
-        buffer[1][0] = '\0';
-    }
-
-    ~RawReader() {
-        tcsetattr(STDIN_FILENO, TCSAFLUSH, &original);
-    }
-
-    RawReader(const RawReader&) = delete;
-    RawReader(RawReader&&) = delete;
-    RawReader& operator=(const RawReader&) = delete;
-    RawReader& operator=(RawReader&&) = delete;
-
-    bool canRead() const {
-        return noError;
-    }
-
-    const char* readLine() {
-        refreshLine();
-        while(true) {
-            char c = 0;
-            int bytes = read(STDIN_FILENO, &c, 1);
-            if(bytes < 0) {
-                markError();
-                break;
-            } else if(bytes == 0 || c < 0) {
-                break;
-            }
-
-            if(c == 27) {
-                readEscapeSequence();
-            } else if(iscntrl(c)) {
-                if(handleControlKey(c)) {
-                    clearLine();
-                    return swapBuffers();
-                }
-            } else {
-                addChar(c);
-            }
-            refreshLine();
-        }
-        clearLine();
-        return nullptr;
-    }
-
-    void updateLine() {
-        refreshLine();
-        clearLine();
-    }
-
-private:
-    void print(const char* s) {
-        fputs(s, stdout);
-    }
-
-    void clearLine() {
-        print("\33[2K\r");
-    }
-
-    void markError() {
-        noError = false;
-    }
-
-    const char* swapBuffers() {
-        addToHistory();
-        bufferIndex = !bufferIndex;
-        clear();
-        return buffer[!bufferIndex];
-    }
-
-    void refreshLine() {
-        clearLine();
-        print(prefix);
-        print(buffer[bufferIndex]);
-        if(move > 0) {
-            printf("\33[%dD", move);
-        }
-        fflush(stdout);
-    }
-
-    bool handleControlKey(char c) {
-        switch(c) {
-            case 3: return clear();
-            case 13: return true;
-            case 127: return removeChar();
-        }
-        return false;
-    }
-
-    bool clear() {
-        index = 0;
-        move = 0;
-        buffer[bufferIndex][0] = '\0';
-        historyOffset = 0;
-        return false;
-    }
-
-    bool removeChar() {
-        int pos = index - move - 1;
-        if(pos >= 0) {
-            index--;
-            while(buffer[bufferIndex][pos] != '\0') {
-                buffer[bufferIndex][pos] = buffer[bufferIndex][pos + 1];
-                pos++;
-            }
-        }
-        if(index - move < 0) {
-            move--;
-        }
-        return false;
-    }
-
-    void addChar(char c) {
-        if(index + 1 < N) {
-            index++;
-            for(int i = 0; i < move + 1; i++) {
-                buffer[bufferIndex][index - i] = buffer[bufferIndex][index - i - 1];
-            }
-            buffer[bufferIndex][index - move - 1] = c;
-        }
-    }
-
-    char readChar() {
-        char c;
-        return read(STDIN_FILENO, &c, 1) == 1 ? c : -1;
-    }
-
-    void readEscapeSequence() {
-        if(readChar() != '[') {
-            return;
-        }
-        switch(readChar()) {
-            case 'A': handleUpArrow(); break;
-            case 'B': handleDownArrow(); break;
-            case 'C': handleRightArrow(); break;
-            case 'D': handleLeftArrow(); break;
-        }
-    }
-
-    void handleUpArrow() {
-        if(historyOffset >= historyLength) {
-            return;
-        }
-        historyOffset++;
-        copyHistory();
-    }
-
-    void handleDownArrow() {
-        if(historyOffset <= 1) {
-            return;
-        }
-        historyOffset--;
-        copyHistory();
-    }
-
-    void handleLeftArrow() {
-        if(move < index) {
-            move++;
-        }
-    }
-
-    void handleRightArrow() {
-        if(move > 0) {
-            move--;
-        }
-    }
-
-    void addToHistory() {
-        if(historyLength < H) {
-            historyLength++;
-        }
-        strncpy(history[historyIndex], buffer[bufferIndex], N);
-        historyIndex = (historyIndex + 1) % H;
-    }
-
-    void copyHistory() {
-        strncpy(buffer[bufferIndex], history[(historyIndex - historyOffset + H) % H], N);
-        move = 0;
-        index = strnlen(buffer[bufferIndex], N);
-    }
-};
-
-#endif