module Reply private struct CharReader enum Sequence EOF UP DOWN RIGHT LEFT ENTER ESCAPE DELETE BACKSPACE CTRL_A CTRL_B CTRL_C CTRL_D CTRL_E CTRL_F CTRL_K CTRL_N CTRL_P CTRL_R CTRL_U CTRL_X CTRL_UP CTRL_DOWN CTRL_LEFT CTRL_RIGHT CTRL_ENTER CTRL_DELETE CTRL_BACKSPACE ALT_B ALT_D ALT_F ALT_ENTER ALT_BACKSPACE TAB SHIFT_TAB HOME END end def initialize(buffer_size = 8192) @slice_buffer = Bytes.new(buffer_size) end def read_char(from io : IO = STDIN) nb_read = raw(io, &.read(@slice_buffer)) parse_escape_sequence(@slice_buffer[0...nb_read]) end private def parse_escape_sequence(chars : Bytes) : Char | Sequence | String? return String.new(chars) if chars.size > 6 return Sequence::EOF if chars.empty? case chars[0]? when '\e'.ord case chars[1]? when '['.ord case chars[2]? when 'A'.ord then Sequence::UP when 'B'.ord then Sequence::DOWN when 'C'.ord then Sequence::RIGHT when 'D'.ord then Sequence::LEFT when 'Z'.ord then Sequence::SHIFT_TAB when '3'.ord if {chars[3]?, chars[4]?} == {';'.ord, '5'.ord} case chars[5]? when '~'.ord then Sequence::CTRL_DELETE end elsif chars[3]? == '~'.ord Sequence::DELETE end when '1'.ord if {chars[3]?, chars[4]?} == {';'.ord, '5'.ord} case chars[5]? when 'A'.ord then Sequence::CTRL_UP when 'B'.ord then Sequence::CTRL_DOWN when 'C'.ord then Sequence::CTRL_RIGHT when 'D'.ord then Sequence::CTRL_LEFT end elsif chars[3]? == '~'.ord # linux console HOME Sequence::HOME end when '4'.ord # linux console END if chars[3]? == '~'.ord Sequence::END end when 'H'.ord # xterm HOME Sequence::HOME when 'F'.ord # xterm END Sequence::END end when '\t'.ord Sequence::SHIFT_TAB when '\r'.ord Sequence::ALT_ENTER when 0x7f Sequence::ALT_BACKSPACE when 'O'.ord if chars[2]? == 'H'.ord # gnome terminal HOME Sequence::HOME elsif chars[2]? == 'F'.ord # gnome terminal END Sequence::END end when 'b' Sequence::ALT_B when 'd' Sequence::ALT_D when 'f' Sequence::ALT_F when Nil Sequence::ESCAPE end when '\r'.ord Sequence::ENTER when '\n'.ord {% if flag?(:win32) %} Sequence::CTRL_ENTER {% else %} Sequence::ENTER {% end %} when '\t'.ord Sequence::TAB when '\b'.ord Sequence::CTRL_BACKSPACE when ctrl('a') Sequence::CTRL_A when ctrl('b') Sequence::CTRL_B when ctrl('c') Sequence::CTRL_C when ctrl('d') Sequence::CTRL_D when ctrl('e') Sequence::CTRL_E when ctrl('f') Sequence::CTRL_F when ctrl('k') Sequence::CTRL_K when ctrl('n') Sequence::CTRL_N when ctrl('p') Sequence::CTRL_P when ctrl('r') Sequence::CTRL_R when ctrl('u') Sequence::CTRL_U when ctrl('x') Sequence::CTRL_X when '\0'.ord Sequence::EOF when 0x7f Sequence::BACKSPACE else if chars.size == 1 chars[0].chr end end || String.new(chars) end private def raw(io : T, &) forall T {% if T.has_method?(:raw) %} if io.tty? io.raw { yield io } else yield io end {% else %} yield io {% end %} end private def ctrl(k) (k.ord & 0x1f) end end end