#include "internal/io/io.h" namespace bed::internal::io { KeyEvent::ReadResult IO::get_next_byte(char &out) { if (!input_queue.empty()) { out = input_queue.front(); input_queue.pop_front(); return KeyEvent::ReadResult::SUCCESS; } if (resized.load()) return KeyEvent::ReadResult::RESIZE; ssize_t n = read(STDIN_FILENO, &out, 1); if (n == 1) return KeyEvent::ReadResult::SUCCESS; if (n == -1 && errno == EINTR && resized.load()) return KeyEvent::ReadResult::RESIZE; if (n == -1 && errno == EINTR) return get_next_byte(out); return KeyEvent::ReadResult::EOF_; } void IO::enqueue_bytes(const std::string &bytes) { input_queue.insert(input_queue.begin(), bytes.begin(), bytes.end()); } int IO::utf8_seq_len(uint8_t byte) { if ((byte & 0x80) == 0x00) return 1; if ((byte & 0xE0) == 0xC0) return 2; if ((byte & 0xF0) == 0xE0) return 3; if ((byte & 0xF8) == 0xF0) return 4; return 1; } KeyEvent::ReadResult IO::read_next_unit(std::string &out) { out.clear(); char header; KeyEvent::ReadResult res = get_next_byte(header); if (res != KeyEvent::ReadResult::SUCCESS) return res; if (header == '\x1b') { out.push_back(header); char c; if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) return res; out.push_back(c); if (c != '[') return KeyEvent::ReadResult::SUCCESS; if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) return res; out.push_back(c); if (c == 'M') { for (int i = 0; i < 3; ++i) { if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) return res; out.push_back(c); } return KeyEvent::ReadResult::SUCCESS; } while ((uint8_t)c < 0x40 || (uint8_t)c > 0x7E) { if (out.size() >= 32) break; if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) return res; out.push_back(c); } return KeyEvent::ReadResult::SUCCESS; } int seq_len = utf8_seq_len((uint8_t)header); out.push_back(header); if (seq_len == 1) return KeyEvent::ReadResult::SUCCESS; for (int i = 1; i < seq_len; i++) { char c; if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) { enqueue_bytes(out); out.clear(); return res; } out.push_back(c); } uint32_t prev_cp, cur_cp; grapheme_decode_utf8(out.data(), out.size(), &prev_cp); uint16_t state = 0; while (true) { char next_header; if ((res = get_next_byte(next_header)) != KeyEvent::ReadResult::SUCCESS) break; int next_len = utf8_seq_len((uint8_t)next_header); std::string next_seq(1, next_header); bool complete = true; for (int i = 1; i < next_len; i++) { char c; if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) { complete = false; break; } next_seq.push_back(c); } if (!complete) { enqueue_bytes(next_seq); break; } grapheme_decode_utf8(next_seq.data(), next_seq.size(), &cur_cp); if (grapheme_is_character_break(prev_cp, cur_cp, &state)) { enqueue_bytes(next_seq); break; } out += next_seq; prev_cp = cur_cp; } return KeyEvent::ReadResult::SUCCESS; } KeyEvent::ReadResult IO::read_bracketed_paste(std::string &out) { KeyEvent::ReadResult res = KeyEvent::ReadResult::SUCCESS; out.clear(); std::string window; while (true) { char c; if ((res = get_next_byte(c)) != KeyEvent::ReadResult::SUCCESS) return res; window.push_back(c); if (window.size() == 5 && window == "\x1b[201") { char tilde; if ((res = get_next_byte(tilde)) != KeyEvent::ReadResult::SUCCESS) return res; if (tilde == '~') return res; out += window; out.push_back(tilde); window.clear(); continue; } if (window.size() == 5) { out.push_back(window.front()); window.erase(window.begin()); } } } KeyEvent IO::parse_mouse(const std::string &buf) { KeyEvent ev; if (buf.size() < 6) return ev; uint8_t code = (uint8_t)buf[3] - 32; uint8_t button = code & 0x03; if (button != 0 && button != 3) return ev; ev.type = KeyEvent::KeyType::MOUSE; ev.mouse_state = (button == 3) ? KeyEvent::MouseState::RELEASE : KeyEvent::MouseState::PRESS; ev.mouse_x = (uint8_t)buf[4] - 33; ev.mouse_y = (uint8_t)buf[5] - 33; return ev; } KeyEvent IO::parse_escape(const std::string &buf) { KeyEvent ev; ev.type = KeyEvent::KeyType::SPECIAL; bool has_modifier = buf.size() > 3 && buf[3] == ';'; size_t pos; if (!has_modifier) { pos = 2; } else { pos = 5; switch (buf.size() > 4 ? buf[4] : 0) { case '2': ev.modifier = KeyEvent::Modifier::SHIFT; break; case '3': ev.modifier = KeyEvent::Modifier::ALT; break; case '5': ev.modifier = KeyEvent::Modifier::CTRL; break; case '7': ev.modifier = KeyEvent::Modifier::CTRL_ALT; break; default: ev.modifier = KeyEvent::Modifier::NONE; break; } } char key = pos < buf.size() ? buf[pos] : 0; switch (key) { case 'A': ev.special_key = KeyEvent::SpecialKey::UP; break; case 'B': ev.special_key = KeyEvent::SpecialKey::DOWN; break; case 'C': ev.special_key = KeyEvent::SpecialKey::RIGHT; break; case 'D': ev.special_key = KeyEvent::SpecialKey::LEFT; break; case '3': ev.special_key = KeyEvent::SpecialKey::DELETE; break; default: ev.special_key = KeyEvent::SpecialKey::UNKNOWN; break; } return ev; } KeyEvent IO::read_key() { while (true) { std::string buf; auto res = read_next_unit(buf); KeyEvent ev; switch (res) { case KeyEvent::ReadResult::EOF_: ev.type = KeyEvent::KeyType::NONE; return ev; case KeyEvent::ReadResult::RESIZE: resized.store(false); ev.type = KeyEvent::KeyType::RESIZE; return ev; case KeyEvent::ReadResult::SUCCESS: break; } if (buf.size() >= 6 && buf[0] == '\x1b' && buf[1] == '[' && buf.compare(2, 4, "200~") == 0) { std::string pasted; switch (read_bracketed_paste(pasted)) { case KeyEvent::ReadResult::SUCCESS: ev.type = KeyEvent::KeyType::PASTE; ev.text = std::move(pasted); break; case KeyEvent::ReadResult::EOF_: ev.type = KeyEvent::KeyType::NONE; break; case KeyEvent::ReadResult::RESIZE: resized.store(false); ev.type = KeyEvent::KeyType::RESIZE; break; } return ev; } if (buf.size() >= 3 && buf[0] == '\x1b' && buf[1] == '[' && buf[2] == 'M') { ev = parse_mouse(buf); if (ev.type == KeyEvent::KeyType::NONE) continue; return ev; } if (buf.size() >= 2 && buf[0] == '\x1b' && buf[1] == '[') return parse_escape(buf); ev.type = KeyEvent::KeyType::CHAR; ev.modifier = KeyEvent::Modifier::NONE; if (buf.size() == 1) { uint8_t c = (uint8_t)buf[0]; if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') { ev.modifier = KeyEvent::Modifier::CTRL; ev.text = 'a' + c - 1; return ev; } } if (buf.size() == 2 && (uint8_t)buf[0] == 0x1B) { uint8_t c = (uint8_t)buf[1]; if (c >= 1 && c <= 26 && c != '\t' && c != '\n' && c != '\r' && c != '\x08') { ev.modifier = KeyEvent::Modifier::CTRL_ALT; ev.text = 'a' + c - 1; return ev; } ev.modifier = KeyEvent::Modifier::ALT; ev.text = buf.substr(1); return ev; } ev.text = std::move(buf); return ev; } } } // namespace bed::internal::io