|
@@ -0,0 +1,232 @@
|
|
|
+#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;
|
|
|
+
|
|
|
+public:
|
|
|
+ typedef int DeciSeconds;
|
|
|
+
|
|
|
+ RawReader(DeciSeconds timeout) : index(0), bufferIndex(false), noError(true), move(0),
|
|
|
+ historyOffset(0), historyIndex(0), historyLength(0) {
|
|
|
+ // 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(const char* prefix) {
|
|
|
+ refreshLine(prefix);
|
|
|
+ while(true) {
|
|
|
+ char c = 0;
|
|
|
+ int bytes = read(STDIN_FILENO, &c, 1);
|
|
|
+ if(bytes < 0) {
|
|
|
+ markError();
|
|
|
+ break;
|
|
|
+ } else if(bytes == 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(c == 27) {
|
|
|
+ readEscapeSequence();
|
|
|
+ } else if(iscntrl(c)) {
|
|
|
+ if(handleControlKey(c)) {
|
|
|
+ clearLine();
|
|
|
+ return swapBuffers();
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ addChar(c);
|
|
|
+ }
|
|
|
+ refreshLine(prefix);
|
|
|
+ }
|
|
|
+ clearLine();
|
|
|
+ return nullptr;
|
|
|
+ }
|
|
|
+
|
|
|
+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(const char* prefix) {
|
|
|
+ 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) {
|
|
|
+ buffer[bufferIndex][index++] = c;
|
|
|
+ buffer[bufferIndex][index] = '\0';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ 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
|