123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303 |
- #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;
- }
|