#include "readline.hpp" #define MAXLINE 512 static CodepointString n_backspaces(int n) { CodepointString result(3 * n, 0); for (int i = 0; i < n; i++) { result[i*3 + 0] = '\b'; result[i*3 + 1] = ' '; result[i*3 + 2] = '\b'; } return result; } static int common_prefix_length(const CodepointString &a, const CodepointString &b) { int minlen = std::min(a.size(), b.size()); for (int i = 0; i < minlen; i++) { if (a[i] != b[i]) return i; } return minlen; } static int buffer_codepoint_utf8(char32_t scp, char *buffer) { uint32_t cp = (uint32_t)scp; unsigned char *c = (unsigned char *)buffer; if (cp < 0) { return 0; } else if (cp <= 0x7F) { c[0] = cp; return 1; } else if (cp <= 0x7FF) { c[0] = (cp>>6)+192; c[1] = (cp&63)+128; return 2; } else if (cp <= 0xFFFF) { c[0] = (cp>>12)+224; c[1] = ((cp>>6)&63)+128; c[2] = (cp&63)+128; return 3; } else if (cp <= 0x10FFFF) { c[0] = (cp>>18)+240; c[1] = ((cp>>12)&63)+128; c[2] = ((cp>>6)&63)+128; c[3] = (cp&63)+128; return 4; } else { return 0; } } static int32_t read_codepoint_utf8(std::string_view &source) { size_t size = source.size(); if (size == 0) return -1; const unsigned char *bytes = (const unsigned char *)source.data(); int codepoint; size_t seqlen; if ((bytes[0] & 0x80) == 0x00) { // U+0000 to U+007F codepoint = (bytes[0] & 0x7F); seqlen = 1; } else if ((bytes[0] & 0xE0) == 0xC0) { // U+0080 to U+07FF codepoint = (bytes[0] & 0x1F); seqlen = 2; } else if ((bytes[0] & 0xF0) == 0xE0) { // U+0800 to U+FFFF codepoint = (bytes[0] & 0x0F); seqlen = 3; } else if ((bytes[0] & 0xF8) == 0xF0) { // U+10000 to U+10FFFF codepoint = (bytes[0] & 0x07); seqlen = 4; } else { // Bad character. Drop a byte and return invalid CP. source.remove_prefix(1); return -2; } if (seqlen > size) { return -1; } for (size_t i = 1; i < seqlen; ++i) { if ((bytes[i] & 0xC0) != 0x80) { // Bad character. Drop a byte and return invalid CP. source.remove_prefix(1); return -2; } codepoint = (codepoint << 6) | (bytes[i] & 0x3F); } if ((codepoint > 0x10FFFF) || ((codepoint <= 0x007F) && (seqlen != 1)) || ((codepoint >= 0x0080) && (codepoint <= 0x07FF) && (seqlen != 2)) || ((codepoint >= 0x0800) && (codepoint <= 0xFFFF) && (seqlen != 3)) || ((codepoint >= 0x10000) && (codepoint <= 0x1FFFFF) && (seqlen != 4))) { // Bad character. Drop a byte and return invalid CP. source.remove_prefix(1); return -2; } source.remove_prefix(seqlen); return codepoint; } ReadlineDevice::ReadlineDevice() { desired_prompt_ = CodepointString(1, '>'); } void ReadlineDevice::set_print_callback(print_callback cb) { print_cb_ = cb; } void ReadlineDevice::set_prompt(const CodepointString &prompt) { desired_prompt_ = prompt; echo_command(); } void ReadlineDevice::erase_command() { int ccsize = current_prompt_.size() + current_command_.size(); if (ccsize > 0) { print_cb_(n_backspaces(ccsize)); current_prompt_.clear(); current_command_.clear(); } } void ReadlineDevice::echo_command() { // If the prompt has changed, erase everything and start over. if (desired_prompt_ != current_prompt_) { int ccsize = current_prompt_.size() + current_command_.size(); print_cb_(n_backspaces(ccsize)); print_cb_(desired_prompt_); current_command_.clear(); current_prompt_ = desired_prompt_; } // Find out how much of the command matches. int match = common_prefix_length(current_command_, desired_command_); // Echo backspaces to remove the non-matching part. int remove = current_command_.size() - match; if (remove > 0) { print_cb_(n_backspaces(remove)); current_command_ = current_command_.substr(0, match); } // Echo the new part. CodepointString newpart = desired_command_.substr(current_command_.size()); if (!newpart.empty()) { print_cb_(newpart); current_command_ = desired_command_; } } CodepointString ReadlineDevice::putcode(char32_t c) { if ((c == '\n') && (readline_lastc_ == '\r')) { // Ignore newline immediately after carriage return. // Otherwise, crlf produces two newlines. return CodepointString(); } else if ((c == '\r') || (c == '\n')) { CodepointString white(1, ' '); CodepointString newline(1, '\n'); echo_command(); print_cb_(white + newline); CodepointString result = desired_command_ + newline; desired_command_.clear(); current_prompt_.clear(); current_command_.clear(); echo_command(); return result; } else if ((c == '\b') || (c == 127)) { int len = desired_command_.size(); if (len > 0) { desired_command_ = desired_command_.substr(0, len-1); } echo_command(); return CodepointString(); } else if ((c >= 32)&&(c <= 0x10FFFF)) { int len = desired_command_.size(); if (len < MAXLINE) { desired_command_ = desired_command_ + c; } echo_command(); return CodepointString(); } readline_lastc_ = c; return CodepointString(); } void ReadlineDevice::print(const CodepointString &s) { if (!s.empty()) { erase_command(); print_cb_(s); echo_command(); } } std::string ReadlineDevice::to_utf8(const CodepointString &s) { std::string result(s.size() * 4, 0); char *buffer = &result[0]; int len = 0; for (char32_t c : s) { int clen = buffer_codepoint_utf8(c, buffer + len); len += clen; } return result.substr(0, len); } CodepointString ReadlineDevice::from_utf8(std::string_view s, int *consumed) { std::string_view rest = s; CodepointString result(s.size(), 0); int len = 0; while (true) { int32_t c = read_codepoint_utf8(rest); if (c == -1) break; // EOF reached; if (c == -2) continue; // Filter out bad UTF8 but continue. result[len++] = (char32_t)c; } if (consumed != nullptr) { *consumed = s.size() - rest.size(); } return result.substr(0, len); }