#include "terminal-code-eater.hh" #include "escape-char.hh" #include #include #include namespace nix { static constexpr const bool DEBUG_EATER = false; void TerminalCodeEater::feed(char c, std::function on_char) { auto isParamChar = [](char v) -> bool { return v >= 0x30 && v <= 0x3f; }; auto isIntermediateChar = [](char v) -> bool { return v >= 0x20 && v <= 0x2f; }; auto isFinalChar = [](char v) -> bool { return v >= 0x40 && v <= 0x7e; }; if constexpr (DEBUG_EATER) { std::cerr << "eater" << MaybeHexEscapedChar{c} << "\n"; } switch (state) { case State::ExpectESC: switch (c) { case '\e': transition(State::ExpectESCSeq); return; // Just eat \r, since it is part of clearing a line case '\r': return; default: break; } if constexpr (DEBUG_EATER) { std::cerr << "eater uneat" << MaybeHexEscapedChar{c} << "\n"; } on_char(c); break; case State::ExpectESCSeq: switch (c) { // CSI case '[': transition(State::InCSIParams); return; default: transition(State::ExpectESC); return; } break; // https://en.wikipedia.org/wiki/ANSI_escape_code#CSI_(Control_Sequence_Introducer)_sequences // A CSI sequence is: CSI [\x30-\x3f]* [\x20-\x2f]* [\x40-\x7e] // ^ params ^ intermediates ^ final byte case State::InCSIParams: if (isFinalChar(c)) { transition(State::ExpectESC); return; } else if (isIntermediateChar(c)) { transition(State::InCSIIntermediates); return; } else if (isParamChar(c)) { return; } else { // Corrupt escape sequence? Throw an assert, for now. // transition(State::ExpectESC); assert(false && "Corrupt terminal escape sequence"); return; } break; case State::InCSIIntermediates: if (isFinalChar(c)) { transition(State::ExpectESC); return; } else if (isIntermediateChar(c)) { return; } else { // Corrupt escape sequence? Throw an assert, for now. // transition(State::ExpectESC); assert(false && "Corrupt terminal escape sequence in intermediates"); return; } break; } } void TerminalCodeEater::transition(State new_state) { state = new_state; } };