#ifndef RAWREADER_H #define RAWREADER_H #include #include #include #include template 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