From 8bba3d7fd70e958ff5d049834d4ad662e71a3d7a Mon Sep 17 00:00:00 2001 From: Tyler Wilding Date: Sun, 7 Mar 2021 20:41:21 -0800 Subject: [PATCH] REPL: Add clear-screen / auto-complete / basic hints and syntax highlighting (#316) * swap to replxx from linenoise * repl: Implement form auto-tab-completion * repl: color coordinate the prompts * repl: Add some basic syntax highlighting, bracket pairs and forms (all one color) * repl: A more consistent starting screen for the repl * repl: bug fix for auto-complete * debug linux * linting --- CMakeLists.txt | 5 + README.md | 2 +- common/CMakeLists.txt | 4 +- common/goos/Interpreter.cpp | 4 +- common/goos/Interpreter.h | 2 +- common/goos/Reader.cpp | 14 +- common/goos/Reader.h | 4 +- common/goos/ReplHistory.cpp | 38 - common/goos/ReplHistory.h | 16 - common/goos/ReplUtils.cpp | 138 + common/goos/ReplUtils.h | 33 + goalc/compiler/Compiler.cpp | 33 +- goalc/compiler/Compiler.h | 15 +- goalc/compiler/compilation/Atoms.cpp | 1 + .../compiler/compilation/CompilerControl.cpp | 166 +- goalc/main.cpp | 24 +- third-party/linenoise.h | 2415 ----------------- third-party/replxx/.appveyor.yml | 11 + third-party/replxx/.editorconfig | 8 + third-party/replxx/.gitignore | 14 + third-party/replxx/.travis.yml | 14 + third-party/replxx/CMakeLists.txt | 220 ++ third-party/replxx/LICENSE.md | 63 + third-party/replxx/README.md | 119 + third-party/replxx/build-all.sh | 93 + third-party/replxx/examples/c-api.c | 235 ++ third-party/replxx/examples/cxx-api.cxx | 453 ++++ third-party/replxx/examples/util.c | 34 + third-party/replxx/examples/util.h | 13 + third-party/replxx/gen-coverage.sh | 22 + third-party/replxx/include/replxx.h | 567 ++++ third-party/replxx/include/replxx.hxx | 607 +++++ third-party/replxx/make.ps1 | 80 + third-party/replxx/replxx-config.cmake.in | 3 + third-party/replxx/src/ConvertUTF.cpp | 271 ++ third-party/replxx/src/ConvertUTF.h | 139 + third-party/replxx/src/conversion.cxx | 108 + third-party/replxx/src/conversion.hxx | 30 + third-party/replxx/src/escape.cxx | 890 ++++++ third-party/replxx/src/escape.hxx | 37 + third-party/replxx/src/history.cxx | 402 +++ third-party/replxx/src/history.hxx | 141 + third-party/replxx/src/killring.hxx | 78 + third-party/replxx/src/prompt.cxx | 154 ++ third-party/replxx/src/prompt.hxx | 47 + third-party/replxx/src/replxx.cxx | 648 +++++ third-party/replxx/src/replxx_impl.cxx | 2195 +++++++++++++++ third-party/replxx/src/replxx_impl.hxx | 270 ++ third-party/replxx/src/terminal.cxx | 742 +++++ third-party/replxx/src/terminal.hxx | 94 + third-party/replxx/src/unicodestring.hxx | 191 ++ third-party/replxx/src/utf8string.hxx | 94 + third-party/replxx/src/util.cxx | 170 ++ third-party/replxx/src/util.hxx | 26 + third-party/replxx/src/wcwidth.cpp | 296 ++ third-party/replxx/src/windows.cxx | 144 + third-party/replxx/src/windows.hxx | 44 + third-party/replxx/tests.py | 2029 ++++++++++++++ 58 files changed, 12141 insertions(+), 2569 deletions(-) delete mode 100644 common/goos/ReplHistory.cpp delete mode 100644 common/goos/ReplHistory.h create mode 100644 common/goos/ReplUtils.cpp create mode 100644 common/goos/ReplUtils.h delete mode 100644 third-party/linenoise.h create mode 100644 third-party/replxx/.appveyor.yml create mode 100644 third-party/replxx/.editorconfig create mode 100644 third-party/replxx/.gitignore create mode 100644 third-party/replxx/.travis.yml create mode 100644 third-party/replxx/CMakeLists.txt create mode 100644 third-party/replxx/LICENSE.md create mode 100644 third-party/replxx/README.md create mode 100644 third-party/replxx/build-all.sh create mode 100644 third-party/replxx/examples/c-api.c create mode 100644 third-party/replxx/examples/cxx-api.cxx create mode 100644 third-party/replxx/examples/util.c create mode 100644 third-party/replxx/examples/util.h create mode 100644 third-party/replxx/gen-coverage.sh create mode 100644 third-party/replxx/include/replxx.h create mode 100644 third-party/replxx/include/replxx.hxx create mode 100644 third-party/replxx/make.ps1 create mode 100644 third-party/replxx/replxx-config.cmake.in create mode 100644 third-party/replxx/src/ConvertUTF.cpp create mode 100644 third-party/replxx/src/ConvertUTF.h create mode 100644 third-party/replxx/src/conversion.cxx create mode 100644 third-party/replxx/src/conversion.hxx create mode 100644 third-party/replxx/src/escape.cxx create mode 100644 third-party/replxx/src/escape.hxx create mode 100644 third-party/replxx/src/history.cxx create mode 100644 third-party/replxx/src/history.hxx create mode 100644 third-party/replxx/src/killring.hxx create mode 100644 third-party/replxx/src/prompt.cxx create mode 100644 third-party/replxx/src/prompt.hxx create mode 100644 third-party/replxx/src/replxx.cxx create mode 100644 third-party/replxx/src/replxx_impl.cxx create mode 100644 third-party/replxx/src/replxx_impl.hxx create mode 100644 third-party/replxx/src/terminal.cxx create mode 100644 third-party/replxx/src/terminal.hxx create mode 100644 third-party/replxx/src/unicodestring.hxx create mode 100644 third-party/replxx/src/utf8string.hxx create mode 100644 third-party/replxx/src/util.cxx create mode 100644 third-party/replxx/src/util.hxx create mode 100644 third-party/replxx/src/wcwidth.cpp create mode 100644 third-party/replxx/src/windows.cxx create mode 100644 third-party/replxx/src/windows.hxx create mode 100644 third-party/replxx/tests.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 5342969be0..49b5ae4334 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,8 @@ # Top Level CMakeLists.txt cmake_minimum_required(VERSION 3.10) +set(CMAKE_POSITION_INDEPENDENT_CODE ON) + project(jak) include(CTest) if(NOT CMAKE_BUILD_TYPE) @@ -61,6 +63,9 @@ include_directories(./) include_directories(SYSTEM third-party/inja) +# build repl library +add_subdirectory(third-party/replxx EXCLUDE_FROM_ALL) + # build common library add_subdirectory(common) diff --git a/README.md b/README.md index 3b2a5174ad..dc4a291ea6 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ The final component is the "runtime", located in `game`. This is the part of the - `run-clang-tidy` - `zydis`: x86-64 disassembler used in the OpenGOAL debugger - `json`: A JSON library - - `linenoise`: Used for the REPL input. Support history and useful editing shortcuts. + - `replxx`: Used for the REPL input. Support history and useful editing shortcuts. - `svpng`: Save a PNG file diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index 0859ab4bc2..3090fbe587 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -8,7 +8,7 @@ add_library(common goos/PrettyPrinter.cpp goos/Reader.cpp goos/TextDB.cpp - goos/ReplHistory.cpp + goos/ReplUtils.cpp log/log.cpp type_system/deftype.cpp type_system/Type.cpp @@ -22,7 +22,7 @@ add_library(common util/Timer.cpp ) -target_link_libraries(common fmt lzokay) +target_link_libraries(common fmt lzokay replxx) if(WIN32) target_link_libraries(common wsock32 ws2_32) diff --git a/common/goos/Interpreter.cpp b/common/goos/Interpreter.cpp index e2c941085e..0602beeea9 100644 --- a/common/goos/Interpreter.cpp +++ b/common/goos/Interpreter.cpp @@ -125,11 +125,11 @@ Object Interpreter::intern(const std::string& name) { /*! * Display the REPL, which will run until the user executes exit. */ -void Interpreter::execute_repl() { +void Interpreter::execute_repl(ReplWrapper& repl) { while (!want_exit) { try { // read something from the user - Object obj = reader.read_from_stdin("goos"); + Object obj = reader.read_from_stdin("goos", repl); // evaluate Object evald = eval_with_rewind(obj, global_environment.as_env()); // print diff --git a/common/goos/Interpreter.h b/common/goos/Interpreter.h index 9aa98d17a2..f869623a7d 100644 --- a/common/goos/Interpreter.h +++ b/common/goos/Interpreter.h @@ -15,7 +15,7 @@ class Interpreter { public: Interpreter(); ~Interpreter(); - void execute_repl(); + void execute_repl(ReplWrapper& repl); void throw_eval_error(const Object& o, const std::string& err); Object eval_with_rewind(const Object& obj, const std::shared_ptr& env); bool get_global_variable_by_name(const std::string& name, Object* dest); diff --git a/common/goos/Reader.cpp b/common/goos/Reader.cpp index 2a6312460c..a0c1792fb3 100644 --- a/common/goos/Reader.cpp +++ b/common/goos/Reader.cpp @@ -13,7 +13,7 @@ #include "common/util/FileUtil.h" #include "third-party/fmt/core.h" #include -#include "ReplHistory.h" +#include "ReplUtils.h" namespace goos { @@ -103,9 +103,6 @@ void TextStream::seek_past_whitespace_and_comments() { } Reader::Reader() { - // third-party library used for a fancy line in - ReplHistory::repl_set_history_max_size(5000); - // add default macros add_reader_macro("'", "quote"); add_reader_macro("`", "quasiquote"); @@ -139,12 +136,11 @@ Reader::Reader() { /*! * Prompt the user and read the result. */ -Object Reader::read_from_stdin(const std::string& prompt_name) { - std::string line; +Object Reader::read_from_stdin(const std::string& prompt, ReplWrapper& repl) { // escape code will make sure that we remove any color - std::string prompt_full = "\033[0m" + prompt_name + "> "; - ReplHistory::repl_readline(prompt_full.c_str(), line); - ReplHistory::repl_add_to_history(line.c_str()); + std::string prompt_full = "\033[0m" + prompt; + std::string line = repl.readline(prompt_full); + repl.add_to_history(line.c_str()); // todo, decide if we should keep reading or not. // create text fragment and add to the DB diff --git a/common/goos/Reader.h b/common/goos/Reader.h index 0382ceb423..337c78bc28 100644 --- a/common/goos/Reader.h +++ b/common/goos/Reader.h @@ -19,6 +19,8 @@ #include "common/goos/Object.h" #include "common/goos/TextDB.h" +#include "ReplUtils.h" + namespace goos { /*! @@ -68,7 +70,7 @@ class Reader { public: Reader(); Object read_from_string(const std::string& str, bool add_top_level = true); - Object read_from_stdin(const std::string& prompt_name); + Object read_from_stdin(const std::string& prompt, ReplWrapper& repl); Object read_from_file(const std::vector& file_path); std::string get_source_dir(); diff --git a/common/goos/ReplHistory.cpp b/common/goos/ReplHistory.cpp deleted file mode 100644 index 3dd325b3a3..0000000000 --- a/common/goos/ReplHistory.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include "ReplHistory.h" - -#include "common/util/FileUtil.h" -#include "third-party/linenoise.h" - -bool ReplHistory::repl_set_history_max_size(size_t len) { - return linenoise::SetHistoryMaxLen(len); -} - -bool ReplHistory::repl_readline(const char* prompt, std::string& line) { - return linenoise::Readline(prompt, line); -} - -bool ReplHistory::repl_add_to_history(const char* line) { - return linenoise::AddHistory(line); -} - -bool ReplHistory::repl_save_history() { - // NOTE - library doesn't seem unicode safe on windows - std::filesystem::path path = file_util::get_user_home_dir(); - if (std::filesystem::exists(path)) { - return linenoise::SaveHistory((path / ".opengoal.repl.history").string().c_str()); - } - return false; -} - -bool ReplHistory::repl_load_history() { - // NOTE - library doesn't seem unicode safe on windows - std::filesystem::path path = file_util::get_user_home_dir(); - if (std::filesystem::exists(path)) { - return linenoise::LoadHistory((path / ".opengoal.repl.history").string().c_str()); - } - return false; -} - -const std::vector& ReplHistory::repl_get_history() { - return linenoise::GetHistory(); -} diff --git a/common/goos/ReplHistory.h b/common/goos/ReplHistory.h deleted file mode 100644 index f38c503ed4..0000000000 --- a/common/goos/ReplHistory.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include -#include - -// Linenoise declares history as a static array, so when trying to use the -// library across files, the history array is different -// Solve this by wrapping the API -namespace ReplHistory { -bool repl_set_history_max_size(size_t len); -bool repl_readline(const char* prompt, std::string& line); -bool repl_add_to_history(const char* line); -bool repl_save_history(); -bool repl_load_history(); -const std::vector& repl_get_history(); -}; // namespace ReplHistory diff --git a/common/goos/ReplUtils.cpp b/common/goos/ReplUtils.cpp new file mode 100644 index 0000000000..e6e49f62e0 --- /dev/null +++ b/common/goos/ReplUtils.cpp @@ -0,0 +1,138 @@ +#include "ReplUtils.h" + +#include "common/util/FileUtil.h" +#include "third-party/replxx/include/replxx.hxx" +#include "common/versions.h" +#include "third-party/fmt/color.h" +#include "third-party/fmt/core.h" + +// TODO - expand a list of hints (ie. a hint for defun to show at a glance how to write a function, +// or perhaps, show the docstring for the current function being used?) + +using Replxx = replxx::Replxx; + +void ReplWrapper::clear_screen() { + repl.clear_screen(); +} + +void ReplWrapper::print_welcome_message() { + clear_screen(); + // Welcome message / brief intro for documentation + std::string ascii; + ascii += " _____ _____ _____ _____ __ \n"; + ascii += "| |___ ___ ___| __| | _ | | \n"; + ascii += "| | | . | -_| | | | | | | |__ \n"; + ascii += "|_____| _|___|_|_|_____|_____|__|__|_____|\n"; + ascii += " |_| \n"; + fmt::print(fmt::emphasis::bold | fg(fmt::color::orange), ascii); + + fmt::print("Welcome to OpenGOAL {}.{}!\n", versions::GOAL_VERSION_MAJOR, + versions::GOAL_VERSION_MINOR); + fmt::print("Run "); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(repl-help)"); + fmt::print(" for help with common commands and REPL usage.\n"); + fmt::print("Run "); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)"); + fmt::print(" to connect to the local target.\n\n"); +} + +void ReplWrapper::set_history_max_size(size_t len) { + repl.set_max_history_size(len); +} + +const char* ReplWrapper::readline(const std::string& prompt) { + return repl.input(prompt); +} + +void ReplWrapper::add_to_history(const std::string& line) { + repl.history_add(line); +} + +void ReplWrapper::save_history() { + // NOTE - library doesn't seem unicode safe on windows + std::filesystem::path path = file_util::get_user_home_dir(); + if (std::filesystem::exists(path)) { + repl.history_save((path / ".opengoal.repl.history").string()); + } +} + +void ReplWrapper::load_history() { + // NOTE - library doesn't seem unicode safe on windows + std::filesystem::path path = file_util::get_user_home_dir(); + if (std::filesystem::exists(path)) { + repl.history_load((path / ".opengoal.repl.history").string()); + } +} + +std::pair ReplWrapper::get_current_repl_token(std::string const& context) { + // Find the current token + std::string token = ""; + for (auto c = context.crbegin(); c != context.crend(); c++) { + if (std::isspace(*c)) { + break; + } else { + token = *c + token; + } + } + + // If there is a preceeding '(' remove it + if (!token.empty() && token.at(0) == '(') { + token.erase(0, 1); + return {token, true}; + } + return {token, false}; +} + +void ReplWrapper::print_help_message() { + fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(:clear)\n"); + fmt::print(" - Clear the current screen\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(e)\n"); + fmt::print(" - Exit the compiler once the current REPL command is finished\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt [ip-address] [port-number])\n"); + fmt::print( + " - Connect the listener to a running target. The IP address defaults to `127.0.0.1` and the " + "port to `8112`\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(r [ip-address] [port-number])\n"); + fmt::print( + " - Attempt to reset the target and reconnect. After this, the target will have nothing " + "loaded.\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(:status)\n"); + fmt::print(" - Send a ping-like message to the target. Requires the target to be connected\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(shutdown-target)\n"); + fmt::print(" - If the target is connected, make it exit\n"); + + fmt::print(fmt::emphasis::bold, "\nCompiling & Building:\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), "(m \"filename\")\n"); + fmt::print(" - Compile an OpenGOAL source file\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), "(ml \"filename\")\n"); + fmt::print(" - Compile and Load an OpenGOAL source file\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), "(build-game)\n"); + fmt::print(" - Loads and builds all game files and rebuilds DGOs\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), "(build-kernel)\n"); + fmt::print(" - Similar to (build-game) but only the kernel files\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), "(blg)\n"); + fmt::print(" - Performs a (build-game) and then loads all CGOs\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), + "(build-dgos \"path/to/dgos/description/file\")\n"); + fmt::print( + " - Builds all the DGO files described in the DGO description file. See " + "`goal_src/builds/dgos.txt` for an example.\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), + "(asm-data-file tool-name \"file-name\")\n"); + fmt::print( + " - Build a data file. The `tool-name` refers to which data building tool should be used.\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::lime_green), "(build-data)\n"); + fmt::print(" - Macro for rebuilding all data files\n"); + + fmt::print(fmt::emphasis::bold, "\nOther:\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), "(gs)\n"); + fmt::print(" - Enter a GOOS REPL\n"); + fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), + "(set-config! config-name config-value)\n"); + fmt::print(" - Used to set compiler configuration\n"); +} + +void ReplWrapper::init_default_settings() { + repl.set_word_break_characters(" \t"); +} diff --git a/common/goos/ReplUtils.h b/common/goos/ReplUtils.h new file mode 100644 index 0000000000..283431f6a9 --- /dev/null +++ b/common/goos/ReplUtils.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +#include "third-party/replxx/include/replxx.hxx" + +using Replxx = replxx::Replxx; + +class ReplWrapper { + Replxx repl; + + public: + ReplWrapper() {} + Replxx& get_repl() { return repl; } + void init_default_settings(); + + // Functionality / Commands + void clear_screen(); + void print_welcome_message(); + void set_history_max_size(size_t len); + const char* readline(const std::string& prompt); + void add_to_history(const std::string& line); + void save_history(); + void load_history(); + void print_help_message(); + std::pair get_current_repl_token(std::string const& context); + + std::vector examples{}; + using cl = Replxx::Color; + std::vector> regex_colors{}; +}; diff --git a/goalc/compiler/Compiler.cpp b/goalc/compiler/Compiler.cpp index ab496ecb4e..797624f3a3 100644 --- a/goalc/compiler/Compiler.cpp +++ b/goalc/compiler/Compiler.cpp @@ -9,7 +9,8 @@ using namespace goos; -Compiler::Compiler() : m_debugger(&m_listener) { +Compiler::Compiler(std::unique_ptr repl) + : m_debugger(&m_listener), m_repl(std::move(repl)) { m_listener.add_debugger(&m_debugger); m_ts.add_builtin_types(); m_global_env = std::make_unique(); @@ -26,25 +27,33 @@ Compiler::Compiler() : m_debugger(&m_listener) { } ReplStatus Compiler::execute_repl() { + // init repl + m_repl.get()->print_welcome_message(); + auto examples = m_repl.get()->examples; + auto regex_colors = m_repl.get()->regex_colors; + m_repl.get()->init_default_settings(); + using namespace std::placeholders; + m_repl.get()->get_repl().set_completion_callback( + std::bind(&Compiler::find_symbols_by_prefix, this, _1, _2, std::cref(examples))); + m_repl.get()->get_repl().set_hint_callback( + std::bind(&Compiler::find_hints_by_prefix, this, _1, _2, _3, std::cref(examples))); + m_repl.get()->get_repl().set_highlighter_callback( + std::bind(&Compiler::repl_coloring, this, _1, _2, std::cref(regex_colors))); + while (!m_want_exit && !m_want_reload) { try { // 1). get a line from the user (READ) - std::string prompt = "g"; + std::string prompt = fmt::format(fmt::emphasis::bold | fg(fmt::color::cyan), "g > "); if (m_listener.is_connected()) { - prompt += "c"; - } else { - prompt += " "; + prompt = fmt::format(fmt::emphasis::bold | fg(fmt::color::lime_green), "gc> "); } - if (m_debugger.is_halted()) { - prompt += "s"; + prompt = fmt::format(fmt::emphasis::bold | fg(fmt::color::magenta), "gs> "); } else if (m_debugger.is_attached()) { - prompt += "r"; - } else { - prompt += " "; + prompt = fmt::format(fmt::emphasis::bold | fg(fmt::color::red), "gr> "); } - Object code = m_goos.reader.read_from_stdin(prompt); + Object code = m_goos.reader.read_from_stdin(prompt, *m_repl.get()); // 2). compile auto obj_file = compile_object_file("repl", code, m_listener.is_connected()); @@ -365,4 +374,4 @@ void Compiler::typecheck_reg_type_allow_false(const goos::Object& form, } } typecheck(form, expected, coerce_to_reg_type(actual->type()), error_message); -} \ No newline at end of file +} diff --git a/goalc/compiler/Compiler.h b/goalc/compiler/Compiler.h index 0d2a4cd14a..91b1d92998 100644 --- a/goalc/compiler/Compiler.h +++ b/goalc/compiler/Compiler.h @@ -18,6 +18,7 @@ #include "CompilerException.h" #include "goalc/compiler/SymbolInfo.h" #include "Enum.h" +#include "common/goos/ReplUtils.h" enum MathMode { MATH_INT, MATH_BINT, MATH_FLOAT, MATH_INVALID }; @@ -25,7 +26,7 @@ enum class ReplStatus { OK, WANT_EXIT, WANT_RELOAD }; class Compiler { public: - Compiler(); + Compiler(std::unique_ptr repl = nullptr); ReplStatus execute_repl(); goos::Interpreter& get_goos() { return m_goos; } FileEnv* compile_object_file(const std::string& name, goos::Object code, bool allow_emit); @@ -48,6 +49,16 @@ class Compiler { listener::Listener& listener() { return m_listener; } void poke_target() { m_listener.send_poke(); } bool connect_to_target(); + Replxx::completions_t find_symbols_by_prefix(std::string const& context, + int& contextLen, + std::vector const& user_data); + Replxx::hints_t find_hints_by_prefix(std::string const& context, + int& contextLen, + Replxx::Color& color, + std::vector const& user_data); + void repl_coloring(std::string const& str, + Replxx::colors_t& colors, + std::vector> const& user_data); private: bool get_true_or_false(const goos::Object& form, const goos::Object& boolean); @@ -203,6 +214,7 @@ class Compiler { CompilerSettings m_settings; bool m_throw_on_define_extern_redefinition = false; SymbolInfoMap m_symbol_info; + std::unique_ptr m_repl; MathMode get_math_mode(const TypeSpec& ts); bool is_number(const TypeSpec& ts); @@ -424,6 +436,7 @@ class Compiler { Val* compile_seval(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_exit(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_asm_file(const goos::Object& form, const goos::Object& rest, Env* env); + Val* compile_repl_clear_screen(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_asm_data_file(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_repl_help(const goos::Object& form, const goos::Object& rest, Env* env); Val* compile_listen_to_target(const goos::Object& form, const goos::Object& rest, Env* env); diff --git a/goalc/compiler/compilation/Atoms.cpp b/goalc/compiler/compilation/Atoms.cpp index 33bfa9c95a..ab71526790 100644 --- a/goalc/compiler/compilation/Atoms.cpp +++ b/goalc/compiler/compilation/Atoms.cpp @@ -107,6 +107,7 @@ const std::unordered_map< // COMPILER CONTROL {"repl-help", &Compiler::compile_repl_help}, + {":clear", &Compiler::compile_repl_clear_screen}, {"gs", &Compiler::compile_gs}, {":exit", &Compiler::compile_exit}, {"asm-file", &Compiler::compile_asm_file}, diff --git a/goalc/compiler/compilation/CompilerControl.cpp b/goalc/compiler/compilation/CompilerControl.cpp index dd0ff255b7..4fc5715ea5 100644 --- a/goalc/compiler/compilation/CompilerControl.cpp +++ b/goalc/compiler/compilation/CompilerControl.cpp @@ -11,7 +11,9 @@ #include "common/util/FileUtil.h" #include "goalc/data_compiler/game_text.h" #include "goalc/data_compiler/game_count.h" -#include "common/goos/ReplHistory.h" +#include "common/goos/ReplUtils.h" +#include +#include /*! * Exit the compiler. Disconnects the listener and tells the target to reset itself. @@ -31,7 +33,7 @@ Val* Compiler::compile_exit(const goos::Object& form, const goos::Object& rest, } // flag for the REPL. m_want_exit = true; - ReplHistory::repl_save_history(); + m_repl->save_history(); return get_none(); } @@ -198,52 +200,7 @@ Val* Compiler::compile_asm_file(const goos::Object& form, const goos::Object& re * Simple help / documentation command */ Val* Compiler::compile_repl_help(const goos::Object&, const goos::Object&, Env*) { - fmt::print(fmt::emphasis::bold, "\nREPL Controls:\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(e)\n"); - fmt::print(" - Exit the compiler once the current REPL command is finished\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt [ip-address] [port-number])\n"); - fmt::print( - " - Connect the listener to a running target. The IP address defaults to `127.0.0.1` and the " - "port to `8112`\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(r [ip-address] [port-number])\n"); - fmt::print( - " - Attempt to reset the target and reconnect. After this, the target will have nothing " - "loaded.\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(:status)\n"); - fmt::print(" - Send a ping-like message to the target. Requires the target to be connected\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(shutdown-target)\n"); - fmt::print(" - If the target is connected, make it exit\n"); - - fmt::print(fmt::emphasis::bold, "\nCompiling & Building:\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(m \"filename\")\n"); - fmt::print(" - Compile an OpenGOAL source file\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(ml \"filename\")\n"); - fmt::print(" - Compile and Load an OpenGOAL source file\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-game)\n"); - fmt::print(" - Loads and builds all game files and rebuilds DGOs\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-kernel)\n"); - fmt::print(" - Similar to (build-game) but only the kernel files\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(blg)\n"); - fmt::print(" - Performs a (build-game) and then loads all CGOs\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), - "(build-dgos \"path/to/dgos/description/file\")\n"); - fmt::print( - " - Builds all the DGO files described in the DGO description file. See " - "`goal_src/builds/dgos.txt` for an example.\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), - "(asm-data-file tool-name \"file-name\")\n"); - fmt::print( - " - Build a data file. The `tool-name` refers to which data building tool should be used.\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::yellow), "(build-data)\n"); - fmt::print(" - Macro for rebuilding all data files\n"); - - fmt::print(fmt::emphasis::bold, "\nOther:\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), "(gs)\n"); - fmt::print(" - Enter a GOOS REPL\n"); - fmt::print(fmt::emphasis::bold | fg(fmt::color::magenta), - "(set-config! config-name config-value)\n"); - fmt::print(" - Used to set compiler configuration\n"); - + m_repl.get()->print_help_message(); return get_none(); } @@ -282,6 +239,13 @@ Val* Compiler::compile_listen_to_target(const goos::Object& form, return get_none(); } +Val* Compiler::compile_repl_clear_screen(const goos::Object& form, + const goos::Object& rest, + Env* env) { + m_repl.get()->clear_screen(); + return get_none(); +} + /*! * Send the target a command to reset, which totally resets the state of the target. * Optionally takes a :shutdown command which causes the exec_runtime function of the target @@ -321,7 +285,7 @@ Val* Compiler::compile_gs(const goos::Object& form, const goos::Object& rest, En (void)env; auto args = get_va(form, rest); va_check(form, args, {}, {}); - m_goos.execute_repl(); + m_goos.execute_repl(*m_repl.get()); return get_none(); } @@ -443,6 +407,108 @@ Val* Compiler::compile_get_info(const goos::Object& form, const goos::Object& re return get_none(); } +Replxx::completions_t Compiler::find_symbols_by_prefix(std::string const& context, + int& contextLen, + std::vector const& user_data) { + auto token = m_repl.get()->get_current_repl_token(context); + auto possible_forms = m_symbol_info.lookup_symbols_starting_with(token.first); + Replxx::completions_t completions; + for (auto& x : possible_forms) { + completions.push_back(token.second ? "(" + x : x); + } + return completions; +} + +Replxx::hints_t Compiler::find_hints_by_prefix(std::string const& context, + int& contextLen, + Replxx::Color& color, + std::vector const& user_data) { + auto token = m_repl.get()->get_current_repl_token(context); + auto possible_forms = m_symbol_info.lookup_symbols_starting_with(token.first); + + Replxx::hints_t hints; + + // Only show hints if there are <= 3 possibilities + if (possible_forms.size() <= 3) { + for (auto& x : possible_forms) { + hints.push_back(token.second ? "(" + x : x); + } + } + + // set hint color to green if single match found + if (hints.size() == 1) { + color = Replxx::Color::GREEN; + } + + return hints; +} + +void Compiler::repl_coloring( + std::string const& context, + Replxx::colors_t& colors, + std::vector> const& regex_color) { + using cl = Replxx::Color; + // TODO - a proper circular queue would be cleaner to use + std::deque paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA}; + std::stack> expression_stack; + + std::pair curr_symbol = {-1, ""}; + for (std::string::size_type i = 0; i < context.size(); i++) { + char curr = context.at(i); + // We lookup every potential symbol and color it based on it's type + if (std::isspace(curr) || curr == ')') { + // Lookup the symbol, if its legit, color it + if (!curr_symbol.second.empty() && curr_symbol.second.at(0) == '(') { + curr_symbol.second.erase(0, 1); + curr_symbol.first++; + } + std::vector* sym_match = m_symbol_info.lookup_exact_name(curr_symbol.second); + if (sym_match != nullptr && sym_match->size() == 1) { + SymbolInfo sym_info = sym_match->at(0); + for (int pos = curr_symbol.first; pos <= i; pos++) { + // TODO - currently just coloring all types brown/gold + // - would be nice to have a different color for globals, functions, etc + colors.at(pos) = cl::BROWN; + } + } + curr_symbol = {-1, ""}; + } else { + if (curr_symbol.first == -1) { + curr_symbol.first = i; + } + curr_symbol.second += curr; + } + // Rainbow paren coloring and known-form coloring + if (curr == '(') { + cl color = paren_colors.front(); + expression_stack.push({curr, color}); + colors.at(i) = color; + paren_colors.pop_front(); + paren_colors.push_back(color); + } else if (curr == ')') { + if (expression_stack.empty()) { + colors.at(i) = cl::RED; + } else { + auto& matching_paren = expression_stack.top(); + expression_stack.pop(); + if (matching_paren.first == '(') { + if (i == context.size() - 1 && !expression_stack.empty()) { + colors.at(i) = cl::RED; + } else { + colors.at(i) = matching_paren.second; + } + } + } + } + // Reset the color order + if (expression_stack.empty()) { + paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA}; + } + } + + // TODO - general syntax highlighting with regexes (quotes, symbols, etc) +} + Val* Compiler::compile_autocomplete(const goos::Object& form, const goos::Object& rest, Env* env) { (void)env; auto args = get_va(form, rest); @@ -460,4 +526,4 @@ Val* Compiler::compile_autocomplete(const goos::Object& form, const goos::Object m_symbol_info.symbol_count(), time); return get_none(); -} \ No newline at end of file +} diff --git a/goalc/main.cpp b/goalc/main.cpp index d9b0c31dfb..5216f15e80 100644 --- a/goalc/main.cpp +++ b/goalc/main.cpp @@ -7,7 +7,7 @@ #include "third-party/fmt/core.h" #include "third-party/fmt/color.h" -#include "common/goos/ReplHistory.h" +#include "common/goos/ReplUtils.h" void setup_logging(bool verbose) { lg::set_file(file_util::get_file_path({"log/compiler.txt"})); @@ -43,31 +43,13 @@ int main(int argc, char** argv) { lg::info("OpenGOAL Compiler {}.{}", versions::GOAL_VERSION_MAJOR, versions::GOAL_VERSION_MINOR); + // Init REPL std::unique_ptr compiler = std::make_unique(); - // Welcome message / brief intro for documentation - std::string ascii; - ascii += " _____ _____ _____ _____ __ \n"; - ascii += "| |___ ___ ___| __| | _ | | \n"; - ascii += "| | | . | -_| | | | | | | |__ \n"; - ascii += "|_____| _|___|_|_|_____|_____|__|__|_____|\n"; - ascii += " |_| \n"; - fmt::print(fmt::emphasis::bold | fg(fmt::color::orange), ascii); - - fmt::print("Welcome to OpenGOAL {}.{}!\n", versions::GOAL_VERSION_MAJOR, - versions::GOAL_VERSION_MINOR); - fmt::print("Run "); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(repl-help)"); - fmt::print(" for help with common commands and REPL usage.\n"); - fmt::print("Run "); - fmt::print(fmt::emphasis::bold | fg(fmt::color::cyan), "(lt)"); - fmt::print(" to connect to the local target.\n"); - - ReplHistory::repl_load_history(); if (argument.empty()) { ReplStatus status = ReplStatus::WANT_RELOAD; while (status == ReplStatus::WANT_RELOAD) { - compiler = std::make_unique(); + compiler = std::make_unique(std::make_unique()); status = compiler->execute_repl(); if (status == ReplStatus::WANT_RELOAD) { fmt::print("Reloading compiler...\n"); diff --git a/third-party/linenoise.h b/third-party/linenoise.h deleted file mode 100644 index c6e6f7cb2f..0000000000 --- a/third-party/linenoise.h +++ /dev/null @@ -1,2415 +0,0 @@ -/* - * linenoise.hpp -- Multi-platfrom C++ header-only linenoise library. - * - * All credits and commendations have to go to the authors of the - * following excellent libraries. - * - * - linenoise.h and linenose.c (https://github.com/antirez/linenoise) - * - ANSI.c (https://github.com/adoxa/ansicon) - * - Win32_ANSI.h and Win32_ANSI.c (https://github.com/MSOpenTech/redis) - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2015 yhirose - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND - * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* linenoise.h -- guerrilla line editing library against the idea that a - * line editing lib needs to be 20,000 lines of C code. - * - * See linenoise.c for more information. - * - * ------------------------------------------------------------------------ - * - * Copyright (c) 2010, Salvatore Sanfilippo - * Copyright (c) 2010, Pieter Noordhuis - * - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * ANSI.c - ANSI escape sequence console driver. - * - * Copyright (C) 2005-2014 Jason Hood - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the author be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * 2. Altered source versions must be plainly marked as such, and must not be - * misrepresented as being the original software. - * 3. This notice may not be removed or altered from any source distribution. - * - * Jason Hood - * jadoxa@yahoo.com.au - */ - -/* - * Win32_ANSI.h and Win32_ANSI.c - * - * Derived from ANSI.c by Jason Hood, from his ansicon project (https://github.com/adoxa/ansicon), with modifications. - * - * Copyright (c), Microsoft Open Technologies, Inc. - * All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef LINENOISE_HPP -#define LINENOISE_HPP - -#ifndef _WIN32 -#include -#include -#include -#else -#ifndef NOMINMAX -#define NOMINMAX -#endif -#include -#include -#ifndef STDIN_FILENO -#define STDIN_FILENO (_fileno(stdin)) -#endif -#ifndef STDOUT_FILENO -#define STDOUT_FILENO 1 -#endif -#define isatty _isatty -#define write win32_write -#define read _read -#pragma warning(push) -#pragma warning(disable : 4996) -#endif -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace linenoise { - - typedef std::function&)> CompletionCallback; - -#ifdef _WIN32 - - namespace ansi { - -#define lenof(array) (sizeof(array)/sizeof(*(array))) - -typedef struct -{ - BYTE foreground; // ANSI base color (0 to 7; add 30) - BYTE background; // ANSI base color (0 to 7; add 40) - BYTE bold; // console FOREGROUND_INTENSITY bit - BYTE underline; // console BACKGROUND_INTENSITY bit - BYTE rvideo; // swap foreground/bold & background/underline - BYTE concealed; // set foreground/bold to background/underline - BYTE reverse; // swap console foreground & background attributes -} GRM, *PGRM; // Graphic Rendition Mode - - -inline bool is_digit(char c) { return '0' <= c && c <= '9'; } - -// ========== Global variables and constants - -HANDLE hConOut; // handle to CONOUT$ - -const char ESC = '\x1B'; // ESCape character -const char BEL = '\x07'; -const char SO = '\x0E'; // Shift Out -const char SI = '\x0F'; // Shift In - -const int MAX_ARG = 16; // max number of args in an escape sequence -int state; // automata state -WCHAR prefix; // escape sequence prefix ( '[', ']' or '(' ); -WCHAR prefix2; // secondary prefix ( '?' or '>' ); -WCHAR suffix; // escape sequence suffix -int es_argc; // escape sequence args count -int es_argv[MAX_ARG]; // escape sequence args -WCHAR Pt_arg[MAX_PATH * 2]; // text parameter for Operating System Command -int Pt_len; -BOOL shifted; - - -// DEC Special Graphics Character Set from -// http://vt100.net/docs/vt220-rm/table2-4.html -// Some of these may not look right, depending on the font and code page (in -// particular, the Control Pictures probably won't work at all). -const WCHAR G1[] = -{ - ' ', // _ - blank - L'\x2666', // ` - Black Diamond Suit - L'\x2592', // a - Medium Shade - L'\x2409', // b - HT - L'\x240c', // c - FF - L'\x240d', // d - CR - L'\x240a', // e - LF - L'\x00b0', // f - Degree Sign - L'\x00b1', // g - Plus-Minus Sign - L'\x2424', // h - NL - L'\x240b', // i - VT - L'\x2518', // j - Box Drawings Light Up And Left - L'\x2510', // k - Box Drawings Light Down And Left - L'\x250c', // l - Box Drawings Light Down And Right - L'\x2514', // m - Box Drawings Light Up And Right - L'\x253c', // n - Box Drawings Light Vertical And Horizontal - L'\x00af', // o - SCAN 1 - Macron - L'\x25ac', // p - SCAN 3 - Black Rectangle - L'\x2500', // q - SCAN 5 - Box Drawings Light Horizontal - L'_', // r - SCAN 7 - Low Line - L'_', // s - SCAN 9 - Low Line - L'\x251c', // t - Box Drawings Light Vertical And Right - L'\x2524', // u - Box Drawings Light Vertical And Left - L'\x2534', // v - Box Drawings Light Up And Horizontal - L'\x252c', // w - Box Drawings Light Down And Horizontal - L'\x2502', // x - Box Drawings Light Vertical - L'\x2264', // y - Less-Than Or Equal To - L'\x2265', // z - Greater-Than Or Equal To - L'\x03c0', // { - Greek Small Letter Pi - L'\x2260', // | - Not Equal To - L'\x00a3', // } - Pound Sign - L'\x00b7', // ~ - Middle Dot -}; - -#define FIRST_G1 '_' -#define LAST_G1 '~' - - -// color constants - -#define FOREGROUND_BLACK 0 -#define FOREGROUND_WHITE FOREGROUND_RED|FOREGROUND_GREEN|FOREGROUND_BLUE - -#define BACKGROUND_BLACK 0 -#define BACKGROUND_WHITE BACKGROUND_RED|BACKGROUND_GREEN|BACKGROUND_BLUE - -const BYTE foregroundcolor[8] = - { - FOREGROUND_BLACK, // black foreground - FOREGROUND_RED, // red foreground - FOREGROUND_GREEN, // green foreground - FOREGROUND_RED | FOREGROUND_GREEN, // yellow foreground - FOREGROUND_BLUE, // blue foreground - FOREGROUND_BLUE | FOREGROUND_RED, // magenta foreground - FOREGROUND_BLUE | FOREGROUND_GREEN, // cyan foreground - FOREGROUND_WHITE // white foreground - }; - -const BYTE backgroundcolor[8] = - { - BACKGROUND_BLACK, // black background - BACKGROUND_RED, // red background - BACKGROUND_GREEN, // green background - BACKGROUND_RED | BACKGROUND_GREEN, // yellow background - BACKGROUND_BLUE, // blue background - BACKGROUND_BLUE | BACKGROUND_RED, // magenta background - BACKGROUND_BLUE | BACKGROUND_GREEN, // cyan background - BACKGROUND_WHITE, // white background - }; - -const BYTE attr2ansi[8] = // map console attribute to ANSI number -{ - 0, // black - 4, // blue - 2, // green - 6, // cyan - 1, // red - 5, // magenta - 3, // yellow - 7 // white -}; - -GRM grm; - -// saved cursor position -COORD SavePos; - -// ========== Print Buffer functions - -#define BUFFER_SIZE 2048 - -int nCharInBuffer; -WCHAR ChBuffer[BUFFER_SIZE]; - -//----------------------------------------------------------------------------- -// FlushBuffer() -// Writes the buffer to the console and empties it. -//----------------------------------------------------------------------------- - -inline void FlushBuffer(void) -{ - DWORD nWritten; - if (nCharInBuffer <= 0) return; - WriteConsoleW(hConOut, ChBuffer, nCharInBuffer, &nWritten, NULL); - nCharInBuffer = 0; -} - -//----------------------------------------------------------------------------- -// PushBuffer( WCHAR c ) -// Adds a character in the buffer. -//----------------------------------------------------------------------------- - -inline void PushBuffer(WCHAR c) -{ - if (shifted && c >= FIRST_G1 && c <= LAST_G1) - c = G1[c - FIRST_G1]; - ChBuffer[nCharInBuffer] = c; - if (++nCharInBuffer == BUFFER_SIZE) - FlushBuffer(); -} - -//----------------------------------------------------------------------------- -// SendSequence( LPCWSTR seq ) -// Send the string to the input buffer. -//----------------------------------------------------------------------------- - -inline void SendSequence(LPCWSTR seq) -{ - DWORD out; - INPUT_RECORD in; - HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE); - - in.EventType = KEY_EVENT; - in.Event.KeyEvent.bKeyDown = TRUE; - in.Event.KeyEvent.wRepeatCount = 1; - in.Event.KeyEvent.wVirtualKeyCode = 0; - in.Event.KeyEvent.wVirtualScanCode = 0; - in.Event.KeyEvent.dwControlKeyState = 0; - for (; *seq; ++seq) - { - in.Event.KeyEvent.uChar.UnicodeChar = *seq; - WriteConsoleInput(hStdIn, &in, 1, &out); - } -} - -// ========== Print functions - -//----------------------------------------------------------------------------- -// InterpretEscSeq() -// Interprets the last escape sequence scanned by ParseAndPrintANSIString -// prefix escape sequence prefix -// es_argc escape sequence args count -// es_argv[] escape sequence args array -// suffix escape sequence suffix -// -// for instance, with \e[33;45;1m we have -// prefix = '[', -// es_argc = 3, es_argv[0] = 33, es_argv[1] = 45, es_argv[2] = 1 -// suffix = 'm' -//----------------------------------------------------------------------------- - -inline void InterpretEscSeq(void) -{ - int i; - WORD attribut; - CONSOLE_SCREEN_BUFFER_INFO Info; - CONSOLE_CURSOR_INFO CursInfo; - DWORD len, NumberOfCharsWritten; - COORD Pos; - SMALL_RECT Rect; - CHAR_INFO CharInfo; - - if (prefix == '[') - { - if (prefix2 == '?' && (suffix == 'h' || suffix == 'l')) - { - if (es_argc == 1 && es_argv[0] == 25) - { - GetConsoleCursorInfo(hConOut, &CursInfo); - CursInfo.bVisible = (suffix == 'h'); - SetConsoleCursorInfo(hConOut, &CursInfo); - return; - } - } - // Ignore any other \e[? or \e[> sequences. - if (prefix2 != 0) - return; - - GetConsoleScreenBufferInfo(hConOut, &Info); - switch (suffix) - { - case 'm': - if (es_argc == 0) es_argv[es_argc++] = 0; - for (i = 0; i < es_argc; i++) - { - if (30 <= es_argv[i] && es_argv[i] <= 37) - grm.foreground = es_argv[i] - 30; - else if (40 <= es_argv[i] && es_argv[i] <= 47) - grm.background = es_argv[i] - 40; - else switch (es_argv[i]) - { - case 0: - case 39: - case 49: - { - WCHAR def[4]; - int a; - *def = '7'; def[1] = '\0'; - GetEnvironmentVariableW(L"ANSICON_DEF", def, lenof(def)); - a = wcstol(def, NULL, 16); - grm.reverse = FALSE; - if (a < 0) - { - grm.reverse = TRUE; - a = -a; - } - if (es_argv[i] != 49) - grm.foreground = attr2ansi[a & 7]; - if (es_argv[i] != 39) - grm.background = attr2ansi[(a >> 4) & 7]; - if (es_argv[i] == 0) - { - if (es_argc == 1) - { - grm.bold = a & FOREGROUND_INTENSITY; - grm.underline = a & BACKGROUND_INTENSITY; - } - else - { - grm.bold = 0; - grm.underline = 0; - } - grm.rvideo = 0; - grm.concealed = 0; - } - } - break; - - case 1: grm.bold = FOREGROUND_INTENSITY; break; - case 5: // blink - case 4: grm.underline = BACKGROUND_INTENSITY; break; - case 7: grm.rvideo = 1; break; - case 8: grm.concealed = 1; break; - case 21: // oops, this actually turns on double underline - case 22: grm.bold = 0; break; - case 25: - case 24: grm.underline = 0; break; - case 27: grm.rvideo = 0; break; - case 28: grm.concealed = 0; break; - } - } - if (grm.concealed) - { - if (grm.rvideo) - { - attribut = foregroundcolor[grm.foreground] - | backgroundcolor[grm.foreground]; - if (grm.bold) - attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - else - { - attribut = foregroundcolor[grm.background] - | backgroundcolor[grm.background]; - if (grm.underline) - attribut |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - } - else if (grm.rvideo) - { - attribut = foregroundcolor[grm.background] - | backgroundcolor[grm.foreground]; - if (grm.bold) - attribut |= BACKGROUND_INTENSITY; - if (grm.underline) - attribut |= FOREGROUND_INTENSITY; - } - else - attribut = foregroundcolor[grm.foreground] | grm.bold - | backgroundcolor[grm.background] | grm.underline; - if (grm.reverse) - attribut = ((attribut >> 4) & 15) | ((attribut & 15) << 4); - SetConsoleTextAttribute(hConOut, attribut); - return; - - case 'J': - if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[J == ESC[0J - if (es_argc != 1) return; - switch (es_argv[0]) - { - case 0: // ESC[0J erase from cursor to end of display - len = (Info.dwSize.Y - Info.dwCursorPosition.Y - 1) * Info.dwSize.X - + Info.dwSize.X - Info.dwCursorPosition.X - 1; - FillConsoleOutputCharacter(hConOut, ' ', len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1J erase from start to cursor. - Pos.X = 0; - Pos.Y = 0; - len = Info.dwCursorPosition.Y * Info.dwSize.X - + Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2J Clear screen and home cursor - Pos.X = 0; - Pos.Y = 0; - len = Info.dwSize.X * Info.dwSize.Y; - FillConsoleOutputCharacter(hConOut, ' ', len, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, Pos, - &NumberOfCharsWritten); - SetConsoleCursorPosition(hConOut, Pos); - return; - - default: - return; - } - - case 'K': - if (es_argc == 0) es_argv[es_argc++] = 0; // ESC[K == ESC[0K - if (es_argc != 1) return; - switch (es_argv[0]) - { - case 0: // ESC[0K Clear to end of line - len = Info.dwSize.X - Info.dwCursorPosition.X + 1; - FillConsoleOutputCharacter(hConOut, ' ', len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, len, - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 1: // ESC[1K Clear from start of line to cursor - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwCursorPosition.X + 1, Pos, - &NumberOfCharsWritten); - return; - - case 2: // ESC[2K Clear whole line. - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - FillConsoleOutputCharacter(hConOut, ' ', Info.dwSize.X, Pos, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, - Info.dwSize.X, Pos, - &NumberOfCharsWritten); - return; - - default: - return; - } - - case 'X': // ESC[#X Erase # characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[X == ESC[1X - if (es_argc != 1) return; - FillConsoleOutputCharacter(hConOut, ' ', es_argv[0], - Info.dwCursorPosition, - &NumberOfCharsWritten); - FillConsoleOutputAttribute(hConOut, Info.wAttributes, es_argv[0], - Info.dwCursorPosition, - &NumberOfCharsWritten); - return; - - case 'L': // ESC[#L Insert # blank lines. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[L == ESC[1L - if (es_argc != 1) return; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'M': // ESC[#M Delete # lines. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[M == ESC[1M - if (es_argc != 1) return; - if (es_argv[0] > Info.dwSize.Y - Info.dwCursorPosition.Y) - es_argv[0] = Info.dwSize.Y - Info.dwCursorPosition.Y; - Rect.Left = 0; - Rect.Top = Info.dwCursorPosition.Y + es_argv[0]; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwSize.Y - 1; - Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'P': // ESC[#P Delete # characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[P == ESC[1P - if (es_argc != 1) return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X + es_argv[0]; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1; - Rect.Bottom = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Info.dwCursorPosition, - &CharInfo); - return; - - case '@': // ESC[#@ Insert # blank characters. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[@ == ESC[1@ - if (es_argc != 1) return; - if (Info.dwCursorPosition.X + es_argv[0] > Info.dwSize.X - 1) - es_argv[0] = Info.dwSize.X - Info.dwCursorPosition.X; - Rect.Left = Info.dwCursorPosition.X; - Rect.Top = Info.dwCursorPosition.Y; - Rect.Right = Info.dwSize.X - 1 - es_argv[0]; - Rect.Bottom = Info.dwCursorPosition.Y; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - Pos.Y = Info.dwCursorPosition.Y; - CharInfo.Char.UnicodeChar = ' '; - CharInfo.Attributes = Info.wAttributes; - ScrollConsoleScreenBuffer(hConOut, &Rect, NULL, Pos, &CharInfo); - return; - - case 'k': // ESC[#k - case 'A': // ESC[#A Moves cursor up # lines - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[A == ESC[1A - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) Pos.Y = 0; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'e': // ESC[#e - case 'B': // ESC[#B Moves cursor down # lines - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[B == ESC[1B - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - Pos.X = Info.dwCursorPosition.X; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'a': // ESC[#a - case 'C': // ESC[#C Moves cursor forward # spaces - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[C == ESC[1C - if (es_argc != 1) return; - Pos.X = Info.dwCursorPosition.X + es_argv[0]; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'j': // ESC[#j - case 'D': // ESC[#D Moves cursor back # spaces - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[D == ESC[1D - if (es_argc != 1) return; - Pos.X = Info.dwCursorPosition.X - es_argv[0]; - if (Pos.X < 0) Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'E': // ESC[#E Moves cursor down # lines, column 1. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[E == ESC[1E - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y + es_argv[0]; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'F': // ESC[#F Moves cursor up # lines, column 1. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[F == ESC[1F - if (es_argc != 1) return; - Pos.Y = Info.dwCursorPosition.Y - es_argv[0]; - if (Pos.Y < 0) Pos.Y = 0; - Pos.X = 0; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case '`': // ESC[#` - case 'G': // ESC[#G Moves cursor column # in current row. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[G == ESC[1G - if (es_argc != 1) return; - Pos.X = es_argv[0] - 1; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - if (Pos.X < 0) Pos.X = 0; - Pos.Y = Info.dwCursorPosition.Y; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'd': // ESC[#d Moves cursor row #, current column. - if (es_argc == 0) es_argv[es_argc++] = 1; // ESC[d == ESC[1d - if (es_argc != 1) return; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 'f': // ESC[#;#f - case 'H': // ESC[#;#H Moves cursor to line #, column # - if (es_argc == 0) - es_argv[es_argc++] = 1; // ESC[H == ESC[1;1H - if (es_argc == 1) - es_argv[es_argc++] = 1; // ESC[#H == ESC[#;1H - if (es_argc > 2) return; - Pos.X = es_argv[1] - 1; - if (Pos.X < 0) Pos.X = 0; - if (Pos.X >= Info.dwSize.X) Pos.X = Info.dwSize.X - 1; - Pos.Y = es_argv[0] - 1; - if (Pos.Y < 0) Pos.Y = 0; - if (Pos.Y >= Info.dwSize.Y) Pos.Y = Info.dwSize.Y - 1; - SetConsoleCursorPosition(hConOut, Pos); - return; - - case 's': // ESC[s Saves cursor position for recall later - if (es_argc != 0) return; - SavePos = Info.dwCursorPosition; - return; - - case 'u': // ESC[u Return to saved cursor position - if (es_argc != 0) return; - SetConsoleCursorPosition(hConOut, SavePos); - return; - - case 'n': // ESC[#n Device status report - if (es_argc != 1) return; // ESC[n == ESC[0n -> ignored - switch (es_argv[0]) - { - case 5: // ESC[5n Report status - SendSequence(L"\33[0n"); // "OK" - return; - - case 6: // ESC[6n Report cursor position - { - WCHAR buf[32]; - swprintf(buf, 32, L"\33[%d;%dR", Info.dwCursorPosition.Y + 1, - Info.dwCursorPosition.X + 1); - SendSequence(buf); - } - return; - - default: - return; - } - - case 't': // ESC[#t Window manipulation - if (es_argc != 1) return; - if (es_argv[0] == 21) // ESC[21t Report xterm window's title - { - WCHAR buf[MAX_PATH * 2]; - DWORD len = GetConsoleTitleW(buf + 3, lenof(buf) - 3 - 2); - // Too bad if it's too big or fails. - buf[0] = ESC; - buf[1] = ']'; - buf[2] = 'l'; - buf[3 + len] = ESC; - buf[3 + len + 1] = '\\'; - buf[3 + len + 2] = '\0'; - SendSequence(buf); - } - return; - - default: - return; - } - } - else // (prefix == ']') - { - // Ignore any \e]? or \e]> sequences. - if (prefix2 != 0) - return; - - if (es_argc == 1 && es_argv[0] == 0) // ESC]0;titleST - { - SetConsoleTitleW(Pt_arg); - } - } -} - -//----------------------------------------------------------------------------- -// ParseAndPrintANSIString(hDev, lpBuffer, nNumberOfBytesToWrite) -// Parses the string lpBuffer, interprets the escapes sequences and prints the -// characters in the device hDev (console). -// The lexer is a three states automata. -// If the number of arguments es_argc > MAX_ARG, only the MAX_ARG-1 firsts and -// the last arguments are processed (no es_argv[] overflow). -//----------------------------------------------------------------------------- - -inline BOOL ParseAndPrintANSIString(HANDLE hDev, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten) -{ - DWORD i; - LPCSTR s; - - if (hDev != hConOut) // reinit if device has changed - { - hConOut = hDev; - state = 1; - shifted = FALSE; - } - for (i = nNumberOfBytesToWrite, s = (LPCSTR)lpBuffer; i > 0; i--, s++) - { - if (state == 1) - { - if (*s == ESC) state = 2; - else if (*s == SO) shifted = TRUE; - else if (*s == SI) shifted = FALSE; - else PushBuffer(*s); - } - else if (state == 2) - { - if (*s == ESC); // \e\e...\e == \e - else if ((*s == '[') || (*s == ']')) - { - FlushBuffer(); - prefix = *s; - prefix2 = 0; - state = 3; - Pt_len = 0; - *Pt_arg = '\0'; - } - else if (*s == ')' || *s == '(') state = 6; - else state = 1; - } - else if (state == 3) - { - if (is_digit(*s)) - { - es_argc = 0; - es_argv[0] = *s - '0'; - state = 4; - } - else if (*s == ';') - { - es_argc = 1; - es_argv[0] = 0; - es_argv[1] = 0; - state = 4; - } - else if (*s == '?' || *s == '>') - { - prefix2 = *s; - } - else - { - es_argc = 0; - suffix = *s; - InterpretEscSeq(); - state = 1; - } - } - else if (state == 4) - { - if (is_digit(*s)) - { - es_argv[es_argc] = 10 * es_argv[es_argc] + (*s - '0'); - } - else if (*s == ';') - { - if (es_argc < MAX_ARG - 1) es_argc++; - es_argv[es_argc] = 0; - if (prefix == ']') - state = 5; - } - else - { - es_argc++; - suffix = *s; - InterpretEscSeq(); - state = 1; - } - } - else if (state == 5) - { - if (*s == BEL) - { - Pt_arg[Pt_len] = '\0'; - InterpretEscSeq(); - state = 1; - } - else if (*s == '\\' && Pt_len > 0 && Pt_arg[Pt_len - 1] == ESC) - { - Pt_arg[--Pt_len] = '\0'; - InterpretEscSeq(); - state = 1; - } - else if (Pt_len < lenof(Pt_arg) - 1) - Pt_arg[Pt_len++] = *s; - } - else if (state == 6) - { - // Ignore it (ESC ) 0 is implicit; nothing else is supported). - state = 1; - } - } - FlushBuffer(); - if (lpNumberOfBytesWritten != NULL) - *lpNumberOfBytesWritten = nNumberOfBytesToWrite - i; - return (i == 0); -} - -} // namespace ansi - -HANDLE hOut; -HANDLE hIn; -DWORD consolemodeIn = 0; - -inline int win32read(int *c) { - DWORD foo; - INPUT_RECORD b; - KEY_EVENT_RECORD e; - BOOL altgr; - - while (1) { - if (!ReadConsoleInput(hIn, &b, 1, &foo)) return 0; - if (!foo) return 0; - - if (b.EventType == KEY_EVENT && b.Event.KeyEvent.bKeyDown) { - - e = b.Event.KeyEvent; - *c = b.Event.KeyEvent.uChar.AsciiChar; - - altgr = e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED); - - if (e.dwControlKeyState & (LEFT_CTRL_PRESSED | RIGHT_CTRL_PRESSED) && !altgr) { - - /* Ctrl+Key */ - switch (*c) { - case 'D': - *c = 4; - return 1; - case 'C': - *c = 3; - return 1; - case 'H': - *c = 8; - return 1; - case 'T': - *c = 20; - return 1; - case 'B': /* ctrl-b, left_arrow */ - *c = 2; - return 1; - case 'F': /* ctrl-f right_arrow*/ - *c = 6; - return 1; - case 'P': /* ctrl-p up_arrow*/ - *c = 16; - return 1; - case 'N': /* ctrl-n down_arrow*/ - *c = 14; - return 1; - case 'U': /* Ctrl+u, delete the whole line. */ - *c = 21; - return 1; - case 'K': /* Ctrl+k, delete from current to end of line. */ - *c = 11; - return 1; - case 'A': /* Ctrl+a, go to the start of the line */ - *c = 1; - return 1; - case 'E': /* ctrl+e, go to the end of the line */ - *c = 5; - return 1; - } - - /* Other Ctrl+KEYs ignored */ - } else { - - switch (e.wVirtualKeyCode) { - - case VK_ESCAPE: /* ignore - send ctrl-c, will return -1 */ - *c = 3; - return 1; - case VK_RETURN: /* enter */ - *c = 13; - return 1; - case VK_LEFT: /* left */ - *c = 2; - return 1; - case VK_RIGHT: /* right */ - *c = 6; - return 1; - case VK_UP: /* up */ - *c = 16; - return 1; - case VK_DOWN: /* down */ - *c = 14; - return 1; - case VK_HOME: - *c = 1; - return 1; - case VK_END: - *c = 5; - return 1; - case VK_BACK: - *c = 8; - return 1; - case VK_DELETE: - *c = 127; - return 1; - default: - if (*c) return 1; - } - } - } - } - - return -1; /* Makes compiler happy */ -} - -inline int win32_write(int fd, const void *buffer, unsigned int count) { - if (fd == _fileno(stdout)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_OUTPUT_HANDLE), buffer, (DWORD)count, &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } - } else if (fd == _fileno(stderr)) { - DWORD bytesWritten = 0; - if (FALSE != ansi::ParseAndPrintANSIString(GetStdHandle(STD_ERROR_HANDLE), buffer, (DWORD)count, &bytesWritten)) { - return (int)bytesWritten; - } else { - errno = GetLastError(); - return 0; - } - } else { - return _write(fd, buffer, count); - } -} -#endif // _WIN32 - -#define LINENOISE_DEFAULT_HISTORY_MAX_LEN 100 -#define LINENOISE_MAX_LINE 4096 - static const char *unsupported_term[] = {"dumb","cons25","emacs",NULL}; - static CompletionCallback completionCallback; - -#ifndef _WIN32 - static struct termios orig_termios; /* In order to restore at exit.*/ -#endif - static bool rawmode = false; /* For atexit() function to check if restore is needed*/ - static bool mlmode = false; /* Multi line mode. Default is single line. */ - static bool atexit_registered = false; /* Register atexit just 1 time. */ - static size_t history_max_len = LINENOISE_DEFAULT_HISTORY_MAX_LEN; - static std::vector history; - -/* The linenoiseState structure represents the state during line editing. - * We pass this state to functions implementing specific editing - * functionalities. */ - struct linenoiseState { - int ifd; /* Terminal stdin file descriptor. */ - int ofd; /* Terminal stdout file descriptor. */ - char *buf; /* Edited line buffer. */ - int buflen; /* Edited line buffer size. */ - std::string prompt; /* Prompt to display. */ - int pos; /* Current cursor position. */ - int oldcolpos; /* Previous refresh cursor column position. */ - int len; /* Current edited line length. */ - int cols; /* Number of columns in terminal. */ - int maxrows; /* Maximum num of rows used so far (multiline mode) */ - int history_index; /* The history index we are currently editing. */ - }; - - enum KEY_ACTION { - KEY_NULL = 0, /* NULL */ - CTRL_A = 1, /* Ctrl+a */ - CTRL_B = 2, /* Ctrl-b */ - CTRL_C = 3, /* Ctrl-c */ - CTRL_D = 4, /* Ctrl-d */ - CTRL_E = 5, /* Ctrl-e */ - CTRL_F = 6, /* Ctrl-f */ - CTRL_H = 8, /* Ctrl-h */ - TAB = 9, /* Tab */ - CTRL_K = 11, /* Ctrl+k */ - CTRL_L = 12, /* Ctrl+l */ - ENTER = 13, /* Enter */ - CTRL_N = 14, /* Ctrl-n */ - CTRL_P = 16, /* Ctrl-p */ - CTRL_T = 20, /* Ctrl-t */ - CTRL_U = 21, /* Ctrl+u */ - CTRL_W = 23, /* Ctrl+w */ - ESC = 27, /* Escape */ - BACKSPACE = 127 /* Backspace */ - }; - - void linenoiseAtExit(void); - bool AddHistory(const char *line); - void refreshLine(struct linenoiseState *l); - -/* ============================ UTF8 utilities ============================== */ - - static unsigned long unicodeWideCharTable[][2] = { - { 0x1100, 0x115F }, { 0x2329, 0x232A }, { 0x2E80, 0x2E99, }, { 0x2E9B, 0x2EF3, }, - { 0x2F00, 0x2FD5, }, { 0x2FF0, 0x2FFB, }, { 0x3000, 0x303E, }, { 0x3041, 0x3096, }, - { 0x3099, 0x30FF, }, { 0x3105, 0x312D, }, { 0x3131, 0x318E, }, { 0x3190, 0x31BA, }, - { 0x31C0, 0x31E3, }, { 0x31F0, 0x321E, }, { 0x3220, 0x3247, }, { 0x3250, 0x4DBF, }, - { 0x4E00, 0xA48C, }, { 0xA490, 0xA4C6, }, { 0xA960, 0xA97C, }, { 0xAC00, 0xD7A3, }, - { 0xF900, 0xFAFF, }, { 0xFE10, 0xFE19, }, { 0xFE30, 0xFE52, }, { 0xFE54, 0xFE66, }, - { 0xFE68, 0xFE6B, }, { 0xFF01, 0xFFE6, }, - { 0x1B000, 0x1B001, }, { 0x1F200, 0x1F202, }, { 0x1F210, 0x1F23A, }, - { 0x1F240, 0x1F248, }, { 0x1F250, 0x1F251, }, { 0x20000, 0x3FFFD, }, - }; - - static int unicodeWideCharTableSize = sizeof(unicodeWideCharTable) / sizeof(unicodeWideCharTable[0]); - - static int unicodeIsWideChar(unsigned long cp) - { - int i; - for (i = 0; i < unicodeWideCharTableSize; i++) { - if (unicodeWideCharTable[i][0] <= cp && cp <= unicodeWideCharTable[i][1]) { - return 1; - } - } - return 0; - } - - static unsigned long unicodeCombiningCharTable[] = { - 0x0300,0x0301,0x0302,0x0303,0x0304,0x0305,0x0306,0x0307, - 0x0308,0x0309,0x030A,0x030B,0x030C,0x030D,0x030E,0x030F, - 0x0310,0x0311,0x0312,0x0313,0x0314,0x0315,0x0316,0x0317, - 0x0318,0x0319,0x031A,0x031B,0x031C,0x031D,0x031E,0x031F, - 0x0320,0x0321,0x0322,0x0323,0x0324,0x0325,0x0326,0x0327, - 0x0328,0x0329,0x032A,0x032B,0x032C,0x032D,0x032E,0x032F, - 0x0330,0x0331,0x0332,0x0333,0x0334,0x0335,0x0336,0x0337, - 0x0338,0x0339,0x033A,0x033B,0x033C,0x033D,0x033E,0x033F, - 0x0340,0x0341,0x0342,0x0343,0x0344,0x0345,0x0346,0x0347, - 0x0348,0x0349,0x034A,0x034B,0x034C,0x034D,0x034E,0x034F, - 0x0350,0x0351,0x0352,0x0353,0x0354,0x0355,0x0356,0x0357, - 0x0358,0x0359,0x035A,0x035B,0x035C,0x035D,0x035E,0x035F, - 0x0360,0x0361,0x0362,0x0363,0x0364,0x0365,0x0366,0x0367, - 0x0368,0x0369,0x036A,0x036B,0x036C,0x036D,0x036E,0x036F, - 0x0483,0x0484,0x0485,0x0486,0x0487,0x0591,0x0592,0x0593, - 0x0594,0x0595,0x0596,0x0597,0x0598,0x0599,0x059A,0x059B, - 0x059C,0x059D,0x059E,0x059F,0x05A0,0x05A1,0x05A2,0x05A3, - 0x05A4,0x05A5,0x05A6,0x05A7,0x05A8,0x05A9,0x05AA,0x05AB, - 0x05AC,0x05AD,0x05AE,0x05AF,0x05B0,0x05B1,0x05B2,0x05B3, - 0x05B4,0x05B5,0x05B6,0x05B7,0x05B8,0x05B9,0x05BA,0x05BB, - 0x05BC,0x05BD,0x05BF,0x05C1,0x05C2,0x05C4,0x05C5,0x05C7, - 0x0610,0x0611,0x0612,0x0613,0x0614,0x0615,0x0616,0x0617, - 0x0618,0x0619,0x061A,0x064B,0x064C,0x064D,0x064E,0x064F, - 0x0650,0x0651,0x0652,0x0653,0x0654,0x0655,0x0656,0x0657, - 0x0658,0x0659,0x065A,0x065B,0x065C,0x065D,0x065E,0x065F, - 0x0670,0x06D6,0x06D7,0x06D8,0x06D9,0x06DA,0x06DB,0x06DC, - 0x06DF,0x06E0,0x06E1,0x06E2,0x06E3,0x06E4,0x06E7,0x06E8, - 0x06EA,0x06EB,0x06EC,0x06ED,0x0711,0x0730,0x0731,0x0732, - 0x0733,0x0734,0x0735,0x0736,0x0737,0x0738,0x0739,0x073A, - 0x073B,0x073C,0x073D,0x073E,0x073F,0x0740,0x0741,0x0742, - 0x0743,0x0744,0x0745,0x0746,0x0747,0x0748,0x0749,0x074A, - 0x07A6,0x07A7,0x07A8,0x07A9,0x07AA,0x07AB,0x07AC,0x07AD, - 0x07AE,0x07AF,0x07B0,0x07EB,0x07EC,0x07ED,0x07EE,0x07EF, - 0x07F0,0x07F1,0x07F2,0x07F3,0x0816,0x0817,0x0818,0x0819, - 0x081B,0x081C,0x081D,0x081E,0x081F,0x0820,0x0821,0x0822, - 0x0823,0x0825,0x0826,0x0827,0x0829,0x082A,0x082B,0x082C, - 0x082D,0x0859,0x085A,0x085B,0x08E3,0x08E4,0x08E5,0x08E6, - 0x08E7,0x08E8,0x08E9,0x08EA,0x08EB,0x08EC,0x08ED,0x08EE, - 0x08EF,0x08F0,0x08F1,0x08F2,0x08F3,0x08F4,0x08F5,0x08F6, - 0x08F7,0x08F8,0x08F9,0x08FA,0x08FB,0x08FC,0x08FD,0x08FE, - 0x08FF,0x0900,0x0901,0x0902,0x093A,0x093C,0x0941,0x0942, - 0x0943,0x0944,0x0945,0x0946,0x0947,0x0948,0x094D,0x0951, - 0x0952,0x0953,0x0954,0x0955,0x0956,0x0957,0x0962,0x0963, - 0x0981,0x09BC,0x09C1,0x09C2,0x09C3,0x09C4,0x09CD,0x09E2, - 0x09E3,0x0A01,0x0A02,0x0A3C,0x0A41,0x0A42,0x0A47,0x0A48, - 0x0A4B,0x0A4C,0x0A4D,0x0A51,0x0A70,0x0A71,0x0A75,0x0A81, - 0x0A82,0x0ABC,0x0AC1,0x0AC2,0x0AC3,0x0AC4,0x0AC5,0x0AC7, - 0x0AC8,0x0ACD,0x0AE2,0x0AE3,0x0B01,0x0B3C,0x0B3F,0x0B41, - 0x0B42,0x0B43,0x0B44,0x0B4D,0x0B56,0x0B62,0x0B63,0x0B82, - 0x0BC0,0x0BCD,0x0C00,0x0C3E,0x0C3F,0x0C40,0x0C46,0x0C47, - 0x0C48,0x0C4A,0x0C4B,0x0C4C,0x0C4D,0x0C55,0x0C56,0x0C62, - 0x0C63,0x0C81,0x0CBC,0x0CBF,0x0CC6,0x0CCC,0x0CCD,0x0CE2, - 0x0CE3,0x0D01,0x0D41,0x0D42,0x0D43,0x0D44,0x0D4D,0x0D62, - 0x0D63,0x0DCA,0x0DD2,0x0DD3,0x0DD4,0x0DD6,0x0E31,0x0E34, - 0x0E35,0x0E36,0x0E37,0x0E38,0x0E39,0x0E3A,0x0E47,0x0E48, - 0x0E49,0x0E4A,0x0E4B,0x0E4C,0x0E4D,0x0E4E,0x0EB1,0x0EB4, - 0x0EB5,0x0EB6,0x0EB7,0x0EB8,0x0EB9,0x0EBB,0x0EBC,0x0EC8, - 0x0EC9,0x0ECA,0x0ECB,0x0ECC,0x0ECD,0x0F18,0x0F19,0x0F35, - 0x0F37,0x0F39,0x0F71,0x0F72,0x0F73,0x0F74,0x0F75,0x0F76, - 0x0F77,0x0F78,0x0F79,0x0F7A,0x0F7B,0x0F7C,0x0F7D,0x0F7E, - 0x0F80,0x0F81,0x0F82,0x0F83,0x0F84,0x0F86,0x0F87,0x0F8D, - 0x0F8E,0x0F8F,0x0F90,0x0F91,0x0F92,0x0F93,0x0F94,0x0F95, - 0x0F96,0x0F97,0x0F99,0x0F9A,0x0F9B,0x0F9C,0x0F9D,0x0F9E, - 0x0F9F,0x0FA0,0x0FA1,0x0FA2,0x0FA3,0x0FA4,0x0FA5,0x0FA6, - 0x0FA7,0x0FA8,0x0FA9,0x0FAA,0x0FAB,0x0FAC,0x0FAD,0x0FAE, - 0x0FAF,0x0FB0,0x0FB1,0x0FB2,0x0FB3,0x0FB4,0x0FB5,0x0FB6, - 0x0FB7,0x0FB8,0x0FB9,0x0FBA,0x0FBB,0x0FBC,0x0FC6,0x102D, - 0x102E,0x102F,0x1030,0x1032,0x1033,0x1034,0x1035,0x1036, - 0x1037,0x1039,0x103A,0x103D,0x103E,0x1058,0x1059,0x105E, - 0x105F,0x1060,0x1071,0x1072,0x1073,0x1074,0x1082,0x1085, - 0x1086,0x108D,0x109D,0x135D,0x135E,0x135F,0x1712,0x1713, - 0x1714,0x1732,0x1733,0x1734,0x1752,0x1753,0x1772,0x1773, - 0x17B4,0x17B5,0x17B7,0x17B8,0x17B9,0x17BA,0x17BB,0x17BC, - 0x17BD,0x17C6,0x17C9,0x17CA,0x17CB,0x17CC,0x17CD,0x17CE, - 0x17CF,0x17D0,0x17D1,0x17D2,0x17D3,0x17DD,0x180B,0x180C, - 0x180D,0x18A9,0x1920,0x1921,0x1922,0x1927,0x1928,0x1932, - 0x1939,0x193A,0x193B,0x1A17,0x1A18,0x1A1B,0x1A56,0x1A58, - 0x1A59,0x1A5A,0x1A5B,0x1A5C,0x1A5D,0x1A5E,0x1A60,0x1A62, - 0x1A65,0x1A66,0x1A67,0x1A68,0x1A69,0x1A6A,0x1A6B,0x1A6C, - 0x1A73,0x1A74,0x1A75,0x1A76,0x1A77,0x1A78,0x1A79,0x1A7A, - 0x1A7B,0x1A7C,0x1A7F,0x1AB0,0x1AB1,0x1AB2,0x1AB3,0x1AB4, - 0x1AB5,0x1AB6,0x1AB7,0x1AB8,0x1AB9,0x1ABA,0x1ABB,0x1ABC, - 0x1ABD,0x1B00,0x1B01,0x1B02,0x1B03,0x1B34,0x1B36,0x1B37, - 0x1B38,0x1B39,0x1B3A,0x1B3C,0x1B42,0x1B6B,0x1B6C,0x1B6D, - 0x1B6E,0x1B6F,0x1B70,0x1B71,0x1B72,0x1B73,0x1B80,0x1B81, - 0x1BA2,0x1BA3,0x1BA4,0x1BA5,0x1BA8,0x1BA9,0x1BAB,0x1BAC, - 0x1BAD,0x1BE6,0x1BE8,0x1BE9,0x1BED,0x1BEF,0x1BF0,0x1BF1, - 0x1C2C,0x1C2D,0x1C2E,0x1C2F,0x1C30,0x1C31,0x1C32,0x1C33, - 0x1C36,0x1C37,0x1CD0,0x1CD1,0x1CD2,0x1CD4,0x1CD5,0x1CD6, - 0x1CD7,0x1CD8,0x1CD9,0x1CDA,0x1CDB,0x1CDC,0x1CDD,0x1CDE, - 0x1CDF,0x1CE0,0x1CE2,0x1CE3,0x1CE4,0x1CE5,0x1CE6,0x1CE7, - 0x1CE8,0x1CED,0x1CF4,0x1CF8,0x1CF9,0x1DC0,0x1DC1,0x1DC2, - 0x1DC3,0x1DC4,0x1DC5,0x1DC6,0x1DC7,0x1DC8,0x1DC9,0x1DCA, - 0x1DCB,0x1DCC,0x1DCD,0x1DCE,0x1DCF,0x1DD0,0x1DD1,0x1DD2, - 0x1DD3,0x1DD4,0x1DD5,0x1DD6,0x1DD7,0x1DD8,0x1DD9,0x1DDA, - 0x1DDB,0x1DDC,0x1DDD,0x1DDE,0x1DDF,0x1DE0,0x1DE1,0x1DE2, - 0x1DE3,0x1DE4,0x1DE5,0x1DE6,0x1DE7,0x1DE8,0x1DE9,0x1DEA, - 0x1DEB,0x1DEC,0x1DED,0x1DEE,0x1DEF,0x1DF0,0x1DF1,0x1DF2, - 0x1DF3,0x1DF4,0x1DF5,0x1DFC,0x1DFD,0x1DFE,0x1DFF,0x20D0, - 0x20D1,0x20D2,0x20D3,0x20D4,0x20D5,0x20D6,0x20D7,0x20D8, - 0x20D9,0x20DA,0x20DB,0x20DC,0x20E1,0x20E5,0x20E6,0x20E7, - 0x20E8,0x20E9,0x20EA,0x20EB,0x20EC,0x20ED,0x20EE,0x20EF, - 0x20F0,0x2CEF,0x2CF0,0x2CF1,0x2D7F,0x2DE0,0x2DE1,0x2DE2, - 0x2DE3,0x2DE4,0x2DE5,0x2DE6,0x2DE7,0x2DE8,0x2DE9,0x2DEA, - 0x2DEB,0x2DEC,0x2DED,0x2DEE,0x2DEF,0x2DF0,0x2DF1,0x2DF2, - 0x2DF3,0x2DF4,0x2DF5,0x2DF6,0x2DF7,0x2DF8,0x2DF9,0x2DFA, - 0x2DFB,0x2DFC,0x2DFD,0x2DFE,0x2DFF,0x302A,0x302B,0x302C, - 0x302D,0x3099,0x309A,0xA66F,0xA674,0xA675,0xA676,0xA677, - 0xA678,0xA679,0xA67A,0xA67B,0xA67C,0xA67D,0xA69E,0xA69F, - 0xA6F0,0xA6F1,0xA802,0xA806,0xA80B,0xA825,0xA826,0xA8C4, - 0xA8E0,0xA8E1,0xA8E2,0xA8E3,0xA8E4,0xA8E5,0xA8E6,0xA8E7, - 0xA8E8,0xA8E9,0xA8EA,0xA8EB,0xA8EC,0xA8ED,0xA8EE,0xA8EF, - 0xA8F0,0xA8F1,0xA926,0xA927,0xA928,0xA929,0xA92A,0xA92B, - 0xA92C,0xA92D,0xA947,0xA948,0xA949,0xA94A,0xA94B,0xA94C, - 0xA94D,0xA94E,0xA94F,0xA950,0xA951,0xA980,0xA981,0xA982, - 0xA9B3,0xA9B6,0xA9B7,0xA9B8,0xA9B9,0xA9BC,0xA9E5,0xAA29, - 0xAA2A,0xAA2B,0xAA2C,0xAA2D,0xAA2E,0xAA31,0xAA32,0xAA35, - 0xAA36,0xAA43,0xAA4C,0xAA7C,0xAAB0,0xAAB2,0xAAB3,0xAAB4, - 0xAAB7,0xAAB8,0xAABE,0xAABF,0xAAC1,0xAAEC,0xAAED,0xAAF6, - 0xABE5,0xABE8,0xABED,0xFB1E,0xFE00,0xFE01,0xFE02,0xFE03, - 0xFE04,0xFE05,0xFE06,0xFE07,0xFE08,0xFE09,0xFE0A,0xFE0B, - 0xFE0C,0xFE0D,0xFE0E,0xFE0F,0xFE20,0xFE21,0xFE22,0xFE23, - 0xFE24,0xFE25,0xFE26,0xFE27,0xFE28,0xFE29,0xFE2A,0xFE2B, - 0xFE2C,0xFE2D,0xFE2E,0xFE2F, - 0x101FD,0x102E0,0x10376,0x10377,0x10378,0x10379,0x1037A,0x10A01, - 0x10A02,0x10A03,0x10A05,0x10A06,0x10A0C,0x10A0D,0x10A0E,0x10A0F, - 0x10A38,0x10A39,0x10A3A,0x10A3F,0x10AE5,0x10AE6,0x11001,0x11038, - 0x11039,0x1103A,0x1103B,0x1103C,0x1103D,0x1103E,0x1103F,0x11040, - 0x11041,0x11042,0x11043,0x11044,0x11045,0x11046,0x1107F,0x11080, - 0x11081,0x110B3,0x110B4,0x110B5,0x110B6,0x110B9,0x110BA,0x11100, - 0x11101,0x11102,0x11127,0x11128,0x11129,0x1112A,0x1112B,0x1112D, - 0x1112E,0x1112F,0x11130,0x11131,0x11132,0x11133,0x11134,0x11173, - 0x11180,0x11181,0x111B6,0x111B7,0x111B8,0x111B9,0x111BA,0x111BB, - 0x111BC,0x111BD,0x111BE,0x111CA,0x111CB,0x111CC,0x1122F,0x11230, - 0x11231,0x11234,0x11236,0x11237,0x112DF,0x112E3,0x112E4,0x112E5, - 0x112E6,0x112E7,0x112E8,0x112E9,0x112EA,0x11300,0x11301,0x1133C, - 0x11340,0x11366,0x11367,0x11368,0x11369,0x1136A,0x1136B,0x1136C, - 0x11370,0x11371,0x11372,0x11373,0x11374,0x114B3,0x114B4,0x114B5, - 0x114B6,0x114B7,0x114B8,0x114BA,0x114BF,0x114C0,0x114C2,0x114C3, - 0x115B2,0x115B3,0x115B4,0x115B5,0x115BC,0x115BD,0x115BF,0x115C0, - 0x115DC,0x115DD,0x11633,0x11634,0x11635,0x11636,0x11637,0x11638, - 0x11639,0x1163A,0x1163D,0x1163F,0x11640,0x116AB,0x116AD,0x116B0, - 0x116B1,0x116B2,0x116B3,0x116B4,0x116B5,0x116B7,0x1171D,0x1171E, - 0x1171F,0x11722,0x11723,0x11724,0x11725,0x11727,0x11728,0x11729, - 0x1172A,0x1172B,0x16AF0,0x16AF1,0x16AF2,0x16AF3,0x16AF4,0x16B30, - 0x16B31,0x16B32,0x16B33,0x16B34,0x16B35,0x16B36,0x16F8F,0x16F90, - 0x16F91,0x16F92,0x1BC9D,0x1BC9E,0x1D167,0x1D168,0x1D169,0x1D17B, - 0x1D17C,0x1D17D,0x1D17E,0x1D17F,0x1D180,0x1D181,0x1D182,0x1D185, - 0x1D186,0x1D187,0x1D188,0x1D189,0x1D18A,0x1D18B,0x1D1AA,0x1D1AB, - 0x1D1AC,0x1D1AD,0x1D242,0x1D243,0x1D244,0x1DA00,0x1DA01,0x1DA02, - 0x1DA03,0x1DA04,0x1DA05,0x1DA06,0x1DA07,0x1DA08,0x1DA09,0x1DA0A, - 0x1DA0B,0x1DA0C,0x1DA0D,0x1DA0E,0x1DA0F,0x1DA10,0x1DA11,0x1DA12, - 0x1DA13,0x1DA14,0x1DA15,0x1DA16,0x1DA17,0x1DA18,0x1DA19,0x1DA1A, - 0x1DA1B,0x1DA1C,0x1DA1D,0x1DA1E,0x1DA1F,0x1DA20,0x1DA21,0x1DA22, - 0x1DA23,0x1DA24,0x1DA25,0x1DA26,0x1DA27,0x1DA28,0x1DA29,0x1DA2A, - 0x1DA2B,0x1DA2C,0x1DA2D,0x1DA2E,0x1DA2F,0x1DA30,0x1DA31,0x1DA32, - 0x1DA33,0x1DA34,0x1DA35,0x1DA36,0x1DA3B,0x1DA3C,0x1DA3D,0x1DA3E, - 0x1DA3F,0x1DA40,0x1DA41,0x1DA42,0x1DA43,0x1DA44,0x1DA45,0x1DA46, - 0x1DA47,0x1DA48,0x1DA49,0x1DA4A,0x1DA4B,0x1DA4C,0x1DA4D,0x1DA4E, - 0x1DA4F,0x1DA50,0x1DA51,0x1DA52,0x1DA53,0x1DA54,0x1DA55,0x1DA56, - 0x1DA57,0x1DA58,0x1DA59,0x1DA5A,0x1DA5B,0x1DA5C,0x1DA5D,0x1DA5E, - 0x1DA5F,0x1DA60,0x1DA61,0x1DA62,0x1DA63,0x1DA64,0x1DA65,0x1DA66, - 0x1DA67,0x1DA68,0x1DA69,0x1DA6A,0x1DA6B,0x1DA6C,0x1DA75,0x1DA84, - 0x1DA9B,0x1DA9C,0x1DA9D,0x1DA9E,0x1DA9F,0x1DAA1,0x1DAA2,0x1DAA3, - 0x1DAA4,0x1DAA5,0x1DAA6,0x1DAA7,0x1DAA8,0x1DAA9,0x1DAAA,0x1DAAB, - 0x1DAAC,0x1DAAD,0x1DAAE,0x1DAAF,0x1E8D0,0x1E8D1,0x1E8D2,0x1E8D3, - 0x1E8D4,0x1E8D5,0x1E8D6,0xE0100,0xE0101,0xE0102,0xE0103,0xE0104, - 0xE0105,0xE0106,0xE0107,0xE0108,0xE0109,0xE010A,0xE010B,0xE010C, - 0xE010D,0xE010E,0xE010F,0xE0110,0xE0111,0xE0112,0xE0113,0xE0114, - 0xE0115,0xE0116,0xE0117,0xE0118,0xE0119,0xE011A,0xE011B,0xE011C, - 0xE011D,0xE011E,0xE011F,0xE0120,0xE0121,0xE0122,0xE0123,0xE0124, - 0xE0125,0xE0126,0xE0127,0xE0128,0xE0129,0xE012A,0xE012B,0xE012C, - 0xE012D,0xE012E,0xE012F,0xE0130,0xE0131,0xE0132,0xE0133,0xE0134, - 0xE0135,0xE0136,0xE0137,0xE0138,0xE0139,0xE013A,0xE013B,0xE013C, - 0xE013D,0xE013E,0xE013F,0xE0140,0xE0141,0xE0142,0xE0143,0xE0144, - 0xE0145,0xE0146,0xE0147,0xE0148,0xE0149,0xE014A,0xE014B,0xE014C, - 0xE014D,0xE014E,0xE014F,0xE0150,0xE0151,0xE0152,0xE0153,0xE0154, - 0xE0155,0xE0156,0xE0157,0xE0158,0xE0159,0xE015A,0xE015B,0xE015C, - 0xE015D,0xE015E,0xE015F,0xE0160,0xE0161,0xE0162,0xE0163,0xE0164, - 0xE0165,0xE0166,0xE0167,0xE0168,0xE0169,0xE016A,0xE016B,0xE016C, - 0xE016D,0xE016E,0xE016F,0xE0170,0xE0171,0xE0172,0xE0173,0xE0174, - 0xE0175,0xE0176,0xE0177,0xE0178,0xE0179,0xE017A,0xE017B,0xE017C, - 0xE017D,0xE017E,0xE017F,0xE0180,0xE0181,0xE0182,0xE0183,0xE0184, - 0xE0185,0xE0186,0xE0187,0xE0188,0xE0189,0xE018A,0xE018B,0xE018C, - 0xE018D,0xE018E,0xE018F,0xE0190,0xE0191,0xE0192,0xE0193,0xE0194, - 0xE0195,0xE0196,0xE0197,0xE0198,0xE0199,0xE019A,0xE019B,0xE019C, - 0xE019D,0xE019E,0xE019F,0xE01A0,0xE01A1,0xE01A2,0xE01A3,0xE01A4, - 0xE01A5,0xE01A6,0xE01A7,0xE01A8,0xE01A9,0xE01AA,0xE01AB,0xE01AC, - 0xE01AD,0xE01AE,0xE01AF,0xE01B0,0xE01B1,0xE01B2,0xE01B3,0xE01B4, - 0xE01B5,0xE01B6,0xE01B7,0xE01B8,0xE01B9,0xE01BA,0xE01BB,0xE01BC, - 0xE01BD,0xE01BE,0xE01BF,0xE01C0,0xE01C1,0xE01C2,0xE01C3,0xE01C4, - 0xE01C5,0xE01C6,0xE01C7,0xE01C8,0xE01C9,0xE01CA,0xE01CB,0xE01CC, - 0xE01CD,0xE01CE,0xE01CF,0xE01D0,0xE01D1,0xE01D2,0xE01D3,0xE01D4, - 0xE01D5,0xE01D6,0xE01D7,0xE01D8,0xE01D9,0xE01DA,0xE01DB,0xE01DC, - 0xE01DD,0xE01DE,0xE01DF,0xE01E0,0xE01E1,0xE01E2,0xE01E3,0xE01E4, - 0xE01E5,0xE01E6,0xE01E7,0xE01E8,0xE01E9,0xE01EA,0xE01EB,0xE01EC, - 0xE01ED,0xE01EE,0xE01EF, - }; - - static int unicodeCombiningCharTableSize = sizeof(unicodeCombiningCharTable) / sizeof(unicodeCombiningCharTable[0]); - - inline int unicodeIsCombiningChar(unsigned long cp) - { - int i; - for (i = 0; i < unicodeCombiningCharTableSize; i++) { - if (unicodeCombiningCharTable[i] == cp) { - return 1; - } - } - return 0; - } - -/* Get length of previous UTF8 character - */ - inline int unicodePrevUTF8CharLen(char* buf, int pos) - { - int end = pos--; - while (pos >= 0 && ((unsigned char)buf[pos] & 0xC0) == 0x80) { - pos--; - } - return end - pos; - } - -/* Get length of previous UTF8 character - */ - inline int unicodeUTF8CharLen(char* buf, int buf_len, int pos) - { - if (pos == buf_len) { return 0; } - unsigned char ch = buf[pos]; - if (ch < 0x80) { return 1; } - else if (ch < 0xE0) { return 2; } - else if (ch < 0xF0) { return 3; } - else { return 4; } - } - -/* Convert UTF8 to Unicode code point - */ - inline int unicodeUTF8CharToCodePoint( - const char* buf, - int len, - int* cp) - { - if (len) { - unsigned char byte = buf[0]; - if ((byte & 0x80) == 0) { - *cp = byte; - return 1; - } else if ((byte & 0xE0) == 0xC0) { - if (len >= 2) { - *cp = (((unsigned long)(buf[0] & 0x1F)) << 6) | - ((unsigned long)(buf[1] & 0x3F)); - return 2; - } - } else if ((byte & 0xF0) == 0xE0) { - if (len >= 3) { - *cp = (((unsigned long)(buf[0] & 0x0F)) << 12) | - (((unsigned long)(buf[1] & 0x3F)) << 6) | - ((unsigned long)(buf[2] & 0x3F)); - return 3; - } - } else if ((byte & 0xF8) == 0xF0) { - if (len >= 4) { - *cp = (((unsigned long)(buf[0] & 0x07)) << 18) | - (((unsigned long)(buf[1] & 0x3F)) << 12) | - (((unsigned long)(buf[2] & 0x3F)) << 6) | - ((unsigned long)(buf[3] & 0x3F)); - return 4; - } - } - } - return 0; - } - -/* Get length of grapheme - */ - inline int unicodeGraphemeLen(char* buf, int buf_len, int pos) - { - if (pos == buf_len) { - return 0; - } - int beg = pos; - pos += unicodeUTF8CharLen(buf, buf_len, pos); - while (pos < buf_len) { - int len = unicodeUTF8CharLen(buf, buf_len, pos); - int cp = 0; - unicodeUTF8CharToCodePoint(buf + pos, len, &cp); - if (!unicodeIsCombiningChar(cp)) { - return pos - beg; - } - pos += len; - } - return pos - beg; - } - -/* Get length of previous grapheme - */ - inline int unicodePrevGraphemeLen(char* buf, int pos) - { - if (pos == 0) { - return 0; - } - int end = pos; - while (pos > 0) { - int len = unicodePrevUTF8CharLen(buf, pos); - pos -= len; - int cp = 0; - unicodeUTF8CharToCodePoint(buf + pos, len, &cp); - if (!unicodeIsCombiningChar(cp)) { - return end - pos; - } - } - return 0; - } - - inline int isAnsiEscape(const char* buf, int buf_len, int* len) - { - if (buf_len > 2 && !memcmp("\033[", buf, 2)) { - int off = 2; - while (off < buf_len) { - switch (buf[off++]) { - case 'A': case 'B': case 'C': case 'D': - case 'E': case 'F': case 'G': case 'H': - case 'J': case 'K': case 'S': case 'T': - case 'f': case 'm': - *len = off; - return 1; - } - } - } - return 0; - } - -/* Get column position for the single line mode. - */ - inline int unicodeColumnPos(const char* buf, int buf_len) - { - int ret = 0; - - int off = 0; - while (off < buf_len) { - int len; - if (isAnsiEscape(buf + off, buf_len - off, &len)) { - off += len; - continue; - } - - int cp = 0; - len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); - - if (!unicodeIsCombiningChar(cp)) { - ret += unicodeIsWideChar(cp) ? 2 : 1; - } - - off += len; - } - - return ret; - } - -/* Get column position for the multi line mode. - */ - inline int unicodeColumnPosForMultiLine(char* buf, int buf_len, int pos, int cols, int ini_pos) - { - int ret = 0; - int colwid = ini_pos; - - int off = 0; - while (off < buf_len) { - int cp = 0; - int len = unicodeUTF8CharToCodePoint(buf + off, buf_len - off, &cp); - - int wid = 0; - if (!unicodeIsCombiningChar(cp)) { - wid = unicodeIsWideChar(cp) ? 2 : 1; - } - - int dif = (int)(colwid + wid) - (int)cols; - if (dif > 0) { - ret += dif; - colwid = wid; - } else if (dif == 0) { - colwid = 0; - } else { - colwid += wid; - } - - if (off >= pos) { - break; - } - - off += len; - ret += wid; - } - - return ret; - } - -/* Read UTF8 character from file. - */ - inline int unicodeReadUTF8Char(int fd, char* buf, int* cp) - { - int nread = read(fd,&buf[0],1); - - if (nread <= 0) { return nread; } - - unsigned char byte = buf[0]; - - if ((byte & 0x80) == 0) { - ; - } else if ((byte & 0xE0) == 0xC0) { - nread = read(fd,&buf[1],1); - if (nread <= 0) { return nread; } - } else if ((byte & 0xF0) == 0xE0) { - nread = read(fd,&buf[1],2); - if (nread <= 0) { return nread; } - } else if ((byte & 0xF8) == 0xF0) { - nread = read(fd,&buf[1],3); - if (nread <= 0) { return nread; } - } else { - return -1; - } - - return unicodeUTF8CharToCodePoint(buf, 4, cp); - } - -/* ======================= Low level terminal handling ====================== */ - -/* Set if to use or not the multi line mode. */ - inline void SetMultiLine(bool ml) { - mlmode = ml; - } - -/* Return true if the terminal name is in the list of terminals we know are - * not able to understand basic escape sequences. */ - inline bool isUnsupportedTerm(void) { -#ifndef _WIN32 - char *term = getenv("TERM"); - int j; - - if (term == NULL) return false; - for (j = 0; unsupported_term[j]; j++) - if (!strcasecmp(term,unsupported_term[j])) return true; -#endif - return false; - } - -/* Raw mode: 1960 magic shit. */ - inline bool enableRawMode(int fd) { -#ifndef _WIN32 - struct termios raw; - - if (!isatty(STDIN_FILENO)) goto fatal; - if (!atexit_registered) { - atexit(linenoiseAtExit); - atexit_registered = true; - } - if (tcgetattr(fd,&orig_termios) == -1) goto fatal; - - raw = orig_termios; /* modify the original mode */ - /* input modes: no break, no CR to NL, no parity check, no strip char, - * no start/stop output control. */ - raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); - /* output modes - disable post processing */ - // NOTE: Multithreaded issue #20 (https://github.com/yhirose/cpp-linenoise/issues/20) - // raw.c_oflag &= ~(OPOST); - /* control modes - set 8 bit chars */ - raw.c_cflag |= (CS8); - /* local modes - echoing off, canonical off, no extended functions, - * no signal chars (^Z,^C) */ - raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); - /* control chars - set return condition: min number of bytes and timer. - * We want read to return every single byte, without timeout. */ - raw.c_cc[VMIN] = 1; raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ - - /* put terminal in raw mode after flushing */ - if (tcsetattr(fd,TCSAFLUSH,&raw) < 0) goto fatal; - rawmode = true; -#else - if (!atexit_registered) { - /* Cleanup them at exit */ - atexit(linenoiseAtExit); - atexit_registered = true; - - /* Init windows console handles only once */ - hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut==INVALID_HANDLE_VALUE) goto fatal; - } - - DWORD consolemodeOut; - if (!GetConsoleMode(hOut, &consolemodeOut)) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - }; - - hIn = GetStdHandle(STD_INPUT_HANDLE); - if (hIn == INVALID_HANDLE_VALUE) { - CloseHandle(hOut); - errno = ENOTTY; - return false; - } - - GetConsoleMode(hIn, &consolemodeIn); - /* Enable raw mode */ - SetConsoleMode(hIn, consolemodeIn & ~ENABLE_PROCESSED_INPUT); - - rawmode = true; -#endif - return true; - - fatal: - errno = ENOTTY; - return false; - } - - inline void disableRawMode(int fd) { -#ifdef _WIN32 - if (consolemodeIn) { - SetConsoleMode(hIn, consolemodeIn); - consolemodeIn = 0; - } - rawmode = false; -#else - /* Don't even check the return value as it's too late. */ - if (rawmode && tcsetattr(fd,TCSAFLUSH,&orig_termios) != -1) - rawmode = false; -#endif - } - -/* Use the ESC [6n escape sequence to query the horizontal cursor position - * and return it. On error -1 is returned, on success the position of the - * cursor. */ - inline int getCursorPosition(int ifd, int ofd) { - char buf[32]; - int cols, rows; - unsigned int i = 0; - - /* Report cursor location */ - if (write(ofd, "\x1b[6n", 4) != 4) return -1; - - /* Read the response: ESC [ rows ; cols R */ - while (i < sizeof(buf)-1) { - if (read(ifd,buf+i,1) != 1) break; - if (buf[i] == 'R') break; - i++; - } - buf[i] = '\0'; - - /* Parse it. */ - if (buf[0] != ESC || buf[1] != '[') return -1; - if (sscanf(buf+2,"%d;%d",&rows,&cols) != 2) return -1; - return cols; - } - -/* Try to get the number of columns in the current terminal, or assume 80 - * if it fails. */ - inline int getColumns(int ifd, int ofd) { -#ifdef _WIN32 - CONSOLE_SCREEN_BUFFER_INFO b; - - if (!GetConsoleScreenBufferInfo(hOut, &b)) return 80; - return b.srWindow.Right - b.srWindow.Left; -#else - struct winsize ws; - - if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) { - /* ioctl() failed. Try to query the terminal itself. */ - int start, cols; - - /* Get the initial position so we can restore it later. */ - start = getCursorPosition(ifd,ofd); - if (start == -1) goto failed; - - /* Go to right margin and get position. */ - if (write(ofd,"\x1b[999C",6) != 6) goto failed; - cols = getCursorPosition(ifd,ofd); - if (cols == -1) goto failed; - - /* Restore position. */ - if (cols > start) { - char seq[32]; - snprintf(seq,32,"\x1b[%dD",cols-start); - if (write(ofd,seq,strlen(seq)) == -1) { - /* Can't recover... */ - } - } - return cols; - } else { - return ws.ws_col; - } - - failed: - return 80; -#endif - } - -/* Clear the screen. Used to handle ctrl+l */ - inline void linenoiseClearScreen(void) { - if (write(STDOUT_FILENO,"\x1b[H\x1b[2J",7) <= 0) { - /* nothing to do, just to avoid warning. */ - } - } - -/* Beep, used for completion when there is nothing to complete or when all - * the choices were already shown. */ - inline void linenoiseBeep(void) { - fprintf(stderr, "\x7"); - fflush(stderr); - } - -/* ============================== Completion ================================ */ - -/* This is an helper function for linenoiseEdit() and is called when the - * user types the key in order to complete the string currently in the - * input. - * - * The state of the editing is encapsulated into the pointed linenoiseState - * structure as described in the structure definition. */ - inline int completeLine(struct linenoiseState *ls, char *cbuf, int *c) { - std::vector lc; - int nread = 0, nwritten; - *c = 0; - - completionCallback(ls->buf,lc); - if (lc.empty()) { - linenoiseBeep(); - } else { - int stop = 0, i = 0; - - while(!stop) { - /* Show completion or original buffer */ - if (i < static_cast(lc.size())) { - struct linenoiseState saved = *ls; - - ls->len = ls->pos = static_cast(lc[i].size()); - ls->buf = &lc[i][0]; - refreshLine(ls); - ls->len = saved.len; - ls->pos = saved.pos; - ls->buf = saved.buf; - } else { - refreshLine(ls); - } - - //nread = read(ls->ifd,&c,1); -#ifdef _WIN32 - nread = win32read(c); - if (nread == 1) { - cbuf[0] = *c; - } -#else - nread = unicodeReadUTF8Char(ls->ifd,cbuf,c); -#endif - if (nread <= 0) { - *c = -1; - return nread; - } - - switch(*c) { - case 9: /* tab */ - i = (i+1) % (lc.size()+1); - if (i == static_cast(lc.size())) linenoiseBeep(); - break; - case 27: /* escape */ - /* Re-show original buffer */ - if (i < static_cast(lc.size())) refreshLine(ls); - stop = 1; - break; - default: - /* Update buffer and return */ - if (i < static_cast(lc.size())) { - nwritten = snprintf(ls->buf,ls->buflen,"%s",&lc[i][0]); - ls->len = ls->pos = nwritten; - } - stop = 1; - break; - } - } - } - - return nread; - } - -/* Register a callback function to be called for tab-completion. */ - inline void SetCompletionCallback(CompletionCallback fn) { - completionCallback = fn; - } - -/* =========================== Line editing ================================= */ - -/* Single line low level line refresh. - * - * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ - inline void refreshSingleLine(struct linenoiseState *l) { - char seq[64]; - int pcolwid = unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); - int fd = l->ofd; - char *buf = l->buf; - int len = l->len; - int pos = l->pos; - std::string ab; - - while((pcolwid+unicodeColumnPos(buf, pos)) >= l->cols) { - int glen = unicodeGraphemeLen(buf, len, 0); - buf += glen; - len -= glen; - pos -= glen; - } - while (pcolwid+unicodeColumnPos(buf, len) > l->cols) { - len -= unicodePrevGraphemeLen(buf, len); - } - - /* Cursor to left edge */ - snprintf(seq,64,"\r"); - ab += seq; - /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(buf, len); - /* Erase to right */ - snprintf(seq,64,"\x1b[0K"); - ab += seq; - /* Move cursor to original position. */ - snprintf(seq,64,"\r\x1b[%dC", (int)(unicodeColumnPos(buf, pos)+pcolwid)); - ab += seq; - if (write(fd,ab.c_str(), static_cast(ab.length())) == -1) {} /* Can't recover from write error. */ - } - -/* Multi line low level line refresh. - * - * Rewrite the currently edited line accordingly to the buffer content, - * cursor position, and number of columns of the terminal. */ - inline void refreshMultiLine(struct linenoiseState *l) { - char seq[64]; - int pcolwid = unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length())); - int colpos = unicodeColumnPosForMultiLine(l->buf, l->len, l->len, l->cols, pcolwid); - int colpos2; /* cursor column position. */ - int rows = (pcolwid+colpos+l->cols-1)/l->cols; /* rows used by current buf. */ - int rpos = (pcolwid+l->oldcolpos+l->cols)/l->cols; /* cursor relative row. */ - int rpos2; /* rpos after refresh. */ - int col; /* colum position, zero-based. */ - int old_rows = (int)l->maxrows; - int fd = l->ofd, j; - std::string ab; - - /* Update maxrows if needed. */ - if (rows > (int)l->maxrows) l->maxrows = rows; - - /* First step: clear all the lines used before. To do so start by - * going to the last row. */ - if (old_rows-rpos > 0) { - snprintf(seq,64,"\x1b[%dB", old_rows-rpos); - ab += seq; - } - - /* Now for every row clear it, go up. */ - for (j = 0; j < old_rows-1; j++) { - snprintf(seq,64,"\r\x1b[0K\x1b[1A"); - ab += seq; - } - - /* Clean the top line. */ - snprintf(seq,64,"\r\x1b[0K"); - ab += seq; - - /* Write the prompt and the current buffer content */ - ab += l->prompt; - ab.append(l->buf, l->len); - - /* Get text width to cursor position */ - colpos2 = unicodeColumnPosForMultiLine(l->buf, l->len, l->pos, l->cols, pcolwid); - - /* If we are at the very end of the screen with our prompt, we need to - * emit a newline and move the prompt to the first column. */ - if (l->pos && - l->pos == l->len && - (colpos2+pcolwid) % l->cols == 0) - { - ab += "\n"; - snprintf(seq,64,"\r"); - ab += seq; - rows++; - if (rows > (int)l->maxrows) l->maxrows = rows; - } - - /* Move cursor to right position. */ - rpos2 = (pcolwid+colpos2+l->cols)/l->cols; /* current cursor relative row. */ - - /* Go up till we reach the expected positon. */ - if (rows-rpos2 > 0) { - snprintf(seq,64,"\x1b[%dA", rows-rpos2); - ab += seq; - } - - /* Set column. */ - col = (pcolwid + colpos2) % l->cols; - if (col) - snprintf(seq,64,"\r\x1b[%dC", col); - else - snprintf(seq,64,"\r"); - ab += seq; - - l->oldcolpos = colpos2; - - if (write(fd,ab.c_str(), static_cast(ab.length())) == -1) {} /* Can't recover from write error. */ - } - -/* Calls the two low level functions refreshSingleLine() or - * refreshMultiLine() according to the selected mode. */ - inline void refreshLine(struct linenoiseState *l) { - if (mlmode) - refreshMultiLine(l); - else - refreshSingleLine(l); - } - -/* Insert the character 'c' at cursor current position. - * - * On error writing to the terminal -1 is returned, otherwise 0. */ - inline int linenoiseEditInsert(struct linenoiseState *l, const char* cbuf, int clen) { - if (l->len < l->buflen) { - if (l->len == l->pos) { - memcpy(&l->buf[l->pos],cbuf,clen); - l->pos+=clen; - l->len+=clen;; - l->buf[l->len] = '\0'; - if ((!mlmode && unicodeColumnPos(l->prompt.c_str(), static_cast(l->prompt.length()))+unicodeColumnPos(l->buf,l->len) < l->cols) /* || mlmode */) { - /* Avoid a full update of the line in the - * trivial case. */ - if (write(l->ofd,cbuf,clen) == -1) return -1; - } else { - refreshLine(l); - } - } else { - memmove(l->buf+l->pos+clen,l->buf+l->pos,l->len-l->pos); - memcpy(&l->buf[l->pos],cbuf,clen); - l->pos+=clen; - l->len+=clen; - l->buf[l->len] = '\0'; - refreshLine(l); - } - } - return 0; - } - -/* Move cursor on the left. */ - inline void linenoiseEditMoveLeft(struct linenoiseState *l) { - if (l->pos > 0) { - l->pos -= unicodePrevGraphemeLen(l->buf, l->pos); - refreshLine(l); - } - } - -/* Move cursor on the right. */ - inline void linenoiseEditMoveRight(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos += unicodeGraphemeLen(l->buf, l->len, l->pos); - refreshLine(l); - } - } - -/* Move cursor to the start of the line. */ - inline void linenoiseEditMoveHome(struct linenoiseState *l) { - if (l->pos != 0) { - l->pos = 0; - refreshLine(l); - } - } - -/* Move cursor to the end of the line. */ - inline void linenoiseEditMoveEnd(struct linenoiseState *l) { - if (l->pos != l->len) { - l->pos = l->len; - refreshLine(l); - } - } - -/* Substitute the currently edited line with the next or previous history - * entry as specified by 'dir'. */ -#define LINENOISE_HISTORY_NEXT 0 -#define LINENOISE_HISTORY_PREV 1 - inline void linenoiseEditHistoryNext(struct linenoiseState *l, int dir) { - if (history.size() > 1) { - /* Update the current history entry before to - * overwrite it with the next one. */ - history[history.size() - 1 - l->history_index] = l->buf; - /* Show the new entry */ - l->history_index += (dir == LINENOISE_HISTORY_PREV) ? 1 : -1; - if (l->history_index < 0) { - l->history_index = 0; - return; - } else if (l->history_index >= (int)history.size()) { - l->history_index = static_cast(history.size())-1; - return; - } - memset(l->buf, 0, l->buflen); - strcpy(l->buf,history[history.size() - 1 - l->history_index].c_str()); - l->len = l->pos = static_cast(strlen(l->buf)); - refreshLine(l); - } - } - -/* Delete the character at the right of the cursor without altering the cursor - * position. Basically this is what happens with the "Delete" keyboard key. */ - inline void linenoiseEditDelete(struct linenoiseState *l) { - if (l->len > 0 && l->pos < l->len) { - int glen = unicodeGraphemeLen(l->buf,l->len,l->pos); - memmove(l->buf+l->pos,l->buf+l->pos+glen,l->len-l->pos-glen); - l->len-=glen; - l->buf[l->len] = '\0'; - refreshLine(l); - } - } - -/* Backspace implementation. */ - inline void linenoiseEditBackspace(struct linenoiseState *l) { - if (l->pos > 0 && l->len > 0) { - int glen = unicodePrevGraphemeLen(l->buf,l->pos); - memmove(l->buf+l->pos-glen,l->buf+l->pos,l->len-l->pos); - l->pos-=glen; - l->len-=glen; - l->buf[l->len] = '\0'; - refreshLine(l); - } - } - -/* Delete the previosu word, maintaining the cursor at the start of the - * current word. */ - inline void linenoiseEditDeletePrevWord(struct linenoiseState *l) { - int old_pos = l->pos; - int diff; - - while (l->pos > 0 && l->buf[l->pos-1] == ' ') - l->pos--; - while (l->pos > 0 && l->buf[l->pos-1] != ' ') - l->pos--; - diff = old_pos - l->pos; - memmove(l->buf+l->pos,l->buf+old_pos,l->len-old_pos+1); - l->len -= diff; - refreshLine(l); - } - -/* This function is the core of the line editing capability of linenoise. - * It expects 'fd' to be already in "raw mode" so that every key pressed - * will be returned ASAP to read(). - * - * The resulting string is put into 'buf' when the user type enter, or - * when ctrl+d is typed. - * - * The function returns the length of the current buffer. */ - inline int linenoiseEdit(int stdin_fd, int stdout_fd, char *buf, int buflen, const char *prompt) - { - struct linenoiseState l; - - /* Populate the linenoise state that we pass to functions implementing - * specific editing functionalities. */ - l.ifd = stdin_fd; - l.ofd = stdout_fd; - l.buf = buf; - l.buflen = buflen; - l.prompt = prompt; - l.oldcolpos = l.pos = 0; - l.len = 0; - l.cols = getColumns(stdin_fd, stdout_fd); - l.maxrows = 0; - l.history_index = 0; - - /* Buffer starts empty. */ - l.buf[0] = '\0'; - l.buflen--; /* Make sure there is always space for the nulterm */ - - /* The latest history entry is always our current buffer, that - * initially is just an empty string. */ - AddHistory(""); - - if (write(l.ofd,prompt, static_cast(l.prompt.length())) == -1) return -1; - while(1) { - int c; - char cbuf[4]; - int nread; - char seq[3]; - -#ifdef _WIN32 - nread = win32read(&c); - if (nread == 1) { - cbuf[0] = c; - } -#else - nread = unicodeReadUTF8Char(l.ifd,cbuf,&c); -#endif - if (nread <= 0) return (int)l.len; - - /* Only autocomplete when the callback is set. It returns < 0 when - * there was an error reading from fd. Otherwise it will return the - * character that should be handled next. */ - if (c == 9 && completionCallback != NULL) { - nread = completeLine(&l,cbuf,&c); - /* Return on errors */ - if (c < 0) return l.len; - /* Read next character when 0 */ - if (c == 0) continue; - } - - switch(c) { - case ENTER: /* enter */ - if (!history.empty()) history.pop_back(); - if (mlmode) linenoiseEditMoveEnd(&l); - return (int)l.len; - case CTRL_C: /* ctrl-c */ - errno = EAGAIN; - return -1; - case BACKSPACE: /* backspace */ - case 8: /* ctrl-h */ - linenoiseEditBackspace(&l); - break; - case CTRL_D: /* ctrl-d, remove char at right of cursor, or if the - line is empty, act as end-of-file. */ - if (l.len > 0) { - linenoiseEditDelete(&l); - } else { - history.pop_back(); - return -1; - } - break; - case CTRL_T: /* ctrl-t, swaps current character with previous. */ - if (l.pos > 0 && l.pos < l.len) { - char aux = buf[l.pos-1]; - buf[l.pos-1] = buf[l.pos]; - buf[l.pos] = aux; - if (l.pos != l.len-1) l.pos++; - refreshLine(&l); - } - break; - case CTRL_B: /* ctrl-b */ - linenoiseEditMoveLeft(&l); - break; - case CTRL_F: /* ctrl-f */ - linenoiseEditMoveRight(&l); - break; - case CTRL_P: /* ctrl-p */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case CTRL_N: /* ctrl-n */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case ESC: /* escape sequence */ - /* Read the next two bytes representing the escape sequence. - * Use two calls to handle slow terminals returning the two - * chars at different times. */ - if (read(l.ifd,seq,1) == -1) break; - if (read(l.ifd,seq+1,1) == -1) break; - - /* ESC [ sequences. */ - if (seq[0] == '[') { - if (seq[1] >= '0' && seq[1] <= '9') { - /* Extended escape, read additional byte. */ - if (read(l.ifd,seq+2,1) == -1) break; - if (seq[2] == '~') { - switch(seq[1]) { - case '3': /* Delete key. */ - linenoiseEditDelete(&l); - break; - } - } - } else { - switch(seq[1]) { - case 'A': /* Up */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_PREV); - break; - case 'B': /* Down */ - linenoiseEditHistoryNext(&l, LINENOISE_HISTORY_NEXT); - break; - case 'C': /* Right */ - linenoiseEditMoveRight(&l); - break; - case 'D': /* Left */ - linenoiseEditMoveLeft(&l); - break; - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - } - - /* ESC O sequences. */ - else if (seq[0] == 'O') { - switch(seq[1]) { - case 'H': /* Home */ - linenoiseEditMoveHome(&l); - break; - case 'F': /* End*/ - linenoiseEditMoveEnd(&l); - break; - } - } - break; - default: - if (linenoiseEditInsert(&l,cbuf,nread)) return -1; - break; - case CTRL_U: /* Ctrl+u, delete the whole line. */ - buf[0] = '\0'; - l.pos = l.len = 0; - refreshLine(&l); - break; - case CTRL_K: /* Ctrl+k, delete from current to end of line. */ - buf[l.pos] = '\0'; - l.len = l.pos; - refreshLine(&l); - break; - case CTRL_A: /* Ctrl+a, go to the start of the line */ - linenoiseEditMoveHome(&l); - break; - case CTRL_E: /* ctrl+e, go to the end of the line */ - linenoiseEditMoveEnd(&l); - break; - case CTRL_L: /* ctrl+l, clear screen */ - linenoiseClearScreen(); - refreshLine(&l); - break; - case CTRL_W: /* ctrl+w, delete previous word */ - linenoiseEditDeletePrevWord(&l); - break; - } - } - return l.len; - } - -/* This function calls the line editing function linenoiseEdit() using - * the STDIN file descriptor set in raw mode. */ - inline bool linenoiseRaw(const char *prompt, std::string& line) { - bool quit = false; - - if (!isatty(STDIN_FILENO)) { - /* Not a tty: read from file / pipe. */ - std::getline(std::cin, line); - } else { - /* Interactive editing. */ - if (enableRawMode(STDIN_FILENO) == false) { - return quit; - } - - char buf[LINENOISE_MAX_LINE]; - auto count = linenoiseEdit(STDIN_FILENO, STDOUT_FILENO, buf, LINENOISE_MAX_LINE, prompt); - if (count == -1) { - quit = true; - } else { - line.assign(buf, count); - } - - disableRawMode(STDIN_FILENO); - printf("\n"); - } - return quit; - } - -/* The high level function that is the main API of the linenoise library. - * This function checks if the terminal has basic capabilities, just checking - * for a blacklist of stupid terminals, and later either calls the line - * editing function or uses dummy fgets() so that you will be able to type - * something even in the most desperate of the conditions. */ - inline bool Readline(const char *prompt, std::string& line) { - if (isUnsupportedTerm()) { - printf("%s",prompt); - fflush(stdout); - std::getline(std::cin, line); - return false; - } else { - return linenoiseRaw(prompt, line); - } - } - - inline std::string Readline(const char *prompt, bool& quit) { - std::string line; - quit = Readline(prompt, line); - return line; - } - - inline std::string Readline(const char *prompt) { - bool quit; // dummy - return Readline(prompt, quit); - } - -/* ================================ History ================================= */ - -/* At exit we'll try to fix the terminal to the initial conditions. */ - inline void linenoiseAtExit(void) { - disableRawMode(STDIN_FILENO); - } - -/* This is the API call to add a new entry in the linenoise history. - * It uses a fixed array of char pointers that are shifted (memmoved) - * when the history max length is reached in order to remove the older - * entry and make room for the new one, so it is not exactly suitable for huge - * histories, but will work well for a few hundred of entries. - * - * Using a circular buffer is smarter, but a bit more complex to handle. */ - inline bool AddHistory(const char* line) { - if (history_max_len == 0) return false; - - /* Don't add duplicated lines. */ - if (!history.empty() && history.back() == line) return false; - - /* If we reached the max length, remove the older line. */ - if (history.size() == history_max_len) { - history.erase(history.begin()); - } - history.push_back(line); - - return true; - } - -/* Set the maximum length for the history. This function can be called even - * if there is already some history, the function will make sure to retain - * just the latest 'len' elements if the new history length value is smaller - * than the amount of items already inside the history. */ - inline bool SetHistoryMaxLen(size_t len) { - if (len < 1) return false; - history_max_len = len; - if (len < history.size()) { - history.resize(len); - } - return true; - } - -/* Save the history in the specified file. On success *true* is returned - * otherwise *false* is returned. */ - inline bool SaveHistory(const char* path) { - std::ofstream f(path); // TODO: need 'std::ios::binary'? - if (!f) return false; - for (const auto& h: history) { - f << h << std::endl; - } - return true; - } - -/* Load the history from the specified file. If the file does not exist - * zero is returned and no operation is performed. - * - * If the file exists and the operation succeeded *true* is returned, otherwise - * on error *false* is returned. */ - inline bool LoadHistory(const char* path) { - std::ifstream f(path); - if (!f) return false; - std::string line; - while (std::getline(f, line)) { - AddHistory(line.c_str()); - } - return true; - } - - inline const std::vector& GetHistory() { - return history; - } - -} // namespace linenoise - -#ifdef _WIN32 -#undef isatty -#undef write -#undef read -#pragma warning(pop) -#endif - -#endif /* __LINENOISE_HPP */ \ No newline at end of file diff --git a/third-party/replxx/.appveyor.yml b/third-party/replxx/.appveyor.yml new file mode 100644 index 0000000000..acc9a253bb --- /dev/null +++ b/third-party/replxx/.appveyor.yml @@ -0,0 +1,11 @@ +version: 1.0.{build} +branches: + only: + - master +configuration: Release +build: +build_script: + - md build + - cd %APPVEYOR_BUILD_FOLDER%\build + - cmake -G "Visual Studio 12 Win64" -DCMAKE_BUILD_TYPE=Release .. + - cmake --build . --config Release diff --git a/third-party/replxx/.editorconfig b/third-party/replxx/.editorconfig new file mode 100644 index 0000000000..e13caf58f2 --- /dev/null +++ b/third-party/replxx/.editorconfig @@ -0,0 +1,8 @@ +[*] + +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = tab +indent_size = 2 + diff --git a/third-party/replxx/.gitignore b/third-party/replxx/.gitignore new file mode 100644 index 0000000000..ae6cfd83dc --- /dev/null +++ b/third-party/replxx/.gitignore @@ -0,0 +1,14 @@ +CMakeCache.txt +CMakeFiles +Makefile +build +cmake_install.cmake +install_manifest.txt +example +linenoise_example +libreplxx.a +*.dSYM +history.txt +*.o +*~ +.vs \ No newline at end of file diff --git a/third-party/replxx/.travis.yml b/third-party/replxx/.travis.yml new file mode 100644 index 0000000000..13af478fb2 --- /dev/null +++ b/third-party/replxx/.travis.yml @@ -0,0 +1,14 @@ +language: C++ +dist: bionic +script: + - ./build-all.sh + - env SKIP=8bit_encoding ./tests.py +addons: + apt: + packages: + - g++ + - cmake + - python3-pexpect +sudo: false +cache: + ccache: true diff --git a/third-party/replxx/CMakeLists.txt b/third-party/replxx/CMakeLists.txt new file mode 100644 index 0000000000..fd71afee64 --- /dev/null +++ b/third-party/replxx/CMakeLists.txt @@ -0,0 +1,220 @@ +cmake_minimum_required(VERSION 3.5) +project( + replxx + # HOMEPAGE_URL "https://github.com/AmokHuginnsson/replxx" + # DESCRIPTION "replxx - Read Evaluate Print Loop library" + VERSION 0.0.4 + LANGUAGES CXX C +) + +if (NOT DEFINED CMAKE_CXX_STANDARD) + set(CMAKE_CXX_STANDARD 11) +endif() + +if (NOT DEFINED CMAKE_C_STANDARD) + set(CMAKE_C_STANDARD 99) +endif() + +set(CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS ON) + +include(CMakePackageConfigHelpers) +include(CMakeDependentOption) +include(GenerateExportHeader) +include(GNUInstallDirs) + +find_package(Threads) + +cmake_dependent_option( + REPLXX_BUILD_EXAMPLES + "Build the examples" ON + "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF +) +cmake_dependent_option( + REPLXX_BUILD_PACKAGE + "Generate package target" ON + "CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR" OFF +) + +if (NOT CMAKE_BUILD_TYPE) + message(AUTHOR_WARNING "CMAKE_BUILD_TYPE not set. Defaulting to Release") + set(CMAKE_BUILD_TYPE Release) +endif() + +# INFO +set(REPLXX_URL_INFO_ABOUT "https://github.com/AmokHuginnsson/replxx") +set(REPLXX_DISPLAY_NAME "replxx") +set(REPLXX_CONTACT "amok@codestation.org") + +set(is-clang $,$>) +set(is-msvc $) +set(is-gnu $) +set(compiler-id-clang-or-gnu $) + +set(coverage-config $,$>) + +set(replxx-source-patterns "src/*.cpp" "src/*.cxx") + +if (CMAKE_VERSION VERSION_GREATER 3.11) + list(INSERT replxx-source-patterns 0 CONFIGURE_DEPENDS) +endif() + +file(GLOB replxx-sources ${replxx-source-patterns}) + +add_library(replxx ${replxx-sources}) +add_library(replxx::replxx ALIAS replxx) + +target_include_directories( + replxx + PUBLIC + $ + $ + PRIVATE + $ +) +target_compile_definitions( + replxx + PUBLIC + $<$>:REPLXX_STATIC> + $<$:REPLXX_BUILDING_DLL> + PRIVATE + $<$:_CRT_SECURE_NO_WARNINGS=1 /ignore:4503> +) +target_compile_options( + replxx + PRIVATE + $<$,${compiler-id-clang-or-gnu}>:-fomit-frame-pointer> + $<$,${compiler-id-clang-or-gnu}>:-Os> + $<$,$>:-g -ggdb -g3 -ggdb3> + $<${coverage-config}:-O0 --coverage> + $<${coverage-config}:-fno-inline -fno-default-inline> + $<${coverage-config}:-fno-inline-small-functions> + $<${compiler-id-clang-or-gnu}:-Wall -Wextra> + $<$:-Wno-unknown-pragmas> +) +if (NOT CMAKE_VERSION VERSION_LESS 3.13) + target_link_options( + replxx + PRIVATE + $<${coverage-config}:--coverage> + $<${is-msvc}:/ignore:4099> + ) +else() +# "safest" way prior to 3.13 + target_link_libraries( + replxx + PRIVATE + $<${coverage-config}:--coverage> + $<${is-msvc}:/ignore:4099> + ) +endif() +target_link_libraries(replxx PUBLIC Threads::Threads) +set_target_properties(replxx PROPERTIES VERSION ${PROJECT_VERSION}) + +set_property(TARGET replxx PROPERTY DEBUG_POSTFIX -d) +set_property(TARGET replxx PROPERTY RELWITHDEBINFO_POSTFIX -rd) +set_property(TARGET replxx PROPERTY MINSIZEREL_POSTFIX) +if ( NOT BUILD_SHARED_LIBS AND MSVC ) + set_property(TARGET replxx PROPERTY OUTPUT_NAME replxx-static) +endif() + +generate_export_header(replxx) + +configure_package_config_file( + "${PROJECT_SOURCE_DIR}/replxx-config.cmake.in" + "${PROJECT_BINARY_DIR}/replxx-config.cmake" + INSTALL_DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/replxx + NO_CHECK_REQUIRED_COMPONENTS_MACRO + NO_SET_AND_CHECK_MACRO +) + +write_basic_package_version_file( + "${PROJECT_BINARY_DIR}/replxx-config-version.cmake" + COMPATIBILITY AnyNewerVersion +) + +install( + TARGETS replxx EXPORT replxx-targets + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +) + +install( + EXPORT replxx-targets + NAMESPACE replxx:: + DESTINATION ${CMAKE_INSTALL_DATADIR}/cmake/replxx +) + +install( + FILES + "${PROJECT_BINARY_DIR}/replxx-config-version.cmake" + "${PROJECT_BINARY_DIR}/replxx-config.cmake" + DESTINATION + ${CMAKE_INSTALL_DATADIR}/cmake/replxx +) + +# headers +install( + FILES + include/replxx.hxx + include/replxx.h + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR} +) + +if (REPLXX_BUILD_EXAMPLES) + add_executable(replxx-example-cxx-api "") + add_executable(replxx-example-c-api "") + + target_sources( + replxx-example-cxx-api + PRIVATE + examples/cxx-api.cxx + examples/util.c + ) + target_sources( + replxx-example-c-api + PRIVATE + examples/c-api.c + examples/util.c + ) + target_compile_definitions(replxx-example-cxx-api PRIVATE REPLXX_STATIC $<$:_CRT_SECURE_NO_WARNINGS=1>) + target_compile_definitions(replxx-example-c-api PRIVATE REPLXX_STATIC $<$:_CRT_SECURE_NO_WARNINGS=1>) + target_link_libraries(replxx-example-cxx-api PRIVATE replxx::replxx) + target_link_libraries( + replxx-example-c-api + PRIVATE + replxx::replxx + $<${compiler-id-clang-or-gnu}:stdc++> + $<${is-clang}:m> + $<${coverage-config}:--coverage> + ) + target_link_libraries( + replxx-example-cxx-api + PRIVATE + $<${coverage-config}:--coverage> + ) +endif() + +if (NOT REPLXX_BUILD_PACKAGE) + return() +endif() + +include(CPack) + +set(CPACK_SET_DESTDIR ON) + +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Readline and libedit replacement library") +set(CPACK_PACKAGE_HOMEPAGE_URL "${REPLXX_URL_INFO_ABOUT}") +set(CPACK_PACKAGE_VENDOR "codestation.org") +set(CPACK_PACKAGE_CONTACT "amok@codestation.org") +set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}") + +set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + +set(CPACK_STRIP_FILES "ON") + +set(CPACK_DEBIAN_PACKAGE_SECTION "utilities") + diff --git a/third-party/replxx/LICENSE.md b/third-party/replxx/LICENSE.md new file mode 100644 index 0000000000..c73c3f241e --- /dev/null +++ b/third-party/replxx/LICENSE.md @@ -0,0 +1,63 @@ +Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) +Copyright (c) 2010, Salvatore Sanfilippo (antirez at gmail dot com) +Copyright (c) 2010, Pieter Noordhuis (pcnoordhuis at gmail dot com) + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Redis nor the names of its contributors may be used + to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + + +wcwidth.cpp +=========== + +Markus Kuhn -- 2007-05-26 (Unicode 5.0) + +Permission to use, copy, modify, and distribute this software +for any purpose and without fee is hereby granted. The author +disclaims all warranties with regard to this software. + + +ConvertUTF.cpp +============== + +Copyright 2001-2004 Unicode, Inc. + +Disclaimer + +This source code is provided as is by Unicode, Inc. No claims are +made as to fitness for any particular purpose. No warranties of any +kind are expressed or implied. The recipient agrees to determine +applicability of information provided. If this file has been +purchased on magnetic or optical media from Unicode, Inc., the +sole remedy for any claim will be exchange of defective media +within 90 days of receipt. + +Limitations on Rights to Redistribute This Code + +Unicode, Inc. hereby grants the right to freely use the information +supplied in this file in the creation of products supporting the +Unicode Standard, and to make copies of this file in any form +for internal or external distribution as long as this notice +remains attached. diff --git a/third-party/replxx/README.md b/third-party/replxx/README.md new file mode 100644 index 0000000000..f66a12a47b --- /dev/null +++ b/third-party/replxx/README.md @@ -0,0 +1,119 @@ +# Read Evaluate Print Loop ++ + +![demo](https://drive.google.com/uc?export=download&id=0B53g2Y3z7rWNT2dCRGVVNldaRnc) + +[![Build Status](https://travis-ci.org/AmokHuginnsson/replxx.svg?branch=master)](https://travis-ci.org/AmokHuginnsson/replxx) + +A small, portable GNU readline replacement for Linux, Windows and +MacOS which is capable of handling UTF-8 characters. Unlike GNU +readline, which is GPL, this library uses a BSD license and can be +used in any kind of program. + +## Origin + +This replxx implementation is based on the work by +[ArangoDB Team](https://github.com/arangodb/linenoise-ng) and +[Salvatore Sanfilippo](https://github.com/antirez/linenoise) and +10gen Inc. The goal is to create a zero-config, BSD +licensed, readline replacement usable in Apache2 or BSD licensed +programs. + +## Features + +* single-line and multi-line editing mode with the usual key bindings implemented +* history handling +* completion +* syntax highlighting +* hints +* BSD license source code +* Only uses a subset of VT100 escapes (ANSI.SYS compatible) +* UTF8 aware +* support for Linux, MacOS and Windows + +It deviates from Salvatore's original goal to have a minimal readline +replacement for the sake of supporting UTF8 and Windows. It deviates +from 10gen Inc.'s goal to create a C++ interface to linenoise. This +library uses C++ internally, but to the user it provides a pure C +interface that is compatible with the original linenoise API. +C interface. + +## Requirements + +To build this library, you will need a C++11-enabled compiler and +some recent version of CMake. + +## Build instructions + +### *nix + +1. Create a build directory + +```bash +mkdir -p build && cd build +``` + +2. Build the library + +```bash +cmake -DCMAKE_BUILD_TYPE=Release .. && make +``` + +3. Install the library at the default target location + +```bash +sudo make install +``` + +The default installation location can be adjusted by setting the `DESTDIR` +variable when invoking `make install`: + +```bash +make DESTDIR=/tmp install +``` + +### Windows + +1. Create a build directory in MS-DOS command prompt + +``` +md build +cd build +``` + +2. Generate Visual Studio solution file with cmake + +* 32 bit: +```bash +cmake -G "Visual Studio 12 2013" -DCMAKE_BUILD_TYPE=Release .. +``` +* 64 bit: +```bash +cmake -G "Visual Studio 12 2013 Win64" -DCMAKE_BUILD_TYPE=Release .. +``` + +3. Open the generated file `replxx.sln` in the `build` subdirectory with Visual Studio. + +## Tested with... + + * Linux text only console ($TERM = linux) + * Linux KDE terminal application ($TERM = xterm) + * Linux xterm ($TERM = xterm) + * Linux Buildroot ($TERM = vt100) + * Mac OS X iTerm ($TERM = xterm) + * Mac OS X default Terminal.app ($TERM = xterm) + * OpenBSD 4.5 through an OSX Terminal.app ($TERM = screen) + * IBM AIX 6.1 + * FreeBSD xterm ($TERM = xterm) + * ANSI.SYS + * Emacs comint mode ($TERM = dumb) + * Windows + +Please test it everywhere you can and report back! + +## Let's push this forward! + +Patches should be provided in the respect of linenoise sensibility for +small and easy to understand code that and the license +restrictions. Extensions must be submitted under a BSD license-style. +A contributor license is required for contributions. + diff --git a/third-party/replxx/build-all.sh b/third-party/replxx/build-all.sh new file mode 100644 index 0000000000..468ae95f94 --- /dev/null +++ b/third-party/replxx/build-all.sh @@ -0,0 +1,93 @@ +#! /bin/sh + +while [ ${#} -gt 0 ] ; do + if [ \( ${#} -gt 0 \) -a \( "x${1}" = "xpurge" \) ] ; then + ${sudo} /bin/rm -rf build + shift + continue + fi + + if [ \( ${#} -gt 0 \) -a \( "x${1}" = "xsudo" \) ] ; then + sudo="sudo" + shift + continue + fi + + if [ \( ${#} -gt 0 \) -a \( "x${1}" = "xmsvcxx" \) ] ; then + msvcxx=1 + shift + continue + fi + + if [ \( ${#} -gt 0 \) -a \( "x${1}" = "xx86" \) ] ; then + arch="-A Win32" + shift + continue + fi + + if [ \( ${#} -gt 0 \) -a \( "x${1}" = "xstatic-only" \) ] ; then + skip="shared" + shift + continue + fi + + if [ \( ${#} -gt 0 \) -a \( "x${1}" = "xshared-only" \) ] ; then + skip="static" + shift + continue + fi + + if [ ${#} -gt 0 ] ; then + installPrefix="-DCMAKE_INSTALL_PREFIX=${1}" + shift + continue + fi +done + +umask 0022 + +build_target() { + target="${1}" + linkMode="${2}" + if [ "x${linkMode}" = "xshared" ] ; then + shared="-DBUILD_SHARED_LIBS=ON" + examples="-DREPLXX_BUILD_EXAMPLES=OFF" + else + shared="-DBUILD_SHARED_LIBS=OFF" + examples="-DREPLXX_BUILD_EXAMPLES=ON" + fi + mkdir -p "build/${target}" + cd "build/${target}" + if [ "x${msvcxx}" = "x1" ] ; then + vswhere="/cygdrive/c/Program Files (x86)/Microsoft Visual Studio/Installer/vswhere.exe" + if [ -f "${vswhere}" ] ; then + name="$("${vswhere}" -latest -property catalog_productName | tr -d '\r')" + ver=$("${vswhere}" -latest -property installationVersion | awk -F '.' '{print $1}') + else + name="Visual Studio" + ver="14" + fi + cmake="/cygdrive/c/Program Files/CMake/bin/cmake.exe" + "${cmake}" ${STATIC} ${shared} ${examples} -G "${name} ${ver}" ${arch} ${installPrefix} ../../ + "${cmake}" --build . --config Debug + "${cmake}" --build . --config Release + "${cmake}" --build . --config Debug --target Install + "${cmake}" --build . --config Release --target Install + else + cmake -DCMAKE_BUILD_TYPE=${target} ${shared} ${examples} ${installPrefix} ../../ + make + echo "### `hostname` ###" + ${sudo} make install + fi + cd ../.. +} + +for target in debug release ; do + for linkMode in shared static ; do + if [ "x${linkMode}" = "x${skip}" ] ; then + continue + fi + build_target "${target}" "${linkMode}" + done +done + diff --git a/third-party/replxx/examples/c-api.c b/third-party/replxx/examples/c-api.c new file mode 100644 index 0000000000..84a67da148 --- /dev/null +++ b/third-party/replxx/examples/c-api.c @@ -0,0 +1,235 @@ +#ifndef __sun +#define _POSIX_C_SOURCE 200809L +#else +#define __EXTENSIONS__ 1 +#endif + +#include +#include +#include +#include +#include +#include + +#include "replxx.h" +#include "util.h" + +void modify_callback(char** line, int* cursorPosition, void* ud) { + char* s = *line; + char* p = strchr( s, '*' ); + if ( p ) { + int len = (int)strlen( s ); + char* n = *line = calloc( len * 2, 1 ); + int i = (int)( p - s ); + strncpy(n, s, i); + n += i; + strncpy(n, s, i); + n += i; + strncpy(n, p + 1, len - i - 1); + n += ( len - i - 1 ); + strncpy(n, p + 1, len - i - 1); + *cursorPosition *= 2; + free( s ); + } +} + +void completionHook(char const* context, replxx_completions* lc, int* contextLen, void* ud) { + char** examples = (char**)( ud ); + size_t i; + + int utf8ContextLen = context_len( context ); + int prefixLen = (int)strlen( context ) - utf8ContextLen; + *contextLen = utf8str_codepoint_len( context + prefixLen, utf8ContextLen ); + for (i = 0; examples[i] != NULL; ++i) { + if (strncmp(context + prefixLen, examples[i], utf8ContextLen) == 0) { + replxx_add_completion(lc, examples[i]); + } + } +} + +void hintHook(char const* context, replxx_hints* lc, int* contextLen, ReplxxColor* c, void* ud) { + char** examples = (char**)( ud ); + int i; + int utf8ContextLen = context_len( context ); + int prefixLen = (int)strlen( context ) - utf8ContextLen; + *contextLen = utf8str_codepoint_len( context + prefixLen, utf8ContextLen ); + if ( *contextLen > 0 ) { + for (i = 0; examples[i] != NULL; ++i) { + if (strncmp(context + prefixLen, examples[i], utf8ContextLen) == 0) { + replxx_add_hint(lc, examples[i]); + } + } + } +} + +void colorHook( char const* str_, ReplxxColor* colors_, int size_, void* ud ) { + int i = 0; + for ( ; i < size_; ++ i ) { + if ( isdigit( str_[i] ) ) { + colors_[i] = REPLXX_COLOR_BRIGHTMAGENTA; + } + } + if ( ( size_ > 0 ) && ( str_[size_ - 1] == '(' ) ) { + replxx_emulate_key_press( ud, ')' ); + replxx_emulate_key_press( ud, REPLXX_KEY_LEFT ); + } +} + +ReplxxActionResult word_eater( int ignored, void* ud ) { + Replxx* replxx = (Replxx*)ud; + return ( replxx_invoke( replxx, REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD, 0 ) ); +} + +ReplxxActionResult upper_case_line( int ignored, void* ud ) { + Replxx* replxx = (Replxx*)ud; + ReplxxState state; + replxx_get_state( replxx, &state ); + int l = (int)strlen( state.text ); +#ifdef _WIN32 +#define strdup _strdup +#endif + char* d = strdup( state.text ); +#undef strdup + for ( int i = 0; i < l; ++ i ) { + d[i] = toupper( d[i] ); + } + state.text = d; + state.cursorPosition /= 2; + replxx_set_state( replxx, &state ); + free( d ); + return ( REPLXX_ACTION_RESULT_CONTINUE ); +} + +char const* recode( char* s ) { + char const* r = s; + while ( *s ) { + if ( *s == '~' ) { + *s = '\n'; + } + ++ s; + } + return ( r ); +} + +void split( char* str_, char** data_, int size_ ) { + int i = 0; + char* p = str_, *o = p; + while ( i < size_ ) { + int last = *p == 0; + if ( ( *p == ',' ) || last ) { + *p = 0; + data_[i ++] = o; + o = p + 1; + if ( last ) { + break; + } + } + ++ p; + } + data_[i] = 0; +} + +int main( int argc, char** argv ) { +#define MAX_EXAMPLE_COUNT 128 + char* examples[MAX_EXAMPLE_COUNT + 1] = { + "db", "hello", "hallo", "hans", "hansekogge", "seamann", "quetzalcoatl", "quit", "power", NULL + }; + Replxx* replxx = replxx_init(); + replxx_install_window_change_handler( replxx ); + + int quiet = 0; + char const* prompt = "\x1b[1;32mreplxx\x1b[0m> "; + int installModifyCallback = 0; + int installCompletionCallback = 1; + int installHighlighterCallback = 1; + int installHintsCallback = 1; + while ( argc > 1 ) { + -- argc; + ++ argv; +#ifdef __REPLXX_DEBUG__ + if ( !strcmp( *argv, "--keycodes" ) ) { + replxx_debug_dump_print_codes(); + exit(0); + } +#endif + switch ( (*argv)[0] ) { + case 'b': replxx_set_beep_on_ambiguous_completion( replxx, (*argv)[1] - '0' ); break; + case 'c': replxx_set_completion_count_cutoff( replxx, atoi( (*argv) + 1 ) ); break; + case 'e': replxx_set_complete_on_empty( replxx, (*argv)[1] - '0' ); break; + case 'd': replxx_set_double_tab_completion( replxx, (*argv)[1] - '0' ); break; + case 'h': replxx_set_max_hint_rows( replxx, atoi( (*argv) + 1 ) ); break; + case 'H': replxx_set_hint_delay( replxx, atoi( (*argv) + 1 ) ); break; + case 's': replxx_set_max_history_size( replxx, atoi( (*argv) + 1 ) ); break; + case 'i': replxx_set_preload_buffer( replxx, recode( (*argv) + 1 ) ); break; + case 'I': replxx_set_immediate_completion( replxx, (*argv)[1] - '0' ); break; + case 'u': replxx_set_unique_history( replxx, (*argv)[1] - '0' ); break; + case 'w': replxx_set_word_break_characters( replxx, (*argv) + 1 ); break; + case 'm': replxx_set_no_color( replxx, (*argv)[1] - '0' ); break; + case 'B': replxx_enable_bracketed_paste( replxx ); break; + case 'p': prompt = recode( (*argv) + 1 ); break; + case 'q': quiet = atoi( (*argv) + 1 ); break; + case 'M': installModifyCallback = atoi( (*argv) + 1 ); break; + case 'C': installCompletionCallback = 0; break; + case 'S': installHighlighterCallback = 0; break; + case 'N': installHintsCallback = 0; break; + case 'x': split( (*argv) + 1, examples, MAX_EXAMPLE_COUNT ); break; + } + + } + + const char* file = "./replxx_history.txt"; + + replxx_history_load( replxx, file ); + if ( installModifyCallback ) { + replxx_set_modify_callback( replxx, modify_callback, 0 ); + } + if ( installCompletionCallback ) { + replxx_set_completion_callback( replxx, completionHook, examples ); + } + if ( installHighlighterCallback ) { + replxx_set_highlighter_callback( replxx, colorHook, replxx ); + } + if ( installHintsCallback ) { + replxx_set_hint_callback( replxx, hintHook, examples ); + } + replxx_bind_key( replxx, '.', word_eater, replxx ); + replxx_bind_key( replxx, REPLXX_KEY_F2, upper_case_line, replxx ); + + printf("starting...\n"); + + while (1) { + char const* result = NULL; + do { + result = replxx_input( replxx, prompt ); + } while ( ( result == NULL ) && ( errno == EAGAIN ) ); + + if (result == NULL) { + printf("\n"); + break; + } else if (!strncmp(result, "/history", 9)) { + /* Display the current history. */ + int index = 0; + int size = replxx_history_size( replxx ); + ReplxxHistoryScan* hs = replxx_history_scan_start( replxx ); + ReplxxHistoryEntry he; + for ( ; replxx_history_scan_next( replxx, hs, &he ) == 0; ++index ) { + replxx_print( replxx, "%4d: %s\n", index, he.text ); + } + replxx_history_scan_stop( replxx, hs ); + } else if (!strncmp(result, "/unique", 8)) { + replxx_set_unique_history( replxx, 1 ); + } else if (!strncmp(result, "/eb", 4)) { + replxx_enable_bracketed_paste( replxx ); + } else if (!strncmp(result, "/db", 4)) { + replxx_disable_bracketed_paste( replxx ); + } + if (*result != '\0') { + replxx_print( replxx, quiet ? "%s\n" : "thanks for the input: %s\n", result ); + replxx_history_add( replxx, result ); + } + } + replxx_history_save( replxx, file ); + printf( "Exiting Replxx\n" ); + replxx_end( replxx ); +} + diff --git a/third-party/replxx/examples/cxx-api.cxx b/third-party/replxx/examples/cxx-api.cxx new file mode 100644 index 0000000000..4c28f10eb1 --- /dev/null +++ b/third-party/replxx/examples/cxx-api.cxx @@ -0,0 +1,453 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "replxx.hxx" +#include "util.h" + +using Replxx = replxx::Replxx; + +class Tick { + typedef std::vector keys_t; + std::thread _thread; + int _tick; + bool _alive; + keys_t _keys; + Replxx& _replxx; +public: + Tick( Replxx& replxx_, std::string const& keys_ = {} ) + : _thread() + , _tick( 0 ) + , _alive( false ) + , _keys( keys_.begin(), keys_.end() ) + , _replxx( replxx_ ) { + } + void start() { + _alive = true; + _thread = std::thread( &Tick::run, this ); + } + void stop() { + _alive = false; + _thread.join(); + } + void run() { + std::string s; + while ( _alive ) { + if ( _keys.empty() ) { + _replxx.print( "%d\n", _tick ); + } else if ( _tick < static_cast( _keys.size() ) ) { + _replxx.emulate_key_press( _keys[_tick] ); + } else { + break; + } + ++ _tick; + std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); + } + } +}; + +// prototypes +Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& user_data); +Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& user_data); +void hook_color(std::string const& str, Replxx::colors_t& colors, std::vector> const& user_data); + +Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& examples) { + Replxx::completions_t completions; + int utf8ContextLen( context_len( context.c_str() ) ); + int prefixLen( static_cast( context.length() ) - utf8ContextLen ); + if ( ( prefixLen > 0 ) && ( context[prefixLen - 1] == '\\' ) ) { + -- prefixLen; + ++ utf8ContextLen; + } + contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen ); + + std::string prefix { context.substr(prefixLen) }; + if ( prefix == "\\pi" ) { + completions.push_back( "Ï€" ); + } else { + for (auto const& e : examples) { + if (e.compare(0, prefix.size(), prefix) == 0) { + Replxx::Color c( Replxx::Color::DEFAULT ); + if ( e.find( "brightred" ) != std::string::npos ) { + c = Replxx::Color::BRIGHTRED; + } else if ( e.find( "red" ) != std::string::npos ) { + c = Replxx::Color::RED; + } + completions.emplace_back(e.c_str(), c); + } + } + } + + return completions; +} + +Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& examples) { + Replxx::hints_t hints; + + // only show hint if prefix is at least 'n' chars long + // or if prefix begins with a specific character + + int utf8ContextLen( context_len( context.c_str() ) ); + int prefixLen( static_cast( context.length() ) - utf8ContextLen ); + contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen ); + std::string prefix { context.substr(prefixLen) }; + + if (prefix.size() >= 2 || (! prefix.empty() && prefix.at(0) == '.')) { + for (auto const& e : examples) { + if (e.compare(0, prefix.size(), prefix) == 0) { + hints.emplace_back(e.c_str()); + } + } + } + + // set hint color to green if single match found + if (hints.size() == 1) { + color = Replxx::Color::GREEN; + } + + return hints; +} + +void hook_color(std::string const& context, Replxx::colors_t& colors, std::vector> const& regex_color) { + // highlight matching regex sequences + for (auto const& e : regex_color) { + size_t pos {0}; + std::string str = context; + std::smatch match; + + while(std::regex_search(str, match, std::regex(e.first))) { + std::string c{ match[0] }; + std::string prefix( match.prefix().str() ); + pos += utf8str_codepoint_len( prefix.c_str(), static_cast( prefix.length() ) ); + int len( utf8str_codepoint_len( c.c_str(), static_cast( c.length() ) ) ); + + for (int i = 0; i < len; ++i) { + colors.at(pos + i) = e.second; + } + + pos += len; + str = match.suffix(); + } + } +} + +Replxx::ACTION_RESULT message( Replxx& replxx, std::string s, char32_t ) { + replxx.invoke( Replxx::ACTION::CLEAR_SELF, 0 ); + replxx.print( "%s\n", s.c_str() ); + replxx.invoke( Replxx::ACTION::REPAINT, 0 ); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +int main( int argc_, char** argv_ ) { + // words to be completed + std::vector examples { + ".help", ".history", ".quit", ".exit", ".clear", ".prompt ", + "hello", "world", "db", "data", "drive", "print", "put", + "color_black", "color_red", "color_green", "color_brown", "color_blue", + "color_magenta", "color_cyan", "color_lightgray", "color_gray", + "color_brightred", "color_brightgreen", "color_yellow", "color_brightblue", + "color_brightmagenta", "color_brightcyan", "color_white", "color_normal", + }; + + // highlight specific words + // a regex string, and a color + // the order matters, the last match will take precedence + using cl = Replxx::Color; + std::vector> regex_color { + // single chars + {"\\`", cl::BRIGHTCYAN}, + {"\\'", cl::BRIGHTBLUE}, + {"\\\"", cl::BRIGHTBLUE}, + {"\\-", cl::BRIGHTBLUE}, + {"\\+", cl::BRIGHTBLUE}, + {"\\=", cl::BRIGHTBLUE}, + {"\\/", cl::BRIGHTBLUE}, + {"\\*", cl::BRIGHTBLUE}, + {"\\^", cl::BRIGHTBLUE}, + {"\\.", cl::BRIGHTMAGENTA}, + {"\\(", cl::BRIGHTMAGENTA}, + {"\\)", cl::BRIGHTMAGENTA}, + {"\\[", cl::BRIGHTMAGENTA}, + {"\\]", cl::BRIGHTMAGENTA}, + {"\\{", cl::BRIGHTMAGENTA}, + {"\\}", cl::BRIGHTMAGENTA}, + + // color keywords + {"color_black", cl::BLACK}, + {"color_red", cl::RED}, + {"color_green", cl::GREEN}, + {"color_brown", cl::BROWN}, + {"color_blue", cl::BLUE}, + {"color_magenta", cl::MAGENTA}, + {"color_cyan", cl::CYAN}, + {"color_lightgray", cl::LIGHTGRAY}, + {"color_gray", cl::GRAY}, + {"color_brightred", cl::BRIGHTRED}, + {"color_brightgreen", cl::BRIGHTGREEN}, + {"color_yellow", cl::YELLOW}, + {"color_brightblue", cl::BRIGHTBLUE}, + {"color_brightmagenta", cl::BRIGHTMAGENTA}, + {"color_brightcyan", cl::BRIGHTCYAN}, + {"color_white", cl::WHITE}, + {"color_normal", cl::NORMAL}, + + // commands + {"\\.help", cl::BRIGHTMAGENTA}, + {"\\.history", cl::BRIGHTMAGENTA}, + {"\\.quit", cl::BRIGHTMAGENTA}, + {"\\.exit", cl::BRIGHTMAGENTA}, + {"\\.clear", cl::BRIGHTMAGENTA}, + {"\\.prompt", cl::BRIGHTMAGENTA}, + + // numbers + {"[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // integers + {"[\\-|+]{0,1}[0-9]*\\.[0-9]+", cl::YELLOW}, // decimals + {"[\\-|+]{0,1}[0-9]+e[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // scientific notation + + // strings + {"\".*?\"", cl::BRIGHTGREEN}, // double quotes + {"\'.*?\'", cl::BRIGHTGREEN}, // single quotes + }; + + // init the repl + Replxx rx; + Tick tick( rx, argc_ > 1 ? argv_[1] : "" ); + rx.install_window_change_handler(); + + // the path to the history file + std::string history_file {"./replxx_history.txt"}; + + // load the history file if it exists + rx.history_load(history_file); + + // set the max history size + rx.set_max_history_size(128); + + // set the max number of hint rows to show + rx.set_max_hint_rows(3); + + // set the callbacks + using namespace std::placeholders; + rx.set_completion_callback( std::bind( &hook_completion, _1, _2, cref( examples ) ) ); + rx.set_highlighter_callback( std::bind( &hook_color, _1, _2, cref( regex_color ) ) ); + rx.set_hint_callback( std::bind( &hook_hint, _1, _2, _3, cref( examples ) ) ); + + // other api calls + rx.set_word_break_characters( " \t.,-%!;:=*~^'\"/?<>|[](){}" ); + rx.set_completion_count_cutoff( 128 ); + rx.set_double_tab_completion( false ); + rx.set_complete_on_empty( true ); + rx.set_beep_on_ambiguous_completion( false ); + rx.set_no_color( false ); + + // showcase key bindings + rx.bind_key_internal( Replxx::KEY::BACKSPACE, "delete_character_left_of_cursor" ); + rx.bind_key_internal( Replxx::KEY::DELETE, "delete_character_under_cursor" ); + rx.bind_key_internal( Replxx::KEY::LEFT, "move_cursor_left" ); + rx.bind_key_internal( Replxx::KEY::RIGHT, "move_cursor_right" ); + rx.bind_key_internal( Replxx::KEY::UP, "history_previous" ); + rx.bind_key_internal( Replxx::KEY::DOWN, "history_next" ); + rx.bind_key_internal( Replxx::KEY::PAGE_UP, "history_first" ); + rx.bind_key_internal( Replxx::KEY::PAGE_DOWN, "history_last" ); + rx.bind_key_internal( Replxx::KEY::HOME, "move_cursor_to_begining_of_line" ); + rx.bind_key_internal( Replxx::KEY::END, "move_cursor_to_end_of_line" ); + rx.bind_key_internal( Replxx::KEY::TAB, "complete_line" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::LEFT ), "move_cursor_one_word_left" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::RIGHT ), "move_cursor_one_word_right" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::UP ), "hint_previous" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::DOWN ), "hint_next" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::ENTER ), "commit_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'R' ), "history_incremental_search" ); + rx.bind_key_internal( Replxx::KEY::control( 'W' ), "kill_to_begining_of_word" ); + rx.bind_key_internal( Replxx::KEY::control( 'U' ), "kill_to_begining_of_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'K' ), "kill_to_end_of_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'Y' ), "yank" ); + rx.bind_key_internal( Replxx::KEY::control( 'L' ), "clear_screen" ); + rx.bind_key_internal( Replxx::KEY::control( 'D' ), "send_eof" ); + rx.bind_key_internal( Replxx::KEY::control( 'C' ), "abort_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'T' ), "transpose_characters" ); +#ifndef _WIN32 + rx.bind_key_internal( Replxx::KEY::control( 'V' ), "verbatim_insert" ); + rx.bind_key_internal( Replxx::KEY::control( 'Z' ), "suspend" ); +#endif + rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), "kill_to_whitespace_on_left" ); + rx.bind_key_internal( Replxx::KEY::meta( 'p' ), "history_common_prefix_search" ); + rx.bind_key_internal( Replxx::KEY::meta( 'n' ), "history_common_prefix_search" ); + rx.bind_key_internal( Replxx::KEY::meta( 'd' ), "kill_to_end_of_word" ); + rx.bind_key_internal( Replxx::KEY::meta( 'y' ), "yank_cycle" ); + rx.bind_key_internal( Replxx::KEY::meta( 'u' ), "uppercase_word" ); + rx.bind_key_internal( Replxx::KEY::meta( 'l' ), "lowercase_word" ); + rx.bind_key_internal( Replxx::KEY::meta( 'c' ), "capitalize_word" ); + rx.bind_key_internal( 'a', "insert_character" ); + rx.bind_key_internal( Replxx::KEY::INSERT, "toggle_overwrite_mode" ); + rx.bind_key( Replxx::KEY::F1, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F2, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F3, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F4, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F5, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F6, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F7, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F8, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F9, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F10, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F11, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F12, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::TAB ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_UP ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_DOWN ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::LEFT ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::RIGHT ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::UP ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::DOWN ), std::bind( &message, std::ref( rx ), "", _1 ) ); + + // display initial welcome message + std::cout + << "Welcome to Replxx\n" + << "Press 'tab' to view autocompletions\n" + << "Type '.help' for help\n" + << "Type '.quit' or '.exit' to exit\n\n"; + + // set the repl prompt + std::string prompt {"\x1b[1;32mreplxx\x1b[0m> "}; + + // main repl loop + if ( argc_ > 1 ) { + tick.start(); + } + for (;;) { + // display the prompt and retrieve input from the user + char const* cinput{ nullptr }; + + do { + cinput = rx.input(prompt); + } while ( ( cinput == nullptr ) && ( errno == EAGAIN ) ); + + if (cinput == nullptr) { + break; + } + + // change cinput into a std::string + // easier to manipulate + std::string input {cinput}; + + if (input.empty()) { + // user hit enter on an empty line + + continue; + + } else if (input.compare(0, 5, ".quit") == 0 || input.compare(0, 5, ".exit") == 0) { + // exit the repl + + rx.history_add(input); + break; + + } else if (input.compare(0, 5, ".help") == 0) { + // display the help output + std::cout + << ".help\n\tdisplays the help output\n" + << ".quit\n\texit the repl\n" + << ".exit\n\texit the repl\n" + << ".clear\n\tclears the screen\n" + << ".history\n\tdisplays the history output\n" + << ".prompt \n\tset the repl prompt to \n"; + + rx.history_add(input); + continue; + + } else if (input.compare(0, 7, ".prompt") == 0) { + // set the repl prompt text + auto pos = input.find(" "); + if (pos == std::string::npos) { + std::cout << "Error: '.prompt' missing argument\n"; + } else { + prompt = input.substr(pos + 1) + " "; + } + + rx.history_add(input); + continue; + + } else if (input.compare(0, 8, ".history") == 0) { + // display the current history + Replxx::HistoryScan hs( rx.history_scan() ); + for ( int i( 0 ); hs.next(); ++ i ) { + std::cout << std::setw(4) << i << ": " << hs.get().text() << "\n"; + } + + rx.history_add(input); + continue; + + } else if (input.compare(0, 6, ".merge") == 0) { + history_file = "replxx_history_alt.txt"; + + rx.history_add(input); + continue; + + } else if (input.compare(0, 5, ".save") == 0) { + history_file = "replxx_history_alt.txt"; + rx.history_save(history_file); + continue; + + } else if (input.compare(0, 6, ".clear") == 0) { + // clear the screen + rx.clear_screen(); + + rx.history_add(input); + continue; + + } else { + // default action + // echo the input + + rx.print( "%s\n", input.c_str() ); + + rx.history_add( input ); + continue; + } + } + if ( argc_ > 1 ) { + tick.stop(); + } + + // save the history + rx.history_sync(history_file); + + std::cout << "\nExiting Replxx\n"; + + return 0; +} diff --git a/third-party/replxx/examples/util.c b/third-party/replxx/examples/util.c new file mode 100644 index 0000000000..6fb18c0721 --- /dev/null +++ b/third-party/replxx/examples/util.c @@ -0,0 +1,34 @@ +#include + +int utf8str_codepoint_len( char const* s, int utf8len ) { + int codepointLen = 0; + unsigned char m4 = 128 + 64 + 32 + 16; + unsigned char m3 = 128 + 64 + 32; + unsigned char m2 = 128 + 64; + for ( int i = 0; i < utf8len; ++ i, ++ codepointLen ) { + char c = s[i]; + if ( ( c & m4 ) == m4 ) { + i += 3; + } else if ( ( c & m3 ) == m3 ) { + i += 2; + } else if ( ( c & m2 ) == m2 ) { + i += 1; + } + } + return ( codepointLen ); +} + +int context_len( char const* prefix ) { + char const wb[] = " \t\n\r\v\f-=+*&^%$#@!,./?<>;:`~'\"[]{}()\\|"; + int i = (int)strlen( prefix ) - 1; + int cl = 0; + while ( i >= 0 ) { + if ( strchr( wb, prefix[i] ) != NULL ) { + break; + } + ++ cl; + -- i; + } + return ( cl ); +} + diff --git a/third-party/replxx/examples/util.h b/third-party/replxx/examples/util.h new file mode 100644 index 0000000000..c13bcf2f7a --- /dev/null +++ b/third-party/replxx/examples/util.h @@ -0,0 +1,13 @@ +#pragma once + +// strrpbrk would suffice but is not portable. +#ifdef __cplusplus +extern "C" { +#endif + +int context_len( char const* ); +int utf8str_codepoint_len( char const*, int ); + +#ifdef __cplusplus +} +#endif diff --git a/third-party/replxx/gen-coverage.sh b/third-party/replxx/gen-coverage.sh new file mode 100644 index 0000000000..8334392e2e --- /dev/null +++ b/third-party/replxx/gen-coverage.sh @@ -0,0 +1,22 @@ +#! /bin/sh + +rm -rf build +mkdir -p build/debug +cd build/debug + +cmake -DCMAKE_BUILD_TYPE=coverage ../../ +make + +cd .. + +lcov --base-directory .. --directory debug/CMakeFiles --capture --initial --output-file replxx-baseline.info + +cd .. +./tests.py +cd - + +lcov --base-directory .. --directory debug/CMakeFiles --capture --output-file replxx-test.info +lcov --add-tracefile replxx-baseline.info --add-tracefile replxx-test.info --output-file replxx-total.info +lcov --extract replxx-total.info '*/replxx/src/*' '*/replxx/include/*' --output-file replxx-coverage.info +genhtml replxx-coverage.info --legend --num-spaces=2 --output-directory replxx-coverage-html + diff --git a/third-party/replxx/include/replxx.h b/third-party/replxx/include/replxx.h new file mode 100644 index 0000000000..ce09040127 --- /dev/null +++ b/third-party/replxx/include/replxx.h @@ -0,0 +1,567 @@ +/* linenoise.h -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * Copyright (c) 2010, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __REPLXX_H +#define __REPLXX_H + +#define REPLXX_VERSION "0.0.2" +#define REPLXX_VERSION_MAJOR 0 +#define REPLXX_VERSION_MINOR 0 + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * For use in Windows DLLs: + * + * If you are building replxx into a DLL, + * unless you are using supplied CMake based build, + * ensure that 'REPLXX_BUILDING_DLL' is defined when + * building the DLL so that proper symbols are exported. + */ +#if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) +# ifdef REPLXX_BUILDING_DLL +# define REPLXX_IMPEXP __declspec( dllexport ) +# else +# define REPLXX_IMPEXP __declspec( dllimport ) +# endif +#else +# define REPLXX_IMPEXP /**/ +#endif + +/*! \brief Color definitions to use in highlighter callbacks. + */ +typedef enum { + REPLXX_COLOR_BLACK = 0, + REPLXX_COLOR_RED = 1, + REPLXX_COLOR_GREEN = 2, + REPLXX_COLOR_BROWN = 3, + REPLXX_COLOR_BLUE = 4, + REPLXX_COLOR_MAGENTA = 5, + REPLXX_COLOR_CYAN = 6, + REPLXX_COLOR_LIGHTGRAY = 7, + REPLXX_COLOR_GRAY = 8, + REPLXX_COLOR_BRIGHTRED = 9, + REPLXX_COLOR_BRIGHTGREEN = 10, + REPLXX_COLOR_YELLOW = 11, + REPLXX_COLOR_BRIGHTBLUE = 12, + REPLXX_COLOR_BRIGHTMAGENTA = 13, + REPLXX_COLOR_BRIGHTCYAN = 14, + REPLXX_COLOR_WHITE = 15, + REPLXX_COLOR_NORMAL = REPLXX_COLOR_LIGHTGRAY, + REPLXX_COLOR_DEFAULT = -1, + REPLXX_COLOR_ERROR = -2 +} ReplxxColor; + +enum { REPLXX_KEY_BASE = 0x0010ffff + 1 }; +enum { REPLXX_KEY_BASE_SHIFT = 0x01000000 }; +enum { REPLXX_KEY_BASE_CONTROL = 0x02000000 }; +enum { REPLXX_KEY_BASE_META = 0x04000000 }; +enum { REPLXX_KEY_ESCAPE = 27 }; +enum { REPLXX_KEY_PAGE_UP = REPLXX_KEY_BASE + 1 }; +enum { REPLXX_KEY_PAGE_DOWN = REPLXX_KEY_PAGE_UP + 1 }; +enum { REPLXX_KEY_DOWN = REPLXX_KEY_PAGE_DOWN + 1 }; +enum { REPLXX_KEY_UP = REPLXX_KEY_DOWN + 1 }; +enum { REPLXX_KEY_LEFT = REPLXX_KEY_UP + 1 }; +enum { REPLXX_KEY_RIGHT = REPLXX_KEY_LEFT + 1 }; +enum { REPLXX_KEY_HOME = REPLXX_KEY_RIGHT + 1 }; +enum { REPLXX_KEY_END = REPLXX_KEY_HOME + 1 }; +enum { REPLXX_KEY_DELETE = REPLXX_KEY_END + 1 }; +enum { REPLXX_KEY_INSERT = REPLXX_KEY_DELETE + 1 }; +enum { REPLXX_KEY_F1 = REPLXX_KEY_INSERT + 1 }; +enum { REPLXX_KEY_F2 = REPLXX_KEY_F1 + 1 }; +enum { REPLXX_KEY_F3 = REPLXX_KEY_F2 + 1 }; +enum { REPLXX_KEY_F4 = REPLXX_KEY_F3 + 1 }; +enum { REPLXX_KEY_F5 = REPLXX_KEY_F4 + 1 }; +enum { REPLXX_KEY_F6 = REPLXX_KEY_F5 + 1 }; +enum { REPLXX_KEY_F7 = REPLXX_KEY_F6 + 1 }; +enum { REPLXX_KEY_F8 = REPLXX_KEY_F7 + 1 }; +enum { REPLXX_KEY_F9 = REPLXX_KEY_F8 + 1 }; +enum { REPLXX_KEY_F10 = REPLXX_KEY_F9 + 1 }; +enum { REPLXX_KEY_F11 = REPLXX_KEY_F10 + 1 }; +enum { REPLXX_KEY_F12 = REPLXX_KEY_F11 + 1 }; +enum { REPLXX_KEY_F13 = REPLXX_KEY_F12 + 1 }; +enum { REPLXX_KEY_F14 = REPLXX_KEY_F13 + 1 }; +enum { REPLXX_KEY_F15 = REPLXX_KEY_F14 + 1 }; +enum { REPLXX_KEY_F16 = REPLXX_KEY_F15 + 1 }; +enum { REPLXX_KEY_F17 = REPLXX_KEY_F16 + 1 }; +enum { REPLXX_KEY_F18 = REPLXX_KEY_F17 + 1 }; +enum { REPLXX_KEY_F19 = REPLXX_KEY_F18 + 1 }; +enum { REPLXX_KEY_F20 = REPLXX_KEY_F19 + 1 }; +enum { REPLXX_KEY_F21 = REPLXX_KEY_F20 + 1 }; +enum { REPLXX_KEY_F22 = REPLXX_KEY_F21 + 1 }; +enum { REPLXX_KEY_F23 = REPLXX_KEY_F22 + 1 }; +enum { REPLXX_KEY_F24 = REPLXX_KEY_F23 + 1 }; +enum { REPLXX_KEY_MOUSE = REPLXX_KEY_F24 + 1 }; +enum { REPLXX_KEY_PASTE_START = REPLXX_KEY_MOUSE + 1 }; +enum { REPLXX_KEY_PASTE_FINISH = REPLXX_KEY_PASTE_START + 1 }; + +#define REPLXX_KEY_SHIFT( key ) ( ( key ) | REPLXX_KEY_BASE_SHIFT ) +#define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL ) +#define REPLXX_KEY_META( key ) ( ( key ) | REPLXX_KEY_BASE_META ) + +enum { REPLXX_KEY_BACKSPACE = REPLXX_KEY_CONTROL( 'H' ) }; +enum { REPLXX_KEY_TAB = REPLXX_KEY_CONTROL( 'I' ) }; +enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) }; + +/*! \brief List of built-in actions that act upon user input. + */ +typedef enum { + REPLXX_ACTION_INSERT_CHARACTER, + REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR, + REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR, + REPLXX_ACTION_KILL_TO_END_OF_LINE, + REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE, + REPLXX_ACTION_KILL_TO_END_OF_WORD, + REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD, + REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT, + REPLXX_ACTION_YANK, + REPLXX_ACTION_YANK_CYCLE, + REPLXX_ACTION_YANK_LAST_ARG, + REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE, + REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE, + REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT, + REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT, + REPLXX_ACTION_MOVE_CURSOR_LEFT, + REPLXX_ACTION_MOVE_CURSOR_RIGHT, + REPLXX_ACTION_HISTORY_NEXT, + REPLXX_ACTION_HISTORY_PREVIOUS, + REPLXX_ACTION_HISTORY_FIRST, + REPLXX_ACTION_HISTORY_LAST, + REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH, + REPLXX_ACTION_HISTORY_COMMON_PREFIX_SEARCH, + REPLXX_ACTION_HINT_NEXT, + REPLXX_ACTION_HINT_PREVIOUS, + REPLXX_ACTION_CAPITALIZE_WORD, + REPLXX_ACTION_LOWERCASE_WORD, + REPLXX_ACTION_UPPERCASE_WORD, + REPLXX_ACTION_TRANSPOSE_CHARACTERS, + REPLXX_ACTION_TOGGLE_OVERWRITE_MODE, +#ifndef _WIN32 + REPLXX_ACTION_VERBATIM_INSERT, + REPLXX_ACTION_SUSPEND, +#endif + REPLXX_ACTION_BRACKETED_PASTE, + REPLXX_ACTION_CLEAR_SCREEN, + REPLXX_ACTION_CLEAR_SELF, + REPLXX_ACTION_REPAINT, + REPLXX_ACTION_COMPLETE_LINE, + REPLXX_ACTION_COMPLETE_NEXT, + REPLXX_ACTION_COMPLETE_PREVIOUS, + REPLXX_ACTION_COMMIT_LINE, + REPLXX_ACTION_ABORT_LINE, + REPLXX_ACTION_SEND_EOF +} ReplxxAction; + +/*! \brief Possible results of key-press handler actions. + */ +typedef enum { + REPLXX_ACTION_RESULT_CONTINUE, /*!< Continue processing user input. */ + REPLXX_ACTION_RESULT_RETURN, /*!< Return user input entered so far. */ + REPLXX_ACTION_RESULT_BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ +} ReplxxActionResult; + +typedef struct ReplxxStateTag { + char const* text; + int cursorPosition; +} ReplxxState; + +typedef struct Replxx Replxx; +typedef struct ReplxxHistoryScan ReplxxHistoryScan; +typedef struct ReplxxHistoryEntryTag { + char const* timestamp; + char const* text; +} ReplxxHistoryEntry; + +/*! \brief Create Replxx library resource holder. + * + * Use replxx_end() to free resources acquired with this function. + * + * \return Replxx library resource holder. + */ +REPLXX_IMPEXP Replxx* replxx_init( void ); + +/*! \brief Cleanup resources used by Replxx library. + * + * \param replxx - a Replxx library resource holder. + */ +REPLXX_IMPEXP void replxx_end( Replxx* replxx ); + +/*! \brief Line modification callback type definition. + * + * User can observe and modify line contents (and cursor position) + * in response to changes to both introduced by the user through + * normal interactions. + * + * When callback returns Replxx updates current line content + * and current cursor position to the ones updated by the callback. + * + * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. + * \param cursorPosition[in,out] - a R/W reference to current cursor position. + * \param userData - pointer to opaque user data block. + */ +typedef void (replxx_modify_callback_t)(char** input, int* contextLen, void* userData); + +/*! \brief Register modify callback. + * + * \param fn - user defined callback function. + * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. + */ +REPLXX_IMPEXP void replxx_set_modify_callback( Replxx*, replxx_modify_callback_t* fn, void* userData ); + +/*! \brief Highlighter callback type definition. + * + * If user want to have colorful input she must simply install highlighter callback. + * The callback would be invoked by the library after each change to the input done by + * the user. After callback returns library uses data from colors buffer to colorize + * displayed user input. + * + * \e size of \e colors buffer is equal to number of code points in user \e input + * which will be different from simple `strlen( input )`! + * + * \param input - an UTF-8 encoded input entered by the user so far. + * \param colors - output buffer for color information. + * \param size - size of output buffer for color information. + * \param userData - pointer to opaque user data block. + */ +typedef void (replxx_highlighter_callback_t)(char const* input, ReplxxColor* colors, int size, void* userData); + +/*! \brief Register highlighter callback. + * + * \param fn - user defined callback function. + * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. + */ +REPLXX_IMPEXP void replxx_set_highlighter_callback( Replxx*, replxx_highlighter_callback_t* fn, void* userData ); + +typedef struct replxx_completions replxx_completions; + +/*! \brief Completions callback type definition. + * + * \e contextLen is counted in Unicode code points (not in bytes!). + * + * For user input: + * if ( obj.me + * + * input == "if ( obj.me" + * contextLen == 2 (depending on \e replxx_set_word_break_characters()) + * + * Client application is free to update \e contextLen to be 6 (or any other non-negative + * number not greater than the number of code points in input) if it makes better sense + * for given client application semantics. + * + * \param input - UTF-8 encoded input entered by the user until current cursor position. + * \param completions - pointer to opaque list of user completions. + * \param contextLen[in,out] - length of the additional context to provide while displaying completions. + * \param userData - pointer to opaque user data block. + */ +typedef void(replxx_completion_callback_t)(const char* input, replxx_completions* completions, int* contextLen, void* userData); + +/*! \brief Register completion callback. + * + * \param fn - user defined callback function. + * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. + */ +REPLXX_IMPEXP void replxx_set_completion_callback( Replxx*, replxx_completion_callback_t* fn, void* userData ); + +/*! \brief Add another possible completion for current user input. + * + * \param completions - pointer to opaque list of user completions. + * \param str - UTF-8 encoded completion string. + */ +REPLXX_IMPEXP void replxx_add_completion( replxx_completions* completions, const char* str ); + +/*! \brief Add another possible completion for current user input. + * + * \param completions - pointer to opaque list of user completions. + * \param str - UTF-8 encoded completion string. + * \param color - a color for the completion. + */ +REPLXX_IMPEXP void replxx_add_color_completion( replxx_completions* completions, const char* str, ReplxxColor color ); + +typedef struct replxx_hints replxx_hints; + +/*! \brief Hints callback type definition. + * + * \e contextLen is counted in Unicode code points (not in bytes!). + * + * For user input: + * if ( obj.me + * + * input == "if ( obj.me" + * contextLen == 2 (depending on \e replxx_set_word_break_characters()) + * + * Client application is free to update \e contextLen to be 6 (or any other non-negative + * number not greater than the number of code points in input) if it makes better sense + * for given client application semantics. + * + * \param input - UTF-8 encoded input entered by the user until current cursor position. + * \param hints - pointer to opaque list of possible hints. + * \param contextLen[in,out] - length of the additional context to provide while displaying hints. + * \param color - a color used for displaying hints. + * \param userData - pointer to opaque user data block. + */ +typedef void(replxx_hint_callback_t)(const char* input, replxx_hints* hints, int* contextLen, ReplxxColor* color, void* userData); + +/*! \brief Register hints callback. + * + * \param fn - user defined callback function. + * \param userData - pointer to opaque user data block to be passed into each invocation of the callback. + */ +REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn, void* userData ); + +/*! \brief Key press handler type definition. + * + * \param code - the key code replxx got from terminal. + * \return Decision on how should input() behave after this key handler returns. + */ +typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); + +/*! \brief Add another possible hint for current user input. + * + * \param hints - pointer to opaque list of hints. + * \param str - UTF-8 encoded hint string. + */ +REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str ); + +/*! \brief Read line of user input. + * + * Returned pointer is managed by the library and is not to be freed in the client. + * + * \param prompt - prompt to be displayed before getting user input. + * \return An UTF-8 encoded input given by the user (or nullptr on EOF). + */ +REPLXX_IMPEXP char const* replxx_input( Replxx*, const char* prompt ); + +/*! \brief Get current state data. + * + * This call is intended to be used in handlers. + * + * \param state - buffer for current state of the model. + */ +REPLXX_IMPEXP void replxx_get_state( Replxx*, ReplxxState* state ); + +/*! \brief Set new state data. + * + * This call is intended to be used in handlers. + * + * \param state - new state of the model. + */ +REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state ); + +/*! \brief Print formatted string to standard output. + * + * This function ensures proper handling of ANSI escape sequences + * contained in printed data, which is especially useful on Windows + * since Unixes handle them correctly out of the box. + * + * \param fmt - printf style format. + */ +REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... ); + +/*! \brief Prints a char array with the given length to standard output. + * + * \copydetails print + * + * \param str - The char array to print. + * \param length - The length of the array. + */ +REPLXX_IMPEXP int replxx_write( Replxx*, char const* str, int length ); + +/*! \brief Schedule an emulated key press event. + * + * \param code - key press code to be emulated. + */ +REPLXX_IMPEXP void replxx_emulate_key_press( Replxx*, int unsigned code ); + +/*! \brief Invoke built-in action handler. + * + * \pre This function can be called only from key-press handler. + * + * \param action - a built-in action to invoke. + * \param code - a supplementary key-code to consume by built-in action handler. + * \return The action result informing the replxx what shall happen next. + */ +REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, int unsigned code ); + +/*! \brief Bind user defined action to handle given key-press event. + * + * \param code - handle this key-press event with following handler. + * \param handler - use this handler to handle key-press event. + * \param userData - supplementary user data passed to invoked handlers. + */ +REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData ); + +/*! \brief Bind internal `replxx` action (by name) to handle given key-press event. + * + * Action names are the same as unique part of names of ReplxxAction enumerations + * but in lower case, e.g.: an action for recalling previous history line + * is \e REPLXX_ACTION_HISTORY_PREVIOUS so action name to be used in this + * interface for the same effect is "history_previous". + * + * \param code - handle this key-press event with following handler. + * \param actionName - name of internal action to be invoked on key press. + * \return -1 if invalid action name was used, 0 otherwise. + */ +int replxx_bind_key_internal( Replxx*, int code, char const* actionName ); + +REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText ); + +REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line ); +REPLXX_IMPEXP int replxx_history_size( Replxx* ); + +/*! \brief Set set of word break characters. + * + * This setting influences word based cursor movement and line editing capabilities. + * + * \param wordBreakers - 7-bit ASCII set of word breaking characters. + */ +REPLXX_IMPEXP void replxx_set_word_break_characters( Replxx*, char const* wordBreakers ); + +/*! \brief How many completions should trigger pagination. + */ +REPLXX_IMPEXP void replxx_set_completion_count_cutoff( Replxx*, int count ); + +/*! \brief Set maximum number of displayed hint rows. + */ +REPLXX_IMPEXP void replxx_set_max_hint_rows( Replxx*, int count ); + +/*! \brief Set a delay before hint are shown after user stopped typing.. + * + * \param milliseconds - a number of milliseconds to wait before showing hints. + */ +REPLXX_IMPEXP void replxx_set_hint_delay( Replxx*, int milliseconds ); + +/*! \brief Set tab completion behavior. + * + * \param val - use double tab to invoke completions (if != 0). + */ +REPLXX_IMPEXP void replxx_set_double_tab_completion( Replxx*, int val ); + +/*! \brief Set tab completion behavior. + * + * \param val - invoke completion even if user input is empty (if != 0). + */ +REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val ); + +/*! \brief Set tab completion behavior. + * + * \param val - beep if completion is ambiguous (if != 0). + */ +REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val ); + +/*! \brief Set complete next/complete previous behavior. + * + * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations, + * in case when a partial completion is possible complete only partial part (`false` setting) + * or complete first proposed completion fully (`true` setting). + * The default is to complete fully (a `true` setting - complete immediately). + * + * \param val - complete immediately. + */ +REPLXX_IMPEXP void replxx_set_immediate_completion( Replxx*, int val ); + +/*! \brief Set history duplicate entries behaviour. + * + * \param val - should history contain only unique entries? + */ +REPLXX_IMPEXP void replxx_set_unique_history( Replxx*, int val ); + +/*! \brief Disable output coloring. + * + * \param val - if set to non-zero disable output colors. + */ +REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val ); + +/*! \brief Set maximum number of entries in history list. + */ +REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len ); +REPLXX_IMPEXP ReplxxHistoryScan* replxx_history_scan_start( Replxx* ); +REPLXX_IMPEXP void replxx_history_scan_stop( Replxx*, ReplxxHistoryScan* ); +REPLXX_IMPEXP int replxx_history_scan_next( Replxx*, ReplxxHistoryScan*, ReplxxHistoryEntry* ); + +/*! \brief Synchronize REPL's history with given file. + * + * Synchronizing means loading existing history from given file, + * merging it with current history sorted by timestamps, + * saving merged version to given file, + * keeping merged version as current REPL's history. + * + * This call is an equivalent of calling: + * replxx_history_save( rx, "some-file" ); + * replxx_history_load( rx, "some-file" ); + * + * \param filename - a path to the file with which REPL's current history should be synchronized. + * \return 0 iff history file was successfully created, -1 otherwise. + */ +REPLXX_IMPEXP int replxx_history_sync( Replxx*, const char* filename ); + +/*! \brief Save REPL's history into given file. + * + * Saving means loading existing history from given file, + * merging it with current history sorted by timestamps, + * saving merged version to given file, + * keeping original (NOT merged) version as current REPL's history. + * + * \param filename - a path to the file where REPL's history should be saved. + * \return 0 iff history file was successfully created, -1 otherwise. + */ +REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename ); + +/*! \brief Load REPL's history from given file. + * + * \param filename - a path to the file which contains REPL's history that should be loaded. + * \return 0 iff history file was successfully opened, -1 otherwise. + */ +REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename ); + +/*! \brief Clear REPL's in-memory history. + */ +REPLXX_IMPEXP void replxx_history_clear( Replxx* ); +REPLXX_IMPEXP void replxx_clear_screen( Replxx* ); +#ifdef __REPLXX_DEBUG__ +void replxx_debug_dump_print_codes(void); +#endif +/* the following is extension to the original linenoise API */ +REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* ); +REPLXX_IMPEXP void replxx_enable_bracketed_paste( Replxx* ); +REPLXX_IMPEXP void replxx_disable_bracketed_paste( Replxx* ); + +#ifdef __cplusplus +} +#endif + +#endif /* __REPLXX_H */ + diff --git a/third-party/replxx/include/replxx.hxx b/third-party/replxx/include/replxx.hxx new file mode 100644 index 0000000000..92ca690518 --- /dev/null +++ b/third-party/replxx/include/replxx.hxx @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HAVE_REPLXX_HXX_INCLUDED +#define HAVE_REPLXX_HXX_INCLUDED 1 + +#include +#include +#include +#include + +/* + * For use in Windows DLLs: + * + * If you are building replxx into a DLL, + * unless you are using supplied CMake based build, + * ensure that 'REPLXX_BUILDING_DLL' is defined when + * building the DLL so that proper symbols are exported. + */ +#if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) +# ifdef REPLXX_BUILDING_DLL +# define REPLXX_IMPEXP __declspec( dllexport ) +# else +# define REPLXX_IMPEXP __declspec( dllimport ) +# endif +#else +# define REPLXX_IMPEXP /**/ +#endif + +#ifdef ERROR +enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR }; +#undef ERROR +enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 }; +#endif +#ifdef DELETE +enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE }; +#undef DELETE +enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E }; +#endif + +namespace replxx { + +class REPLXX_IMPEXP Replxx { +public: + enum class Color { + BLACK = 0, + RED = 1, + GREEN = 2, + BROWN = 3, + BLUE = 4, + MAGENTA = 5, + CYAN = 6, + LIGHTGRAY = 7, + GRAY = 8, + BRIGHTRED = 9, + BRIGHTGREEN = 10, + YELLOW = 11, + BRIGHTBLUE = 12, + BRIGHTMAGENTA = 13, + BRIGHTCYAN = 14, + WHITE = 15, + NORMAL = LIGHTGRAY, + DEFAULT = -1, + ERROR = -2 + }; + struct KEY { + static char32_t const BASE = 0x0010ffff + 1; + static char32_t const BASE_SHIFT = 0x01000000; + static char32_t const BASE_CONTROL = 0x02000000; + static char32_t const BASE_META = 0x04000000; + static char32_t const ESCAPE = 27; + static char32_t const PAGE_UP = BASE + 1; + static char32_t const PAGE_DOWN = PAGE_UP + 1; + static char32_t const DOWN = PAGE_DOWN + 1; + static char32_t const UP = DOWN + 1; + static char32_t const LEFT = UP + 1; + static char32_t const RIGHT = LEFT + 1; + static char32_t const HOME = RIGHT + 1; + static char32_t const END = HOME + 1; + static char32_t const DELETE = END + 1; + static char32_t const INSERT = DELETE + 1; + static char32_t const F1 = INSERT + 1; + static char32_t const F2 = F1 + 1; + static char32_t const F3 = F2 + 1; + static char32_t const F4 = F3 + 1; + static char32_t const F5 = F4 + 1; + static char32_t const F6 = F5 + 1; + static char32_t const F7 = F6 + 1; + static char32_t const F8 = F7 + 1; + static char32_t const F9 = F8 + 1; + static char32_t const F10 = F9 + 1; + static char32_t const F11 = F10 + 1; + static char32_t const F12 = F11 + 1; + static char32_t const F13 = F12 + 1; + static char32_t const F14 = F13 + 1; + static char32_t const F15 = F14 + 1; + static char32_t const F16 = F15 + 1; + static char32_t const F17 = F16 + 1; + static char32_t const F18 = F17 + 1; + static char32_t const F19 = F18 + 1; + static char32_t const F20 = F19 + 1; + static char32_t const F21 = F20 + 1; + static char32_t const F22 = F21 + 1; + static char32_t const F23 = F22 + 1; + static char32_t const F24 = F23 + 1; + static char32_t const MOUSE = F24 + 1; + static char32_t const PASTE_START = MOUSE + 1; + static char32_t const PASTE_FINISH = PASTE_START + 1; + static constexpr char32_t shift( char32_t key_ ) { + return ( key_ | BASE_SHIFT ); + } + static constexpr char32_t control( char32_t key_ ) { + return ( key_ | BASE_CONTROL ); + } + static constexpr char32_t meta( char32_t key_ ) { + return ( key_ | BASE_META ); + } + static char32_t const BACKSPACE = 'H' | BASE_CONTROL; + static char32_t const TAB = 'I' | BASE_CONTROL; + static char32_t const ENTER = 'M' | BASE_CONTROL; + }; + /*! \brief List of built-in actions that act upon user input. + */ + enum class ACTION { + INSERT_CHARACTER, + DELETE_CHARACTER_UNDER_CURSOR, + DELETE_CHARACTER_LEFT_OF_CURSOR, + KILL_TO_END_OF_LINE, + KILL_TO_BEGINING_OF_LINE, + KILL_TO_END_OF_WORD, + KILL_TO_BEGINING_OF_WORD, + KILL_TO_WHITESPACE_ON_LEFT, + YANK, + YANK_CYCLE, + YANK_LAST_ARG, + MOVE_CURSOR_TO_BEGINING_OF_LINE, + MOVE_CURSOR_TO_END_OF_LINE, + MOVE_CURSOR_ONE_WORD_LEFT, + MOVE_CURSOR_ONE_WORD_RIGHT, + MOVE_CURSOR_LEFT, + MOVE_CURSOR_RIGHT, + HISTORY_NEXT, + HISTORY_PREVIOUS, + HISTORY_FIRST, + HISTORY_LAST, + HISTORY_INCREMENTAL_SEARCH, + HISTORY_COMMON_PREFIX_SEARCH, + HINT_NEXT, + HINT_PREVIOUS, + CAPITALIZE_WORD, + LOWERCASE_WORD, + UPPERCASE_WORD, + TRANSPOSE_CHARACTERS, + TOGGLE_OVERWRITE_MODE, +#ifndef _WIN32 + VERBATIM_INSERT, + SUSPEND, +#endif + BRACKETED_PASTE, + CLEAR_SCREEN, + CLEAR_SELF, + REPAINT, + COMPLETE_LINE, + COMPLETE_NEXT, + COMPLETE_PREVIOUS, + COMMIT_LINE, + ABORT_LINE, + SEND_EOF + }; + /*! \brief Possible results of key-press handler actions. + */ + enum class ACTION_RESULT { + CONTINUE, /*!< Continue processing user input. */ + RETURN, /*!< Return user input entered so far. */ + BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ + }; + typedef std::vector colors_t; + class Completion { + std::string _text; + Color _color; + public: + Completion( char const* text_ ) + : _text( text_ ) + , _color( Color::DEFAULT ) { + } + Completion( std::string const& text_ ) + : _text( text_ ) + , _color( Color::DEFAULT ) { + } + Completion( std::string const& text_, Color color_ ) + : _text( text_ ) + , _color( color_ ) { + } + std::string const& text( void ) const { + return ( _text ); + } + Color color( void ) const { + return ( _color ); + } + }; + typedef std::vector completions_t; + class HistoryEntry { + std::string _timestamp; + std::string _text; + public: + HistoryEntry( std::string const& timestamp_, std::string const& text_ ) + : _timestamp( timestamp_ ) + , _text( text_ ) { + } + std::string const& timestamp( void ) const { + return ( _timestamp ); + } + std::string const& text( void ) const { + return ( _text ); + } + }; + class HistoryScanImpl; + class HistoryScan { + public: + typedef std::unique_ptr impl_t; + private: +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4251) +#endif + impl_t _impl; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + public: + HistoryScan( impl_t ); + HistoryScan( HistoryScan&& ) = default; + HistoryScan& operator = ( HistoryScan&& ) = default; + bool next( void ); + HistoryEntry const& get( void ) const; + private: + HistoryScan( HistoryScan const& ) = delete; + HistoryScan& operator = ( HistoryScan const& ) = delete; + }; + typedef std::vector hints_t; + + /*! \brief Line modification callback type definition. + * + * User can observe and modify line contents (and cursor position) + * in response to changes to both introduced by the user through + * normal interactions. + * + * When callback returns Replxx updates current line content + * and current cursor position to the ones updated by the callback. + * + * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. + * \param cursorPosition[in,out] - a R/W reference to current cursor position. + */ + typedef std::function modify_callback_t; + + /*! \brief Completions callback type definition. + * + * \e contextLen is counted in Unicode code points (not in bytes!). + * + * For user input: + * if ( obj.me + * + * input == "if ( obj.me" + * contextLen == 2 (depending on \e set_word_break_characters()) + * + * Client application is free to update \e contextLen to be 6 (or any other non-negative + * number not greater than the number of code points in input) if it makes better sense + * for given client application semantics. + * + * \param input - UTF-8 encoded input entered by the user until current cursor position. + * \param[in,out] contextLen - length of the additional context to provide while displaying completions. + * \return A list of user completions. + */ + typedef std::function completion_callback_t; + + /*! \brief Highlighter callback type definition. + * + * If user want to have colorful input she must simply install highlighter callback. + * The callback would be invoked by the library after each change to the input done by + * the user. After callback returns library uses data from colors buffer to colorize + * displayed user input. + * + * Size of \e colors buffer is equal to number of code points in user \e input + * which will be different from simple `input.lenght()`! + * + * \param input - an UTF-8 encoded input entered by the user so far. + * \param colors - output buffer for color information. + */ + typedef std::function highlighter_callback_t; + + /*! \brief Hints callback type definition. + * + * \e contextLen is counted in Unicode code points (not in bytes!). + * + * For user input: + * if ( obj.me + * + * input == "if ( obj.me" + * contextLen == 2 (depending on \e set_word_break_characters()) + * + * Client application is free to update \e contextLen to be 6 (or any other non-negative + * number not greater than the number of code points in input) if it makes better sense + * for given client application semantics. + * + * \param input - UTF-8 encoded input entered by the user until current cursor position. + * \param contextLen[in,out] - length of the additional context to provide while displaying hints. + * \param color - a color used for displaying hints. + * \return A list of possible hints. + */ + typedef std::function hint_callback_t; + + /*! \brief Key press handler type definition. + * + * \param code - the key code replxx got from terminal. + * \return Decision on how should input() behave after this key handler returns. + */ + typedef std::function key_press_handler_t; + + struct State { + char const* _text; + int _cursorPosition; + State( char const* text_, int cursorPosition_ = -1 ) + : _text( text_ ) + , _cursorPosition( cursorPosition_ ) { + } + State( State const& ) = default; + State& operator = ( State const& ) = default; + char const* text( void ) const { + return ( _text ); + } + int cursor_position( void ) const { + return ( _cursorPosition ); + } + }; + + class ReplxxImpl; +private: + typedef std::unique_ptr impl_t; +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4251) +#endif + impl_t _impl; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +public: + Replxx( void ); + Replxx( Replxx&& ) = default; + Replxx& operator = ( Replxx&& ) = default; + + /*! \brief Register modify callback. + * + * \param fn - user defined callback function. + */ + void set_modify_callback( modify_callback_t const& fn ); + + /*! \brief Register completion callback. + * + * \param fn - user defined callback function. + */ + void set_completion_callback( completion_callback_t const& fn ); + + /*! \brief Register highlighter callback. + * + * \param fn - user defined callback function. + */ + void set_highlighter_callback( highlighter_callback_t const& fn ); + + /*! \brief Register hints callback. + * + * \param fn - user defined callback function. + */ + void set_hint_callback( hint_callback_t const& fn ); + + /*! \brief Read line of user input. + * + * Returned pointer is managed by the library and is not to be freed in the client. + * + * \param prompt - prompt to be displayed before getting user input. + * \return An UTF-8 encoded input given by the user (or nullptr on EOF). + */ + char const* input( std::string const& prompt ); + + /*! \brief Get current state data. + * + * This call is intended to be used in handlers. + * + * \return Current state of the model. + */ + State get_state( void ) const; + + /*! \brief Set new state data. + * + * This call is intended to be used in handlers. + * + * \param state - new state of the model. + */ + void set_state( State const& state ); + + /*! \brief Print formatted string to standard output. + * + * This function ensures proper handling of ANSI escape sequences + * contained in printed data, which is especially useful on Windows + * since Unixes handle them correctly out of the box. + * + * \param fmt - printf style format. + */ + void print( char const* fmt, ... ); + + /*! \brief Prints a char array with the given length to standard output. + * + * \copydetails print + * + * \param str - The char array to print. + * \param length - The length of the array. + */ + void write( char const* str, int length ); + + /*! \brief Schedule an emulated key press event. + * + * \param code - key press code to be emulated. + */ + void emulate_key_press( char32_t code ); + + /*! \brief Invoke built-in action handler. + * + * \pre This method can be called only from key-press handler. + * + * \param action - a built-in action to invoke. + * \param code - a supplementary key-code to consume by built-in action handler. + * \return The action result informing the replxx what shall happen next. + */ + ACTION_RESULT invoke( ACTION action, char32_t code ); + + /*! \brief Bind user defined action to handle given key-press event. + * + * \param code - handle this key-press event with following handler. + * \param handle - use this handler to handle key-press event. + */ + void bind_key( char32_t code, key_press_handler_t handler ); + + /*! \brief Bind internal `replxx` action (by name) to handle given key-press event. + * + * Action names are the same as names of Replxx::ACTION enumerations + * but in lower case, e.g.: an action for recalling previous history line + * is \e Replxx::ACTION::HISTORY_PREVIOUS so action name to be used in this + * interface for the same effect is "history_previous". + * + * \param code - handle this key-press event with following handler. + * \param actionName - name of internal action to be invoked on key press. + */ + void bind_key_internal( char32_t code, char const* actionName ); + + void history_add( std::string const& line ); + + /*! \brief Synchronize REPL's history with given file. + * + * Synchronizing means loading existing history from given file, + * merging it with current history sorted by timestamps, + * saving merged version to given file, + * keeping merged version as current REPL's history. + * + * This call is an equivalent of calling: + * history_save( "some-file" ); + * history_load( "some-file" ); + * + * \param filename - a path to the file with which REPL's current history should be synchronized. + * \return True iff history file was successfully created. + */ + bool history_sync( std::string const& filename ); + + /*! \brief Save REPL's history into given file. + * + * Saving means loading existing history from given file, + * merging it with current history sorted by timestamps, + * saving merged version to given file, + * keeping original (NOT merged) version as current REPL's history. + * + * \param filename - a path to the file where REPL's history should be saved. + * \return True iff history file was successfully created. + */ + bool history_save( std::string const& filename ); + + /*! \brief Load REPL's history from given file. + * + * \param filename - a path to the file which contains REPL's history that should be loaded. + * \return True iff history file was successfully opened. + */ + bool history_load( std::string const& filename ); + + /*! \brief Clear REPL's in-memory history. + */ + void history_clear( void ); + int history_size( void ) const; + HistoryScan history_scan( void ) const; + + void set_preload_buffer( std::string const& preloadText ); + + /*! \brief Set set of word break characters. + * + * This setting influences word based cursor movement and line editing capabilities. + * + * \param wordBreakers - 7-bit ASCII set of word breaking characters. + */ + void set_word_break_characters( char const* wordBreakers ); + + /*! \brief How many completions should trigger pagination. + */ + void set_completion_count_cutoff( int count ); + + /*! \brief Set maximum number of displayed hint rows. + */ + void set_max_hint_rows( int count ); + + /*! \brief Set a delay before hint are shown after user stopped typing.. + * + * \param milliseconds - a number of milliseconds to wait before showing hints. + */ + void set_hint_delay( int milliseconds ); + + /*! \brief Set tab completion behavior. + * + * \param val - use double tab to invoke completions. + */ + void set_double_tab_completion( bool val ); + + /*! \brief Set tab completion behavior. + * + * \param val - invoke completion even if user input is empty. + */ + void set_complete_on_empty( bool val ); + + /*! \brief Set tab completion behavior. + * + * \param val - beep if completion is ambiguous. + */ + void set_beep_on_ambiguous_completion( bool val ); + + /*! \brief Set complete next/complete previous behavior. + * + * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations, + * in case when a partial completion is possible complete only partial part (`false` setting) + * or complete first proposed completion fully (`true` setting). + * The default is to complete fully (a `true` setting - complete immediately). + * + * \param val - complete immediately. + */ + void set_immediate_completion( bool val ); + + /*! \brief Set history duplicate entries behaviour. + * + * \param val - should history contain only unique entries? + */ + void set_unique_history( bool val ); + + /*! \brief Disable output coloring. + * + * \param val - if set to non-zero disable output colors. + */ + void set_no_color( bool val ); + + /*! \brief Set maximum number of entries in history list. + */ + void set_max_history_size( int len ); + void clear_screen( void ); + int install_window_change_handler( void ); + void enable_bracketed_paste( void ); + void disable_bracketed_paste( void ); + +private: + Replxx( Replxx const& ) = delete; + Replxx& operator = ( Replxx const& ) = delete; +}; + +} + +#endif /* HAVE_REPLXX_HXX_INCLUDED */ + diff --git a/third-party/replxx/make.ps1 b/third-party/replxx/make.ps1 new file mode 100644 index 0000000000..9772870d2e --- /dev/null +++ b/third-party/replxx/make.ps1 @@ -0,0 +1,80 @@ +param ( + [Parameter(Mandatory=$True)] [string]$target, + [Parameter(Mandatory=$False)] [string]$prefix = "", + [Parameter(Mandatory=$False)] [string]$generator = "", + [Parameter(Mandatory=$False)] [switch]$with_shared = $false, + [Parameter(Mandatory=$False)] [switch]$with_examples = $false +) + +function make_absolute( [string]$path ) { + if ( -Not( [System.IO.Path]::IsPathRooted( $path ) ) ) { + $path = [IO.Path]::GetFullPath( [IO.Path]::Combine( ( ($pwd).Path ), ( $path ) ) ) + } + return $path.Replace( "\", "/" ) +} + +function purge { + Write-Host -NoNewline "Purging... " + Remove-Item "build" -Recurse -ErrorAction Ignore + Write-Host "done." +} + +function build( [string]$config, [boolean]$install, [boolean]$build_shared ) { + New-Item -ItemType Directory -Force -Path "build/$config" > $null + if ( $prefix -eq "" ) { + throw "The ``prefix`` paremeter was not specified." + } + $prefix = make_absolute( $prefix ) + Push-Location "build/$config" + $shared="-DBUILD_SHARED_LIBS=$(if ( $build_shared ) { "ON" } else { "OFF" } )" + $examples="-DREPLXX_BUILD_EXAMPLES=$( if ( $with_examples ) { "ON" } else { "OFF" } )" + if ( $generator -ne "" ) { + $genOpt = "-G" + } + cmake $shared $examples $genOpt $generator "-DCMAKE_INSTALL_PREFIX=$prefix" ../../ + cmake --build . --config $config + if ( $install ) { + cmake --build . --target install --config $config + } + Pop-Location +} + +function debug( [boolean]$install = $false ) { + build "debug" $install $false + if ( $with_shared ) { + build "debug" $install $true + } +} + +function release( [boolean]$install = $false ) { + build "release" $install $false + if ( $with_shared ) { + build "release" $install $true + } +} + +function install-debug { + debug $true +} + +function install-release { + release $true +} + +if ( + ( $target -ne "debug" ) -and + ( $target -ne "release" ) -and + ( $target -ne "install-debug" ) -and + ( $target -ne "install-release" ) -and + ( $target -ne "purge" ) +) { + Write-Error "Unknown target: ``$target``" + exit 1 +} + +try { + &$target +} catch { + Pop-Location + Write-Error "$_" +} diff --git a/third-party/replxx/replxx-config.cmake.in b/third-party/replxx/replxx-config.cmake.in new file mode 100644 index 0000000000..6a8862cb69 --- /dev/null +++ b/third-party/replxx/replxx-config.cmake.in @@ -0,0 +1,3 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/replxx-targets.cmake") diff --git a/third-party/replxx/src/ConvertUTF.cpp b/third-party/replxx/src/ConvertUTF.cpp new file mode 100644 index 0000000000..3609c6249e --- /dev/null +++ b/third-party/replxx/src/ConvertUTF.cpp @@ -0,0 +1,271 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Source code file. + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Sept 2001: fixed const & error conditions per + mods suggested by S. Parent & A. Lillich. + June 2002: Tim Dodd added detection and handling of incomplete + source sequences, enhanced error detection, added casts + to eliminate compiler warnings. + July 2003: slight mods to back out aggressive FFFE detection. + Jan 2004: updated switches in from-UTF8 conversions. + Oct 2004: updated to use UNI_MAX_LEGAL_UTF32 in UTF-32 conversions. + + See the header file "ConvertUTF.h" for complete documentation. + +------------------------------------------------------------------------ */ + +#include "ConvertUTF.h" +#ifdef CVTUTF_DEBUG +#include +#endif + +namespace replxx { + +#define UNI_SUR_HIGH_START (UTF32)0xD800 +#define UNI_SUR_LOW_END (UTF32)0xDFFF + +/* --------------------------------------------------------------------- */ + +/* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. + */ +static const char trailingBytesForUTF8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 +}; + +/* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. + */ +static const UTF32 offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + +/* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. + */ +static const UTF8 firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + +/* --------------------------------------------------------------------- */ + +/* The interface converts a whole buffer to avoid function-call overhead. + * Constants have been gathered. Loops & conditionals have been removed as + * much as possible for efficiency, in favor of drop-through switches. + * (See "Note A" at the bottom of the file for equivalent code.) + * If your compiler supports it, the "isLegalUTF8" call can be turned + * into an inline function. + */ + +/* --------------------------------------------------------------------- */ + +/* + * Utility routine to tell whether a sequence of bytes is legal UTF-8. + * This must be called with the length pre-determined by the first byte. + * If not calling this from ConvertUTF8to*, then the length can be set by: + * length = trailingBytesForUTF8[*source]+1; + * and the sequence is illegal right away if there aren't that many bytes + * available. + * If presented with a length > 4, this returns false. The Unicode + * definition of UTF-8 goes up to 4-byte sequences. + */ + +static bool isLegalUTF8(const UTF8 *source, int length) { + UTF8 a; + const UTF8 *srcptr = source+length; + switch (length) { + default: return false; + /* Everything else falls through when "true"... */ + case 4: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */ + case 3: { if ((a = (*--srcptr)) < 0x80 || a > 0xBF) return false; } /* fall through */ + case 2: { + if ((a = (*--srcptr)) > 0xBF) return false; + + switch (*source) { + /* no fall-through in this inner switch */ + case 0xE0: if (a < 0xA0) return false; break; + case 0xED: if (a > 0x9F) return false; break; + case 0xF0: if (a < 0x90) return false; break; + case 0xF4: if (a > 0x8F) return false; break; + default: if (a < 0x80) return false; + } + } /* fall through */ + case 1: { if (*source >= 0x80 && *source < 0xC2) return false; } /* fall through */ + } + if (*source > 0xF4) return false; + return true; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF32* source = *sourceStart; + UTF8* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch; + unsigned short bytesToWrite = 0; + const UTF32 byteMask = 0xBF; + const UTF32 byteMark = 0x80; + ch = *source++; + if (flags == strictConversion ) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + --source; /* return to the illegal value itself */ + result = sourceIllegal; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (UTF32)0x80) { bytesToWrite = 1; + } else if (ch < (UTF32)0x800) { bytesToWrite = 2; + } else if (ch < (UTF32)0x10000) { bytesToWrite = 3; + } else if (ch <= UNI_MAX_LEGAL_UTF32) { bytesToWrite = 4; + } else { bytesToWrite = 3; + ch = UNI_REPLACEMENT_CHAR; + result = sourceIllegal; + } + + target += bytesToWrite; + if (target > targetEnd) { + --source; /* Back up source pointer! */ + target -= bytesToWrite; result = targetExhausted; break; + } + switch (bytesToWrite) { /* note: everything falls through. */ + case 4: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ + case 3: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ + case 2: { *--target = (UTF8)((ch | byteMark) & byteMask); ch >>= 6; } /* fall through */ + case 1: { *--target = (UTF8) (ch | firstByteMark[bytesToWrite]); } /* fall through */ + } + target += bytesToWrite; + } + *sourceStart = source; + *targetStart = target; + return result; +} + +/* --------------------------------------------------------------------- */ + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags) { + ConversionResult result = conversionOK; + const UTF8* source = *sourceStart; + UTF32* target = *targetStart; + while (source < sourceEnd) { + UTF32 ch = 0; + unsigned short extraBytesToRead = trailingBytesForUTF8[*source]; + if (source + extraBytesToRead >= sourceEnd) { + result = sourceExhausted; break; + } + /* Do this check whether lenient or strict */ + if (! isLegalUTF8(source, extraBytesToRead+1)) { + result = sourceIllegal; + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extraBytesToRead) { + case 5: { ch += *source++; ch <<= 6; } /* fall through */ + case 4: { ch += *source++; ch <<= 6; } /* fall through */ + case 3: { ch += *source++; ch <<= 6; } /* fall through */ + case 2: { ch += *source++; ch <<= 6; } /* fall through */ + case 1: { ch += *source++; ch <<= 6; } /* fall through */ + case 0: { ch += *source++; } /* fall through */ + } + ch -= offsetsFromUTF8[extraBytesToRead]; + + if (target >= targetEnd) { + source -= (extraBytesToRead+1); /* Back up the source pointer! */ + result = targetExhausted; break; + } + if (ch <= UNI_MAX_LEGAL_UTF32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (ch >= UNI_SUR_HIGH_START && ch <= UNI_SUR_LOW_END) { + if (flags == strictConversion) { + source -= (extraBytesToRead+1); /* return to the illegal value itself */ + result = sourceIllegal; + break; + } else { + *target++ = UNI_REPLACEMENT_CHAR; + } + } else { + *target++ = ch; + } + } else { /* i.e., ch > UNI_MAX_LEGAL_UTF32 */ + result = sourceIllegal; + *target++ = UNI_REPLACEMENT_CHAR; + } + } + *sourceStart = source; + *targetStart = target; + return result; +} + +} + +/* --------------------------------------------------------------------- + + Note A. + The fall-through switches in UTF-8 reading code save a + temp variable, some decrements & conditionals. The switches + are equivalent to the following loop: + { + int tmpBytesToRead = extraBytesToRead+1; + do { + ch += *source++; + --tmpBytesToRead; + if (tmpBytesToRead) ch <<= 6; + } while (tmpBytesToRead > 0); + } + In UTF-8 writing code, the switches on "bytesToWrite" are + similarly unrolled loops. + + --------------------------------------------------------------------- */ diff --git a/third-party/replxx/src/ConvertUTF.h b/third-party/replxx/src/ConvertUTF.h new file mode 100644 index 0000000000..f91d557324 --- /dev/null +++ b/third-party/replxx/src/ConvertUTF.h @@ -0,0 +1,139 @@ +/* + * Copyright 2001-2004 Unicode, Inc. + * + * Disclaimer + * + * This source code is provided as is by Unicode, Inc. No claims are + * made as to fitness for any particular purpose. No warranties of any + * kind are expressed or implied. The recipient agrees to determine + * applicability of information provided. If this file has been + * purchased on magnetic or optical media from Unicode, Inc., the + * sole remedy for any claim will be exchange of defective media + * within 90 days of receipt. + * + * Limitations on Rights to Redistribute This Code + * + * Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard, and to make copies of this file in any form + * for internal or external distribution as long as this notice + * remains attached. + */ + +/* --------------------------------------------------------------------- + + Conversions between UTF32, UTF-16, and UTF-8. Header file. + + Several funtions are included here, forming a complete set of + conversions between the three formats. UTF-7 is not included + here, but is handled in a separate source file. + + Each of these routines takes pointers to input buffers and output + buffers. The input buffers are const. + + Each routine converts the text between *sourceStart and sourceEnd, + putting the result into the buffer between *targetStart and + targetEnd. Note: the end pointers are *after* the last item: e.g. + *(sourceEnd - 1) is the last item. + + The return result indicates whether the conversion was successful, + and if not, whether the problem was in the source or target buffers. + (Only the first encountered problem is indicated.) + + After the conversion, *sourceStart and *targetStart are both + updated to point to the end of last text successfully converted in + the respective buffers. + + Input parameters: + sourceStart - pointer to a pointer to the source buffer. + The contents of this are modified on return so that + it points at the next thing to be converted. + targetStart - similarly, pointer to pointer to the target buffer. + sourceEnd, targetEnd - respectively pointers to the ends of the + two buffers, for overflow checking only. + + These conversion functions take a ConversionFlags argument. When this + flag is set to strict, both irregular sequences and isolated surrogates + will cause an error. When the flag is set to lenient, both irregular + sequences and isolated surrogates are converted. + + Whether the flag is strict or lenient, all illegal sequences will cause + an error return. This includes sequences such as: , , + or in UTF-8, and values above 0x10FFFF in UTF-32. Conformant code + must check for illegal sequences. + + When the flag is set to lenient, characters over 0x10FFFF are converted + to the replacement character; otherwise (when the flag is set to strict) + they constitute an error. + + Output parameters: + The value "sourceIllegal" is returned from some routines if the input + sequence is malformed. When "sourceIllegal" is returned, the source + value will point to the illegal value that caused the problem. E.g., + in UTF-8 when a sequence is malformed, it points to the start of the + malformed sequence. + + Author: Mark E. Davis, 1994. + Rev History: Rick McGowan, fixes & updates May 2001. + Fixes & updates, Sept 2001. + +------------------------------------------------------------------------ */ + +/* --------------------------------------------------------------------- + The following 4 definitions are compiler-specific. + The C standard does not guarantee that wchar_t has at least + 16 bits, so wchar_t is no less portable than unsigned short! + All should be unsigned values to avoid sign extension during + bit mask & shift operations. +------------------------------------------------------------------------ */ + +#ifndef REPLXX_CONVERT_UTF8_H_INCLUDED +#define REPLXX_CONVERT_UTF8_H_INCLUDED 1 + +#if 0 +typedef unsigned long UTF32; /* at least 32 bits */ +typedef unsigned short UTF16; /* at least 16 bits */ +typedef unsigned char UTF8; /* typically 8 bits */ +#endif + +#include +#include + +namespace replxx { + +typedef uint32_t UTF32; +typedef uint16_t UTF16; +typedef uint8_t UTF8; + +/* Some fundamental constants */ +#define UNI_REPLACEMENT_CHAR (UTF32)0x0000FFFD +#define UNI_MAX_BMP (UTF32)0x0000FFFF +#define UNI_MAX_UTF16 (UTF32)0x0010FFFF +#define UNI_MAX_UTF32 (UTF32)0x7FFFFFFF +#define UNI_MAX_LEGAL_UTF32 (UTF32)0x0010FFFF + +typedef enum { + conversionOK, /* conversion successful */ + sourceExhausted, /* partial character in source, but hit end */ + targetExhausted, /* insuff. room in target for conversion */ + sourceIllegal /* source sequence is illegal/malformed */ +} ConversionResult; + +typedef enum { + strictConversion = 0, + lenientConversion +} ConversionFlags; + +ConversionResult ConvertUTF8toUTF32 ( + const UTF8** sourceStart, const UTF8* sourceEnd, + UTF32** targetStart, UTF32* targetEnd, ConversionFlags flags); + +ConversionResult ConvertUTF32toUTF8 ( + const UTF32** sourceStart, const UTF32* sourceEnd, + UTF8** targetStart, UTF8* targetEnd, ConversionFlags flags); + +} + +#endif /* REPLXX_CONVERT_UTF8_H_INCLUDED */ + +/* --------------------------------------------------------------------- */ diff --git a/third-party/replxx/src/conversion.cxx b/third-party/replxx/src/conversion.cxx new file mode 100644 index 0000000000..bcdbe048ec --- /dev/null +++ b/third-party/replxx/src/conversion.cxx @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include + +#include "conversion.hxx" + +#ifdef _WIN32 +#define strdup _strdup +#endif + +using namespace std; + +namespace replxx { + +namespace locale { + +void to_lower( std::string& s_ ) { + transform( s_.begin(), s_.end(), s_.begin(), static_cast( &tolower ) ); +} + +bool is_8bit_encoding( void ) { + bool is8BitEncoding( false ); + string origLC( setlocale( LC_CTYPE, nullptr ) ); + string lc( origLC ); + to_lower( lc ); + if ( lc == "c" ) { + setlocale( LC_CTYPE, "" ); + } + lc = setlocale( LC_CTYPE, nullptr ); + setlocale( LC_CTYPE, origLC.c_str() ); + to_lower( lc ); + if ( lc.find( "8859" ) != std::string::npos ) { + is8BitEncoding = true; + } + return ( is8BitEncoding ); +} + +bool is8BitEncoding( is_8bit_encoding() ); + +} + +ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) { + ConversionResult res = ConversionResult::conversionOK; + if ( ! locale::is8BitEncoding ) { + const UTF8* sourceStart = reinterpret_cast(src); + const UTF8* sourceEnd = sourceStart + strlen(src); + UTF32* targetStart = reinterpret_cast(dst); + UTF32* targetEnd = targetStart + dstSize; + + res = ConvertUTF8toUTF32( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); + + if (res == conversionOK) { + dstCount = static_cast( targetStart - reinterpret_cast( dst ) ); + + if (dstCount < dstSize) { + *targetStart = 0; + } + } + } else { + for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) { + dst[dstCount] = src[dstCount]; + } + } + return res; +} + +ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char8_t* src) { + return copyString8to32( + dst, dstSize, dstCount, reinterpret_cast(src) + ); +} + +int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) { + int resCount( 0 ); + if ( ! locale::is8BitEncoding ) { + const UTF32* sourceStart = reinterpret_cast(src); + const UTF32* sourceEnd = sourceStart + srcSize; + UTF8* targetStart = reinterpret_cast(dst); + UTF8* targetEnd = targetStart + dstSize; + + ConversionResult res = ConvertUTF32toUTF8( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion + ); + + if ( res == conversionOK ) { + resCount = static_cast( targetStart - reinterpret_cast( dst ) ); + if ( resCount < dstSize ) { + *targetStart = 0; + } + } + } else { + int i( 0 ); + for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) { + dst[i] = static_cast( src[i] ); + } + resCount = i; + if ( i < dstSize ) { + dst[i] = 0; + } + } + return ( resCount ); +} + +} + diff --git a/third-party/replxx/src/conversion.hxx b/third-party/replxx/src/conversion.hxx new file mode 100644 index 0000000000..6587ad0e2f --- /dev/null +++ b/third-party/replxx/src/conversion.hxx @@ -0,0 +1,30 @@ +#ifndef REPLXX_CONVERSION_HXX_INCLUDED +#define REPLXX_CONVERSION_HXX_INCLUDED 1 + +#include "ConvertUTF.h" + +#ifdef __has_include +#if __has_include( ) +#include +#endif +#endif + +#if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) ) +namespace replxx { +typedef unsigned char char8_t; +} +#endif + +namespace replxx { + +ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char const* src ); +ConversionResult copyString8to32( char32_t* dst, int dstSize, int& dstCount, char8_t const* src ); +int copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize ); + +namespace locale { +extern bool is8BitEncoding; +} + +} + +#endif diff --git a/third-party/replxx/src/escape.cxx b/third-party/replxx/src/escape.cxx new file mode 100644 index 0000000000..dda1ab0be0 --- /dev/null +++ b/third-party/replxx/src/escape.cxx @@ -0,0 +1,890 @@ +#include "escape.hxx" +#include "terminal.hxx" +#include "replxx.hxx" + +#ifndef _WIN32 + +namespace replxx { + +namespace EscapeSequenceProcessing { // move these out of global namespace + +// This chunk of code does parsing of the escape sequences sent by various Linux +// terminals. +// +// It handles arrow keys, Home, End and Delete keys by interpreting the +// sequences sent by +// gnome terminal, xterm, rxvt, konsole, aterm and yakuake including the Alt and +// Ctrl key +// combinations that are understood by replxx. +// +// The parsing uses tables, a bunch of intermediate dispatch routines and a +// doDispatch +// loop that reads the tables and sends control to "deeper" routines to continue +// the +// parsing. The starting call to doDispatch( c, initialDispatch ) will +// eventually return +// either a character (with optional CTRL and META bits set), or -1 if parsing +// fails, or +// zero if an attempt to read from the keyboard fails. +// +// This is rather sloppy escape sequence processing, since we're not paying +// attention to what the +// actual TERM is set to and are processing all key sequences for all terminals, +// but it works with +// the most common keystrokes on the most common terminals. It's intricate, but +// the nested 'if' +// statements required to do it directly would be worse. This way has the +// advantage of allowing +// changes and extensions without having to touch a lot of code. + + +static char32_t thisKeyMetaCtrl = 0; // holds pre-set Meta and/or Ctrl modifiers + +// This dispatch routine is given a dispatch table and then farms work out to +// routines +// listed in the table based on the character it is called with. The dispatch +// routines can +// read more input characters to decide what should eventually be returned. +// Eventually, +// a called routine returns either a character or -1 to indicate parsing +// failure. +// +char32_t doDispatch(char32_t c, CharacterDispatch& dispatchTable) { + for (unsigned int i = 0; i < dispatchTable.len; ++i) { + if (static_cast(dispatchTable.chars[i]) == c) { + return dispatchTable.dispatch[i](c); + } + } + return dispatchTable.dispatch[dispatchTable.len](c); +} + +// Final dispatch routines -- return something +// +static char32_t normalKeyRoutine(char32_t c) { return thisKeyMetaCtrl | c; } +static char32_t upArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::UP;; +} +static char32_t downArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::DOWN; +} +static char32_t rightArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::RIGHT; +} +static char32_t leftArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::LEFT; +} +static char32_t homeKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::HOME; } +static char32_t endKeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::END; } +static char32_t shiftTabRoutine(char32_t) { return Replxx::KEY::BASE_SHIFT | Replxx::KEY::TAB; } +static char32_t f1KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F1; } +static char32_t f2KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F2; } +static char32_t f3KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F3; } +static char32_t f4KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F4; } +static char32_t f5KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F5; } +static char32_t f6KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F6; } +static char32_t f7KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F7; } +static char32_t f8KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F8; } +static char32_t f9KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F9; } +static char32_t f10KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F10; } +static char32_t f11KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F11; } +static char32_t f12KeyRoutine(char32_t) { return thisKeyMetaCtrl | Replxx::KEY::F12; } +static char32_t pageUpKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::PAGE_UP; +} +static char32_t pageDownKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::PAGE_DOWN; +} +static char32_t deleteCharRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::BACKSPACE; +} // key labeled Backspace +static char32_t insertKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::INSERT; +} // key labeled Delete +static char32_t deleteKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::DELETE; +} // key labeled Delete +static char32_t ctrlUpArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::UP; +} +static char32_t ctrlDownArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::DOWN; +} +static char32_t ctrlRightArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::RIGHT; +} +static char32_t ctrlLeftArrowKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::LEFT; +} +static char32_t bracketPasteStartKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::PASTE_START; +} +static char32_t bracketPasteFinishKeyRoutine(char32_t) { + return thisKeyMetaCtrl | Replxx::KEY::PASTE_FINISH; +} +static char32_t escFailureRoutine(char32_t) { + beep(); + return -1; +} + +// Handle ESC [ 1 ; 2 or 3 (or 5) escape sequences +// +static CharacterDispatchRoutine escLeftBracket1Semicolon2or3or5Routines[] = { + upArrowKeyRoutine, + downArrowKeyRoutine, + rightArrowKeyRoutine, + leftArrowKeyRoutine, + homeKeyRoutine, + endKeyRoutine, + f1KeyRoutine, + f2KeyRoutine, + f3KeyRoutine, + f4KeyRoutine, + escFailureRoutine +}; +static CharacterDispatch escLeftBracket1Semicolon2or3or5Dispatch = { + 10, "ABCDHFPQRS", escLeftBracket1Semicolon2or3or5Routines +}; + +// Handle ESC [ 1 ; escape sequences +// +static char32_t escLeftBracket1Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch); +} +static char32_t escLeftBracket1Semicolon3Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_META; + return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch); +} +static char32_t escLeftBracket1Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket1Semicolon2or3or5Dispatch); +} +static CharacterDispatchRoutine escLeftBracket1SemicolonRoutines[] = { + escLeftBracket1Semicolon2Routine, + escLeftBracket1Semicolon3Routine, + escLeftBracket1Semicolon5Routine, + escFailureRoutine +}; +static CharacterDispatch escLeftBracket1SemicolonDispatch = { + 3, "235", escLeftBracket1SemicolonRoutines +}; + +// Handle ESC [ 1 ; escape sequences +// +static char32_t escLeftBracket1SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket1SemicolonDispatch); +} + +// (S)-F5 +static CharacterDispatchRoutine escLeftBracket15Semicolon2Routines[] = { + f5KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket15Semicolon2Dispatch = { + 1, "~", escLeftBracket15Semicolon2Routines +}; +static char32_t escLeftBracket15Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket15Semicolon2Dispatch); +} + +// (C)-F5 +static CharacterDispatchRoutine escLeftBracket15Semicolon5Routines[] = { + f5KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket15Semicolon5Dispatch = { + 1, "~", escLeftBracket15Semicolon5Routines +}; +static char32_t escLeftBracket15Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket15Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket15SemicolonRoutines[] = { + escLeftBracket15Semicolon2Routine, escLeftBracket15Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket15SemicolonDispatch = { + 2, "25", escLeftBracket15SemicolonRoutines +}; +static char32_t escLeftBracket15SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket15SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket15Routines[] = { + f5KeyRoutine, escLeftBracket15SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket15Dispatch = { + 2, "~;", escLeftBracket15Routines +}; +static char32_t escLeftBracket15Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket15Dispatch); +} + +// (S)-F6 +static CharacterDispatchRoutine escLeftBracket17Semicolon2Routines[] = { + f6KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket17Semicolon2Dispatch = { + 1, "~", escLeftBracket17Semicolon2Routines +}; +static char32_t escLeftBracket17Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket17Semicolon2Dispatch); +} + +// (C)-F6 +static CharacterDispatchRoutine escLeftBracket17Semicolon5Routines[] = { + f6KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket17Semicolon5Dispatch = { + 1, "~", escLeftBracket17Semicolon5Routines +}; +static char32_t escLeftBracket17Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket17Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket17SemicolonRoutines[] = { + escLeftBracket17Semicolon2Routine, escLeftBracket17Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket17SemicolonDispatch = { + 2, "25", escLeftBracket17SemicolonRoutines +}; +static char32_t escLeftBracket17SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket17SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket17Routines[] = { + f6KeyRoutine, escLeftBracket17SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket17Dispatch = { + 2, "~;", escLeftBracket17Routines +}; +static char32_t escLeftBracket17Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket17Dispatch); +} + +// (S)-F7 +static CharacterDispatchRoutine escLeftBracket18Semicolon2Routines[] = { + f7KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket18Semicolon2Dispatch = { + 1, "~", escLeftBracket18Semicolon2Routines +}; +static char32_t escLeftBracket18Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket18Semicolon2Dispatch); +} + +// (C)-F7 +static CharacterDispatchRoutine escLeftBracket18Semicolon5Routines[] = { + f7KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket18Semicolon5Dispatch = { + 1, "~", escLeftBracket18Semicolon5Routines +}; +static char32_t escLeftBracket18Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket18Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket18SemicolonRoutines[] = { + escLeftBracket18Semicolon2Routine, escLeftBracket18Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket18SemicolonDispatch = { + 2, "25", escLeftBracket18SemicolonRoutines +}; +static char32_t escLeftBracket18SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket18SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket18Routines[] = { + f7KeyRoutine, escLeftBracket18SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket18Dispatch = { + 2, "~;", escLeftBracket18Routines +}; +static char32_t escLeftBracket18Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket18Dispatch); +} + +// (S)-F8 +static CharacterDispatchRoutine escLeftBracket19Semicolon2Routines[] = { + f8KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket19Semicolon2Dispatch = { + 1, "~", escLeftBracket19Semicolon2Routines +}; +static char32_t escLeftBracket19Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket19Semicolon2Dispatch); +} + +// (C)-F8 +static CharacterDispatchRoutine escLeftBracket19Semicolon5Routines[] = { + f8KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket19Semicolon5Dispatch = { + 1, "~", escLeftBracket19Semicolon5Routines +}; +static char32_t escLeftBracket19Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket19Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket19SemicolonRoutines[] = { + escLeftBracket19Semicolon2Routine, escLeftBracket19Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket19SemicolonDispatch = { + 2, "25", escLeftBracket19SemicolonRoutines +}; +static char32_t escLeftBracket19SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket19SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket19Routines[] = { + f8KeyRoutine, escLeftBracket19SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket19Dispatch = { + 2, "~;", escLeftBracket19Routines +}; +static char32_t escLeftBracket19Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket19Dispatch); +} + +// Handle ESC [ 1 escape sequences +// +static CharacterDispatchRoutine escLeftBracket1Routines[] = { + homeKeyRoutine, escLeftBracket1SemicolonRoutine, + escLeftBracket15Routine, + escLeftBracket17Routine, + escLeftBracket18Routine, + escLeftBracket19Routine, + escFailureRoutine +}; +static CharacterDispatch escLeftBracket1Dispatch = { + 6, "~;5789", escLeftBracket1Routines +}; + +// Handle ESC [ 2 escape sequences +// + +// (S)-F9 +static CharacterDispatchRoutine escLeftBracket20Semicolon2Routines[] = { + f9KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket20Semicolon2Dispatch = { + 1, "~", escLeftBracket20Semicolon2Routines +}; +static char32_t escLeftBracket20Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket20Semicolon2Dispatch); +} + +// (C)-F9 +static CharacterDispatchRoutine escLeftBracket20Semicolon5Routines[] = { + f9KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket20Semicolon5Dispatch = { + 1, "~", escLeftBracket20Semicolon5Routines +}; +static char32_t escLeftBracket20Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket20Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket20SemicolonRoutines[] = { + escLeftBracket20Semicolon2Routine, escLeftBracket20Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket20SemicolonDispatch = { + 2, "25", escLeftBracket20SemicolonRoutines +}; +static char32_t escLeftBracket20SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket20SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket200Routines[] = { + bracketPasteStartKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket200Dispatch = { + 1, "~", escLeftBracket200Routines +}; +static char32_t escLeftBracket200Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket200Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket201Routines[] = { + bracketPasteFinishKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket201Dispatch = { + 1, "~", escLeftBracket201Routines +}; +static char32_t escLeftBracket201Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket201Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket20Routines[] = { + f9KeyRoutine, escLeftBracket20SemicolonRoutine, escLeftBracket200Routine, escLeftBracket201Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket20Dispatch = { + 4, "~;01", escLeftBracket20Routines +}; +static char32_t escLeftBracket20Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket20Dispatch); +} + +// (S)-F10 +static CharacterDispatchRoutine escLeftBracket21Semicolon2Routines[] = { + f10KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket21Semicolon2Dispatch = { + 1, "~", escLeftBracket21Semicolon2Routines +}; +static char32_t escLeftBracket21Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket21Semicolon2Dispatch); +} + +// (C)-F10 +static CharacterDispatchRoutine escLeftBracket21Semicolon5Routines[] = { + f10KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket21Semicolon5Dispatch = { + 1, "~", escLeftBracket21Semicolon5Routines +}; +static char32_t escLeftBracket21Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket21Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket21SemicolonRoutines[] = { + escLeftBracket21Semicolon2Routine, escLeftBracket21Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket21SemicolonDispatch = { + 2, "25", escLeftBracket21SemicolonRoutines +}; +static char32_t escLeftBracket21SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket21SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket21Routines[] = { + f10KeyRoutine, escLeftBracket21SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket21Dispatch = { + 2, "~;", escLeftBracket21Routines +}; +static char32_t escLeftBracket21Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket21Dispatch); +} + +// (S)-F11 +static CharacterDispatchRoutine escLeftBracket23Semicolon2Routines[] = { + f11KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket23Semicolon2Dispatch = { + 1, "~", escLeftBracket23Semicolon2Routines +}; +static char32_t escLeftBracket23Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket23Semicolon2Dispatch); +} + +// (C)-F11 +static CharacterDispatchRoutine escLeftBracket23Semicolon5Routines[] = { + f11KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket23Semicolon5Dispatch = { + 1, "~", escLeftBracket23Semicolon5Routines +}; +static char32_t escLeftBracket23Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket23Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket23SemicolonRoutines[] = { + escLeftBracket23Semicolon2Routine, escLeftBracket23Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket23SemicolonDispatch = { + 2, "25", escLeftBracket23SemicolonRoutines +}; +static char32_t escLeftBracket23SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket23SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket23Routines[] = { + f11KeyRoutine, escLeftBracket23SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket23Dispatch = { + 2, "~;", escLeftBracket23Routines +}; +static char32_t escLeftBracket23Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket23Dispatch); +} + +// (S)-F12 +static CharacterDispatchRoutine escLeftBracket24Semicolon2Routines[] = { + f12KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket24Semicolon2Dispatch = { + 1, "~", escLeftBracket24Semicolon2Routines +}; +static char32_t escLeftBracket24Semicolon2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_SHIFT; + return doDispatch(c, escLeftBracket24Semicolon2Dispatch); +} + +// (C)-F12 +static CharacterDispatchRoutine escLeftBracket24Semicolon5Routines[] = { + f12KeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket24Semicolon5Dispatch = { + 1, "~", escLeftBracket24Semicolon5Routines +}; +static char32_t escLeftBracket24Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket24Semicolon5Dispatch); +} + +static CharacterDispatchRoutine escLeftBracket24SemicolonRoutines[] = { + escLeftBracket24Semicolon2Routine, escLeftBracket24Semicolon5Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket24SemicolonDispatch = { + 2, "25", escLeftBracket24SemicolonRoutines +}; +static char32_t escLeftBracket24SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket24SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket24Routines[] = { + f12KeyRoutine, escLeftBracket24SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket24Dispatch = { + 2, "~;", escLeftBracket24Routines +}; +static char32_t escLeftBracket24Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket24Dispatch); +} + +// Handle ESC [ 2 escape sequences +// +static CharacterDispatchRoutine escLeftBracket2Routines[] = { + insertKeyRoutine, + escLeftBracket20Routine, + escLeftBracket21Routine, + escLeftBracket23Routine, + escLeftBracket24Routine, + escFailureRoutine +}; +static CharacterDispatch escLeftBracket2Dispatch = { + 5, "~0134", escLeftBracket2Routines +}; + +// Handle ESC [ 3 escape sequences +// +static CharacterDispatchRoutine escLeftBracket3Routines[] = { + deleteKeyRoutine, escFailureRoutine +}; + +static CharacterDispatch escLeftBracket3Dispatch = { + 1, "~", escLeftBracket3Routines +}; + +// Handle ESC [ 4 escape sequences +// +static CharacterDispatchRoutine escLeftBracket4Routines[] = { + endKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket4Dispatch = { + 1, "~", escLeftBracket4Routines +}; + +// Handle ESC [ 5 escape sequences +// +static CharacterDispatchRoutine escLeftBracket5Semicolon5Routines[] = { + pageUpKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket5Semicolon5Dispatch = { + 1, "~", escLeftBracket5Semicolon5Routines +}; +static char32_t escLeftBracket5Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket5Semicolon5Dispatch); +} +static CharacterDispatchRoutine escLeftBracket5SemicolonRoutines[] = { + escLeftBracket5Semicolon5Routine, + escFailureRoutine +}; +static CharacterDispatch escLeftBracket5SemicolonDispatch = { + 1, "5", escLeftBracket5SemicolonRoutines +}; +static char32_t escLeftBracket5SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket5SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket5Routines[] = { + pageUpKeyRoutine, escLeftBracket5SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket5Dispatch = { + 2, "~;", escLeftBracket5Routines +}; + +// Handle ESC [ 6 escape sequences +// +static CharacterDispatchRoutine escLeftBracket6Semicolon5Routines[] = { + pageDownKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket6Semicolon5Dispatch = { + 1, "~", escLeftBracket6Semicolon5Routines +}; +static char32_t escLeftBracket6Semicolon5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + thisKeyMetaCtrl |= Replxx::KEY::BASE_CONTROL; + return doDispatch(c, escLeftBracket6Semicolon5Dispatch); +} +static CharacterDispatchRoutine escLeftBracket6SemicolonRoutines[] = { + escLeftBracket6Semicolon5Routine, + escFailureRoutine +}; +static CharacterDispatch escLeftBracket6SemicolonDispatch = { + 1, "5", escLeftBracket6SemicolonRoutines +}; +static char32_t escLeftBracket6SemicolonRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket6SemicolonDispatch); +} + +static CharacterDispatchRoutine escLeftBracket6Routines[] = { + pageDownKeyRoutine, escLeftBracket6SemicolonRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket6Dispatch = { + 2, "~;", escLeftBracket6Routines +}; + +// Handle ESC [ 7 escape sequences +// +static CharacterDispatchRoutine escLeftBracket7Routines[] = { + homeKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket7Dispatch = { + 1, "~", escLeftBracket7Routines +}; + +// Handle ESC [ 8 escape sequences +// +static CharacterDispatchRoutine escLeftBracket8Routines[] = { + endKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escLeftBracket8Dispatch = { + 1, "~", escLeftBracket8Routines +}; + +// Handle ESC [ escape sequences +// +static char32_t escLeftBracket0Routine(char32_t c) { + return escFailureRoutine(c); +} +static char32_t escLeftBracket1Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket1Dispatch); +} +static char32_t escLeftBracket2Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket2Dispatch); +} +static char32_t escLeftBracket3Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket3Dispatch); +} +static char32_t escLeftBracket4Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket4Dispatch); +} +static char32_t escLeftBracket5Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket5Dispatch); +} +static char32_t escLeftBracket6Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket6Dispatch); +} +static char32_t escLeftBracket7Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket7Dispatch); +} +static char32_t escLeftBracket8Routine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracket8Dispatch); +} +static char32_t escLeftBracket9Routine(char32_t c) { + return escFailureRoutine(c); +} + +// Handle ESC [ escape sequences +// +static CharacterDispatchRoutine escLeftBracketRoutines[] = { + upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, + leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine, + shiftTabRoutine, + escLeftBracket0Routine, escLeftBracket1Routine, escLeftBracket2Routine, + escLeftBracket3Routine, escLeftBracket4Routine, escLeftBracket5Routine, + escLeftBracket6Routine, escLeftBracket7Routine, escLeftBracket8Routine, + escLeftBracket9Routine, escFailureRoutine +}; +static CharacterDispatch escLeftBracketDispatch = {17, "ABCDHFZ0123456789", + escLeftBracketRoutines}; + +// Handle ESC O escape sequences +// +static CharacterDispatchRoutine escORoutines[] = { + upArrowKeyRoutine, downArrowKeyRoutine, rightArrowKeyRoutine, + leftArrowKeyRoutine, homeKeyRoutine, endKeyRoutine, + f1KeyRoutine, f2KeyRoutine, f3KeyRoutine, + f4KeyRoutine, + ctrlUpArrowKeyRoutine, ctrlDownArrowKeyRoutine, ctrlRightArrowKeyRoutine, + ctrlLeftArrowKeyRoutine, escFailureRoutine +}; +static CharacterDispatch escODispatch = {14, "ABCDHFPQRSabcd", escORoutines}; + +// Initial ESC dispatch -- could be a Meta prefix or the start of an escape +// sequence +// +static char32_t escLeftBracketRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escLeftBracketDispatch); +} +static char32_t escORoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escODispatch); +} +static char32_t setMetaRoutine(char32_t c); // need forward reference +static CharacterDispatchRoutine escRoutines[] = { + escLeftBracketRoutine, escORoutine, setMetaRoutine +}; +static CharacterDispatch escDispatch = {2, "[O", escRoutines}; + +// Initial dispatch -- we are not in the middle of anything yet +// +static char32_t escRoutine(char32_t c) { + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escDispatch); +} +static CharacterDispatchRoutine initialRoutines[] = { + escRoutine, deleteCharRoutine, normalKeyRoutine +}; +static CharacterDispatch initialDispatch = {2, "\x1B\x7F", initialRoutines}; + +// Special handling for the ESC key because it does double duty +// +static char32_t setMetaRoutine(char32_t c) { + thisKeyMetaCtrl = Replxx::KEY::BASE_META; + if (c == 0x1B) { // another ESC, stay in ESC processing mode + c = read_unicode_character(); + if (c == 0) return 0; + return doDispatch(c, escDispatch); + } + return doDispatch(c, initialDispatch); +} + +char32_t doDispatch(char32_t c) { + EscapeSequenceProcessing::thisKeyMetaCtrl = 0; // no modifiers yet at initialDispatch + return doDispatch(c, initialDispatch); +} + +} // namespace EscapeSequenceProcessing // move these out of global namespace + +} + +#endif /* #ifndef _WIN32 */ + diff --git a/third-party/replxx/src/escape.hxx b/third-party/replxx/src/escape.hxx new file mode 100644 index 0000000000..6597395328 --- /dev/null +++ b/third-party/replxx/src/escape.hxx @@ -0,0 +1,37 @@ +#ifndef REPLXX_ESCAPE_HXX_INCLUDED +#define REPLXX_ESCAPE_HXX_INCLUDED 1 + +namespace replxx { + +namespace EscapeSequenceProcessing { + +// This is a typedef for the routine called by doDispatch(). It takes the +// current character +// as input, does any required processing including reading more characters and +// calling other +// dispatch routines, then eventually returns the final (possibly extended or +// special) character. +// +typedef char32_t (*CharacterDispatchRoutine)(char32_t); + +// This structure is used by doDispatch() to hold a list of characters to test +// for and +// a list of routines to call if the character matches. The dispatch routine +// list is one +// longer than the character list; the final entry is used if no character +// matches. +// +struct CharacterDispatch { + unsigned int len; // length of the chars list + const char* chars; // chars to test + CharacterDispatchRoutine* dispatch; // array of routines to call +}; + +char32_t doDispatch(char32_t c); + +} + +} + +#endif + diff --git a/third-party/replxx/src/history.cxx b/third-party/replxx/src/history.cxx new file mode 100644 index 0000000000..fe691df089 --- /dev/null +++ b/third-party/replxx/src/history.cxx @@ -0,0 +1,402 @@ +#include +#include +#include +#include + +#ifndef _WIN32 + +#include +#include +#include + +#endif /* _WIN32 */ + +#include "replxx.hxx" +#include "history.hxx" + +using namespace std; + +namespace replxx { + +namespace { +void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) { + delete impl_; +} +} + +static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 ); + +Replxx::HistoryScan::HistoryScan( impl_t impl_ ) + : _impl( std::move( impl_ ) ) { +} + +bool Replxx::HistoryScan::next( void ) { + return ( _impl->next() ); +} + +Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ ) + : _entries( entries_ ) + , _it( _entries.end() ) + , _utf8Cache() + , _entryCache( std::string(), std::string() ) + , _cacheValid( false ) { +} + +Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const { + return ( _impl->get() ); +} + +bool Replxx::HistoryScanImpl::next( void ) { + if ( _it == _entries.end() ) { + _it = _entries.begin(); + } else { + ++ _it; + } + _cacheValid = false; + return ( _it != _entries.end() ); +} + +Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const { + if ( _cacheValid ) { + return ( _entryCache ); + } + _utf8Cache.assign( _it->text() ); + _entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() ); + _cacheValid = true; + return ( _entryCache ); +} + +Replxx::HistoryScan::impl_t History::scan( void ) const { + return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) ); +} + +History::History( void ) + : _entries() + , _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN ) + , _current( _entries.begin() ) + , _yankPos( _entries.end() ) + , _previous( _entries.begin() ) + , _recallMostRecent( false ) + , _unique( true ) { +} + +void History::add( UnicodeString const& line, std::string const& when ) { + if ( _maxSize <= 0 ) { + return; + } + if ( ! _entries.empty() && ( line == _entries.back().text() ) ) { + _entries.back() = Entry( now_ms_str(), line ); + return; + } + remove_duplicate( line ); + trim_to_max_size(); + _entries.emplace_back( when, line ); + _locations.insert( make_pair( line, last() ) ); + if ( _current == _entries.end() ) { + _current = last(); + } + _yankPos = _entries.end(); +} + +#ifndef _WIN32 +class FileLock { + std::string _path; + int _lockFd; +public: + FileLock( std::string const& name_ ) + : _path( name_ + ".lock" ) + , _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) { + static_cast( ::lockf( _lockFd, F_LOCK, 0 ) == 0 ); + } + ~FileLock( void ) { + static_cast( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 ); + ::close( _lockFd ); + ::unlink( _path.c_str() ); + return; + } +}; +#endif + +bool History::save( std::string const& filename, bool sync_ ) { +#ifndef _WIN32 + mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO ); + FileLock fileLock( filename ); +#endif + entries_t entries; + locations_t locations; + if ( ! sync_ ) { + entries.swap( _entries ); + locations.swap( _locations ); + _entries = entries; + reset_iters(); + } + do_load( filename ); + sort(); + remove_duplicates(); + trim_to_max_size(); + ofstream histFile( filename ); + if ( ! histFile ) { + return ( false ); + } +#ifndef _WIN32 + umask( old_umask ); + chmod( filename.c_str(), S_IRUSR | S_IWUSR ); +#endif + Utf8String utf8; + for ( Entry const& h : _entries ) { + if ( ! h.text().is_empty() ) { + utf8.assign( h.text() ); + histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl; + } + } + if ( ! sync_ ) { + _entries = std::move( entries ); + _locations = std::move( locations ); + } + reset_iters(); + return ( true ); +} + +namespace { + +bool is_timestamp( std::string const& s ) { + static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd"; + static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 ); + if ( s.length() != TIMESTAMP_LENGTH ) { + return ( false ); + } + for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) { + if ( TIMESTAMP_PATTERN[i] == 'd' ) { + if ( ! isdigit( s[i] ) ) { + return ( false ); + } + } else if ( s[i] != TIMESTAMP_PATTERN[i] ) { + return ( false ); + } + } + return ( true ); +} + +} + +bool History::do_load( std::string const& filename ) { + ifstream histFile( filename ); + if ( ! histFile ) { + return ( false ); + } + string line; + string when( "0000-00-00 00:00:00.000" ); + while ( getline( histFile, line ).good() ) { + string::size_type eol( line.find_first_of( "\r\n" ) ); + if ( eol != string::npos ) { + line.erase( eol ); + } + if ( is_timestamp( line ) ) { + when.assign( line, 4, std::string::npos ); + continue; + } + if ( ! line.empty() ) { + _entries.emplace_back( when, UnicodeString( line ) ); + } + } + return ( true ); +} + +bool History::load( std::string const& filename ) { + clear(); + bool success( do_load( filename ) ); + sort(); + remove_duplicates(); + trim_to_max_size(); + _previous = _current = last(); + _yankPos = _entries.end(); + return ( success ); +} + +void History::sort( void ) { + typedef std::vector sortable_entries_t; + _locations.clear(); + sortable_entries_t sortableEntries( _entries.begin(), _entries.end() ); + std::stable_sort( sortableEntries.begin(), sortableEntries.end() ); + _entries.clear(); + _entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() ); +} + +void History::clear( void ) { + _locations.clear(); + _entries.clear(); + _current = _entries.begin(); + _recallMostRecent = false; +} + +void History::set_max_size( int size_ ) { + if ( size_ >= 0 ) { + _maxSize = size_; + trim_to_max_size(); + } +} + +void History::reset_yank_iterator( void ) { + _yankPos = _entries.end(); +} + +bool History::next_yank_position( void ) { + bool resetYankSize( false ); + if ( _yankPos == _entries.end() ) { + resetYankSize = true; + } + if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) { + -- _yankPos; + } else { + _yankPos = moved( _entries.end(), -2 ); + } + return ( resetYankSize ); +} + +bool History::move( bool up_ ) { + bool doRecall( _recallMostRecent && ! up_ ); + if ( doRecall ) { + _current = _previous; // emulate Windows down-arrow + } + _recallMostRecent = false; + return ( doRecall || move( _current, up_ ? -1 : 1 ) ); +} + +void History::jump( bool start_, bool reset_ ) { + if ( start_ ) { + _current = _entries.begin(); + } else { + _current = last(); + } + if ( reset_ ) { + _recallMostRecent = false; + } +} + +void History::save_pos( void ) { + _previous = _current; +} + +void History::restore_pos( void ) { + _current = _previous; +} + +bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_ ) { + int step( back_ ? -1 : 1 ); + entries_t::const_iterator it( moved( _current, step, true ) ); + while ( it != _current ) { + if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) { + _current = it; + commit_index(); + return ( true ); + } + move( it, step, true ); + } + return ( false ); +} + +bool History::move( entries_t::const_iterator& it_, int by_, bool wrapped_ ) const { + if ( by_ > 0 ) { + for ( int i( 0 ); i < by_; ++ i ) { + ++ it_; + if ( it_ != _entries.end() ) { + } else if ( wrapped_ ) { + it_ = _entries.begin(); + } else { + -- it_; + return ( false ); + } + } + } else { + for ( int i( 0 ); i > by_; -- i ) { + if ( it_ != _entries.begin() ) { + -- it_; + } else if ( wrapped_ ) { + it_ = last(); + } else { + return ( false ); + } + } + } + return ( true ); +} + +History::entries_t::const_iterator History::moved( entries_t::const_iterator it_, int by_, bool wrapped_ ) const { + move( it_, by_, wrapped_ ); + return ( it_ ); +} + +void History::erase( entries_t::const_iterator it_ ) { + bool invalidated( it_ == _current ); + _locations.erase( it_->text() ); + it_ = _entries.erase( it_ ); + if ( invalidated ) { + _current = it_; + } + if ( ( _current == _entries.end() ) && ! _entries.empty() ) { + -- _current; + } + _yankPos = _entries.end(); + _previous = _current; +} + +void History::trim_to_max_size( void ) { + while ( size() > _maxSize ) { + erase( _entries.begin() ); + } +} + +void History::remove_duplicate( UnicodeString const& line_ ) { + if ( ! _unique ) { + return; + } + locations_t::iterator it( _locations.find( line_ ) ); + if ( it == _locations.end() ) { + return; + } + erase( it->second ); +} + +void History::remove_duplicates( void ) { + if ( ! _unique ) { + return; + } + _locations.clear(); + typedef std::pair locations_insertion_result_t; + for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) { + locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) ); + if ( ! locationsInsertionResult.second ) { + _entries.erase( locationsInsertionResult.first->second ); + locationsInsertionResult.first->second = it; + } + } +} + +void History::update_last( UnicodeString const& line_ ) { + if ( _unique ) { + _locations.erase( _entries.back().text() ); + remove_duplicate( line_ ); + _locations.insert( make_pair( line_, last() ) ); + } + _entries.back() = Entry( now_ms_str(), line_ ); +} + +void History::drop_last( void ) { + erase( last() ); +} + +bool History::is_last( void ) const { + return ( _current == last() ); +} + +History::entries_t::const_iterator History::last( void ) const { + return ( moved( _entries.end(), -1 ) ); +} + +void History::reset_iters( void ) { + _previous = _current = last(); + _yankPos = _entries.end(); +} + +} + diff --git a/third-party/replxx/src/history.hxx b/third-party/replxx/src/history.hxx new file mode 100644 index 0000000000..4e72c03676 --- /dev/null +++ b/third-party/replxx/src/history.hxx @@ -0,0 +1,141 @@ +#ifndef REPLXX_HISTORY_HXX_INCLUDED +#define REPLXX_HISTORY_HXX_INCLUDED 1 + +#include +#include + +#include "unicodestring.hxx" +#include "utf8string.hxx" +#include "conversion.hxx" +#include "util.hxx" + +namespace std { +template<> +struct hash { + std::size_t operator()( replxx::UnicodeString const& us_ ) const { + std::size_t h( 0 ); + char32_t const* p( us_.get() ); + char32_t const* e( p + us_.length() ); + while ( p != e ) { + h *= 31; + h += *p; + ++ p; + } + return ( h ); + } +}; +} + +namespace replxx { + +class History { +public: + class Entry { + std::string _timestamp; + UnicodeString _text; + public: + Entry( std::string const& timestamp_, UnicodeString const& text_ ) + : _timestamp( timestamp_ ) + , _text( text_ ) { + } + std::string const& timestamp( void ) const { + return ( _timestamp ); + } + UnicodeString const& text( void ) const { + return ( _text ); + } + bool operator < ( Entry const& other_ ) const { + return ( _timestamp < other_._timestamp ); + } + }; + typedef std::list entries_t; + typedef std::unordered_map locations_t; +private: + entries_t _entries; + locations_t _locations; + int _maxSize; + entries_t::const_iterator _current; + entries_t::const_iterator _yankPos; + /* + * _previous and _recallMostRecent are used to allow + * HISTORY_NEXT action (a down-arrow key) to have a special meaning + * if invoked after a line from history was accepted without + * any modification. + * Special meaning is: a down arrow shall jump to the line one + * after previously accepted from history. + */ + entries_t::const_iterator _previous; + bool _recallMostRecent; + bool _unique; +public: + History( void ); + void add( UnicodeString const& line, std::string const& when = now_ms_str() ); + bool save( std::string const& filename, bool ); + bool load( std::string const& filename ); + void clear( void ); + void set_max_size( int len ); + void set_unique( bool unique_ ) { + _unique = unique_; + remove_duplicates(); + } + void reset_yank_iterator(); + bool next_yank_position( void ); + void reset_recall_most_recent( void ) { + _recallMostRecent = false; + } + void commit_index( void ) { + _previous = _current; + _recallMostRecent = true; + } + bool is_empty( void ) const { + return ( _entries.empty() ); + } + void update_last( UnicodeString const& ); + void drop_last( void ); + bool is_last( void ) const; + bool move( bool ); + UnicodeString const& current( void ) const { + return ( _current->text() ); + } + UnicodeString const& yank_line( void ) const { + return ( _yankPos->text() ); + } + void jump( bool, bool = true ); + bool common_prefix_search( UnicodeString const&, int, bool ); + int size( void ) const { + return ( static_cast( _entries.size() ) ); + } + Replxx::HistoryScan::impl_t scan( void ) const; + void save_pos( void ); + void restore_pos( void ); +private: + History( History const& ) = delete; + History& operator = ( History const& ) = delete; + bool move( entries_t::const_iterator&, int, bool = false ) const; + entries_t::const_iterator moved( entries_t::const_iterator, int, bool = false ) const; + void erase( entries_t::const_iterator ); + void trim_to_max_size( void ); + void remove_duplicate( UnicodeString const& ); + void remove_duplicates( void ); + bool do_load( std::string const& ); + entries_t::const_iterator last( void ) const; + void sort( void ); + void reset_iters( void ); +}; + +class Replxx::HistoryScanImpl { + History::entries_t const& _entries; + History::entries_t::const_iterator _it; + mutable Utf8String _utf8Cache; + mutable Replxx::HistoryEntry _entryCache; + mutable bool _cacheValid; +public: + HistoryScanImpl( History::entries_t const& ); + bool next( void ); + Replxx::HistoryEntry const& get( void ) const; +}; + +} + +#endif + diff --git a/third-party/replxx/src/killring.hxx b/third-party/replxx/src/killring.hxx new file mode 100644 index 0000000000..0baf108e73 --- /dev/null +++ b/third-party/replxx/src/killring.hxx @@ -0,0 +1,78 @@ +#ifndef REPLXX_KILLRING_HXX_INCLUDED +#define REPLXX_KILLRING_HXX_INCLUDED 1 + +#include + +#include "unicodestring.hxx" + +namespace replxx { + +class KillRing { + static const int capacity = 10; + int size; + int index; + char indexToSlot[10]; + std::vector theRing; + +public: + enum action { actionOther, actionKill, actionYank }; + action lastAction; + + KillRing() + : size(0) + , index(0) + , lastAction(actionOther) { + theRing.reserve(capacity); + } + + void kill(const char32_t* text, int textLen, bool forward) { + if (textLen == 0) { + return; + } + UnicodeString killedText(text, textLen); + if (lastAction == actionKill && size > 0) { + int slot = indexToSlot[0]; + int currentLen = static_cast(theRing[slot].length()); + UnicodeString temp; + if ( forward ) { + temp.append( theRing[slot].get(), currentLen ).append( killedText.get(), textLen ); + } else { + temp.append( killedText.get(), textLen ).append( theRing[slot].get(), currentLen ); + } + theRing[slot] = temp; + } else { + if (size < capacity) { + if (size > 0) { + memmove(&indexToSlot[1], &indexToSlot[0], size); + } + indexToSlot[0] = size; + size++; + theRing.push_back(killedText); + } else { + int slot = indexToSlot[capacity - 1]; + theRing[slot] = killedText; + memmove(&indexToSlot[1], &indexToSlot[0], capacity - 1); + indexToSlot[0] = slot; + } + index = 0; + } + } + + UnicodeString* yank() { return (size > 0) ? &theRing[indexToSlot[index]] : 0; } + + UnicodeString* yankPop() { + if (size == 0) { + return 0; + } + ++index; + if (index == size) { + index = 0; + } + return &theRing[indexToSlot[index]]; + } +}; + +} + +#endif + diff --git a/third-party/replxx/src/prompt.cxx b/third-party/replxx/src/prompt.cxx new file mode 100644 index 0000000000..7cc57428a8 --- /dev/null +++ b/third-party/replxx/src/prompt.cxx @@ -0,0 +1,154 @@ +#ifdef _WIN32 + +#include +#include +#include +#if _MSC_VER < 1900 && defined (_MSC_VER) +#define snprintf _snprintf // Microsoft headers use underscores in some names +#endif +#define strcasecmp _stricmp +#define strdup _strdup +#define write _write +#define STDIN_FILENO 0 + +#else /* _WIN32 */ + +#include + +#endif /* _WIN32 */ + +#include "prompt.hxx" +#include "util.hxx" + +namespace replxx { + +Prompt::Prompt( Terminal& terminal_ ) + : _extraLines( 0 ) + , _lastLinePosition( 0 ) + , _previousInputLen( 0 ) + , _previousLen( 0 ) + , _screenColumns( 0 ) + , _terminal( terminal_ ) { +} + +void Prompt::write() { + _terminal.write32( _text.get(), _byteCount ); +} + +void Prompt::update_screen_columns( void ) { + _screenColumns = _terminal.get_screen_columns(); +} + +void Prompt::set_text( UnicodeString const& text_ ) { + _extraLines = 0; + _lastLinePosition = 0; + _previousInputLen = 0; + _previousLen = 0; + _screenColumns = 0; + update_screen_columns(); + // strip control characters from the prompt -- we do allow newline + _text = text_; + UnicodeString::const_iterator in( text_.begin() ); + UnicodeString::iterator out( _text.begin() ); + + int len = 0; + int x = 0; + + bool const strip = !tty::out; + + while (in != text_.end()) { + char32_t c = *in; + if ('\n' == c || !is_control_code(c)) { + *out = c; + ++out; + ++in; + ++len; + if ('\n' == c || ++x >= _screenColumns) { + x = 0; + ++_extraLines; + _lastLinePosition = len; + } + } else if (c == '\x1b') { + if ( strip ) { + // jump over control chars + ++in; + if (*in == '[') { + ++in; + while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { + ++in; + } + if (*in == 'm') { + ++in; + } + } + } else { + // copy control chars + *out = *in; + ++out; + ++in; + if (*in == '[') { + *out = *in; + ++out; + ++in; + while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { + *out = *in; + ++out; + ++in; + } + if (*in == 'm') { + *out = *in; + ++out; + ++in; + } + } + } + } else { + ++in; + } + } + _characterCount = len; + _byteCount = static_cast(out - _text.begin()); + + _indentation = len - _lastLinePosition; + _cursorRowOffset = _extraLines; +} + +// Used with DynamicPrompt (history search) +// +const UnicodeString forwardSearchBasePrompt("(i-search)`"); +const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`"); +const UnicodeString endSearchBasePrompt("': "); + +DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection ) + : Prompt( terminal_ ) + , _searchText() + , _direction( initialDirection ) { + update_screen_columns(); + _cursorRowOffset = 0; + const UnicodeString* basePrompt = + (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; + size_t promptStartLength = basePrompt->length(); + _characterCount = static_cast(promptStartLength + endSearchBasePrompt.length()); + _byteCount = _characterCount; + _lastLinePosition = _characterCount; // TODO fix this, we are asssuming + // that the history prompt won't wrap (!) + _previousLen = _characterCount; + _text.assign( *basePrompt ).append( endSearchBasePrompt ); + calculate_screen_position( + 0, 0, screen_columns(), _characterCount, + _indentation, _extraLines + ); +} + +void DynamicPrompt::updateSearchPrompt(void) { + const UnicodeString* basePrompt = + (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; + size_t promptStartLength = basePrompt->length(); + _characterCount = static_cast(promptStartLength + _searchText.length() + + endSearchBasePrompt.length()); + _byteCount = _characterCount; + _text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt ); +} + +} + diff --git a/third-party/replxx/src/prompt.hxx b/third-party/replxx/src/prompt.hxx new file mode 100644 index 0000000000..7d8482f485 --- /dev/null +++ b/third-party/replxx/src/prompt.hxx @@ -0,0 +1,47 @@ +#ifndef REPLXX_PROMPT_HXX_INCLUDED +#define REPLXX_PROMPT_HXX_INCLUDED 1 + +#include + +#include "unicodestring.hxx" +#include "terminal.hxx" + +namespace replxx { + +class Prompt { // a convenience struct for grouping prompt info +public: + UnicodeString _text; // our copy of the prompt text, edited + int _characterCount; // chars in _text + int _byteCount; // bytes in _text + int _extraLines; // extra lines (beyond 1) occupied by prompt + int _indentation; // column offset to end of prompt + int _lastLinePosition; // index into _text where last line begins + int _previousInputLen; // _characterCount of previous input line, for clearing + int _cursorRowOffset; // where the cursor is relative to the start of the prompt + int _previousLen; // help erasing +private: + int _screenColumns; // width of screen in columns [cache] + Terminal& _terminal; +public: + Prompt( Terminal& ); + void set_text( UnicodeString const& textPtr ); + void update_screen_columns( void ); + int screen_columns() const { + return ( _screenColumns ); + } + void write(); +}; + +// changing prompt for "(reverse-i-search)`text':" etc. +// +struct DynamicPrompt : public Prompt { + UnicodeString _searchText; // text we are searching for + int _direction; // current search _direction, 1=forward, -1=reverse + + DynamicPrompt( Terminal&, int initialDirection ); + void updateSearchPrompt(void); +}; + +} + +#endif diff --git a/third-party/replxx/src/replxx.cxx b/third-party/replxx/src/replxx.cxx new file mode 100644 index 0000000000..782f9cc64f --- /dev/null +++ b/third-party/replxx/src/replxx.cxx @@ -0,0 +1,648 @@ +/* + * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) + * Copyright (c) 2010, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + * + * line editing lib needs to be 20,000 lines of C code. + * + * You can find the latest source code at: + * + * http://github.com/antirez/linenoise + * + * Does a number of crazy assumptions that happen to be true in 99.9999% of + * the 2010 UNIX computers around. + * + * References: + * - http://invisible-island.net/xterm/ctlseqs/ctlseqs.html + * - http://www.3waylabs.com/nw/WWW/products/wizcon/vt220.html + * + * Todo list: + * - Switch to gets() if $TERM is something we can't support. + * - Filter bogus Ctrl+ combinations. + * - Win32 support + * + * Bloat: + * - Completion? + * - History search like Ctrl+r in readline? + * + * List of escape sequences used by this program, we do everything just + * with three sequences. In order to be so cheap we may have some + * flickering effect with some slow terminal, but the lesser sequences + * the more compatible. + * + * CHA (Cursor Horizontal Absolute) + * Sequence: ESC [ n G + * Effect: moves cursor to column n (1 based) + * + * EL (Erase Line) + * Sequence: ESC [ n K + * Effect: if n is 0 or missing, clear from cursor to end of line + * Effect: if n is 1, clear from beginning of line to cursor + * Effect: if n is 2, clear entire line + * + * CUF (Cursor Forward) + * Sequence: ESC [ n C + * Effect: moves cursor forward of n chars + * + * The following are used to clear the screen: ESC [ H ESC [ 2 J + * This is actually composed of two sequences: + * + * cursorhome + * Sequence: ESC [ H + * Effect: moves the cursor to upper left corner + * + * ED2 (Clear entire screen) + * Sequence: ESC [ 2 J + * Effect: clear the whole screen + * + */ + +#include +#include + +#ifdef _WIN32 + +#include +#define STDIN_FILENO 0 + +#else /* _WIN32 */ + +#include +#include +#include + +#endif /* _WIN32 */ + +#include "replxx.h" +#include "replxx.hxx" +#include "replxx_impl.hxx" +#include "history.hxx" + +static_assert( + static_cast( replxx::Replxx::ACTION::SEND_EOF ) == static_cast( REPLXX_ACTION_SEND_EOF ), + "C and C++ `ACTION` APIs are missaligned!" +); + +static_assert( + static_cast( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast( REPLXX_KEY_PASTE_FINISH ), + "C and C++ `KEY` APIs are missaligned!" +); + +using namespace std; +using namespace std::placeholders; +using namespace replxx; + +namespace replxx { + +namespace { +void delete_ReplxxImpl( Replxx::ReplxxImpl* impl_ ) { + delete impl_; +} +} + +Replxx::Replxx( void ) + : _impl( new Replxx::ReplxxImpl( nullptr, nullptr, nullptr ), delete_ReplxxImpl ) { +} + +void Replxx::set_completion_callback( completion_callback_t const& fn ) { + _impl->set_completion_callback( fn ); +} + +void Replxx::set_modify_callback( modify_callback_t const& fn ) { + _impl->set_modify_callback( fn ); +} + +void Replxx::set_highlighter_callback( highlighter_callback_t const& fn ) { + _impl->set_highlighter_callback( fn ); +} + +void Replxx::set_hint_callback( hint_callback_t const& fn ) { + _impl->set_hint_callback( fn ); +} + +char const* Replxx::input( std::string const& prompt ) { + return ( _impl->input( prompt ) ); +} + +void Replxx::history_add( std::string const& line ) { + _impl->history_add( line ); +} + +bool Replxx::history_sync( std::string const& filename ) { + return ( _impl->history_sync( filename ) ); +} + +bool Replxx::history_save( std::string const& filename ) { + return ( _impl->history_save( filename ) ); +} + +bool Replxx::history_load( std::string const& filename ) { + return ( _impl->history_load( filename ) ); +} + +void Replxx::history_clear( void ) { + _impl->history_clear(); +} + +int Replxx::history_size( void ) const { + return ( _impl->history_size() ); +} + +Replxx::HistoryScan Replxx::history_scan( void ) const { + return ( _impl->history_scan() ); +} + +void Replxx::set_preload_buffer( std::string const& preloadText ) { + _impl->set_preload_buffer( preloadText ); +} + +void Replxx::set_word_break_characters( char const* wordBreakers ) { + _impl->set_word_break_characters( wordBreakers ); +} + +void Replxx::set_max_hint_rows( int count ) { + _impl->set_max_hint_rows( count ); +} + +void Replxx::set_hint_delay( int milliseconds ) { + _impl->set_hint_delay( milliseconds ); +} + +void Replxx::set_completion_count_cutoff( int count ) { + _impl->set_completion_count_cutoff( count ); +} + +void Replxx::set_double_tab_completion( bool val ) { + _impl->set_double_tab_completion( val ); +} + +void Replxx::set_complete_on_empty( bool val ) { + _impl->set_complete_on_empty( val ); +} + +void Replxx::set_beep_on_ambiguous_completion( bool val ) { + _impl->set_beep_on_ambiguous_completion( val ); +} + +void Replxx::set_immediate_completion( bool val ) { + _impl->set_immediate_completion( val ); +} + +void Replxx::set_unique_history( bool val ) { + _impl->set_unique_history( val ); +} + +void Replxx::set_no_color( bool val ) { + _impl->set_no_color( val ); +} + +void Replxx::set_max_history_size( int len ) { + _impl->set_max_history_size( len ); +} + +void Replxx::clear_screen( void ) { + _impl->clear_screen( 0 ); +} + +void Replxx::emulate_key_press( char32_t keyPress_ ) { + _impl->emulate_key_press( keyPress_ ); +} + +Replxx::ACTION_RESULT Replxx::invoke( ACTION action_, char32_t keyPress_ ) { + return ( _impl->invoke( action_, keyPress_ ) ); +} + +void Replxx::bind_key( char32_t keyPress_, key_press_handler_t handler_ ) { + _impl->bind_key( keyPress_, handler_ ); +} + +void Replxx::bind_key_internal( char32_t keyPress_, char const* actionName_ ) { + _impl->bind_key_internal( keyPress_, actionName_ ); +} + +Replxx::State Replxx::get_state( void ) const { + return ( _impl->get_state() ); +} + +void Replxx::set_state( Replxx::State const& state_ ) { + _impl->set_state( state_ ); +} + +int Replxx::install_window_change_handler( void ) { + return ( _impl->install_window_change_handler() ); +} + +void Replxx::enable_bracketed_paste( void ) { + _impl->enable_bracketed_paste(); +} + +void Replxx::disable_bracketed_paste( void ) { + _impl->disable_bracketed_paste(); +} + +void Replxx::print( char const* format_, ... ) { + ::std::va_list ap; + va_start( ap, format_ ); + int size = static_cast( vsnprintf( nullptr, 0, format_, ap ) ); + va_end( ap ); + va_start( ap, format_ ); + unique_ptr buf( new char[size + 1] ); + vsnprintf( buf.get(), static_cast( size + 1 ), format_, ap ); + va_end( ap ); + return ( _impl->print( buf.get(), size ) ); +} + +void Replxx::write( char const* str, int length ) { + return ( _impl->print( str, length ) ); +} + +} + +::Replxx* replxx_init() { + typedef ::Replxx* replxx_data_t; + return ( reinterpret_cast( new replxx::Replxx::ReplxxImpl( nullptr, nullptr, nullptr ) ) ); +} + +void replxx_end( ::Replxx* replxx_ ) { + delete reinterpret_cast( replxx_ ); +} + +void replxx_clear_screen( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->clear_screen( 0 ); +} + +void replxx_emulate_key_press( ::Replxx* replxx_, int unsigned keyPress_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->emulate_key_press( keyPress_ ); +} + +ReplxxActionResult replxx_invoke( ::Replxx* replxx_, ReplxxAction action_, int unsigned keyPress_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( static_cast( replxx->invoke( static_cast( action_ ), keyPress_ ) ) ); +} + +replxx::Replxx::ACTION_RESULT key_press_handler_forwarder( key_press_handler_t handler_, char32_t code_, void* userData_ ) { + return ( static_cast( handler_( code_, userData_ ) ) ); +} + +void replxx_bind_key( ::Replxx* replxx_, int code_, key_press_handler_t handler_, void* userData_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->bind_key( code_, std::bind( key_press_handler_forwarder, handler_, _1, userData_ ) ); +} + +int replxx_bind_key_internal( ::Replxx* replxx_, int code_, char const* actionName_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + try { + replxx->bind_key_internal( code_, actionName_ ); + } catch ( ... ) { + return ( -1 ); + } + return ( 0 ); +} + +void replxx_get_state( ::Replxx* replxx_, ReplxxState* state ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx::Replxx::State s( replxx->get_state() ); + state->text = s.text(); + state->cursorPosition = s.cursor_position(); +} + +void replxx_set_state( ::Replxx* replxx_, ReplxxState* state ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_state( replxx::Replxx::State( state->text, state->cursorPosition ) ); +} + +/** + * replxx_set_preload_buffer provides text to be inserted into the command buffer + * + * the provided text will be processed to be usable and will be used to preload + * the input buffer on the next call to replxx_input() + * + * @param preloadText text to begin with on the next call to replxx_input() + */ +void replxx_set_preload_buffer(::Replxx* replxx_, const char* preloadText) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_preload_buffer( preloadText ? preloadText : "" ); +} + +/** + * replxx_input is a readline replacement. + * + * call it with a prompt to display and it will return a line of input from the + * user + * + * @param prompt text of prompt to display to the user + * @return the returned string is managed by replxx library + * and it must NOT be freed in the client. + */ +char const* replxx_input( ::Replxx* replxx_, const char* prompt ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->input( prompt ) ); +} + +int replxx_print( ::Replxx* replxx_, char const* format_, ... ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + ::std::va_list ap; + va_start( ap, format_ ); + int size = static_cast( vsnprintf( nullptr, 0, format_, ap ) ); + va_end( ap ); + va_start( ap, format_ ); + unique_ptr buf( new char[size + 1] ); + vsnprintf( buf.get(), static_cast( size + 1 ), format_, ap ); + va_end( ap ); + try { + replxx->print( buf.get(), size ); + } catch ( ... ) { + return ( -1 ); + } + return ( size ); +} + +int replxx_write( ::Replxx* replxx_, char const* str, int length ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + try { + replxx->print( str, length ); + } catch ( ... ) { + return ( -1 ); + } + return static_cast( length ); +} + +struct replxx_completions { + replxx::Replxx::completions_t data; +}; + +struct replxx_hints { + replxx::Replxx::hints_t data; +}; + +void modify_fwd( replxx_modify_callback_t fn, std::string& line_, int& cursorPosition_, void* userData_ ) { +#ifdef _WIN32 +#define strdup _strdup +#endif + char* s( strdup( line_.c_str() ) ); +#undef strdup + fn( &s, &cursorPosition_, userData_ ); + line_ = s; + free( s ); + return; +} + +void replxx_set_modify_callback(::Replxx* replxx_, replxx_modify_callback_t* fn, void* userData) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_modify_callback( std::bind( &modify_fwd, fn, _1, _2, userData ) ); +} + +replxx::Replxx::completions_t completions_fwd( replxx_completion_callback_t fn, std::string const& input_, int& contextLen_, void* userData ) { + replxx_completions completions; + fn( input_.c_str(), &completions, &contextLen_, userData ); + return ( completions.data ); +} + +/* Register a callback function to be called for tab-completion. */ +void replxx_set_completion_callback(::Replxx* replxx_, replxx_completion_callback_t* fn, void* userData) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_completion_callback( std::bind( &completions_fwd, fn, _1, _2, userData ) ); +} + +void highlighter_fwd( replxx_highlighter_callback_t fn, std::string const& input, replxx::Replxx::colors_t& colors, void* userData ) { + std::vector colorsTmp( colors.size() ); + std::transform( + colors.begin(), + colors.end(), + colorsTmp.begin(), + []( replxx::Replxx::Color c ) { + return ( static_cast( c ) ); + } + ); + fn( input.c_str(), colorsTmp.data(), static_cast( colors.size() ), userData ); + std::transform( + colorsTmp.begin(), + colorsTmp.end(), + colors.begin(), + []( ReplxxColor c ) { + return ( static_cast( c ) ); + } + ); +} + +void replxx_set_highlighter_callback( ::Replxx* replxx_, replxx_highlighter_callback_t* fn, void* userData ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_highlighter_callback( std::bind( &highlighter_fwd, fn, _1, _2, userData ) ); +} + +replxx::Replxx::hints_t hints_fwd( replxx_hint_callback_t fn, std::string const& input_, int& contextLen_, replxx::Replxx::Color& color_, void* userData ) { + replxx_hints hints; + ReplxxColor c( static_cast( color_ ) ); + fn( input_.c_str(), &hints, &contextLen_, &c, userData ); + return ( hints.data ); +} + +void replxx_set_hint_callback( ::Replxx* replxx_, replxx_hint_callback_t* fn, void* userData ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_hint_callback( std::bind( &hints_fwd, fn, _1, _2, _3, userData ) ); +} + +void replxx_add_hint(replxx_hints* lh, const char* str) { + lh->data.emplace_back(str); +} + +void replxx_add_completion( replxx_completions* lc, const char* str ) { + lc->data.emplace_back( str ); +} + +void replxx_add_completion( replxx_completions* lc, const char* str, ReplxxColor color ) { + lc->data.emplace_back( str, static_cast( color ) ); +} + +void replxx_history_add( ::Replxx* replxx_, const char* line ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->history_add( line ); +} + +void replxx_set_max_history_size( ::Replxx* replxx_, int len ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_max_history_size( len ); +} + +void replxx_set_max_hint_rows( ::Replxx* replxx_, int count ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_max_hint_rows( count ); +} + +void replxx_set_hint_delay( ::Replxx* replxx_, int milliseconds ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_hint_delay( milliseconds ); +} + +void replxx_set_completion_count_cutoff( ::Replxx* replxx_, int count ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_completion_count_cutoff( count ); +} + +void replxx_set_word_break_characters( ::Replxx* replxx_, char const* breakChars_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_word_break_characters( breakChars_ ); +} + +void replxx_set_double_tab_completion( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_double_tab_completion( val ? true : false ); +} + +void replxx_set_complete_on_empty( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_complete_on_empty( val ? true : false ); +} + +void replxx_set_no_color( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_no_color( val ? true : false ); +} + +void replxx_set_beep_on_ambiguous_completion( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_beep_on_ambiguous_completion( val ? true : false ); +} + +void replxx_set_immediate_completion( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_immediate_completion( val ? true : false ); +} + +void replxx_set_unique_history( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_unique_history( val ? true : false ); +} + +void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->enable_bracketed_paste(); +} + +void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->disable_bracketed_paste(); +} + +ReplxxHistoryScan* replxx_history_scan_start( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( reinterpret_cast( replxx->history_scan().release() ) ); +} + +void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) { + delete reinterpret_cast( historyScan_ ); +} + +int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) { + replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast( historyScan_ ) ); + bool hasNext( historyScan->next() ); + if ( hasNext ) { + replxx::Replxx::HistoryEntry const& historyEntry( historyScan->get() ); + historyEntry_->timestamp = historyEntry.timestamp().c_str(); + historyEntry_->text = historyEntry.text().c_str(); + } + return ( hasNext ? 0 : -1 ); +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int replxx_history_sync( ::Replxx* replxx_, const char* filename ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->history_sync( filename ) ? 0 : -1 ); +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int replxx_history_save( ::Replxx* replxx_, const char* filename ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->history_save( filename ) ? 0 : -1 ); +} + +/* Load the history from the specified file. If the file does not exist + * zero is returned and no operation is performed. + * + * If the file exists and the operation succeeded 0 is returned, otherwise + * on error -1 is returned. */ +int replxx_history_load( ::Replxx* replxx_, const char* filename ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->history_load( filename ) ? 0 : -1 ); +} + +void replxx_history_clear( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->history_clear(); +} + +int replxx_history_size( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->history_size() ); +} + +/* This special mode is used by replxx in order to print scan codes + * on screen for debugging / development purposes. It is implemented + * by the replxx-c-api-example program using the --keycodes option. */ +#ifdef __REPLXX_DEBUG__ +void replxx_debug_dump_print_codes(void) { + char quit[4]; + + printf( + "replxx key codes debugging mode.\n" + "Press keys to see scan codes. Type 'quit' at any time to exit.\n"); + if (enableRawMode() == -1) return; + memset(quit, ' ', 4); + while (1) { + char c; + int nread; + +#if _WIN32 + nread = _read(STDIN_FILENO, &c, 1); +#else + nread = read(STDIN_FILENO, &c, 1); +#endif + if (nread <= 0) continue; + memmove(quit, quit + 1, sizeof(quit) - 1); /* shift string to left. */ + quit[sizeof(quit) - 1] = c; /* Insert current char on the right. */ + if (memcmp(quit, "quit", sizeof(quit)) == 0) break; + + printf("'%c' %02x (%d) (type quit to exit)\n", isprint(c) ? c : '?', (int)c, + (int)c); + printf("\r"); /* Go left edge manually, we are in raw mode. */ + fflush(stdout); + } + disableRawMode(); +} +#endif // __REPLXX_DEBUG__ + +int replxx_install_window_change_handler( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->install_window_change_handler() ); +} + diff --git a/third-party/replxx/src/replxx_impl.cxx b/third-party/replxx/src/replxx_impl.cxx new file mode 100644 index 0000000000..4d33408736 --- /dev/null +++ b/third-party/replxx/src/replxx_impl.cxx @@ -0,0 +1,2195 @@ +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#include +#include +#if _MSC_VER < 1900 +#define snprintf _snprintf // Microsoft headers use underscores in some names +#endif +#define strcasecmp _stricmp +#define write _write +#define STDIN_FILENO 0 + +#else /* _WIN32 */ + +#include +#include + +#endif /* _WIN32 */ + +#ifdef _WIN32 +#include "windows.hxx" +#endif + +#include "replxx_impl.hxx" +#include "utf8string.hxx" +#include "prompt.hxx" +#include "util.hxx" +#include "terminal.hxx" +#include "history.hxx" +#include "replxx.hxx" + +using namespace std; + +namespace replxx { + +namespace { + +namespace action_names { + +char const INSERT_CHARACTER[] = "insert_character"; +char const MOVE_CURSOR_TO_BEGINING_OF_LINE[] = "move_cursor_to_begining_of_line"; +char const MOVE_CURSOR_TO_END_OF_LINE[] = "move_cursor_to_end_of_line"; +char const MOVE_CURSOR_LEFT[] = "move_cursor_left"; +char const MOVE_CURSOR_RIGHT[] = "move_cursor_right"; +char const MOVE_CURSOR_ONE_WORD_LEFT[] = "move_cursor_one_word_left"; +char const MOVE_CURSOR_ONE_WORD_RIGHT[] = "move_cursor_one_word_right"; +char const KILL_TO_WHITESPACE_ON_LEFT[] = "kill_to_whitespace_on_left"; +char const KILL_TO_END_OF_WORD[] = "kill_to_end_of_word"; +char const KILL_TO_BEGINING_OF_WORD[] = "kill_to_begining_of_word"; +char const KILL_TO_BEGINING_OF_LINE[] = "kill_to_begining_of_line"; +char const KILL_TO_END_OF_LINE[] = "kill_to_end_of_line"; +char const YANK[] = "yank"; +char const YANK_CYCLE[] = "yank_cycle"; +char const YANK_LAST_ARG[] = "yank_last_arg"; +char const CAPITALIZE_WORD[] = "capitalize_word"; +char const LOWERCASE_WORD[] = "lowercase_word"; +char const UPPERCASE_WORD[] = "uppercase_word"; +char const TRANSPOSE_CHARACTERS[] = "transpose_characters"; +char const ABORT_LINE[] = "abort_line"; +char const SEND_EOF[] = "send_eof"; +char const TOGGLE_OVERWRITE_MODE[] = "toggle_overwrite_mode"; +char const DELETE_CHARACTER_UNDER_CURSOR[] = "delete_character_under_cursor"; +char const DELETE_CHARACTER_LEFT_OF_CURSOR[] = "delete_character_left_of_cursor"; +char const COMMIT_LINE[] = "commit_line"; +char const CLEAR_SCREEN[] = "clear_screen"; +char const COMPLETE_NEXT[] = "complete_next"; +char const COMPLETE_PREVIOUS[] = "complete_previous"; +char const HISTORY_NEXT[] = "history_next"; +char const HISTORY_PREVIOUS[] = "history_previous"; +char const HISTORY_LAST[] = "history_last"; +char const HISTORY_FIRST[] = "history_first"; +char const HINT_PREVIOUS[] = "hint_previous"; +char const HINT_NEXT[] = "hint_next"; +char const VERBATIM_INSERT[] = "verbatim_insert"; +char const SUSPEND[] = "suspend"; +char const COMPLETE_LINE[] = "complete_line"; +char const HISTORY_INCREMENTAL_SEARCH[] = "history_incremental_search"; +char const HISTORY_COMMON_PREFIX_SEARCH[] = "history_common_prefix_search"; +} + +static int const REPLXX_MAX_HINT_ROWS( 4 ); +/* + * All whitespaces and all non-alphanumerical characters from ASCII range + * with an exception of an underscore ('_'). + */ +char const defaultBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?"; +static const char* unsupported_term[] = {"dumb", "cons25", "emacs", NULL}; + +static bool isUnsupportedTerm(void) { + char* term = getenv("TERM"); + if (term == NULL) { + return false; + } + for (int j = 0; unsupported_term[j]; ++j) { + if (!strcasecmp(term, unsupported_term[j])) { + return true; + } + } + return false; +} + +int long long RAPID_REFRESH_MS = 1; +int long long RAPID_REFRESH_US = RAPID_REFRESH_MS * 1000; + +inline int long long now_us( void ) { + return ( std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count() ); +} + +} + +Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* ) + : _utf8Buffer() + , _data() + , _charWidths() + , _display() + , _displayInputLength( 0 ) + , _hint() + , _pos( 0 ) + , _prefix( 0 ) + , _hintSelection( -1 ) + , _history() + , _killRing() + , _lastRefreshTime( now_us() ) + , _refreshSkipped( false ) + , _lastYankSize( 0 ) + , _maxHintRows( REPLXX_MAX_HINT_ROWS ) + , _hintDelay( 0 ) + , _breakChars( defaultBreakChars ) + , _completionCountCutoff( 100 ) + , _overwrite( false ) + , _doubleTabCompletion( false ) + , _completeOnEmpty( true ) + , _beepOnAmbiguousCompletion( false ) + , _immediateCompletion( true ) + , _bracketedPaste( false ) + , _noColor( false ) + , _namedActions() + , _keyPressHandlers() + , _terminal() + , _currentThread() + , _prompt( _terminal ) + , _completionCallback( nullptr ) + , _highlighterCallback( nullptr ) + , _hintCallback( nullptr ) + , _keyPresses() + , _messages() + , _completions() + , _completionContextLength( 0 ) + , _completionSelection( -1 ) + , _preloadedBuffer() + , _errorMessage() + , _previousSearchText() + , _modifiedState( false ) + , _hintColor( Replxx::Color::GRAY ) + , _hintsCache() + , _hintContextLenght( -1 ) + , _hintSeed() + , _mutex() { + using namespace std::placeholders; + _namedActions[action_names::INSERT_CHARACTER] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::INSERT_CHARACTER, _1 ); + _namedActions[action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ); + _namedActions[action_names::MOVE_CURSOR_TO_END_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 ); + _namedActions[action_names::MOVE_CURSOR_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 ); + _namedActions[action_names::MOVE_CURSOR_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 ); + _namedActions[action_names::MOVE_CURSOR_ONE_WORD_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ); + _namedActions[action_names::MOVE_CURSOR_ONE_WORD_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ); + _namedActions[action_names::KILL_TO_WHITESPACE_ON_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT, _1 ); + _namedActions[action_names::KILL_TO_END_OF_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 ); + _namedActions[action_names::KILL_TO_BEGINING_OF_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD, _1 ); + _namedActions[action_names::KILL_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE, _1 ); + _namedActions[action_names::KILL_TO_END_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE, _1 ); + _namedActions[action_names::YANK] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK, _1 ); + _namedActions[action_names::YANK_CYCLE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 ); + _namedActions[action_names::YANK_LAST_ARG] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_LAST_ARG, _1 ); + _namedActions[action_names::CAPITALIZE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 ); + _namedActions[action_names::LOWERCASE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 ); + _namedActions[action_names::UPPERCASE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 ); + _namedActions[action_names::TRANSPOSE_CHARACTERS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS, _1 ); + _namedActions[action_names::ABORT_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE, _1 ); + _namedActions[action_names::SEND_EOF] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF, _1 ); + _namedActions[action_names::TOGGLE_OVERWRITE_MODE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE, _1 ); + _namedActions[action_names::DELETE_CHARACTER_UNDER_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 ); + _namedActions[action_names::DELETE_CHARACTER_LEFT_OF_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 ); + _namedActions[action_names::COMMIT_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 ); + _namedActions[action_names::CLEAR_SCREEN] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN, _1 ); + _namedActions[action_names::COMPLETE_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT, _1 ); + _namedActions[action_names::COMPLETE_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS, _1 ); + _namedActions[action_names::HISTORY_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 ); + _namedActions[action_names::HISTORY_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 ); + _namedActions[action_names::HISTORY_LAST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ); + _namedActions[action_names::HISTORY_FIRST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ); + _namedActions[action_names::HINT_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS, _1 ); + _namedActions[action_names::HINT_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT, _1 ); +#ifndef _WIN32 + _namedActions[action_names::VERBATIM_INSERT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT, _1 ); + _namedActions[action_names::SUSPEND] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND, _1 ); +#else + _namedActions[action_names::VERBATIM_INSERT] = _namedActions[action_names::SUSPEND] = Replxx::key_press_handler_t(); +#endif + _namedActions[action_names::COMPLETE_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE, _1 ); + _namedActions[action_names::HISTORY_INCREMENTAL_SEARCH] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 ); + _namedActions[action_names::HISTORY_COMMON_PREFIX_SEARCH] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ); + + bind_key( Replxx::KEY::control( 'A' ), _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) ); + bind_key( Replxx::KEY::HOME + 0, _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'E' ), _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) ); + bind_key( Replxx::KEY::END + 0, _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'B' ), _namedActions.at( action_names::MOVE_CURSOR_LEFT ) ); + bind_key( Replxx::KEY::LEFT + 0, _namedActions.at( action_names::MOVE_CURSOR_LEFT ) ); + bind_key( Replxx::KEY::control( 'F' ), _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) ); + bind_key( Replxx::KEY::RIGHT + 0, _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) ); + bind_key( Replxx::KEY::meta( 'b' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); + bind_key( Replxx::KEY::meta( 'B' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); + bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); // Emacs allows Meta, readline don't + bind_key( Replxx::KEY::meta( 'f' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); + bind_key( Replxx::KEY::meta( 'F' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); + bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); // Emacs allows Meta, readline don't + bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), _namedActions.at( action_names::KILL_TO_WHITESPACE_ON_LEFT ) ); + bind_key( Replxx::KEY::meta( 'd' ), _namedActions.at( action_names::KILL_TO_END_OF_WORD ) ); + bind_key( Replxx::KEY::meta( 'D' ), _namedActions.at( action_names::KILL_TO_END_OF_WORD ) ); + bind_key( Replxx::KEY::control( 'W' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_WORD ) ); + bind_key( Replxx::KEY::control( 'U' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'K' ), _namedActions.at( action_names::KILL_TO_END_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'Y' ), _namedActions.at( action_names::YANK ) ); + bind_key( Replxx::KEY::meta( 'y' ), _namedActions.at( action_names::YANK_CYCLE ) ); + bind_key( Replxx::KEY::meta( 'Y' ), _namedActions.at( action_names::YANK_CYCLE ) ); + bind_key( Replxx::KEY::meta( '.' ), _namedActions.at( action_names::YANK_LAST_ARG ) ); + bind_key( Replxx::KEY::meta( 'c' ), _namedActions.at( action_names::CAPITALIZE_WORD ) ); + bind_key( Replxx::KEY::meta( 'C' ), _namedActions.at( action_names::CAPITALIZE_WORD ) ); + bind_key( Replxx::KEY::meta( 'l' ), _namedActions.at( action_names::LOWERCASE_WORD ) ); + bind_key( Replxx::KEY::meta( 'L' ), _namedActions.at( action_names::LOWERCASE_WORD ) ); + bind_key( Replxx::KEY::meta( 'u' ), _namedActions.at( action_names::UPPERCASE_WORD ) ); + bind_key( Replxx::KEY::meta( 'U' ), _namedActions.at( action_names::UPPERCASE_WORD ) ); + bind_key( Replxx::KEY::control( 'T' ), _namedActions.at( action_names::TRANSPOSE_CHARACTERS ) ); + bind_key( Replxx::KEY::control( 'C' ), _namedActions.at( action_names::ABORT_LINE ) ); + bind_key( Replxx::KEY::control( 'D' ), _namedActions.at( action_names::SEND_EOF ) ); + bind_key( Replxx::KEY::INSERT + 0, _namedActions.at( action_names::TOGGLE_OVERWRITE_MODE ) ); + bind_key( 127, _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) ); + bind_key( Replxx::KEY::DELETE + 0, _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) ); + bind_key( Replxx::KEY::BACKSPACE + 0, _namedActions.at( action_names::DELETE_CHARACTER_LEFT_OF_CURSOR ) ); + bind_key( Replxx::KEY::control( 'J' ), _namedActions.at( action_names::COMMIT_LINE ) ); + bind_key( Replxx::KEY::ENTER + 0, _namedActions.at( action_names::COMMIT_LINE ) ); + bind_key( Replxx::KEY::control( 'L' ), _namedActions.at( action_names::CLEAR_SCREEN ) ); + bind_key( Replxx::KEY::control( 'N' ), _namedActions.at( action_names::COMPLETE_NEXT ) ); + bind_key( Replxx::KEY::control( 'P' ), _namedActions.at( action_names::COMPLETE_PREVIOUS ) ); + bind_key( Replxx::KEY::DOWN + 0, _namedActions.at( action_names::HISTORY_NEXT ) ); + bind_key( Replxx::KEY::UP + 0, _namedActions.at( action_names::HISTORY_PREVIOUS ) ); + bind_key( Replxx::KEY::meta( '<' ), _namedActions.at( action_names::HISTORY_FIRST ) ); + bind_key( Replxx::KEY::PAGE_UP + 0, _namedActions.at( action_names::HISTORY_FIRST ) ); + bind_key( Replxx::KEY::meta( '>' ), _namedActions.at( action_names::HISTORY_LAST ) ); + bind_key( Replxx::KEY::PAGE_DOWN + 0, _namedActions.at( action_names::HISTORY_LAST ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::UP ), _namedActions.at( action_names::HINT_PREVIOUS ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ), _namedActions.at( action_names::HINT_NEXT ) ); +#ifndef _WIN32 + bind_key( Replxx::KEY::control( 'V' ), _namedActions.at( action_names::VERBATIM_INSERT ) ); + bind_key( Replxx::KEY::control( 'Z' ), _namedActions.at( action_names::SUSPEND ) ); +#endif + bind_key( Replxx::KEY::TAB + 0, _namedActions.at( action_names::COMPLETE_LINE ) ); + bind_key( Replxx::KEY::control( 'R' ), _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) ); + bind_key( Replxx::KEY::control( 'S' ), _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'p' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'P' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'n' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'N' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::PASTE_START, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::BRACKETED_PASTE, _1 ) ); +} + +Replxx::ReplxxImpl::~ReplxxImpl( void ) { + disable_bracketed_paste(); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32_t code ) { + switch ( action_ ) { + case ( Replxx::ACTION::INSERT_CHARACTER ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, code ) ); + case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::delete_character, code ) ); + case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::backspace_character, code ) ); + case ( Replxx::ACTION::KILL_TO_END_OF_LINE ): return ( action( WANT_REFRESH | SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_end_of_line, code ) ); + case ( Replxx::ACTION::KILL_TO_BEGINING_OF_LINE ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_begining_of_line, code ) ); + case ( Replxx::ACTION::KILL_TO_END_OF_WORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_right, code ) ); + case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left, code ) ); + case ( Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_whitespace_to_left, code ) ); + case ( Replxx::ACTION::YANK ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank, code ) ); + case ( Replxx::ACTION::YANK_CYCLE ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank_cycle, code ) ); + case ( Replxx::ACTION::YANK_LAST_ARG ): return ( action( HISTORY_RECALL_MOST_RECENT | DONT_RESET_HIST_YANK_INDEX, &Replxx::ReplxxImpl::yank_last_arg, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_begining_of_line, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_end_of_line, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_left, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_right, code ) ); + case ( Replxx::ACTION::HISTORY_NEXT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_next, code ) ); + case ( Replxx::ACTION::HISTORY_PREVIOUS ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_previous, code ) ); + case ( Replxx::ACTION::HISTORY_FIRST ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_first, code ) ); + case ( Replxx::ACTION::HISTORY_LAST ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_last, code ) ); + case ( Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH ): return ( action( NOOP, &Replxx::ReplxxImpl::incremental_history_search, code ) ); + case ( Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH ): return ( action( RESET_KILL_ACTION | DONT_RESET_PREFIX, &Replxx::ReplxxImpl::common_prefix_search, code ) ); + case ( Replxx::ACTION::HINT_NEXT ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_next, code ) ); + case ( Replxx::ACTION::HINT_PREVIOUS ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_previous, code ) ); + case ( Replxx::ACTION::CAPITALIZE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word, code ) ); + case ( Replxx::ACTION::LOWERCASE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word, code ) ); + case ( Replxx::ACTION::UPPERCASE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word, code ) ); + case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::transpose_characters, code ) ); + case ( Replxx::ACTION::TOGGLE_OVERWRITE_MODE ): return ( action( NOOP, &Replxx::ReplxxImpl::toggle_overwrite_mode, code ) ); +#ifndef _WIN32 + case ( Replxx::ACTION::VERBATIM_INSERT ): return ( action( WANT_REFRESH | RESET_KILL_ACTION, &Replxx::ReplxxImpl::verbatim_insert, code ) ); + case ( Replxx::ACTION::SUSPEND ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::suspend, code ) ); +#endif + case ( Replxx::ACTION::CLEAR_SCREEN ): return ( action( NOOP, &Replxx::ReplxxImpl::clear_screen, code ) ); + case ( Replxx::ACTION::CLEAR_SELF ): clear_self_to_end_of_screen(); return ( Replxx::ACTION_RESULT::CONTINUE ); + case ( Replxx::ACTION::REPAINT ): repaint(); return ( Replxx::ACTION_RESULT::CONTINUE ); + case ( Replxx::ACTION::COMPLETE_LINE ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_line, code ) ); + case ( Replxx::ACTION::COMPLETE_NEXT ): return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_next, code ) ); + case ( Replxx::ACTION::COMPLETE_PREVIOUS ): return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_previous, code ) ); + case ( Replxx::ACTION::COMMIT_LINE ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::commit_line, code ) ); + case ( Replxx::ACTION::ABORT_LINE ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::abort_line, code ) ); + case ( Replxx::ACTION::SEND_EOF ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::send_eof, code ) ); + case ( Replxx::ACTION::BRACKETED_PASTE ): return ( action( WANT_REFRESH | RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::bracketed_paste, code ) ); + } + return ( Replxx::ACTION_RESULT::BAIL ); +} + +void Replxx::ReplxxImpl::bind_key( char32_t code_, Replxx::key_press_handler_t handler_ ) { + _keyPressHandlers[code_] = handler_; +} + +void Replxx::ReplxxImpl::bind_key_internal( char32_t code_, char const* actionName_ ) { + named_actions_t::const_iterator it( _namedActions.find( actionName_ ) ); + if ( it == _namedActions.end() ) { + throw std::runtime_error( std::string( "replxx: Unknown action name: " ).append( actionName_ ) ); + } + if ( !! it->second ) { + bind_key( code_, it->second ); + } +} + +Replxx::State Replxx::ReplxxImpl::get_state( void ) const { + _utf8Buffer.assign( _data ); + return ( Replxx::State( _utf8Buffer.get(), _pos ) ); +} + +void Replxx::ReplxxImpl::set_state( Replxx::State const& state_ ) { + _data.assign( state_.text() ); + if ( state_.cursor_position() >= 0 ) { + _pos = min( state_.cursor_position(), _data.length() ); + } + _modifiedState = true; +} + +char32_t Replxx::ReplxxImpl::read_char( HINT_ACTION hintAction_ ) { + /* try scheduled key presses */ { + std::lock_guard l( _mutex ); + if ( !_keyPresses.empty() ) { + char32_t keyPress( _keyPresses.front() ); + _keyPresses.pop_front(); + return ( keyPress ); + } + } + int hintDelay( + _refreshSkipped + ? static_cast( RAPID_REFRESH_MS * 2 ) + : ( hintAction_ != HINT_ACTION::SKIP ? _hintDelay : 0 ) + ); + while ( true ) { + Terminal::EVENT_TYPE eventType( _terminal.wait_for_input( hintDelay ) ); + if ( eventType == Terminal::EVENT_TYPE::TIMEOUT ) { + refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::REPAINT ); + hintDelay = 0; + _refreshSkipped = false; + continue; + } + if ( eventType == Terminal::EVENT_TYPE::KEY_PRESS ) { + break; + } + if ( eventType == Terminal::EVENT_TYPE::RESIZE ) { + // caught a window resize event + // now redraw the prompt and line + _prompt.update_screen_columns(); + // redraw the original prompt with current input + refresh_line( HINT_ACTION::REPAINT ); + continue; + } + std::lock_guard l( _mutex ); + clear_self_to_end_of_screen(); + while ( ! _messages.empty() ) { + string const& message( _messages.front() ); + _terminal.write8( message.data(), static_cast( message.length() ) ); + _messages.pop_front(); + } + repaint(); + } + /* try scheduled key presses */ { + std::lock_guard l( _mutex ); + if ( !_keyPresses.empty() ) { + char32_t keyPress( _keyPresses.front() ); + _keyPresses.pop_front(); + return ( keyPress ); + } + } + return ( _terminal.read_char() ); +} + +void Replxx::ReplxxImpl::clear( void ) { + _pos = 0; + _prefix = 0; + _completions.clear(); + _completionContextLength = 0; + _completionSelection = -1; + _data.clear(); + _hintSelection = -1; + _hint = UnicodeString(); + _display.clear(); + _displayInputLength = 0; +} + +void Replxx::ReplxxImpl::call_modify_callback( void ) { + if ( ! _modifyCallback ) { + return; + } + _utf8Buffer.assign( _data ); + std::string origLine( _utf8Buffer.get() ); + int pos( _pos ); + std::string line( origLine ); + _modifyCallback( line, pos ); + if ( ( pos != _pos ) || ( line != origLine ) ) { + _data.assign( line.c_str() ); + _pos = min( pos, _data.length() ); + _modifiedState = true; + } +} + +Replxx::ReplxxImpl::completions_t Replxx::ReplxxImpl::call_completer( std::string const& input, int& contextLen_ ) const { + Replxx::completions_t completionsIntermediary( + !! _completionCallback + ? _completionCallback( input, contextLen_ ) + : Replxx::completions_t() + ); + completions_t completions; + completions.reserve( completionsIntermediary.size() ); + for ( Replxx::Completion const& c : completionsIntermediary ) { + completions.emplace_back( c ); + } + return ( completions ); +} + +Replxx::ReplxxImpl::hints_t Replxx::ReplxxImpl::call_hinter( std::string const& input, int& contextLen, Replxx::Color& color ) const { + Replxx::hints_t hintsIntermediary( + !! _hintCallback + ? _hintCallback( input, contextLen, color ) + : Replxx::hints_t() + ); + hints_t hints; + hints.reserve( hintsIntermediary.size() ); + for ( std::string const& h : hintsIntermediary ) { + hints.emplace_back( h.c_str() ); + } + return ( hints ); +} + +void Replxx::ReplxxImpl::set_preload_buffer( std::string const& preloadText ) { + _preloadedBuffer = preloadText; + // remove characters that won't display correctly + bool controlsStripped = false; + int whitespaceSeen( 0 ); + for ( std::string::iterator it( _preloadedBuffer.begin() ); it != _preloadedBuffer.end(); ) { + unsigned char c = *it; + if ( '\r' == c ) { // silently skip CR + _preloadedBuffer.erase( it, it + 1 ); + continue; + } + if ( ( '\n' == c ) || ( '\t' == c ) ) { // note newline or tab + ++ whitespaceSeen; + ++ it; + continue; + } + if ( whitespaceSeen > 0 ) { + it -= whitespaceSeen; + *it = ' '; + _preloadedBuffer.erase( it + 1, it + whitespaceSeen - 1 ); + } + if ( is_control_code( c ) ) { // remove other control characters, flag for message + controlsStripped = true; + if ( whitespaceSeen > 0 ) { + _preloadedBuffer.erase( it, it + 1 ); + -- it; + } else { + *it = ' '; + } + } + whitespaceSeen = 0; + ++ it; + } + if ( whitespaceSeen > 0 ) { + std::string::iterator it = _preloadedBuffer.end() - whitespaceSeen; + *it = ' '; + if ( whitespaceSeen > 1 ) { + _preloadedBuffer.erase( it + 1, _preloadedBuffer.end() ); + } + } + _errorMessage.clear(); + if ( controlsStripped ) { + _errorMessage.assign( " [Edited line: control characters were converted to spaces]\n" ); + } +} + +char const* Replxx::ReplxxImpl::read_from_stdin( void ) { + if ( _preloadedBuffer.empty() ) { + getline( cin, _preloadedBuffer ); + if ( ! cin.good() ) { + return nullptr; + } + } + while ( ! _preloadedBuffer.empty() && ( ( _preloadedBuffer.back() == '\r' ) || ( _preloadedBuffer.back() == '\n' ) ) ) { + _preloadedBuffer.pop_back(); + } + _utf8Buffer.assign( _preloadedBuffer ); + _preloadedBuffer.clear(); + return _utf8Buffer.get(); +} + +void Replxx::ReplxxImpl::emulate_key_press( char32_t keyCode_ ) { + std::lock_guard l( _mutex ); + _keyPresses.push_back( keyCode_ ); + if ( ( _currentThread != std::thread::id() ) && ( _currentThread != std::this_thread::get_id() ) ) { + _terminal.notify_event( Terminal::EVENT_TYPE::KEY_PRESS ); + } +} + +char const* Replxx::ReplxxImpl::input( std::string const& prompt ) { + try { + errno = 0; + if ( ! tty::in ) { // input not from a terminal, we should work with piped input, i.e. redirected stdin + return ( read_from_stdin() ); + } + if (!_errorMessage.empty()) { + printf("%s", _errorMessage.c_str()); + fflush(stdout); + _errorMessage.clear(); + } + if ( isUnsupportedTerm() ) { + cout << prompt << flush; + fflush(stdout); + return ( read_from_stdin() ); + } + if (_terminal.enable_raw_mode() == -1) { + return nullptr; + } + _prompt.set_text( UnicodeString( prompt ) ); + _currentThread = std::this_thread::get_id(); + clear(); + if (!_preloadedBuffer.empty()) { + preload_puffer(_preloadedBuffer.c_str()); + _preloadedBuffer.clear(); + } + if ( get_input_line() == -1 ) { + return ( finalize_input( nullptr ) ); + } + _terminal.write8( "\n", 1 ); + _utf8Buffer.assign( _data ); + return ( finalize_input( _utf8Buffer.get() ) ); + } catch ( std::exception const& ) { + return ( finalize_input( nullptr ) ); + } +} + +char const* Replxx::ReplxxImpl::finalize_input( char const* retVal_ ) { + _currentThread = std::thread::id(); + _terminal.disable_raw_mode(); + return ( retVal_ ); +} + +int Replxx::ReplxxImpl::install_window_change_handler( void ) { +#ifndef _WIN32 + return ( _terminal.install_window_change_handler() ); +#else + return 0; +#endif +} + +void Replxx::ReplxxImpl::enable_bracketed_paste( void ) { + if ( _bracketedPaste ) { + return; + } + _terminal.enable_bracketed_paste(); + _bracketedPaste = true; +} + +void Replxx::ReplxxImpl::disable_bracketed_paste( void ) { + if ( ! _bracketedPaste ) { + return; + } + _terminal.disable_bracketed_paste(); + _bracketedPaste = false; +} + +void Replxx::ReplxxImpl::print( char const* str_, int size_ ) { + if ( ( _currentThread == std::thread::id() ) || ( _currentThread == std::this_thread::get_id() ) ) { + _terminal.write8( str_, size_ ); + } else { + std::lock_guard l( _mutex ); + _messages.emplace_back( str_, size_ ); + _terminal.notify_event( Terminal::EVENT_TYPE::MESSAGE ); + } + return; +} + +void Replxx::ReplxxImpl::preload_puffer(const char* preloadText) { + _data.assign( preloadText ); + _charWidths.resize( _data.length() ); + recompute_character_widths( _data.get(), _charWidths.data(), _data.length() ); + _prefix = _pos = _data.length(); +} + +void Replxx::ReplxxImpl::set_color( Replxx::Color color_ ) { + char const* code( ansi_color( color_ ) ); + while ( *code ) { + _display.push_back( *code ); + ++ code; + } +} + +void Replxx::ReplxxImpl::render( char32_t ch ) { + if ( ch == Replxx::KEY::ESCAPE ) { + _display.push_back( '^' ); + _display.push_back( '[' ); + } else if ( is_control_code( ch ) ) { + _display.push_back( '^' ); + _display.push_back( control_to_human( ch ) ); + } else { + _display.push_back( ch ); + } + return; +} + +void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) { + if ( hintAction_ == HINT_ACTION::TRIM ) { + _display.erase( _display.begin() + _displayInputLength, _display.end() ); + _modifiedState = false; + return; + } + if ( hintAction_ == HINT_ACTION::SKIP ) { + return; + } + _display.clear(); + if ( _noColor ) { + for ( char32_t ch : _data ) { + render( ch ); + } + _displayInputLength = static_cast( _display.size() ); + _modifiedState = false; + return; + } + Replxx::colors_t colors( _data.length(), Replxx::Color::DEFAULT ); + _utf8Buffer.assign( _data ); + if ( !! _highlighterCallback ) { + _highlighterCallback( _utf8Buffer.get(), colors ); + } + paren_info_t pi( matching_paren() ); + if ( pi.index != -1 ) { + colors[pi.index] = pi.error ? Replxx::Color::ERROR : Replxx::Color::BRIGHTRED; + } + Replxx::Color c( Replxx::Color::DEFAULT ); + for ( int i( 0 ); i < _data.length(); ++ i ) { + if ( colors[i] != c ) { + c = colors[i]; + set_color( c ); + } + render( _data[i] ); + } + set_color( Replxx::Color::DEFAULT ); + _displayInputLength = static_cast( _display.size() ); + _modifiedState = false; + return; +} + +int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) { + if ( _noColor ) { + return ( 0 ); + } + if ( ! _hintCallback ) { + return ( 0 ); + } + if ( ( _hintDelay > 0 ) && ( hintAction_ != HINT_ACTION::REPAINT ) ) { + _hintSelection = -1; + return ( 0 ); + } + if ( ( hintAction_ == HINT_ACTION::SKIP ) || ( hintAction_ == HINT_ACTION::TRIM ) ) { + return ( 0 ); + } + if ( _pos != _data.length() ) { + return ( 0 ); + } + _hint = UnicodeString(); + int len( 0 ); + if ( hintAction_ == HINT_ACTION::REGENERATE ) { + _hintSelection = -1; + } + _utf8Buffer.assign( _data, _pos ); + if ( ( _utf8Buffer != _hintSeed ) || ( _hintContextLenght < 0 ) ) { + _hintSeed.assign( _utf8Buffer ); + _hintContextLenght = context_length(); + _hintColor = Replxx::Color::GRAY; + _hintsCache = call_hinter( _utf8Buffer.get(), _hintContextLenght, _hintColor ); + } + int hintCount( static_cast( _hintsCache.size() ) ); + if ( hintCount == 1 ) { + _hint = _hintsCache.front(); + len = _hint.length() - _hintContextLenght; + if ( len > 0 ) { + set_color( _hintColor ); + for ( int i( 0 ); i < len; ++ i ) { + _display.push_back( _hint[i + _hintContextLenght] ); + } + set_color( Replxx::Color::DEFAULT ); + } + } else if ( ( _maxHintRows > 0 ) && ( hintCount > 0 ) ) { + int startCol( _prompt._indentation + _pos ); + int maxCol( _prompt.screen_columns() ); +#ifdef _WIN32 + -- maxCol; +#endif + if ( _hintSelection < -1 ) { + _hintSelection = hintCount - 1; + } else if ( _hintSelection >= hintCount ) { + _hintSelection = -1; + } + if ( _hintSelection != -1 ) { + _hint = _hintsCache[_hintSelection]; + len = min( _hint.length(), maxCol - startCol ); + if ( _hintContextLenght < len ) { + set_color( _hintColor ); + for ( int i( _hintContextLenght ); i < len; ++ i ) { + _display.push_back( _hint[i] ); + } + set_color( Replxx::Color::DEFAULT ); + } + } + startCol -= _hintContextLenght; + for ( int hintRow( 0 ); hintRow < min( hintCount, _maxHintRows ); ++ hintRow ) { +#ifdef _WIN32 + _display.push_back( '\r' ); +#endif + _display.push_back( '\n' ); + int col( 0 ); + for ( int i( 0 ); ( i < startCol ) && ( col < maxCol ); ++ i, ++ col ) { + _display.push_back( ' ' ); + } + set_color( _hintColor ); + for ( int i( _pos - _hintContextLenght ); ( i < _pos ) && ( col < maxCol ); ++ i, ++ col ) { + _display.push_back( _data[i] ); + } + int hintNo( hintRow + _hintSelection + 1 ); + if ( hintNo == hintCount ) { + continue; + } else if ( hintNo > hintCount ) { + -- hintNo; + } + UnicodeString const& h( _hintsCache[hintNo % hintCount] ); + for ( int i( _hintContextLenght ); ( i < h.length() ) && ( col < maxCol ); ++ i, ++ col ) { + _display.push_back( h[i] ); + } + set_color( Replxx::Color::DEFAULT ); + } + } + return ( len ); +} + +Replxx::ReplxxImpl::paren_info_t Replxx::ReplxxImpl::matching_paren( void ) { + if (_pos >= _data.length()) { + return ( paren_info_t{ -1, false } ); + } + /* this scans for a brace matching _data[_pos] to highlight */ + unsigned char part1, part2; + int scanDirection = 0; + if ( strchr("}])", _data[_pos]) ) { + scanDirection = -1; /* backwards */ + if (_data[_pos] == '}') { + part1 = '}'; part2 = '{'; + } else if (_data[_pos] == ']') { + part1 = ']'; part2 = '['; + } else { + part1 = ')'; part2 = '('; + } + } else if ( strchr("{[(", _data[_pos]) ) { + scanDirection = 1; /* forwards */ + if (_data[_pos] == '{') { + //part1 = '{'; part2 = '}'; + part1 = '}'; part2 = '{'; + } else if (_data[_pos] == '[') { + //part1 = '['; part2 = ']'; + part1 = ']'; part2 = '['; + } else { + //part1 = '('; part2 = ')'; + part1 = ')'; part2 = '('; + } + } else { + return ( paren_info_t{ -1, false } ); + } + int highlightIdx = -1; + bool indicateError = false; + int unmatched = scanDirection; + int unmatchedOther = 0; + for (int i = _pos + scanDirection; i >= 0 && i < _data.length(); i += scanDirection) { + /* TODO: the right thing when inside a string */ + if (strchr("}])", _data[i])) { + if (_data[i] == part1) { + --unmatched; + } else { + --unmatchedOther; + } + } else if (strchr("{[(", _data[i])) { + if (_data[i] == part2) { + ++unmatched; + } else { + ++unmatchedOther; + } + } + + if (unmatched == 0) { + highlightIdx = i; + indicateError = (unmatchedOther != 0); + break; + } + } + return ( paren_info_t{ highlightIdx, indicateError } ); +} + +/** + * Refresh the user's input line: the prompt is already onscreen and is not + * redrawn here screen position + */ +void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) { + int long long now( now_us() ); + int long long duration( now - _lastRefreshTime ); + if ( duration < RAPID_REFRESH_US ) { + _lastRefreshTime = now; + _refreshSkipped = true; + return; + } + _refreshSkipped = false; + // check for a matching brace/bracket/paren, remember its position if found + render( hintAction_ ); + int hintLen( handle_hints( hintAction_ ) ); + // calculate the position of the end of the input line + int xEndOfInput( 0 ), yEndOfInput( 0 ); + calculate_screen_position( + _prompt._indentation, 0, _prompt.screen_columns(), + calculate_displayed_length( _data.get(), _data.length() ) + hintLen, + xEndOfInput, yEndOfInput + ); + yEndOfInput += static_cast( count( _display.begin(), _display.end(), '\n' ) ); + + // calculate the desired position of the cursor + int xCursorPos( 0 ), yCursorPos( 0 ); + calculate_screen_position( + _prompt._indentation, 0, _prompt.screen_columns(), + calculate_displayed_length( _data.get(), _pos ), + xCursorPos, yCursorPos + ); + + // position at the end of the prompt, clear to end of previous input + _terminal.set_cursor_visible( false ); + _terminal.jump_cursor( + _prompt._indentation, // 0-based on Win32 + -( _prompt._cursorRowOffset - _prompt._extraLines ) + ); + _prompt._previousInputLen = _data.length(); + // display the input line + _terminal.write32( _display.data(), _displayInputLength ); + _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END ); + _terminal.write32( _display.data() + _displayInputLength, static_cast( _display.size() ) - _displayInputLength ); +#ifndef _WIN32 + // we have to generate our own newline on line wrap + if ( ( xEndOfInput == 0 ) && ( yEndOfInput > 0 ) ) { + _terminal.write8( "\n", 1 ); + } +#endif + // position the cursor + _terminal.jump_cursor( xCursorPos, -( yEndOfInput - yCursorPos ) ); + _terminal.set_cursor_visible( true ); + _prompt._cursorRowOffset = _prompt._extraLines + yCursorPos; // remember row for next pass + _lastRefreshTime = now_us(); +} + +int Replxx::ReplxxImpl::context_length() { + int prefixLength = _pos; + while ( prefixLength > 0 ) { + if ( is_word_break_character( _data[prefixLength - 1] ) ) { + break; + } + -- prefixLength; + } + return ( _pos - prefixLength ); +} + +void Replxx::ReplxxImpl::repaint( void ) { + _prompt.write(); + for ( int i( _prompt._extraLines ); i < _prompt._cursorRowOffset; ++ i ) { + _terminal.write8( "\n", 1 ); + } + refresh_line( HINT_ACTION::SKIP ); +} + +void Replxx::ReplxxImpl::clear_self_to_end_of_screen( Prompt const* prompt_ ) { + // position at the start of the prompt, clear to end of previous input + _terminal.jump_cursor( 0, prompt_ ? -prompt_->_cursorRowOffset : -_prompt._cursorRowOffset ); + _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END ); + return; +} + +namespace { +int longest_common_prefix( Replxx::ReplxxImpl::completions_t const& completions ) { + int completionsCount( static_cast( completions.size() ) ); + if ( completionsCount < 1 ) { + return ( 0 ); + } + int longestCommonPrefix( 0 ); + UnicodeString const& sample( completions.front().text() ); + while ( true ) { + if ( longestCommonPrefix >= sample.length() ) { + return ( longestCommonPrefix ); + } + char32_t sc( sample[longestCommonPrefix] ); + for ( int i( 1 ); i < completionsCount; ++ i ) { + UnicodeString const& candidate( completions[i].text() ); + if ( longestCommonPrefix >= candidate.length() ) { + return ( longestCommonPrefix ); + } + char32_t cc( candidate[longestCommonPrefix] ); + if ( cc != sc ) { + return ( longestCommonPrefix ); + } + } + ++ longestCommonPrefix; + } +} +} + +/** + * Handle command completion, using a completionCallback() routine to provide + * possible substitutions + * This routine handles the mechanics of updating the user's input buffer with + * possible replacement of text as the user selects a proposed completion string, + * or cancels the completion attempt. + * @param pi - Prompt struct holding information about the prompt and our + * screen position + */ +char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) { + char32_t c = 0; + + // completionCallback() expects a parsable entity, so find the previous break + // character and + // extract a copy to parse. we also handle the case where tab is hit while + // not at end-of-line. + + _utf8Buffer.assign( _data, _pos ); + // get a list of completions + _completionSelection = -1; + _completionContextLength = context_length(); + _completions = call_completer( _utf8Buffer.get(), _completionContextLength ); + + // if no completions, we are done + if ( _completions.empty() ) { + beep(); + return 0; + } + + // at least one completion + int longestCommonPrefix = 0; + int completionsCount( static_cast( _completions.size() ) ); + int selectedCompletion( 0 ); + if ( _hintSelection != -1 ) { + selectedCompletion = _hintSelection; + completionsCount = 1; + } + if ( completionsCount == 1 ) { + longestCommonPrefix = static_cast( _completions[selectedCompletion].text().length() ); + } else { + longestCommonPrefix = longest_common_prefix( _completions ); + } + if ( _beepOnAmbiguousCompletion && ( completionsCount != 1 ) ) { // beep if ambiguous + beep(); + } + + // if we can extend the item, extend it and return to main loop + if ( ( longestCommonPrefix > _completionContextLength ) || ( completionsCount == 1 ) ) { + _pos -= _completionContextLength; + _data.erase( _pos, _completionContextLength ); + _data.insert( _pos, _completions[selectedCompletion].text(), 0, longestCommonPrefix ); + _pos = _pos + longestCommonPrefix; + _completionContextLength = longestCommonPrefix; + refresh_line(); + return 0; + } + + if ( ! showCompletions_ ) { + return ( 0 ); + } + + if ( _doubleTabCompletion ) { + // we can't complete any further, wait for second tab + do { + c = read_char(); + } while ( c == static_cast( -1 ) ); + + // if any character other than tab, pass it to the main loop + if ( c != Replxx::KEY::TAB ) { + return c; + } + } + + // we got a second tab, maybe show list of possible completions + bool showCompletions = true; + bool onNewLine = false; + if ( static_cast( _completions.size() ) > _completionCountCutoff ) { + int savePos = _pos; // move cursor to EOL to avoid overwriting the command line + _pos = _data.length(); + refresh_line(); + _pos = savePos; + printf( "\nDisplay all %u possibilities? (y or n)", static_cast( _completions.size() ) ); + fflush(stdout); + onNewLine = true; + while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != Replxx::KEY::control('C')) { + do { + c = read_char(); + } while (c == static_cast(-1)); + } + switch (c) { + case 'n': + case 'N': + showCompletions = false; + break; + case Replxx::KEY::control('C'): + showCompletions = false; + // Display the ^C we got + _terminal.write8( "^C", 2 ); + c = 0; + break; + } + } + + // if showing the list, do it the way readline does it + bool stopList( false ); + if ( showCompletions ) { + int longestCompletion( 0 ); + for ( size_t j( 0 ); j < _completions.size(); ++ j ) { + int itemLength( static_cast( _completions[j].text().length() ) ); + if ( itemLength > longestCompletion ) { + longestCompletion = itemLength; + } + } + longestCompletion += 2; + int columnCount = _prompt.screen_columns() / longestCompletion; + if ( columnCount < 1 ) { + columnCount = 1; + } + if ( ! onNewLine ) { // skip this if we showed "Display all %d possibilities?" + int savePos = _pos; // move cursor to EOL to avoid overwriting the command line + _pos = _data.length(); + refresh_line( HINT_ACTION::TRIM ); + _pos = savePos; + } else { + _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END ); + } + size_t pauseRow = _terminal.get_screen_rows() - 1; + size_t rowCount = (_completions.size() + columnCount - 1) / columnCount; + for (size_t row = 0; row < rowCount; ++row) { + if (row == pauseRow) { + printf("\n--More--"); + fflush(stdout); + c = 0; + bool doBeep = false; + while (c != ' ' && c != Replxx::KEY::ENTER && c != 'y' && c != 'Y' && + c != 'n' && c != 'N' && c != 'q' && c != 'Q' && + c != Replxx::KEY::control('C')) { + if (doBeep) { + beep(); + } + doBeep = true; + do { + c = read_char(); + } while (c == static_cast(-1)); + } + switch (c) { + case ' ': + case 'y': + case 'Y': + printf("\r \r"); + pauseRow += _terminal.get_screen_rows() - 1; + break; + case Replxx::KEY::ENTER: + printf("\r \r"); + ++pauseRow; + break; + case 'n': + case 'N': + case 'q': + case 'Q': + printf("\r \r"); + stopList = true; + break; + case Replxx::KEY::control('C'): + // Display the ^C we got + _terminal.write8( "^C", 2 ); + stopList = true; + break; + } + } else { + _terminal.write8( "\n", 1 ); + } + if (stopList) { + break; + } + static UnicodeString const res( ansi_color( Replxx::Color::DEFAULT ) ); + for (int column = 0; column < columnCount; ++column) { + size_t index = (column * rowCount) + row; + if ( index < _completions.size() ) { + Completion const& c( _completions[index] ); + int itemLength = static_cast(c.text().length()); + fflush(stdout); + + if ( longestCommonPrefix > 0 ) { + static UnicodeString const col( ansi_color( Replxx::Color::BRIGHTMAGENTA ) ); + if (!_noColor) { + _terminal.write32(col.get(), col.length()); + } + _terminal.write32(&_data[_pos - _completionContextLength], longestCommonPrefix); + if (!_noColor) { + _terminal.write32(res.get(), res.length()); + } + } + + if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) { + UnicodeString ac( ansi_color( c.color() ) ); + _terminal.write32( ac.get(), ac.length() ); + } + _terminal.write32( c.text().get() + longestCommonPrefix, itemLength - longestCommonPrefix ); + if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) { + _terminal.write32( res.get(), res.length() ); + } + + if ( ((column + 1) * rowCount) + row < _completions.size() ) { + for ( int k( itemLength ); k < longestCompletion; ++k ) { + printf( " " ); + } + } + } + } + } + fflush(stdout); + } + + // display the prompt on a new line, then redisplay the input buffer + if (!stopList || c == Replxx::KEY::control('C')) { + _terminal.write8( "\n", 1 ); + } + _prompt.write(); + _prompt._cursorRowOffset = _prompt._extraLines; + refresh_line(); + return 0; +} + +int Replxx::ReplxxImpl::get_input_line( void ) { + // The latest history entry is always our current buffer + if ( _data.length() > 0 ) { + _history.add( _data ); + } else { + _history.add( UnicodeString() ); + } + _history.jump( false, false ); + + // display the prompt + _prompt.write(); + + // the cursor starts out at the end of the prompt + _prompt._cursorRowOffset = _prompt._extraLines; + + // kill and yank start in "other" mode + _killRing.lastAction = KillRing::actionOther; + + // if there is already text in the buffer, display it first + if (_data.length() > 0) { + refresh_line(); + } + + // loop collecting characters, respond to line editing characters + Replxx::ACTION_RESULT next( Replxx::ACTION_RESULT::CONTINUE ); + while ( next == Replxx::ACTION_RESULT::CONTINUE ) { + int c( read_char( HINT_ACTION::REPAINT ) ); // get a new keystroke + + if (c == 0) { + return _data.length(); + } + + if (c == -1) { + refresh_line(); + continue; + } + + if (c == -2) { + _prompt.write(); + refresh_line(); + continue; + } + + key_press_handlers_t::iterator it( _keyPressHandlers.find( c ) ); + if ( it != _keyPressHandlers.end() ) { + next = it->second( c ); + if ( _modifiedState ) { + refresh_line(); + } + } else { + next = action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, c ); + } + } + return ( next == Replxx::ACTION_RESULT::RETURN ? _data.length() : -1 ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, key_press_handler_raw_t const& handler_, char32_t code_ ) { + Replxx::ACTION_RESULT res( ( this->*handler_ )( code_ ) ); + call_modify_callback(); + if ( actionTrait_ & HISTORY_RECALL_MOST_RECENT ) { + _history.reset_recall_most_recent(); + } + if ( actionTrait_ & RESET_KILL_ACTION ) { + _killRing.lastAction = KillRing::actionOther; + } + if ( actionTrait_ & SET_KILL_ACTION ) { + _killRing.lastAction = KillRing::actionKill; + } + if ( ! ( actionTrait_ & DONT_RESET_PREFIX ) ) { + _prefix = _pos; + } + if ( ! ( actionTrait_ & DONT_RESET_COMPLETIONS ) ) { + _completions.clear(); + _completionSelection = -1; + _completionContextLength = 0; + } + if ( ! ( actionTrait_ & DONT_RESET_HIST_YANK_INDEX ) ) { + _history.reset_yank_iterator(); + } + if ( actionTrait_ & WANT_REFRESH ) { + _modifiedState = true; + } + return ( res ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) { + /* + * beep on unknown Ctrl and/or Meta keys + * don't insert control characters + */ + if ( ( c >= static_cast( Replxx::KEY::BASE ) ) || is_control_code( c ) ) { + beep(); + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + if ( ! _overwrite || ( _pos >= _data.length() ) ) { + _data.insert( _pos, c ); + } else { + _data[_pos] = c; + } + ++ _pos; + call_modify_callback(); + int long long now( now_us() ); + int long long duration( now - _lastRefreshTime ); + if ( duration < RAPID_REFRESH_US ) { + _lastRefreshTime = now; + _refreshSkipped = true; + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + int inputLen = calculate_displayed_length( _data.get(), _data.length() ); + if ( + ( _pos == _data.length() ) + && ! _modifiedState + && ( _noColor || ! ( !! _highlighterCallback || !! _hintCallback ) ) + && ( _prompt._indentation + inputLen < _prompt.screen_columns() ) + ) { + /* Avoid a full assign of the line in the + * trivial case. */ + if (inputLen > _prompt._previousInputLen) { + _prompt._previousInputLen = inputLen; + } + render( c ); + _displayInputLength = static_cast( _display.size() ); + _terminal.write32( reinterpret_cast( &c ), 1 ); + } else { + refresh_line(); + } + _lastRefreshTime = now_us(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-A, HOME: move cursor to start of line +Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_begining_of_line( char32_t ) { + _pos = 0; + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_end_of_line( char32_t ) { + _pos = _data.length(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-B, move cursor left by one character +Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_left( char32_t ) { + if (_pos > 0) { + --_pos; + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-F, move cursor right by one character +Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_right( char32_t ) { + if ( _pos < _data.length() ) { + ++_pos; + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-B, move cursor left by one word +Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) { + if (_pos > 0) { + while (_pos > 0 && is_word_break_character( _data[_pos - 1] ) ) { + --_pos; + } + while (_pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) { + --_pos; + } + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-F, move cursor right by one word +Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) { + if ( _pos < _data.length() ) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + ++_pos; + } + while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + ++_pos; + } + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-Backspace, kill word to left of cursor +Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) { + if ( _pos > 0 ) { + int startingPos = _pos; + while ( _pos > 0 && is_word_break_character( _data[_pos - 1] ) ) { + -- _pos; + } + while ( _pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) { + -- _pos; + } + _killRing.kill( _data.get() + _pos, startingPos - _pos, false); + _data.erase( _pos, startingPos - _pos ); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-D, kill word to right of cursor +Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) { + if ( _pos < _data.length() ) { + int endingPos = _pos; + while ( endingPos < _data.length() && is_word_break_character( _data[endingPos] ) ) { + ++ endingPos; + } + while ( endingPos < _data.length() && !is_word_break_character( _data[endingPos] ) ) { + ++ endingPos; + } + _killRing.kill( _data.get() + _pos, endingPos - _pos, true ); + _data.erase( _pos, endingPos - _pos ); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-W, kill to whitespace (not word) to left of cursor +Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_whitespace_to_left( char32_t ) { + if ( _pos > 0 ) { + int startingPos = _pos; + while ( ( _pos > 0 ) && isspace( _data[_pos - 1] ) ) { + --_pos; + } + while ( ( _pos > 0 ) && ! isspace( _data[_pos - 1] ) ) { + -- _pos; + } + _killRing.kill( _data.get() + _pos, startingPos - _pos, false ); + _data.erase( _pos, startingPos - _pos ); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-K, kill from cursor to end of line +Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_end_of_line( char32_t ) { + _killRing.kill( _data.get() + _pos, _data.length() - _pos, true ); + _data.erase( _pos, _data.length() - _pos ); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-U, kill all characters to the left of the cursor +Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_begining_of_line( char32_t ) { + if (_pos > 0) { + _killRing.kill( _data.get(), _pos, false ); + _data.erase( 0, _pos ); + _pos = 0; + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-Y, yank killed text +Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank( char32_t ) { + UnicodeString* restoredText( _killRing.yank() ); + if ( restoredText ) { + _data.insert( _pos, *restoredText, 0, restoredText->length() ); + _pos += restoredText->length(); + refresh_line(); + _killRing.lastAction = KillRing::actionYank; + _lastYankSize = restoredText->length(); + } else { + beep(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-Y, "yank-pop", rotate popped text +Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_cycle( char32_t ) { + if ( _killRing.lastAction != KillRing::actionYank ) { + beep(); + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + UnicodeString* restoredText = _killRing.yankPop(); + if ( !restoredText ) { + beep(); + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + _pos -= _lastYankSize; + _data.erase( _pos, _lastYankSize ); + _data.insert( _pos, *restoredText, 0, restoredText->length() ); + _pos += restoredText->length(); + _lastYankSize = restoredText->length(); + refresh_line(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-., "yank-last-arg", on consecutive uses move back in history for popped text +Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_last_arg( char32_t ) { + if ( _history.size() < 2 ) { + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + if ( _history.next_yank_position() ) { + _lastYankSize = 0; + } + UnicodeString const& histLine( _history.yank_line() ); + int endPos( histLine.length() ); + while ( ( endPos > 0 ) && isspace( histLine[endPos - 1] ) ) { + -- endPos; + } + int startPos( endPos ); + while ( ( startPos > 0 ) && ! isspace( histLine[startPos - 1] ) ) { + -- startPos; + } + _pos -= _lastYankSize; + _data.erase( _pos, _lastYankSize ); + _lastYankSize = endPos - startPos; + _data.insert( _pos, histLine, startPos, _lastYankSize ); + _pos += _lastYankSize; + refresh_line(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-C, give word initial Cap +Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) { + if (_pos < _data.length()) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + ++_pos; + } + if (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + if ( _data[_pos] >= 'a' && _data[_pos] <= 'z' ) { + _data[_pos] += 'A' - 'a'; + } + ++_pos; + } + while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) { + _data[_pos] += 'a' - 'A'; + } + ++_pos; + } + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-L, lowercase word +Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) { + if (_pos < _data.length()) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + ++ _pos; + } + while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) { + _data[_pos] += 'a' - 'A'; + } + ++ _pos; + } + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-U, uppercase word +Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) { + if (_pos < _data.length()) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + ++ _pos; + } + while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + if ( _data[_pos] >= 'a' && _data[_pos] <= 'z') { + _data[_pos] += 'A' - 'a'; + } + ++ _pos; + } + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-T, transpose characters +Replxx::ACTION_RESULT Replxx::ReplxxImpl::transpose_characters( char32_t ) { + if ( _pos > 0 && _data.length() > 1 ) { + size_t leftCharPos = ( _pos == _data.length() ) ? _pos - 2 : _pos - 1; + char32_t aux = _data[leftCharPos]; + _data[leftCharPos] = _data[leftCharPos + 1]; + _data[leftCharPos + 1] = aux; + if ( _pos != _data.length() ) { + ++_pos; + } + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-C, abort this line +Replxx::ACTION_RESULT Replxx::ReplxxImpl::abort_line( char32_t ) { + errno = EAGAIN; + _history.drop_last(); + // we need one last refresh with the cursor at the end of the line + // so we don't display the next prompt over the previous input line + _pos = _data.length(); // pass _data.length() as _pos for EOL + _lastRefreshTime = 0; + refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM ); + _terminal.write8( "^C\r\n", 4 ); + return ( Replxx::ACTION_RESULT::BAIL ); +} + +// DEL, delete the character under the cursor +Replxx::ACTION_RESULT Replxx::ReplxxImpl::delete_character( char32_t ) { + if ( ( _data.length() > 0 ) && ( _pos < _data.length() ) ) { + _data.erase( _pos ); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-D, delete the character under the cursor +// on an empty line, exit the shell +Replxx::ACTION_RESULT Replxx::ReplxxImpl::send_eof( char32_t key_ ) { + if ( _data.length() == 0 ) { + _history.drop_last(); + return ( Replxx::ACTION_RESULT::BAIL ); + } + return ( delete_character( key_ ) ); +} + +// backspace/ctrl-H, delete char to left of cursor +Replxx::ACTION_RESULT Replxx::ReplxxImpl::backspace_character( char32_t ) { + if ( _pos > 0 ) { + -- _pos; + _data.erase( _pos ); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-J/linefeed/newline, accept line +// ctrl-M/return/enter +Replxx::ACTION_RESULT Replxx::ReplxxImpl::commit_line( char32_t ) { + // we need one last refresh with the cursor at the end of the line + // so we don't display the next prompt over the previous input line + _pos = _data.length(); // pass _data.length() as _pos for EOL + _lastRefreshTime = 0; + refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM ); + _history.commit_index(); + _history.drop_last(); + return ( Replxx::ACTION_RESULT::RETURN ); +} + +// Down, recall next line in history +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) { + return ( history_move( false ) ); +} + +// Up, recall previous line in history +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) { + return ( history_move( true ) ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_move( bool previous_ ) { + // if not already recalling, add the current line to the history list so + // we don't + // have to special case it + if ( _history.is_last() ) { + _history.update_last( _data ); + } + if ( _history.is_empty() ) { + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + if ( ! _history.move( previous_ ) ) { + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + _data.assign( _history.current() ); + _pos = _data.length(); + refresh_line(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-<, beginning of history +// Page Up, beginning of history +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_first( char32_t ) { + return ( history_jump( true ) ); +} + +// meta->, end of history +// Page Down, end of history +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_last( char32_t ) { + return ( history_jump( false ) ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_jump( bool back_ ) { + // if not already recalling, add the current line to the history list so + // we don't + // have to special case it + if ( _history.is_last() ) { + _history.update_last( _data ); + } + if ( ! _history.is_empty() ) { + _history.jump( back_ ); + _data.assign( _history.current() ); + _pos = _data.length(); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_next( char32_t ) { + return ( hint_move( false ) ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_previous( char32_t ) { + return ( hint_move( true ) ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_move( bool previous_ ) { + if ( ! _noColor ) { + _killRing.lastAction = KillRing::actionOther; + if ( previous_ ) { + -- _hintSelection; + } else { + ++ _hintSelection; + } + refresh_line( HINT_ACTION::REPAINT ); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::toggle_overwrite_mode( char32_t ) { + _overwrite = ! _overwrite; + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +#ifndef _WIN32 +Replxx::ACTION_RESULT Replxx::ReplxxImpl::verbatim_insert( char32_t ) { + static int const MAX_ESC_SEQ( 32 ); + char32_t buf[MAX_ESC_SEQ]; + int len( _terminal.read_verbatim( buf, MAX_ESC_SEQ ) ); + _data.insert( _pos, UnicodeString( buf, len ), 0, len ); + _pos += len; + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-Z, job control +Replxx::ACTION_RESULT Replxx::ReplxxImpl::suspend( char32_t ) { + _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode + raise(SIGSTOP); // Break out in mid-line + _terminal.enable_raw_mode(); // Back from Linux shell, re-enter raw mode + // Redraw prompt + _prompt.write(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} +#endif + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_line( char32_t c ) { + if ( !! _completionCallback && ( _completeOnEmpty || ( _pos > 0 ) ) ) { + // complete_line does the actual completion and replacement + c = do_complete_line( c != 0 ); + + if ( static_cast( c ) < 0 ) { + return ( Replxx::ACTION_RESULT::BAIL ); + } + if ( c != 0 ) { + emulate_key_press( c ); + } + } else { + insert_character( c ); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete( bool previous_ ) { + if ( _completions.empty() ) { + bool first( _completions.empty() ); + int dataLen( _data.length() ); + complete_line( 0 ); + if ( ! _immediateCompletion && first && ( _data.length() > dataLen ) ) { + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + } + int newSelection( _completionSelection + ( previous_ ? -1 : 1 ) ); + if ( newSelection >= static_cast( _completions.size() ) ) { + newSelection = -1; + } else if ( newSelection == -2 ) { + newSelection = static_cast( _completions.size() ) - 1; + } + if ( _completionSelection != -1 ) { + int oldCompletionLength( max( _completions[_completionSelection].text().length() - _completionContextLength, 0 ) ); + _pos -= oldCompletionLength; + _data.erase( _pos, oldCompletionLength ); + } + if ( newSelection != -1 ) { + int newCompletionLength( max( _completions[newSelection].text().length() - _completionContextLength, 0 ) ); + _data.insert( _pos, _completions[newSelection].text(), _completionContextLength, newCompletionLength ); + _pos += newCompletionLength; + } + _completionSelection = newSelection; + refresh_line(); // Refresh the line + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_next( char32_t ) { + return ( complete( false ) ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_previous( char32_t ) { + return ( complete( true ) ); +} + +// Alt-P, reverse history search for prefix +// Alt-P, reverse history search for prefix +// Alt-N, forward history search for prefix +// Alt-N, forward history search for prefix +Replxx::ACTION_RESULT Replxx::ReplxxImpl::common_prefix_search( char32_t startChar ) { + int prefixSize( calculate_displayed_length( _data.get(), _prefix ) ); + if ( + _history.common_prefix_search( + _data, prefixSize, ( startChar == ( Replxx::KEY::meta( 'p' ) ) ) || ( startChar == ( Replxx::KEY::meta( 'P' ) ) ) + ) + ) { + _data.assign( _history.current() ); + _pos = _data.length(); + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-R, reverse history search +// ctrl-S, forward history search +/** + * Incremental history search -- take over the prompt and keyboard as the user + * types a search string, deletes characters from it, changes _direction, + * and either accepts the found line (for execution orediting) or cancels. + * @param startChar - the character that began the search, used to set the initial + * _direction + */ +Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t startChar ) { + // if not already recalling, add the current line to the history list so we + // don't have to special case it + if ( _history.is_last() ) { + _history.update_last( _data ); + } + _history.save_pos(); + int historyLinePosition( _pos ); + clear_self_to_end_of_screen(); + + DynamicPrompt dp( _terminal, (startChar == Replxx::KEY::control('R')) ? -1 : 1 ); + + dp._previousLen = _prompt._previousLen; + dp._previousInputLen = _prompt._previousInputLen; + // draw user's text with our prompt + dynamicRefresh(dp, _data.get(), _data.length(), historyLinePosition); + + // loop until we get an exit character + char32_t c( 0 ); + bool keepLooping = true; + bool useSearchedLine = true; + bool searchAgain = false; + UnicodeString activeHistoryLine; + while ( keepLooping ) { + c = read_char(); + + switch (c) { + // these characters keep the selected text but do not execute it + case Replxx::KEY::control('A'): // ctrl-A, move cursor to start of line + case Replxx::KEY::HOME: + case Replxx::KEY::control('B'): // ctrl-B, move cursor left by one character + case Replxx::KEY::LEFT: + case Replxx::KEY::meta( 'b' ): // meta-B, move cursor left by one word + case Replxx::KEY::meta( 'B' ): + case Replxx::KEY::control( Replxx::KEY::LEFT ): + case Replxx::KEY::meta( Replxx::KEY::LEFT ): // Emacs allows Meta, bash & readline don't + case Replxx::KEY::control('D'): + case Replxx::KEY::meta( 'd' ): // meta-D, kill word to right of cursor + case Replxx::KEY::meta( 'D' ): + case Replxx::KEY::control('E'): // ctrl-E, move cursor to end of line + case Replxx::KEY::END: + case Replxx::KEY::control('F'): // ctrl-F, move cursor right by one character + case Replxx::KEY::RIGHT: + case Replxx::KEY::meta( 'f' ): // meta-F, move cursor right by one word + case Replxx::KEY::meta( 'F' ): + case Replxx::KEY::control( Replxx::KEY::RIGHT ): + case Replxx::KEY::meta( Replxx::KEY::RIGHT ): // Emacs allows Meta, bash & readline don't + case Replxx::KEY::meta( Replxx::KEY::BACKSPACE ): + case Replxx::KEY::control('J'): + case Replxx::KEY::control('K'): // ctrl-K, kill from cursor to end of line + case Replxx::KEY::ENTER: + case Replxx::KEY::control('N'): // ctrl-N, recall next line in history + case Replxx::KEY::control('P'): // ctrl-P, recall previous line in history + case Replxx::KEY::DOWN: + case Replxx::KEY::UP: + case Replxx::KEY::control('T'): // ctrl-T, transpose characters + case Replxx::KEY::control('U'): // ctrl-U, kill all characters to the left of the cursor + case Replxx::KEY::control('W'): + case Replxx::KEY::meta( 'y' ): // meta-Y, "yank-pop", rotate popped text + case Replxx::KEY::meta( 'Y' ): + case 127: + case Replxx::KEY::DELETE: + case Replxx::KEY::meta( '<' ): // start of history + case Replxx::KEY::PAGE_UP: + case Replxx::KEY::meta( '>' ): // end of history + case Replxx::KEY::PAGE_DOWN: { + keepLooping = false; + } break; + + // these characters revert the input line to its previous state + case Replxx::KEY::control('C'): // ctrl-C, abort this line + case Replxx::KEY::control('G'): + case Replxx::KEY::control('L'): { // ctrl-L, clear screen and redisplay line + keepLooping = false; + useSearchedLine = false; + if (c != Replxx::KEY::control('L')) { + c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else + } + } break; + + // these characters stay in search mode and assign the display + case Replxx::KEY::control('S'): + case Replxx::KEY::control('R'): { + if ( dp._searchText.length() == 0 ) { // if no current search text, recall previous text + if ( _previousSearchText.length() > 0 ) { + dp._searchText = _previousSearchText; + } + } + if ( + ( ( dp._direction == 1 ) && ( c == Replxx::KEY::control( 'R' ) ) ) + || ( ( dp._direction == -1 ) && ( c == Replxx::KEY::control( 'S' ) ) ) + ) { + dp._direction = 0 - dp._direction; // reverse direction + dp.updateSearchPrompt(); // change the prompt + } else { + searchAgain = true; // same direction, search again + } + } break; + +// job control is its own thing +#ifndef _WIN32 + case Replxx::KEY::control('Z'): { // ctrl-Z, job control + _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode + raise( SIGSTOP ); // Break out in mid-line + _terminal.enable_raw_mode(); // Back from Linux shell, re-enter raw mode + dynamicRefresh( dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition ); + continue; + } break; +#endif + + // these characters assign the search string, and hence the selected input line + case Replxx::KEY::BACKSPACE: { // backspace/ctrl-H, delete char to left of cursor + if ( dp._searchText.length() > 0 ) { + dp._searchText.erase( dp._searchText.length() - 1 ); + dp.updateSearchPrompt(); + _history.restore_pos(); + historyLinePosition = _pos; + } else { + beep(); + } + } break; + + case Replxx::KEY::control('Y'): { // ctrl-Y, yank killed text + } break; + + default: { + if ( ! is_control_code( c ) && ( c < static_cast( Replxx::KEY::BASE ) ) ) { // not an action character + dp._searchText.insert( dp._searchText.length(), c ); + dp.updateSearchPrompt(); + } else { + beep(); + } + } + } // switch + + // if we are staying in search mode, search now + if ( ! keepLooping ) { + break; + } + activeHistoryLine.assign( _history.current() ); + if ( dp._searchText.length() > 0 ) { + bool found = false; + int lineSearchPos = historyLinePosition; + if ( searchAgain ) { + lineSearchPos += dp._direction; + } + searchAgain = false; + while ( true ) { + while ( + dp._direction < 0 + ? ( lineSearchPos >= 0 ) + : ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) + ) { + if ( + ( lineSearchPos >= 0 ) + && ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) + && std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos ) + ) { + found = true; + break; + } + lineSearchPos += dp._direction; + } + if ( found ) { + historyLinePosition = lineSearchPos; + break; + } else if ( _history.move( dp._direction < 0 ) ) { + activeHistoryLine.assign( _history.current() ); + lineSearchPos = ( dp._direction > 0 ) ? 0 : ( activeHistoryLine.length() - dp._searchText.length() ); + } else { + beep(); + break; + } + } // while + if ( ! found ) { + _history.restore_pos(); + } + } else { + _history.restore_pos(); + historyLinePosition = _pos; + } + activeHistoryLine.assign( _history.current() ); + dynamicRefresh( dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition ); // draw user's text with our prompt + } // while + + // leaving history search, restore previous prompt, maybe make searched line + // current + Prompt pb( _terminal ); + pb._characterCount = _prompt._indentation; + pb._byteCount = _prompt._byteCount; + UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], pb._byteCount - _prompt._lastLinePosition ); + pb._text = tempUnicode; + pb._extraLines = 0; + pb._indentation = _prompt._indentation; + pb._lastLinePosition = 0; + pb._previousInputLen = activeHistoryLine.length(); + pb._cursorRowOffset = dp._cursorRowOffset; + pb.update_screen_columns(); + pb._previousLen = dp._characterCount; + if ( useSearchedLine && ( activeHistoryLine.length() > 0 ) ) { + _history.commit_index(); + _data.assign( activeHistoryLine ); + _pos = historyLinePosition; + } else if ( ! useSearchedLine ) { + _history.restore_pos(); + } + dynamicRefresh(pb, _data.get(), _data.length(), _pos); // redraw the original prompt with current input + _prompt._previousInputLen = _data.length(); + _prompt._cursorRowOffset = _prompt._extraLines + pb._cursorRowOffset; + _previousSearchText = dp._searchText; // save search text for possible reuse on ctrl-R ctrl-R + emulate_key_press( c ); // pass a character or -1 back to main loop + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// ctrl-L, clear screen and redisplay line +Replxx::ACTION_RESULT Replxx::ReplxxImpl::clear_screen( char32_t c ) { + _terminal.clear_screen( Terminal::CLEAR_SCREEN::WHOLE ); + if ( c ) { + _prompt.write(); + _prompt._cursorRowOffset = _prompt._extraLines; + refresh_line(); + } + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +Replxx::ACTION_RESULT Replxx::ReplxxImpl::bracketed_paste( char32_t ) { + UnicodeString buf; + while ( char32_t c = _terminal.read_char() ) { + if ( c == KEY::PASTE_FINISH ) { + break; + } + if ( ( c == '\r' ) || ( c == KEY::control( 'M' ) ) ) { + c = '\n'; + } + buf.push_back( c ); + } + _data.insert( _pos, buf, 0, buf.length() ); + _pos += buf.length(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const { + bool wbc( false ); + if ( char_ < 128 ) { + wbc = strchr( _breakChars.c_str(), static_cast( char_ ) ) != nullptr; + } + return ( wbc ); +} + +void Replxx::ReplxxImpl::history_add( std::string const& line ) { + _history.add( UnicodeString( line ) ); +} + +bool Replxx::ReplxxImpl::history_save( std::string const& filename ) { + return ( _history.save( filename, false ) ); +} + +bool Replxx::ReplxxImpl::history_sync( std::string const& filename ) { + return ( _history.save( filename, true ) ); +} + +bool Replxx::ReplxxImpl::history_load( std::string const& filename ) { + return ( _history.load( filename ) ); +} + +void Replxx::ReplxxImpl::history_clear( void ) { + _history.clear(); +} + +int Replxx::ReplxxImpl::history_size( void ) const { + return ( _history.size() ); +} + +Replxx::HistoryScan::impl_t Replxx::ReplxxImpl::history_scan( void ) const { + return ( _history.scan() ); +} + +void Replxx::ReplxxImpl::set_modify_callback( Replxx::modify_callback_t const& fn ) { + _modifyCallback = fn; +} + +void Replxx::ReplxxImpl::set_completion_callback( Replxx::completion_callback_t const& fn ) { + _completionCallback = fn; +} + +void Replxx::ReplxxImpl::set_highlighter_callback( Replxx::highlighter_callback_t const& fn ) { + _highlighterCallback = fn; +} + +void Replxx::ReplxxImpl::set_hint_callback( Replxx::hint_callback_t const& fn ) { + _hintCallback = fn; +} + +void Replxx::ReplxxImpl::set_max_history_size( int len ) { + _history.set_max_size( len ); +} + +void Replxx::ReplxxImpl::set_completion_count_cutoff( int count ) { + _completionCountCutoff = count; +} + +void Replxx::ReplxxImpl::set_max_hint_rows( int count ) { + _maxHintRows = count; +} + +void Replxx::ReplxxImpl::set_hint_delay( int hintDelay_ ) { + _hintDelay = hintDelay_; +} + +void Replxx::ReplxxImpl::set_word_break_characters( char const* wordBreakers ) { + _breakChars = wordBreakers; +} + +void Replxx::ReplxxImpl::set_double_tab_completion( bool val ) { + _doubleTabCompletion = val; +} + +void Replxx::ReplxxImpl::set_complete_on_empty( bool val ) { + _completeOnEmpty = val; +} + +void Replxx::ReplxxImpl::set_beep_on_ambiguous_completion( bool val ) { + _beepOnAmbiguousCompletion = val; +} + +void Replxx::ReplxxImpl::set_immediate_completion( bool val ) { + _immediateCompletion = val; +} + +void Replxx::ReplxxImpl::set_unique_history( bool val ) { + _history.set_unique( val ); +} + +void Replxx::ReplxxImpl::set_no_color( bool val ) { + _noColor = val; +} + +/** + * Display the dynamic incremental search prompt and the current user input + * line. + * @param pi Prompt struct holding information about the prompt and our + * screen position + * @param buf32 input buffer to be displayed + * @param len count of characters in the buffer + * @param pos current cursor position within the buffer (0 <= pos <= len) + */ +void Replxx::ReplxxImpl::dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos) { + clear_self_to_end_of_screen( &pi ); + // calculate the position of the end of the prompt + int xEndOfPrompt, yEndOfPrompt; + calculate_screen_position( + 0, 0, pi.screen_columns(), pi._characterCount, + xEndOfPrompt, yEndOfPrompt + ); + pi._indentation = xEndOfPrompt; + + // calculate the position of the end of the input line + int xEndOfInput, yEndOfInput; + calculate_screen_position( + xEndOfPrompt, yEndOfPrompt, pi.screen_columns(), + calculate_displayed_length(buf32, len), xEndOfInput, + yEndOfInput + ); + + // calculate the desired position of the cursor + int xCursorPos, yCursorPos; + calculate_screen_position( + xEndOfPrompt, yEndOfPrompt, pi.screen_columns(), + calculate_displayed_length(buf32, pos), xCursorPos, + yCursorPos + ); + + pi._previousLen = pi._indentation; + pi._previousInputLen = len; + + // display the prompt + pi.write(); + + // display the input line + _terminal.write32( buf32, len ); + +#ifndef _WIN32 + // we have to generate our own newline on line wrap + if (xEndOfInput == 0 && yEndOfInput > 0) { + _terminal.write8( "\n", 1 ); + } +#endif + // position the cursor + _terminal.jump_cursor( + xCursorPos, // 0-based on Win32 + -( yEndOfInput - yCursorPos ) + ); + pi._cursorRowOffset = pi._extraLines + yCursorPos; // remember row for next pass +} + +} + diff --git a/third-party/replxx/src/replxx_impl.hxx b/third-party/replxx/src/replxx_impl.hxx new file mode 100644 index 0000000000..17d571582d --- /dev/null +++ b/third-party/replxx/src/replxx_impl.hxx @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED +#define HAVE_REPLXX_REPLXX_IMPL_HXX_INCLUDED 1 + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "replxx.hxx" +#include "history.hxx" +#include "killring.hxx" +#include "utf8string.hxx" +#include "prompt.hxx" + +namespace replxx { + +class Replxx::ReplxxImpl { +public: + class Completion { + UnicodeString _text; + Replxx::Color _color; + public: + Completion( UnicodeString const& text_, Replxx::Color color_ ) + : _text( text_ ) + , _color( color_ ) { + } + Completion( Replxx::Completion const& completion_ ) + : _text( completion_.text() ) + , _color( completion_.color() ) { + } + Completion( Completion const& ) = default; + Completion& operator = ( Completion const& ) = default; + Completion( Completion&& ) = default; + Completion& operator = ( Completion&& ) = default; + UnicodeString const& text( void ) const { + return ( _text ); + } + Replxx::Color color( void ) const { + return ( _color ); + } + }; + typedef std::vector completions_t; + typedef std::vector hints_t; + typedef std::unique_ptr utf8_buffer_t; + typedef std::unique_ptr input_buffer_t; + typedef std::vector char_widths_t; + typedef std::vector display_t; + typedef std::deque key_presses_t; + typedef std::deque messages_t; + enum class HINT_ACTION { + REGENERATE, + REPAINT, + TRIM, + SKIP + }; + typedef std::unordered_map named_actions_t; + typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t ); + typedef std::unordered_map key_press_handlers_t; +private: + typedef int long long unsigned action_trait_t; + static action_trait_t const NOOP = 0; + static action_trait_t const WANT_REFRESH = 1; + static action_trait_t const RESET_KILL_ACTION = 2; + static action_trait_t const SET_KILL_ACTION = 4; + static action_trait_t const DONT_RESET_PREFIX = 8; + static action_trait_t const DONT_RESET_COMPLETIONS = 16; + static action_trait_t const HISTORY_RECALL_MOST_RECENT = 32; + static action_trait_t const DONT_RESET_HIST_YANK_INDEX = 64; +private: + mutable Utf8String _utf8Buffer; + UnicodeString _data; + char_widths_t _charWidths; // character widths from mk_wcwidth() + display_t _display; + int _displayInputLength; + UnicodeString _hint; + int _pos; // character position in buffer ( 0 <= _pos <= _len ) + int _prefix; // prefix length used in common prefix search + int _hintSelection; // Currently selected hint. + History _history; + KillRing _killRing; + int long long _lastRefreshTime; + bool _refreshSkipped; + int _lastYankSize; + int _maxHintRows; + int _hintDelay; + std::string _breakChars; + int _completionCountCutoff; + bool _overwrite; + bool _doubleTabCompletion; + bool _completeOnEmpty; + bool _beepOnAmbiguousCompletion; + bool _immediateCompletion; + bool _bracketedPaste; + bool _noColor; + named_actions_t _namedActions; + key_press_handlers_t _keyPressHandlers; + Terminal _terminal; + std::thread::id _currentThread; + Prompt _prompt; + Replxx::modify_callback_t _modifyCallback; + Replxx::completion_callback_t _completionCallback; + Replxx::highlighter_callback_t _highlighterCallback; + Replxx::hint_callback_t _hintCallback; + key_presses_t _keyPresses; + messages_t _messages; + completions_t _completions; + int _completionContextLength; + int _completionSelection; + std::string _preloadedBuffer; // used with set_preload_buffer + std::string _errorMessage; + UnicodeString _previousSearchText; // remembered across invocations of replxx_input() + bool _modifiedState; + Replxx::Color _hintColor; + hints_t _hintsCache; + int _hintContextLenght; + Utf8String _hintSeed; + mutable std::mutex _mutex; +public: + ReplxxImpl( FILE*, FILE*, FILE* ); + virtual ~ReplxxImpl( void ); + void set_modify_callback( Replxx::modify_callback_t const& fn ); + void set_completion_callback( Replxx::completion_callback_t const& fn ); + void set_highlighter_callback( Replxx::highlighter_callback_t const& fn ); + void set_hint_callback( Replxx::hint_callback_t const& fn ); + char const* input( std::string const& prompt ); + void history_add( std::string const& line ); + bool history_sync( std::string const& filename ); + bool history_save( std::string const& filename ); + bool history_load( std::string const& filename ); + void history_clear( void ); + Replxx::HistoryScan::impl_t history_scan( void ) const; + int history_size( void ) const; + void set_preload_buffer(std::string const& preloadText); + void set_word_break_characters( char const* wordBreakers ); + void set_max_hint_rows( int count ); + void set_hint_delay( int milliseconds ); + void set_double_tab_completion( bool val ); + void set_complete_on_empty( bool val ); + void set_beep_on_ambiguous_completion( bool val ); + void set_immediate_completion( bool val ); + void set_unique_history( bool ); + void set_no_color( bool val ); + void set_max_history_size( int len ); + void set_completion_count_cutoff( int len ); + int install_window_change_handler( void ); + void enable_bracketed_paste( void ); + void disable_bracketed_paste( void ); + void print( char const*, int ); + Replxx::ACTION_RESULT clear_screen( char32_t ); + void emulate_key_press( char32_t ); + Replxx::ACTION_RESULT invoke( Replxx::ACTION, char32_t ); + void bind_key( char32_t, Replxx::key_press_handler_t ); + void bind_key_internal( char32_t, char const* ); + Replxx::State get_state( void ) const; + void set_state( Replxx::State const& ); +private: + ReplxxImpl( ReplxxImpl const& ) = delete; + ReplxxImpl& operator = ( ReplxxImpl const& ) = delete; +private: + void preload_puffer( char const* preloadText ); + int get_input_line( void ); + Replxx::ACTION_RESULT action( action_trait_t, key_press_handler_raw_t const&, char32_t ); + Replxx::ACTION_RESULT insert_character( char32_t ); + Replxx::ACTION_RESULT go_to_begining_of_line( char32_t ); + Replxx::ACTION_RESULT go_to_end_of_line( char32_t ); + Replxx::ACTION_RESULT move_one_char_left( char32_t ); + Replxx::ACTION_RESULT move_one_char_right( char32_t ); + Replxx::ACTION_RESULT move_one_word_left( char32_t ); + Replxx::ACTION_RESULT move_one_word_right( char32_t ); + Replxx::ACTION_RESULT kill_word_to_left( char32_t ); + Replxx::ACTION_RESULT kill_word_to_right( char32_t ); + Replxx::ACTION_RESULT kill_to_whitespace_to_left( char32_t ); + Replxx::ACTION_RESULT kill_to_begining_of_line( char32_t ); + Replxx::ACTION_RESULT kill_to_end_of_line( char32_t ); + Replxx::ACTION_RESULT yank( char32_t ); + Replxx::ACTION_RESULT yank_cycle( char32_t ); + Replxx::ACTION_RESULT yank_last_arg( char32_t ); + Replxx::ACTION_RESULT capitalize_word( char32_t ); + Replxx::ACTION_RESULT lowercase_word( char32_t ); + Replxx::ACTION_RESULT uppercase_word( char32_t ); + Replxx::ACTION_RESULT transpose_characters( char32_t ); + Replxx::ACTION_RESULT abort_line( char32_t ); + Replxx::ACTION_RESULT send_eof( char32_t ); + Replxx::ACTION_RESULT delete_character( char32_t ); + Replxx::ACTION_RESULT backspace_character( char32_t ); + Replxx::ACTION_RESULT commit_line( char32_t ); + Replxx::ACTION_RESULT history_next( char32_t ); + Replxx::ACTION_RESULT history_previous( char32_t ); + Replxx::ACTION_RESULT history_move( bool ); + Replxx::ACTION_RESULT history_first( char32_t ); + Replxx::ACTION_RESULT history_last( char32_t ); + Replxx::ACTION_RESULT history_jump( bool ); + Replxx::ACTION_RESULT hint_next( char32_t ); + Replxx::ACTION_RESULT hint_previous( char32_t ); + Replxx::ACTION_RESULT hint_move( bool ); + Replxx::ACTION_RESULT toggle_overwrite_mode( char32_t ); +#ifndef _WIN32 + Replxx::ACTION_RESULT verbatim_insert( char32_t ); + Replxx::ACTION_RESULT suspend( char32_t ); +#endif + Replxx::ACTION_RESULT complete_line( char32_t ); + Replxx::ACTION_RESULT complete_next( char32_t ); + Replxx::ACTION_RESULT complete_previous( char32_t ); + Replxx::ACTION_RESULT complete( bool ); + Replxx::ACTION_RESULT incremental_history_search( char32_t startChar ); + Replxx::ACTION_RESULT common_prefix_search( char32_t startChar ); + Replxx::ACTION_RESULT bracketed_paste( char32_t startChar ); + char32_t read_char( HINT_ACTION = HINT_ACTION::SKIP ); + char const* read_from_stdin( void ); + char32_t do_complete_line( bool ); + void call_modify_callback( void ); + completions_t call_completer( std::string const& input, int& ) const; + hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const; + void refresh_line( HINT_ACTION = HINT_ACTION::REGENERATE ); + void render( char32_t ); + void render( HINT_ACTION ); + int handle_hints( HINT_ACTION ); + void set_color( Replxx::Color ); + int context_length( void ); + void clear( void ); + void repaint( void ); + bool is_word_break_character( char32_t ) const; + void dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos); + char const* finalize_input( char const* ); + void clear_self_to_end_of_screen( Prompt const* = nullptr ); + typedef struct { + int index; + bool error; + } paren_info_t; + paren_info_t matching_paren( void ); +}; + +} + +#endif + diff --git a/third-party/replxx/src/terminal.cxx b/third-party/replxx/src/terminal.cxx new file mode 100644 index 0000000000..e618219e5d --- /dev/null +++ b/third-party/replxx/src/terminal.cxx @@ -0,0 +1,742 @@ +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 + +#include +#include +#include +#define isatty _isatty +#define strcasecmp _stricmp +#define strdup _strdup +#define write _write +#define STDIN_FILENO 0 + +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; +#endif + +#include "windows.hxx" + +#else /* _WIN32 */ + +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include "terminal.hxx" +#include "conversion.hxx" +#include "escape.hxx" +#include "replxx.hxx" +#include "util.hxx" + +using namespace std; + +namespace replxx { + +namespace tty { + +bool is_a_tty( int fd_ ) { + bool aTTY( isatty( fd_ ) != 0 ); +#ifdef _WIN32 + do { + if ( aTTY ) { + break; + } + HANDLE h( (HANDLE)_get_osfhandle( fd_ ) ); + if ( h == INVALID_HANDLE_VALUE ) { + break; + } + DWORD st( 0 ); + if ( ! GetConsoleMode( h, &st ) ) { + break; + } + aTTY = true; + } while ( false ); +#endif + return ( aTTY ); +} + +bool in( is_a_tty( 0 ) ); +bool out( is_a_tty( 1 ) ); + +} + +#ifndef _WIN32 +Terminal* _terminal_ = nullptr; +static void WindowSizeChanged( int ) { + if ( ! _terminal_ ) { + return; + } + _terminal_->notify_event( Terminal::EVENT_TYPE::RESIZE ); +} +#endif + + +Terminal::Terminal( void ) +#ifdef _WIN32 + : _consoleOut( INVALID_HANDLE_VALUE ) + , _consoleIn( INVALID_HANDLE_VALUE ) + , _origOutMode() + , _origInMode() + , _oldDisplayAttribute() + , _inputCodePage( GetConsoleCP() ) + , _outputCodePage( GetConsoleOutputCP() ) + , _interrupt( INVALID_HANDLE_VALUE ) + , _events() + , _empty() +#else + : _origTermios() + , _interrupt() +#endif + , _rawMode( false ) + , _utf8() { +#ifdef _WIN32 + _interrupt = CreateEvent( nullptr, true, false, TEXT( "replxx_interrupt_event" ) ); +#else + static_cast( ::pipe( _interrupt ) == 0 ); +#endif +} + +Terminal::~Terminal( void ) { + if ( _rawMode ) { + disable_raw_mode(); + } +#ifdef _WIN32 + CloseHandle( _interrupt ); +#else + static_cast( ::close( _interrupt[0] ) == 0 ); + static_cast( ::close( _interrupt[1] ) == 0 ); +#endif +} + +void Terminal::write32( char32_t const* text32, int len32 ) { + _utf8.assign( text32, len32 ); + write8( _utf8.get(), _utf8.size() ); + return; +} + +void Terminal::write8( char const* data_, int size_ ) { +#ifdef _WIN32 + if ( ! _rawMode ) { + enable_out(); + } + int nWritten( win_write( _consoleOut, _autoEscape, data_, size_ ) ); + if ( ! _rawMode ) { + disable_out(); + } +#else + int nWritten( write( 1, data_, size_ ) ); +#endif + if ( nWritten != size_ ) { + throw std::runtime_error( "write failed" ); + } + return; +} + +int Terminal::get_screen_columns( void ) { + int cols( 0 ); +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo( _consoleOut, &inf ); + cols = inf.dwSize.X; +#else + struct winsize ws; + cols = ( ioctl( 1, TIOCGWINSZ, &ws ) == -1 ) ? 80 : ws.ws_col; +#endif + // cols is 0 in certain circumstances like inside debugger, which creates + // further issues + return ( cols > 0 ) ? cols : 80; +} + +int Terminal::get_screen_rows( void ) { + int rows; +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo( _consoleOut, &inf ); + rows = 1 + inf.srWindow.Bottom - inf.srWindow.Top; +#else + struct winsize ws; + rows = (ioctl(1, TIOCGWINSZ, &ws) == -1) ? 24 : ws.ws_row; +#endif + return (rows > 0) ? rows : 24; +} + +namespace { +inline int notty( void ) { + errno = ENOTTY; + return ( -1 ); +} +} + +void Terminal::enable_out( void ) { +#ifdef _WIN32 + _consoleOut = GetStdHandle( STD_OUTPUT_HANDLE ); + SetConsoleOutputCP( 65001 ); + GetConsoleMode( _consoleOut, &_origOutMode ); + _autoEscape = SetConsoleMode( _consoleOut, _origOutMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) != 0; +#endif +} + +void Terminal::disable_out( void ) { +#ifdef _WIN32 + SetConsoleMode( _consoleOut, _origOutMode ); + SetConsoleOutputCP( _outputCodePage ); + _consoleOut = INVALID_HANDLE_VALUE; + _autoEscape = false; +#endif +} + +void Terminal::enable_bracketed_paste( void ) { + static char const BRACK_PASTE_INIT[] = "\033[?2004h"; + write8( BRACK_PASTE_INIT, sizeof ( BRACK_PASTE_INIT ) - 1 ); +} + +void Terminal::disable_bracketed_paste( void ) { + static char const BRACK_PASTE_DISABLE[] = "\033[?2004l"; + write8( BRACK_PASTE_DISABLE, sizeof ( BRACK_PASTE_DISABLE ) - 1 ); +} + +int Terminal::enable_raw_mode( void ) { + if ( ! _rawMode ) { +#ifdef _WIN32 + _consoleIn = GetStdHandle( STD_INPUT_HANDLE ); + SetConsoleCP( 65001 ); + GetConsoleMode( _consoleIn, &_origInMode ); + SetConsoleMode( + _consoleIn, + _origInMode & ~( ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT | ENABLE_PROCESSED_INPUT ) + ); + enable_out(); +#else + struct termios raw; + + if ( ! tty::in ) { + return ( notty() ); + } + if ( tcgetattr( 0, &_origTermios ) == -1 ) { + return ( notty() ); + } + + raw = _origTermios; /* modify the original mode */ + /* input modes: no break, no CR to NL, no parity check, no strip char, + * no start/stop output control. */ + raw.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + /* output modes - disable post processing */ + // this is wrong, we don't want raw output, it turns newlines into straight + // linefeeds + // raw.c_oflag &= ~(OPOST); + /* control modes - set 8 bit chars */ + raw.c_cflag |= (CS8); + /* local modes - echoing off, canonical off, no extended functions, + * no signal chars (^Z,^C) */ + raw.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + /* control chars - set return condition: min number of bytes and timer. + * We want read to return every single byte, without timeout. */ + raw.c_cc[VMIN] = 1; + raw.c_cc[VTIME] = 0; /* 1 byte, no timer */ + + /* put terminal in raw mode after flushing */ + if ( tcsetattr(0, TCSADRAIN, &raw) < 0 ) { + return ( notty() ); + } + _terminal_ = this; +#endif + _rawMode = true; + } + return ( 0 ); +} + +void Terminal::disable_raw_mode(void) { + if ( _rawMode ) { +#ifdef _WIN32 + disable_out(); + SetConsoleMode( _consoleIn, _origInMode ); + SetConsoleCP( _inputCodePage ); + _consoleIn = INVALID_HANDLE_VALUE; +#else + _terminal_ = nullptr; + if ( tcsetattr( 0, TCSADRAIN, &_origTermios ) == -1 ) { + return; + } +#endif + _rawMode = false; + } +} + +#ifndef _WIN32 + +/** + * Read a UTF-8 sequence from the non-Windows keyboard and return the Unicode + * (char32_t) character it encodes + * + * @return char32_t Unicode character + */ +char32_t read_unicode_character(void) { + static char8_t utf8String[5]; + static size_t utf8Count = 0; + while (true) { + char8_t c; + + /* Continue reading if interrupted by signal. */ + ssize_t nread; + do { + nread = read( STDIN_FILENO, &c, 1 ); + } while ((nread == -1) && (errno == EINTR)); + + if (nread <= 0) return 0; + if (c <= 0x7F || locale::is8BitEncoding) { // short circuit ASCII + utf8Count = 0; + return c; + } else if (utf8Count < sizeof(utf8String) - 1) { + utf8String[utf8Count++] = c; + utf8String[utf8Count] = 0; + char32_t unicodeChar[2]; + int ucharCount( 0 ); + ConversionResult res = copyString8to32(unicodeChar, 2, ucharCount, utf8String); + if (res == conversionOK && ucharCount) { + utf8Count = 0; + return unicodeChar[0]; + } + } else { + utf8Count = 0; // this shouldn't happen: got four bytes but no UTF-8 character + } + } +} + +#endif // #ifndef _WIN32 + +void beep() { + fprintf(stderr, "\x7"); // ctrl-G == bell/beep + fflush(stderr); +} + +// replxx_read_char -- read a keystroke or keychord from the keyboard, and translate it +// into an encoded "keystroke". When convenient, extended keys are translated into their +// simpler Emacs keystrokes, so an unmodified "left arrow" becomes Ctrl-B. +// +// A return value of zero means "no input available", and a return value of -1 +// means "invalid key". +// +char32_t Terminal::read_char( void ) { + char32_t c( 0 ); +#ifdef _WIN32 + INPUT_RECORD rec; + DWORD count; + char32_t modifierKeys = 0; + bool escSeen = false; + int highSurrogate( 0 ); + while (true) { + ReadConsoleInputW( _consoleIn, &rec, 1, &count ); +#if __REPLXX_DEBUG__ // helper for debugging keystrokes, display info in the debug "Output" + // window in the debugger + { + if ( rec.EventType == KEY_EVENT ) { + //if ( rec.Event.KeyEvent.uChar.UnicodeChar ) { + char buf[1024]; + sprintf( + buf, + "Unicode character 0x%04X, repeat count %d, virtual keycode 0x%04X, " + "virtual scancode 0x%04X, key %s%s%s%s%s\n", + rec.Event.KeyEvent.uChar.UnicodeChar, + rec.Event.KeyEvent.wRepeatCount, + rec.Event.KeyEvent.wVirtualKeyCode, + rec.Event.KeyEvent.wVirtualScanCode, + rec.Event.KeyEvent.bKeyDown ? "down" : "up", + (rec.Event.KeyEvent.dwControlKeyState & LEFT_CTRL_PRESSED) ? " L-Ctrl" : "", + (rec.Event.KeyEvent.dwControlKeyState & RIGHT_CTRL_PRESSED) ? " R-Ctrl" : "", + (rec.Event.KeyEvent.dwControlKeyState & LEFT_ALT_PRESSED) ? " L-Alt" : "", + (rec.Event.KeyEvent.dwControlKeyState & RIGHT_ALT_PRESSED) ? " R-Alt" : "" + ); + OutputDebugStringA( buf ); + //} + } + } +#endif + if ( rec.EventType != KEY_EVENT ) { + continue; + } + // Windows provides for entry of characters that are not on your keyboard by sending the + // Unicode characters as a "key up" with virtual keycode 0x12 (VK_MENU == Alt key) ... + // accept these characters, otherwise only process characters on "key down" + if ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) { + continue; + } + modifierKeys = 0; + // AltGr is encoded as ( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ), so don't treat this + // combination as either CTRL or META we just turn off those two bits, so it is still + // possible to combine CTRL and/or META with an AltGr key by using right-Ctrl and/or + // left-Alt + DWORD const AltGr( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); + if ( ( rec.Event.KeyEvent.dwControlKeyState & AltGr ) == AltGr ) { + rec.Event.KeyEvent.dwControlKeyState &= ~( LEFT_CTRL_PRESSED | RIGHT_ALT_PRESSED ); + } + if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED ) ) { + modifierKeys |= Replxx::KEY::BASE_CONTROL; + } + if ( rec.Event.KeyEvent.dwControlKeyState & ( RIGHT_ALT_PRESSED | LEFT_ALT_PRESSED ) ) { + modifierKeys |= Replxx::KEY::BASE_META; + } + if ( escSeen ) { + modifierKeys |= Replxx::KEY::BASE_META; + } + int key( rec.Event.KeyEvent.uChar.UnicodeChar ); + if ( key == 0 ) { + switch (rec.Event.KeyEvent.wVirtualKeyCode) { + case VK_LEFT: + return modifierKeys | Replxx::KEY::LEFT; + case VK_RIGHT: + return modifierKeys | Replxx::KEY::RIGHT; + case VK_UP: + return modifierKeys | Replxx::KEY::UP; + case VK_DOWN: + return modifierKeys | Replxx::KEY::DOWN; + case VK_DELETE: + return modifierKeys | Replxx::KEY::DELETE; + case VK_HOME: + return modifierKeys | Replxx::KEY::HOME; + case VK_END: + return modifierKeys | Replxx::KEY::END; + case VK_PRIOR: + return modifierKeys | Replxx::KEY::PAGE_UP; + case VK_NEXT: + return modifierKeys | Replxx::KEY::PAGE_DOWN; + case VK_F1: + return modifierKeys | Replxx::KEY::F1; + case VK_F2: + return modifierKeys | Replxx::KEY::F2; + case VK_F3: + return modifierKeys | Replxx::KEY::F3; + case VK_F4: + return modifierKeys | Replxx::KEY::F4; + case VK_F5: + return modifierKeys | Replxx::KEY::F5; + case VK_F6: + return modifierKeys | Replxx::KEY::F6; + case VK_F7: + return modifierKeys | Replxx::KEY::F7; + case VK_F8: + return modifierKeys | Replxx::KEY::F8; + case VK_F9: + return modifierKeys | Replxx::KEY::F9; + case VK_F10: + return modifierKeys | Replxx::KEY::F10; + case VK_F11: + return modifierKeys | Replxx::KEY::F11; + case VK_F12: + return modifierKeys | Replxx::KEY::F12; + default: + continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them + } + } else if ( key == Replxx::KEY::ESCAPE ) { // ESC, set flag for later + escSeen = true; + continue; + } else if ( ( key >= 0xD800 ) && ( key <= 0xDBFF ) ) { + highSurrogate = key - 0xD800; + continue; + } else { + // we got a real character, return it + if ( ( key >= 0xDC00 ) && ( key <= 0xDFFF ) ) { + key -= 0xDC00; + key |= ( highSurrogate << 10 ); + key += 0x10000; + } + if ( is_control_code( key ) ) { + key = control_to_human( key ); + modifierKeys |= Replxx::KEY::BASE_CONTROL; + } + key |= modifierKeys; + highSurrogate = 0; + c = key; + break; + } + } + +#else + c = read_unicode_character(); + if (c == 0) { + return 0; + } + +// If _DEBUG_LINUX_KEYBOARD is set, then ctrl-^ puts us into a keyboard +// debugging mode +// where we print out decimal and decoded values for whatever the "terminal" +// program +// gives us on different keystrokes. Hit ctrl-C to exit this mode. +// +#ifdef __REPLXX_DEBUG__ + if (c == ctrlChar('^')) { // ctrl-^, special debug mode, prints all keys hit, + // ctrl-C to get out + printf( + "\nEntering keyboard debugging mode (on ctrl-^), press ctrl-C to exit " + "this mode\n"); + while (true) { + unsigned char keys[10]; + int ret = read(0, keys, 10); + + if (ret <= 0) { + printf("\nret: %d\n", ret); + } + for (int i = 0; i < ret; ++i) { + char32_t key = static_cast(keys[i]); + char* friendlyTextPtr; + char friendlyTextBuf[10]; + const char* prefixText = (key < 0x80) ? "" : "0x80+"; + char32_t keyCopy = (key < 0x80) ? key : key - 0x80; + if (keyCopy >= '!' && keyCopy <= '~') { // printable + friendlyTextBuf[0] = '\''; + friendlyTextBuf[1] = keyCopy; + friendlyTextBuf[2] = '\''; + friendlyTextBuf[3] = 0; + friendlyTextPtr = friendlyTextBuf; + } else if (keyCopy == ' ') { + friendlyTextPtr = const_cast("space"); + } else if (keyCopy == 27) { + friendlyTextPtr = const_cast("ESC"); + } else if (keyCopy == 0) { + friendlyTextPtr = const_cast("NUL"); + } else if (keyCopy == 127) { + friendlyTextPtr = const_cast("DEL"); + } else { + friendlyTextBuf[0] = '^'; + friendlyTextBuf[1] = control_to_human( keyCopy ); + friendlyTextBuf[2] = 0; + friendlyTextPtr = friendlyTextBuf; + } + printf("%d x%02X (%s%s) ", key, key, prefixText, friendlyTextPtr); + } + printf("\x1b[1G\n"); // go to first column of new line + + // drop out of this loop on ctrl-C + if (keys[0] == ctrlChar('C')) { + printf("Leaving keyboard debugging mode (on ctrl-C)\n"); + fflush(stdout); + return -2; + } + } + } +#endif // __REPLXX_DEBUG__ + + c = EscapeSequenceProcessing::doDispatch(c); + if ( is_control_code( c ) ) { + c = Replxx::KEY::control( control_to_human( c ) ); + } +#endif // #_WIN32 + return ( c ); +} + +Terminal::EVENT_TYPE Terminal::wait_for_input( int long timeout_ ) { +#ifdef _WIN32 + std::array handles = { _consoleIn, _interrupt }; + while ( true ) { + DWORD event( WaitForMultipleObjects( static_cast( handles.size() ), handles.data(), false, timeout_ > 0 ? timeout_ : INFINITE ) ); + switch ( event ) { + case ( WAIT_OBJECT_0 + 0 ): { + // peek events that will be skipped + INPUT_RECORD rec; + DWORD count; + PeekConsoleInputW( _consoleIn, &rec, 1, &count ); + + if ( + ( rec.EventType != KEY_EVENT ) + || ( !rec.Event.KeyEvent.bKeyDown && ( rec.Event.KeyEvent.wVirtualKeyCode != VK_MENU ) ) + ) { + // read the event to unsignal the handle + ReadConsoleInputW( _consoleIn, &rec, 1, &count ); + continue; + } else if (rec.EventType == KEY_EVENT) { + int key(rec.Event.KeyEvent.uChar.UnicodeChar); + if (key == 0) { + switch (rec.Event.KeyEvent.wVirtualKeyCode) { + case VK_LEFT: + case VK_RIGHT: + case VK_UP: + case VK_DOWN: + case VK_DELETE: + case VK_HOME: + case VK_END: + case VK_PRIOR: + case VK_NEXT: + break; + default: + ReadConsoleInputW(_consoleIn, &rec, 1, &count); + continue; // in raw mode, ReadConsoleInput shows shift, ctrl - ignore them + } + } + } + + return ( EVENT_TYPE::KEY_PRESS ); + } + case ( WAIT_OBJECT_0 + 1 ): { + ResetEvent( _interrupt ); + if ( _events.empty() ) { + continue; + } + EVENT_TYPE eventType( _events.front() ); + _events.pop_front(); + return ( eventType ); + } + case ( WAIT_TIMEOUT ): { + return ( EVENT_TYPE::TIMEOUT ); + } + } + } +#else + fd_set fdSet; + int nfds( max( _interrupt[0], _interrupt[1] ) + 1 ); + while ( true ) { + FD_ZERO( &fdSet ); + FD_SET( 0, &fdSet ); + FD_SET( _interrupt[0], &fdSet ); + timeval tv{ timeout_ / 1000, static_cast( ( timeout_ % 1000 ) * 1000 ) }; + int err( select( nfds, &fdSet, nullptr, nullptr, timeout_ > 0 ? &tv : nullptr ) ); + if ( ( err == -1 ) && ( errno == EINTR ) ) { + continue; + } + if ( err == 0 ) { + return ( EVENT_TYPE::TIMEOUT ); + } + if ( FD_ISSET( _interrupt[0], &fdSet ) ) { + char data( 0 ); + static_cast( read( _interrupt[0], &data, 1 ) == 1 ); + if ( data == 'k' ) { + return ( EVENT_TYPE::KEY_PRESS ); + } + if ( data == 'm' ) { + return ( EVENT_TYPE::MESSAGE ); + } + if ( data == 'r' ) { + return ( EVENT_TYPE::RESIZE ); + } + } + if ( FD_ISSET( 0, &fdSet ) ) { + return ( EVENT_TYPE::KEY_PRESS ); + } + } +#endif +} + +void Terminal::notify_event( EVENT_TYPE eventType_ ) { +#ifdef _WIN32 + _events.push_back( eventType_ ); + SetEvent( _interrupt ); +#else + char data( ( eventType_ == EVENT_TYPE::KEY_PRESS ) ? 'k' : ( eventType_ == EVENT_TYPE::MESSAGE ? 'm' : 'r' ) ); + static_cast( write( _interrupt[1], &data, 1 ) == 1 ); +#endif +} + +/** + * Clear the screen ONLY (no redisplay of anything) + */ +void Terminal::clear_screen( CLEAR_SCREEN clearScreen_ ) { +#ifdef _WIN32 + if ( _autoEscape ) { +#endif + if ( clearScreen_ == CLEAR_SCREEN::WHOLE ) { + char const clearCode[] = "\033c\033[H\033[2J\033[0m"; + static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + } else { + char const clearCode[] = "\033[J"; + static_cast( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 ); + } + return; +#ifdef _WIN32 + } + COORD coord = { 0, 0 }; + CONSOLE_SCREEN_BUFFER_INFO inf; + HANDLE consoleOut( _consoleOut != INVALID_HANDLE_VALUE ? _consoleOut : GetStdHandle( STD_OUTPUT_HANDLE ) ); + GetConsoleScreenBufferInfo( consoleOut, &inf ); + if ( clearScreen_ == CLEAR_SCREEN::TO_END ) { + coord = inf.dwCursorPosition; + DWORD nWritten( 0 ); + SHORT height( inf.srWindow.Bottom - inf.srWindow.Top ); + DWORD yPos( inf.dwCursorPosition.Y - inf.srWindow.Top ); + DWORD toWrite( ( height + 1 - yPos ) * inf.dwSize.X - inf.dwCursorPosition.X ); +// FillConsoleOutputCharacterA( consoleOut, ' ', toWrite, coord, &nWritten ); + _empty.resize( toWrite - 1, ' ' ); + WriteConsoleA( consoleOut, _empty.data(), toWrite - 1, &nWritten, nullptr ); + } else { + COORD scrollTarget = { 0, -inf.dwSize.Y }; + CHAR_INFO fill{ TEXT( ' ' ), inf.wAttributes }; + SMALL_RECT scrollRect = { 0, 0, inf.dwSize.X, inf.dwSize.Y }; + ScrollConsoleScreenBuffer( consoleOut, &scrollRect, nullptr, scrollTarget, &fill ); + } + SetConsoleCursorPosition( consoleOut, coord ); +#endif +} + +void Terminal::jump_cursor( int xPos_, int yOffset_ ) { +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO inf; + GetConsoleScreenBufferInfo( _consoleOut, &inf ); + inf.dwCursorPosition.X = xPos_; + inf.dwCursorPosition.Y += yOffset_; + SetConsoleCursorPosition( _consoleOut, inf.dwCursorPosition ); +#else + char seq[64]; + if ( yOffset_ != 0 ) { // move the cursor up as required + snprintf( seq, sizeof seq, "\033[%d%c", abs( yOffset_ ), yOffset_ > 0 ? 'B' : 'A' ); + write8( seq, strlen( seq ) ); + } + // position at the end of the prompt, clear to end of screen + snprintf( + seq, sizeof seq, "\033[%dG", + xPos_ + 1 /* 1-based on VT100 */ + ); + write8( seq, strlen( seq ) ); +#endif +} + +#ifdef _WIN32 +void Terminal::set_cursor_visible( bool visible_ ) { + CONSOLE_CURSOR_INFO cursorInfo; + GetConsoleCursorInfo( _consoleOut, &cursorInfo ); + cursorInfo.bVisible = visible_; + SetConsoleCursorInfo( _consoleOut, &cursorInfo ); + return; +} +#else +void Terminal::set_cursor_visible( bool ) {} +#endif + +#ifndef _WIN32 +int Terminal::read_verbatim( char32_t* buffer_, int size_ ) { + int len( 0 ); + buffer_[len ++] = read_unicode_character(); + int statusFlags( ::fcntl( STDIN_FILENO, F_GETFL, 0 ) ); + ::fcntl( STDIN_FILENO, F_SETFL, statusFlags | O_NONBLOCK ); + while ( len < size_ ) { + char32_t c( read_unicode_character() ); + if ( c == 0 ) { + break; + } + buffer_[len ++] = c; + } + ::fcntl( STDIN_FILENO, F_SETFL, statusFlags ); + return ( len ); +} + +int Terminal::install_window_change_handler( void ) { + struct sigaction sa; + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; + sa.sa_handler = &WindowSizeChanged; + + if (sigaction(SIGWINCH, &sa, nullptr) == -1) { + return errno; + } + return 0; +} +#endif + +} + diff --git a/third-party/replxx/src/terminal.hxx b/third-party/replxx/src/terminal.hxx new file mode 100644 index 0000000000..e6a25786b9 --- /dev/null +++ b/third-party/replxx/src/terminal.hxx @@ -0,0 +1,94 @@ +#ifndef REPLXX_IO_HXX_INCLUDED +#define REPLXX_IO_HXX_INCLUDED 1 + +#include + +#ifdef _WIN32 +#include +#include +#else +#include +#endif + +#include "utf8string.hxx" + +namespace replxx { + +class Terminal { +public: + enum class EVENT_TYPE { + KEY_PRESS, + MESSAGE, + TIMEOUT, + RESIZE + }; +private: +#ifdef _WIN32 + HANDLE _consoleOut; + HANDLE _consoleIn; + DWORD _origOutMode; + DWORD _origInMode; + bool _autoEscape; + WORD _oldDisplayAttribute; + UINT const _inputCodePage; + UINT const _outputCodePage; + HANDLE _interrupt; + typedef std::deque events_t; + events_t _events; + std::vector _empty; +#else + struct termios _origTermios; /* in order to restore at exit */ + int _interrupt[2]; +#endif + bool _rawMode; /* for destructor to check if restore is needed */ + Utf8String _utf8; +public: + enum class CLEAR_SCREEN { + WHOLE, + TO_END + }; +public: + Terminal( void ); + ~Terminal( void ); + void write32( char32_t const*, int ); + void write8( char const*, int ); + int get_screen_columns(void); + int get_screen_rows(void); + void enable_bracketed_paste( void ); + void disable_bracketed_paste( void ); + int enable_raw_mode(void); + void disable_raw_mode(void); + char32_t read_char(void); + void clear_screen( CLEAR_SCREEN ); + EVENT_TYPE wait_for_input( int long = 0 ); + void notify_event( EVENT_TYPE ); + void jump_cursor( int, int ); + void set_cursor_visible( bool ); +#ifndef _WIN32 + int read_verbatim( char32_t*, int ); + int install_window_change_handler( void ); +#endif +private: + void enable_out( void ); + void disable_out( void ); +private: + Terminal( Terminal const& ) = delete; + Terminal& operator = ( Terminal const& ) = delete; + Terminal( Terminal&& ) = delete; + Terminal& operator = ( Terminal&& ) = delete; +}; + +void beep(); +char32_t read_unicode_character(void); + +namespace tty { + +extern bool in; +extern bool out; + +} + +} + +#endif + diff --git a/third-party/replxx/src/unicodestring.hxx b/third-party/replxx/src/unicodestring.hxx new file mode 100644 index 0000000000..bcc09a09bb --- /dev/null +++ b/third-party/replxx/src/unicodestring.hxx @@ -0,0 +1,191 @@ +#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED +#define REPLXX_UNICODESTRING_HXX_INCLUDED + +#include +#include + +#include "conversion.hxx" + +namespace replxx { + +class UnicodeString { +public: + typedef std::vector data_buffer_t; + typedef data_buffer_t::const_iterator const_iterator; + typedef data_buffer_t::iterator iterator; +private: + data_buffer_t _data; +public: + UnicodeString() + : _data() { + } + + explicit UnicodeString( std::string const& src ) + : _data() { + assign( src ); + } + + explicit UnicodeString( char const* src ) + : _data() { + assign( src ); + } + + explicit UnicodeString( char8_t const* src ) + : UnicodeString( reinterpret_cast( src ) ) { + } + + explicit UnicodeString( char32_t const* src ) + : _data() { + int len( 0 ); + while ( src[len] != 0 ) { + ++ len; + } + _data.assign( src, src + len ); + } + + explicit UnicodeString( char32_t const* src, int len ) + : _data() { + _data.assign( src, src + len ); + } + + explicit UnicodeString( int len ) + : _data() { + _data.resize( len ); + } + + UnicodeString& assign( std::string const& str_ ) { + _data.resize( static_cast( str_.length() ) ); + int len( 0 ); + copyString8to32( _data.data(), static_cast( str_.length() ), len, str_.c_str() ); + _data.resize( len ); + return *this; + } + + UnicodeString& assign( char const* str_ ) { + int byteCount( static_cast( strlen( str_ ) ) ); + _data.resize( byteCount ); + int len( 0 ); + copyString8to32( _data.data(), byteCount, len, str_ ); + _data.resize( len ); + return *this; + } + + UnicodeString& assign( UnicodeString const& other_ ) { + _data = other_._data; + return *this; + } + + explicit UnicodeString( UnicodeString const& ) = default; + UnicodeString& operator = ( UnicodeString const& ) = default; + UnicodeString( UnicodeString&& ) = default; + UnicodeString& operator = ( UnicodeString&& ) = default; + bool operator == ( UnicodeString const& other_ ) const { + return ( _data == other_._data ); + } + + bool operator != ( UnicodeString const& other_ ) const { + return ( _data != other_._data ); + } + + UnicodeString& append( UnicodeString const& other ) { + _data.insert( _data.end(), other._data.begin(), other._data.end() ); + return *this; + } + + void push_back( char32_t c_ ) { + _data.push_back( c_ ); + } + + UnicodeString& append( char32_t const* src, int len ) { + _data.insert( _data.end(), src, src + len ); + return *this; + } + + UnicodeString& insert( int pos_, UnicodeString const& str_, int offset_, int len_ ) { + _data.insert( _data.begin() + pos_, str_._data.begin() + offset_, str_._data.begin() + offset_ + len_ ); + return *this; + } + + UnicodeString& insert( int pos_, char32_t c_ ) { + _data.insert( _data.begin() + pos_, c_ ); + return *this; + } + + UnicodeString& erase( int pos_ ) { + _data.erase( _data.begin() + pos_ ); + return *this; + } + + UnicodeString& erase( int pos_, int len_ ) { + _data.erase( _data.begin() + pos_, _data.begin() + pos_ + len_ ); + return *this; + } + + char32_t const* get() const { + return _data.data(); + } + + char32_t* get() { + return _data.data(); + } + + int length() const { + return static_cast( _data.size() ); + } + + void clear( void ) { + _data.clear(); + } + + const char32_t& operator[]( size_t pos ) const { + return _data[pos]; + } + + char32_t& operator[]( size_t pos ) { + return _data[pos]; + } + + bool starts_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const { + return ( + ( std::distance( first_, last_ ) <= length() ) + && ( std::equal( first_, last_, _data.begin() ) ) + ); + } + + bool ends_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const { + int len( static_cast( std::distance( first_, last_ ) ) ); + return ( + ( len <= length() ) + && ( std::equal( first_, last_, _data.end() - len ) ) + ); + } + + bool is_empty( void ) const { + return ( _data.size() == 0 ); + } + + void swap( UnicodeString& other_ ) { + _data.swap( other_._data ); + } + + const_iterator begin( void ) const { + return ( _data.begin() ); + } + + const_iterator end( void ) const { + return ( _data.end() ); + } + + iterator begin( void ) { + return ( _data.begin() ); + } + + iterator end( void ) { + return ( _data.end() ); + } +}; + +} + +#endif + diff --git a/third-party/replxx/src/utf8string.hxx b/third-party/replxx/src/utf8string.hxx new file mode 100644 index 0000000000..29effa2ce5 --- /dev/null +++ b/third-party/replxx/src/utf8string.hxx @@ -0,0 +1,94 @@ +#ifndef REPLXX_UTF8STRING_HXX_INCLUDED +#define REPLXX_UTF8STRING_HXX_INCLUDED + +#include + +#include "unicodestring.hxx" + +namespace replxx { + +class Utf8String { +private: + typedef std::unique_ptr buffer_t; + buffer_t _data; + int _bufSize; + int _len; +public: + Utf8String( void ) + : _data() + , _bufSize( 0 ) + , _len( 0 ) { + } + explicit Utf8String( UnicodeString const& src ) + : _data() + , _bufSize( 0 ) + , _len( 0 ) { + assign( src, src.length() ); + } + + Utf8String( UnicodeString const& src_, int len_ ) + : _data() + , _bufSize( 0 ) + , _len( 0 ) { + assign( src_, len_ ); + } + + void assign( UnicodeString const& str_ ) { + assign( str_, str_.length() ); + } + + void assign( UnicodeString const& str_, int len_ ) { + assign( str_.get(), len_ ); + } + + void assign( char32_t const* str_, int len_ ) { + int len( len_ * 4 ); + realloc( len ); + _len = copyString32to8( _data.get(), len, str_, len_ ); + } + + void assign( std::string const& str_ ) { + realloc( static_cast( str_.length() ) ); + strncpy( _data.get(), str_.c_str(), str_.length() ); + _len = static_cast( str_.length() ); + } + + void assign( Utf8String const& other_ ) { + realloc( other_._len ); + strncpy( _data.get(), other_._data.get(), other_._len ); + _len = other_._len; + } + + char const* get() const { + return _data.get(); + } + + int size( void ) const { + return ( _len ); + } + + bool operator != ( Utf8String const& other_ ) { + return ( ( other_._len != _len ) || ( memcmp( other_._data.get(), _data.get(), _len ) != 0 ) ); + } + +private: + void realloc( int reqLen ) { + if ( ( reqLen + 1 ) > _bufSize ) { + _bufSize = 1; + while ( ( reqLen + 1 ) > _bufSize ) { + _bufSize *= 2; + } + _data.reset( new char[_bufSize] ); + memset( _data.get(), 0, _bufSize ); + } + _data[reqLen] = 0; + return; + } + Utf8String(const Utf8String&) = delete; + Utf8String& operator=(const Utf8String&) = delete; +}; + +} + +#endif + diff --git a/third-party/replxx/src/util.cxx b/third-party/replxx/src/util.cxx new file mode 100644 index 0000000000..cd5e18888d --- /dev/null +++ b/third-party/replxx/src/util.cxx @@ -0,0 +1,170 @@ +#include +#include +#include +#include +#include + +#include "util.hxx" + +namespace replxx { + +int mk_wcwidth( char32_t ); + +/** + * Recompute widths of all characters in a char32_t buffer + * @param text - input buffer of Unicode characters + * @param widths - output buffer of character widths + * @param charCount - number of characters in buffer + */ +void recompute_character_widths( char32_t const* text, char* widths, int charCount ) { + for (int i = 0; i < charCount; ++i) { + widths[i] = mk_wcwidth(text[i]); + } +} + +/** + * Calculate a new screen position given a starting position, screen width and + * character count + * @param x - initial x position (zero-based) + * @param y - initial y position (zero-based) + * @param screenColumns - screen column count + * @param charCount - character positions to advance + * @param xOut - returned x position (zero-based) + * @param yOut - returned y position (zero-based) + */ +void calculate_screen_position( + int x, int y, int screenColumns, + int charCount, int& xOut, int& yOut +) { + xOut = x; + yOut = y; + int charsRemaining = charCount; + while ( charsRemaining > 0 ) { + int charsThisRow = ( ( x + charsRemaining ) < screenColumns ) + ? charsRemaining + : screenColumns - x; + xOut = x + charsThisRow; + yOut = y; + charsRemaining -= charsThisRow; + x = 0; + ++ y; + } + if ( xOut == screenColumns ) { // we have to special-case line wrap + xOut = 0; + ++ yOut; + } +} + +/** + * Calculate a column width using mk_wcswidth() + * @param buf32 - text to calculate + * @param len - length of text to calculate + */ +int calculate_displayed_length( char32_t const* buf32_, int size_ ) { + int len( 0 ); + for ( int i( 0 ); i < size_; ++ i ) { + char32_t c( buf32_[i] ); + if ( c == '\033' ) { + int escStart( i ); + ++ i; + if ( ( i < size_ ) && ( buf32_[i] != '[' ) ) { + i = escStart; + ++ len; + continue; + } + ++ i; + for ( ; i < size_; ++ i ) { + c = buf32_[i]; + if ( ( c != ';' ) && ( ( c < '0' ) || ( c > '9' ) ) ) { + break; + } + } + if ( ( i < size_ ) && ( buf32_[i] == 'm' ) ) { + continue; + } + i = escStart; + len += 2; + } else if ( is_control_code( c ) ) { + len += 2; + } else { + int wcw( mk_wcwidth( c ) ); + if ( wcw < 0 ) { + len = -1; + break; + } + len += wcw; + } + } + return ( len ); +} + +char const* ansi_color( Replxx::Color color_ ) { + static char const reset[] = "\033[0m"; + static char const black[] = "\033[0;22;30m"; + static char const red[] = "\033[0;22;31m"; + static char const green[] = "\033[0;22;32m"; + static char const brown[] = "\033[0;22;33m"; + static char const blue[] = "\033[0;22;34m"; + static char const magenta[] = "\033[0;22;35m"; + static char const cyan[] = "\033[0;22;36m"; + static char const lightgray[] = "\033[0;22;37m"; + +#ifdef _WIN32 + static bool const has256colorDefault( true ); +#else + static bool const has256colorDefault( false ); +#endif + static char const* TERM( getenv( "TERM" ) ); + static bool const has256color( TERM ? ( strstr( TERM, "256" ) != nullptr ) : has256colorDefault ); + static char const* gray = has256color ? "\033[0;1;90m" : "\033[0;1;30m"; + static char const* brightred = has256color ? "\033[0;1;91m" : "\033[0;1;31m"; + static char const* brightgreen = has256color ? "\033[0;1;92m" : "\033[0;1;32m"; + static char const* yellow = has256color ? "\033[0;1;93m" : "\033[0;1;33m"; + static char const* brightblue = has256color ? "\033[0;1;94m" : "\033[0;1;34m"; + static char const* brightmagenta = has256color ? "\033[0;1;95m" : "\033[0;1;35m"; + static char const* brightcyan = has256color ? "\033[0;1;96m" : "\033[0;1;36m"; + static char const* white = has256color ? "\033[0;1;97m" : "\033[0;1;37m"; + static char const error[] = "\033[101;1;33m"; + + char const* code( reset ); + switch ( color_ ) { + case Replxx::Color::BLACK: code = black; break; + case Replxx::Color::RED: code = red; break; + case Replxx::Color::GREEN: code = green; break; + case Replxx::Color::BROWN: code = brown; break; + case Replxx::Color::BLUE: code = blue; break; + case Replxx::Color::MAGENTA: code = magenta; break; + case Replxx::Color::CYAN: code = cyan; break; + case Replxx::Color::LIGHTGRAY: code = lightgray; break; + case Replxx::Color::GRAY: code = gray; break; + case Replxx::Color::BRIGHTRED: code = brightred; break; + case Replxx::Color::BRIGHTGREEN: code = brightgreen; break; + case Replxx::Color::YELLOW: code = yellow; break; + case Replxx::Color::BRIGHTBLUE: code = brightblue; break; + case Replxx::Color::BRIGHTMAGENTA: code = brightmagenta; break; + case Replxx::Color::BRIGHTCYAN: code = brightcyan; break; + case Replxx::Color::WHITE: code = white; break; + case Replxx::Color::ERROR: code = error; break; + case Replxx::Color::DEFAULT: code = reset; break; + } + return ( code ); +} + +std::string now_ms_str( void ) { + std::chrono::milliseconds ms( std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ) ); + time_t t( ms.count() / 1000 ); + tm broken; +#ifdef _WIN32 +#define localtime_r( t, b ) localtime_s( ( b ), ( t ) ) +#endif + localtime_r( &t, &broken ); +#undef localtime_r + static int const BUFF_SIZE( 32 ); + char str[BUFF_SIZE]; + strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken ); + snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast( ms.count() % 1000 ) ); + return ( str ); +} + +} + diff --git a/third-party/replxx/src/util.hxx b/third-party/replxx/src/util.hxx new file mode 100644 index 0000000000..bc8f100ac3 --- /dev/null +++ b/third-party/replxx/src/util.hxx @@ -0,0 +1,26 @@ +#ifndef REPLXX_UTIL_HXX_INCLUDED +#define REPLXX_UTIL_HXX_INCLUDED 1 + +#include "replxx.hxx" + +namespace replxx { + +inline bool is_control_code(char32_t testChar) { + return (testChar < ' ') || // C0 controls + (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls +} + +inline char32_t control_to_human( char32_t key ) { + return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) ); +} + +void recompute_character_widths( char32_t const* text, char* widths, int charCount ); +void calculate_screen_position( int x, int y, int screenColumns, int charCount, int& xOut, int& yOut ); +int calculate_displayed_length( char32_t const* buf32, int size ); +char const* ansi_color( Replxx::Color ); +std::string now_ms_str( void ); + +} + +#endif + diff --git a/third-party/replxx/src/wcwidth.cpp b/third-party/replxx/src/wcwidth.cpp new file mode 100644 index 0000000000..c6c05fad65 --- /dev/null +++ b/third-party/replxx/src/wcwidth.cpp @@ -0,0 +1,296 @@ +/* + * This is an implementation of wcwidth() and wcswidth() (defined in + * IEEE Std 1002.1-2001) for Unicode. + * + * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html + * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html + * + * In fixed-width output devices, Latin characters all occupy a single + * "cell" position of equal width, whereas ideographic CJK characters + * occupy two such cells. Interoperability between terminal-line + * applications and (teletype-style) character terminals using the + * UTF-8 encoding requires agreement on which character should advance + * the cursor by how many cell positions. No established formal + * standards exist at present on which Unicode character shall occupy + * how many cell positions on character terminals. These routines are + * a first attempt of defining such behavior based on simple rules + * applied to data provided by the Unicode Consortium. + * + * For some graphical characters, the Unicode standard explicitly + * defines a character-cell width via the definition of the East Asian + * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes. + * In all these cases, there is no ambiguity about which width a + * terminal shall use. For characters in the East Asian Ambiguous (A) + * class, the width choice depends purely on a preference of backward + * compatibility with either historic CJK or Western practice. + * Choosing single-width for these characters is easy to justify as + * the appropriate long-term solution, as the CJK practice of + * displaying these characters as double-width comes from historic + * implementation simplicity (8-bit encoded characters were displayed + * single-width and 16-bit ones double-width, even for Greek, + * Cyrillic, etc.) and not any typographic considerations. + * + * Much less clear is the choice of width for the Not East Asian + * (Neutral) class. Existing practice does not dictate a width for any + * of these characters. It would nevertheless make sense + * typographically to allocate two character cells to characters such + * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be + * represented adequately with a single-width glyph. The following + * routines at present merely assign a single-cell width to all + * neutral characters, in the interest of simplicity. This is not + * entirely satisfactory and should be reconsidered before + * establishing a formal standard in this area. At the moment, the + * decision which Not East Asian (Neutral) characters should be + * represented by double-width glyphs cannot yet be answered by + * applying a simple rule from the Unicode database content. Setting + * up a proper standard for the behavior of UTF-8 character terminals + * will require a careful analysis not only of each Unicode character, + * but also of each presentation form, something the author of these + * routines has avoided to do so far. + * + * http://www.unicode.org/unicode/reports/tr11/ + * + * Markus Kuhn -- 2007-05-26 (Unicode 5.0) + * + * Permission to use, copy, modify, and distribute this software + * for any purpose and without fee is hereby granted. The author + * disclaims all warranties with regard to this software. + * + * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c + */ + +#include +#include +#include + +namespace replxx { + +struct interval { + char32_t first; + char32_t last; +}; + +/* auxiliary function for binary search in interval table */ +static int bisearch(char32_t ucs, const struct interval *table, int max) { + int min = 0; + int mid; + + if (ucs < table[0].first || ucs > table[max].last) + return 0; + while (max >= min) { + mid = (min + max) / 2; + if (ucs > table[mid].last) + min = mid + 1; + else if (ucs < table[mid].first) + max = mid - 1; + else + return 1; + } + + return 0; +} + + +/* The following two functions define the column width of an ISO 10646 + * character as follows: + * + * - The null character (U+0000) has a column width of 0. + * + * - Other C0/C1 control characters and DEL will lead to a return + * value of -1. + * + * - Non-spacing and enclosing combining characters (general + * category code Mn or Me in the Unicode database) have a + * column width of 0. + * + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0. + * + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as defined in Unicode Technical + * Report #11 have a column width of 2. + * + * - All remaining characters (including all printable + * ISO 8859-1 and WGL4 characters, Unicode control characters, + * etc.) have a column width of 1. + * + * This implementation assumes that wchar_t characters are encoded + * in ISO 10646. + */ + +int mk_is_wide_char(char32_t ucs) { + static const struct interval wide[] = { + {0x1100, 0x115f}, {0x231a, 0x231b}, {0x2329, 0x232a}, + {0x23e9, 0x23ec}, {0x23f0, 0x23f0}, {0x23f3, 0x23f3}, + {0x25fd, 0x25fe}, {0x2614, 0x2615}, {0x2648, 0x2653}, + {0x267f, 0x267f}, {0x2693, 0x2693}, {0x26a1, 0x26a1}, + {0x26aa, 0x26ab}, {0x26bd, 0x26be}, {0x26c4, 0x26c5}, + {0x26ce, 0x26ce}, {0x26d4, 0x26d4}, {0x26ea, 0x26ea}, + {0x26f2, 0x26f3}, {0x26f5, 0x26f5}, {0x26fa, 0x26fa}, + {0x26fd, 0x26fd}, {0x2705, 0x2705}, {0x270a, 0x270b}, + {0x2728, 0x2728}, {0x274c, 0x274c}, {0x274e, 0x274e}, + {0x2753, 0x2755}, {0x2757, 0x2757}, {0x2795, 0x2797}, + {0x27b0, 0x27b0}, {0x27bf, 0x27bf}, {0x2b1b, 0x2b1c}, + {0x2b50, 0x2b50}, {0x2b55, 0x2b55}, {0x2e80, 0x2fdf}, + {0x2ff0, 0x303e}, {0x3040, 0x3247}, {0x3250, 0x4dbf}, + {0x4e00, 0xa4cf}, {0xa960, 0xa97f}, {0xac00, 0xd7a3}, + {0xf900, 0xfaff}, {0xfe10, 0xfe19}, {0xfe30, 0xfe6f}, + {0xff01, 0xff60}, {0xffe0, 0xffe6}, {0x16fe0, 0x16fe1}, + {0x17000, 0x18aff}, {0x1b000, 0x1b12f}, {0x1b170, 0x1b2ff}, + {0x1f004, 0x1f004}, {0x1f0cf, 0x1f0cf}, {0x1f18e, 0x1f18e}, + {0x1f191, 0x1f19a}, {0x1f200, 0x1f202}, {0x1f210, 0x1f23b}, + {0x1f240, 0x1f248}, {0x1f250, 0x1f251}, {0x1f260, 0x1f265}, + {0x1f300, 0x1f320}, {0x1f32d, 0x1f335}, {0x1f337, 0x1f37c}, + {0x1f37e, 0x1f393}, {0x1f3a0, 0x1f3ca}, {0x1f3cf, 0x1f3d3}, + {0x1f3e0, 0x1f3f0}, {0x1f3f4, 0x1f3f4}, {0x1f3f8, 0x1f43e}, + {0x1f440, 0x1f440}, {0x1f442, 0x1f4fc}, {0x1f4ff, 0x1f53d}, + {0x1f54b, 0x1f54e}, {0x1f550, 0x1f567}, {0x1f57a, 0x1f57a}, + {0x1f595, 0x1f596}, {0x1f5a4, 0x1f5a4}, {0x1f5fb, 0x1f64f}, + {0x1f680, 0x1f6c5}, {0x1f6cc, 0x1f6cc}, {0x1f6d0, 0x1f6d2}, + {0x1f6eb, 0x1f6ec}, {0x1f6f4, 0x1f6f8}, {0x1f910, 0x1f93e}, + {0x1f940, 0x1f94c}, {0x1f950, 0x1f96b}, {0x1f980, 0x1f997}, + {0x1f9c0, 0x1f9c0}, {0x1f9d0, 0x1f9e6}, {0x20000, 0x2fffd}, + {0x30000, 0x3fffd}, + }; + + if ( bisearch(ucs, wide, sizeof(wide) / sizeof(struct interval) - 1) ) { + return 1; + } + + return 0; +} + +int mk_wcwidth(char32_t ucs) { + /* sorted list of non-overlapping intervals of non-spacing characters */ + /* generated by "uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c" */ + static const struct interval combining[] = { + {0x00ad, 0x00ad}, {0x0300, 0x036f}, {0x0483, 0x0489}, + {0x0591, 0x05bd}, {0x05bf, 0x05bf}, {0x05c1, 0x05c2}, + {0x05c4, 0x05c5}, {0x05c7, 0x05c7}, {0x0610, 0x061a}, + {0x061c, 0x061c}, {0x064b, 0x065f}, {0x0670, 0x0670}, + {0x06d6, 0x06dc}, {0x06df, 0x06e4}, {0x06e7, 0x06e8}, + {0x06ea, 0x06ed}, {0x0711, 0x0711}, {0x0730, 0x074a}, + {0x07a6, 0x07b0}, {0x07eb, 0x07f3}, {0x0816, 0x0819}, + {0x081b, 0x0823}, {0x0825, 0x0827}, {0x0829, 0x082d}, + {0x0859, 0x085b}, {0x08d4, 0x08e1}, {0x08e3, 0x0902}, + {0x093a, 0x093a}, {0x093c, 0x093c}, {0x0941, 0x0948}, + {0x094d, 0x094d}, {0x0951, 0x0957}, {0x0962, 0x0963}, + {0x0981, 0x0981}, {0x09bc, 0x09bc}, {0x09c1, 0x09c4}, + {0x09cd, 0x09cd}, {0x09e2, 0x09e3}, {0x0a01, 0x0a02}, + {0x0a3c, 0x0a3c}, {0x0a41, 0x0a42}, {0x0a47, 0x0a48}, + {0x0a4b, 0x0a4d}, {0x0a51, 0x0a51}, {0x0a70, 0x0a71}, + {0x0a75, 0x0a75}, {0x0a81, 0x0a82}, {0x0abc, 0x0abc}, + {0x0ac1, 0x0ac5}, {0x0ac7, 0x0ac8}, {0x0acd, 0x0acd}, + {0x0ae2, 0x0ae3}, {0x0afa, 0x0aff}, {0x0b01, 0x0b01}, + {0x0b3c, 0x0b3c}, {0x0b3f, 0x0b3f}, {0x0b41, 0x0b44}, + {0x0b4d, 0x0b4d}, {0x0b56, 0x0b56}, {0x0b62, 0x0b63}, + {0x0b82, 0x0b82}, {0x0bc0, 0x0bc0}, {0x0bcd, 0x0bcd}, + {0x0c00, 0x0c00}, {0x0c3e, 0x0c40}, {0x0c46, 0x0c48}, + {0x0c4a, 0x0c4d}, {0x0c55, 0x0c56}, {0x0c62, 0x0c63}, + {0x0c81, 0x0c81}, {0x0cbc, 0x0cbc}, {0x0cbf, 0x0cbf}, + {0x0cc6, 0x0cc6}, {0x0ccc, 0x0ccd}, {0x0ce2, 0x0ce3}, + {0x0d00, 0x0d01}, {0x0d3b, 0x0d3c}, {0x0d41, 0x0d44}, + {0x0d4d, 0x0d4d}, {0x0d62, 0x0d63}, {0x0dca, 0x0dca}, + {0x0dd2, 0x0dd4}, {0x0dd6, 0x0dd6}, {0x0e31, 0x0e31}, + {0x0e34, 0x0e3a}, {0x0e47, 0x0e4e}, {0x0eb1, 0x0eb1}, + {0x0eb4, 0x0eb9}, {0x0ebb, 0x0ebc}, {0x0ec8, 0x0ecd}, + {0x0f18, 0x0f19}, {0x0f35, 0x0f35}, {0x0f37, 0x0f37}, + {0x0f39, 0x0f39}, {0x0f71, 0x0f7e}, {0x0f80, 0x0f84}, + {0x0f86, 0x0f87}, {0x0f8d, 0x0f97}, {0x0f99, 0x0fbc}, + {0x0fc6, 0x0fc6}, {0x102d, 0x1030}, {0x1032, 0x1037}, + {0x1039, 0x103a}, {0x103d, 0x103e}, {0x1058, 0x1059}, + {0x105e, 0x1060}, {0x1071, 0x1074}, {0x1082, 0x1082}, + {0x1085, 0x1086}, {0x108d, 0x108d}, {0x109d, 0x109d}, + {0x1160, 0x11ff}, {0x135d, 0x135f}, {0x1712, 0x1714}, + {0x1732, 0x1734}, {0x1752, 0x1753}, {0x1772, 0x1773}, + {0x17b4, 0x17b5}, {0x17b7, 0x17bd}, {0x17c6, 0x17c6}, + {0x17c9, 0x17d3}, {0x17dd, 0x17dd}, {0x180b, 0x180e}, + {0x1885, 0x1886}, {0x18a9, 0x18a9}, {0x1920, 0x1922}, + {0x1927, 0x1928}, {0x1932, 0x1932}, {0x1939, 0x193b}, + {0x1a17, 0x1a18}, {0x1a1b, 0x1a1b}, {0x1a56, 0x1a56}, + {0x1a58, 0x1a5e}, {0x1a60, 0x1a60}, {0x1a62, 0x1a62}, + {0x1a65, 0x1a6c}, {0x1a73, 0x1a7c}, {0x1a7f, 0x1a7f}, + {0x1ab0, 0x1abe}, {0x1b00, 0x1b03}, {0x1b34, 0x1b34}, + {0x1b36, 0x1b3a}, {0x1b3c, 0x1b3c}, {0x1b42, 0x1b42}, + {0x1b6b, 0x1b73}, {0x1b80, 0x1b81}, {0x1ba2, 0x1ba5}, + {0x1ba8, 0x1ba9}, {0x1bab, 0x1bad}, {0x1be6, 0x1be6}, + {0x1be8, 0x1be9}, {0x1bed, 0x1bed}, {0x1bef, 0x1bf1}, + {0x1c2c, 0x1c33}, {0x1c36, 0x1c37}, {0x1cd0, 0x1cd2}, + {0x1cd4, 0x1ce0}, {0x1ce2, 0x1ce8}, {0x1ced, 0x1ced}, + {0x1cf4, 0x1cf4}, {0x1cf8, 0x1cf9}, {0x1dc0, 0x1df9}, + {0x1dfb, 0x1dff}, {0x200b, 0x200f}, {0x202a, 0x202e}, + {0x2060, 0x2064}, {0x2066, 0x206f}, {0x20d0, 0x20f0}, + {0x2cef, 0x2cf1}, {0x2d7f, 0x2d7f}, {0x2de0, 0x2dff}, + {0x302a, 0x302d}, {0x3099, 0x309a}, {0xa66f, 0xa672}, + {0xa674, 0xa67d}, {0xa69e, 0xa69f}, {0xa6f0, 0xa6f1}, + {0xa802, 0xa802}, {0xa806, 0xa806}, {0xa80b, 0xa80b}, + {0xa825, 0xa826}, {0xa8c4, 0xa8c5}, {0xa8e0, 0xa8f1}, + {0xa926, 0xa92d}, {0xa947, 0xa951}, {0xa980, 0xa982}, + {0xa9b3, 0xa9b3}, {0xa9b6, 0xa9b9}, {0xa9bc, 0xa9bc}, + {0xa9e5, 0xa9e5}, {0xaa29, 0xaa2e}, {0xaa31, 0xaa32}, + {0xaa35, 0xaa36}, {0xaa43, 0xaa43}, {0xaa4c, 0xaa4c}, + {0xaa7c, 0xaa7c}, {0xaab0, 0xaab0}, {0xaab2, 0xaab4}, + {0xaab7, 0xaab8}, {0xaabe, 0xaabf}, {0xaac1, 0xaac1}, + {0xaaec, 0xaaed}, {0xaaf6, 0xaaf6}, {0xabe5, 0xabe5}, + {0xabe8, 0xabe8}, {0xabed, 0xabed}, {0xfb1e, 0xfb1e}, + {0xfe00, 0xfe0f}, {0xfe20, 0xfe2f}, {0xfeff, 0xfeff}, + {0xfff9, 0xfffb}, {0x101fd, 0x101fd}, {0x102e0, 0x102e0}, + {0x10376, 0x1037a}, {0x10a01, 0x10a03}, {0x10a05, 0x10a06}, + {0x10a0c, 0x10a0f}, {0x10a38, 0x10a3a}, {0x10a3f, 0x10a3f}, + {0x10ae5, 0x10ae6}, {0x11001, 0x11001}, {0x11038, 0x11046}, + {0x1107f, 0x11081}, {0x110b3, 0x110b6}, {0x110b9, 0x110ba}, + {0x11100, 0x11102}, {0x11127, 0x1112b}, {0x1112d, 0x11134}, + {0x11173, 0x11173}, {0x11180, 0x11181}, {0x111b6, 0x111be}, + {0x111ca, 0x111cc}, {0x1122f, 0x11231}, {0x11234, 0x11234}, + {0x11236, 0x11237}, {0x1123e, 0x1123e}, {0x112df, 0x112df}, + {0x112e3, 0x112ea}, {0x11300, 0x11301}, {0x1133c, 0x1133c}, + {0x11340, 0x11340}, {0x11366, 0x1136c}, {0x11370, 0x11374}, + {0x11438, 0x1143f}, {0x11442, 0x11444}, {0x11446, 0x11446}, + {0x114b3, 0x114b8}, {0x114ba, 0x114ba}, {0x114bf, 0x114c0}, + {0x114c2, 0x114c3}, {0x115b2, 0x115b5}, {0x115bc, 0x115bd}, + {0x115bf, 0x115c0}, {0x115dc, 0x115dd}, {0x11633, 0x1163a}, + {0x1163d, 0x1163d}, {0x1163f, 0x11640}, {0x116ab, 0x116ab}, + {0x116ad, 0x116ad}, {0x116b0, 0x116b5}, {0x116b7, 0x116b7}, + {0x1171d, 0x1171f}, {0x11722, 0x11725}, {0x11727, 0x1172b}, + {0x11a01, 0x11a06}, {0x11a09, 0x11a0a}, {0x11a33, 0x11a38}, + {0x11a3b, 0x11a3e}, {0x11a47, 0x11a47}, {0x11a51, 0x11a56}, + {0x11a59, 0x11a5b}, {0x11a8a, 0x11a96}, {0x11a98, 0x11a99}, + {0x11c30, 0x11c36}, {0x11c38, 0x11c3d}, {0x11c3f, 0x11c3f}, + {0x11c92, 0x11ca7}, {0x11caa, 0x11cb0}, {0x11cb2, 0x11cb3}, + {0x11cb5, 0x11cb6}, {0x11d31, 0x11d36}, {0x11d3a, 0x11d3a}, + {0x11d3c, 0x11d3d}, {0x11d3f, 0x11d45}, {0x11d47, 0x11d47}, + {0x16af0, 0x16af4}, {0x16b30, 0x16b36}, {0x16f8f, 0x16f92}, + {0x1bc9d, 0x1bc9e}, {0x1bca0, 0x1bca3}, {0x1d167, 0x1d169}, + {0x1d173, 0x1d182}, {0x1d185, 0x1d18b}, {0x1d1aa, 0x1d1ad}, + {0x1d242, 0x1d244}, {0x1da00, 0x1da36}, {0x1da3b, 0x1da6c}, + {0x1da75, 0x1da75}, {0x1da84, 0x1da84}, {0x1da9b, 0x1da9f}, + {0x1daa1, 0x1daaf}, {0x1e000, 0x1e006}, {0x1e008, 0x1e018}, + {0x1e01b, 0x1e021}, {0x1e023, 0x1e024}, {0x1e026, 0x1e02a}, + {0x1e8d0, 0x1e8d6}, {0x1e944, 0x1e94a}, {0xe0001, 0xe0001}, + {0xe0020, 0xe007f}, {0xe0100, 0xe01ef}, + }; + + /* test for 8-bit control characters */ + if ( ucs == 0 ) { + return 0; + } + if ( ( ucs < 32 ) || ( ( ucs >= 0x7f ) && ( ucs < 0xa0 ) ) ) { + return -1; + } + + /* binary search in table of non-spacing characters */ + if ( bisearch( ucs, combining, sizeof( combining ) / sizeof( struct interval ) - 1 ) ) { + return 0; + } + + /* if we arrive here, ucs is not a combining or C0/C1 control character */ + return ( mk_is_wide_char( ucs ) ? 2 : 1 ); +} + +} + diff --git a/third-party/replxx/src/windows.cxx b/third-party/replxx/src/windows.cxx new file mode 100644 index 0000000000..715292c0c5 --- /dev/null +++ b/third-party/replxx/src/windows.cxx @@ -0,0 +1,144 @@ +#ifdef _WIN32 + +#include + +#include "windows.hxx" +#include "conversion.hxx" +#include "terminal.hxx" + +using namespace std; + +namespace replxx { + +WinAttributes WIN_ATTR; + +template +T* HandleEsc(HANDLE out_, T* p, T* end) { + if (*p == '[') { + int code = 0; + + int thisBackground( WIN_ATTR._defaultBackground ); + for (++p; p < end; ++p) { + char32_t c = *p; + + if ('0' <= c && c <= '9') { + code = code * 10 + (c - '0'); + } else if (c == 'm' || c == ';') { + switch (code) { + case 0: + WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; + WIN_ATTR._consoleColor = WIN_ATTR._defaultColor | thisBackground; + break; + case 1: // BOLD + case 5: // BLINK + WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; + break; + case 22: + WIN_ATTR._consoleAttribute = WIN_ATTR._defaultAttribute; + break; + case 30: + case 90: + WIN_ATTR._consoleColor = thisBackground; + break; + case 31: + case 91: + WIN_ATTR._consoleColor = FOREGROUND_RED | thisBackground; + break; + case 32: + case 92: + WIN_ATTR._consoleColor = FOREGROUND_GREEN | thisBackground; + break; + case 33: + case 93: + WIN_ATTR._consoleColor = FOREGROUND_RED | FOREGROUND_GREEN | thisBackground; + break; + case 34: + case 94: + WIN_ATTR._consoleColor = FOREGROUND_BLUE | thisBackground; + break; + case 35: + case 95: + WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_RED | thisBackground; + break; + case 36: + case 96: + WIN_ATTR._consoleColor = FOREGROUND_BLUE | FOREGROUND_GREEN | thisBackground; + break; + case 37: + case 97: + WIN_ATTR._consoleColor = FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_BLUE | thisBackground; + break; + case 101: + thisBackground = BACKGROUND_RED; + break; + } + + if ( ( code >= 90 ) && ( code <= 97 ) ) { + WIN_ATTR._consoleAttribute = (WIN_ATTR._defaultAttribute ^ FOREGROUND_INTENSITY) & INTENSITY; + } + + code = 0; + } + + if (*p == 'm') { + ++p; + break; + } + } + } else { + ++p; + } + + SetConsoleTextAttribute( + out_, + WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor + ); + + return p; +} + +int win_write( HANDLE out_, bool autoEscape_, char const* str_, int size_ ) { + int count( 0 ); + if ( tty::out ) { + DWORD nWritten( 0 ); + if ( autoEscape_ ) { + WriteConsoleA( out_, str_, size_, &nWritten, nullptr ); + count = nWritten; + } else { + char const* s( str_ ); + char const* e( str_ + size_ ); + while ( str_ < e ) { + if ( *str_ == 27 ) { + if ( s < str_ ) { + int toWrite( static_cast( str_ - s ) ); + WriteConsoleA( out_, s, static_cast( toWrite ), &nWritten, nullptr ); + count += nWritten; + if ( nWritten != toWrite ) { + s = str_ = nullptr; + break; + } + } + s = HandleEsc( out_, str_ + 1, e ); + int escaped( static_cast( s - str_ ) ); + count += escaped; + str_ = s; + } else { + ++ str_; + } + } + + if ( s < str_ ) { + WriteConsoleA( out_, s, static_cast( str_ - s ), &nWritten, nullptr ); + count += nWritten; + } + } + } else { + count = _write( 1, str_, size_ ); + } + return ( count ); +} + +} + +#endif + diff --git a/third-party/replxx/src/windows.hxx b/third-party/replxx/src/windows.hxx new file mode 100644 index 0000000000..243f41cb7e --- /dev/null +++ b/third-party/replxx/src/windows.hxx @@ -0,0 +1,44 @@ +#ifndef REPLXX_WINDOWS_HXX_INCLUDED +#define REPLXX_WINDOWS_HXX_INCLUDED 1 + +#include +#include +#include + +namespace replxx { + +static const int FOREGROUND_WHITE = + FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; +static const int BACKGROUND_WHITE = + BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE; +static const int INTENSITY = FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + +class WinAttributes { + public: + WinAttributes() { + CONSOLE_SCREEN_BUFFER_INFO info; + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); + _defaultAttribute = info.wAttributes & INTENSITY; + _defaultColor = info.wAttributes & FOREGROUND_WHITE; + _defaultBackground = info.wAttributes & BACKGROUND_WHITE; + + _consoleAttribute = _defaultAttribute; + _consoleColor = _defaultColor | _defaultBackground; + } + + public: + int _defaultAttribute; + int _defaultColor; + int _defaultBackground; + + int _consoleAttribute; + int _consoleColor; +}; + +int win_write( HANDLE, bool, char const*, int ); + +extern WinAttributes WIN_ATTR; + +} + +#endif diff --git a/third-party/replxx/tests.py b/third-party/replxx/tests.py new file mode 100644 index 0000000000..4d8b4bb5e1 --- /dev/null +++ b/third-party/replxx/tests.py @@ -0,0 +1,2029 @@ +#! /usr/bin/python3 + +import pexpect +import unittest +import re +import os +import subprocess +import signal +import time + +keytab = { + "": "\033[1~", + "": "\033[1;2H", + "": "\033[1;5H", + "": "\033[4~", + "": "\033[1;2F", + "": "\033[1;5F", + "": "\033[2~", + "": "\033[2;2~", + "": "\033[2;5~", + "": "\033[3~", + "": "\033[3;2~", + "": "\033[3;5~", + "": "\033[5~", + "": "\033[5;5~", + "": "\033[6~", + "": "\033[6;5~", + "": "", + "": "\t", + "": "\r", + "": "\n", + "": "\033[D", + "": "\033[1;2D", + "": "\033OD", + "": "\033[C", + "": "\033[1;2C", + "": "\033OC", + "": "\033[A", + "": "\033[1;2A", + "": "\033OA", + "": "\033[B", + "": "\033[1;2B", + "": "\033OB", + "": "\033[1;5D", + "": "\033[1;5C", + "": "\033[1;5A", + "": "\033[1;5B", + "": "\033[1;3D", + "": "\033[1;3C", + "": "\033[1;3A", + "": "\033[1;3B", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": " ", + "": " ", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "", + "": "\033b", + "": "\033c", + "": "\033d", + "": "\033f", + "": "\033l", + "": "\033n", + "": "\033p", + "": "\033u", + "": "\033y", + "": "\033.", + "": "\033\177", + "": "\033OP", + "": "\033OQ", + "": "\033OR", + "": "\033OS", + "": "\033[15~", + "": "\033[17~", + "": "\033[18~", + "": "\033[19~", + "": "\033[20~", + "": "\033[21~", + "": "\033[23~", + "": "\033[24~", + "": "\033[1;2P", + "": "\033[1;2Q", + "": "\033[1;2R", + "": "\033[1;2S", + "": "\033[15;2~", + "": "\033[17;2~", + "": "\033[18;2~", + "": "\033[19;2~", + "": "\033[20;2~", + "": "\033[21;2~", + "": "\033[23;2~", + "": "\033[24;2~", + "": "\033[1;5P", + "": "\033[1;5Q", + "": "\033[1;5R", + "": "\033[1;5S", + "": "\033[15;5~", + "": "\033[17;5~", + "": "\033[18;5~", + "": "\033[19;5~", + "": "\033[20;5~", + "": "\033[21;5~", + "": "\033[23;5~", + "": "\033[24;5~", + "": "\033[Z", + "": "\033[200~", + "": "\033[201~" +} + +termseq = { + "\x1bc": "", + "\x1b[0m": "", + "\x1b[H": "", + "\x1b[2J": "", + "\x1b[J": "", + "\x1b[0;22;30m": "", + "\x1b[0;22;31m": "", + "\x1b[0;22;32m": "", + "\x1b[0;22;33m": "", + "\x1b[0;22;34m": "", + "\x1b[0;22;35m": "", + "\x1b[0;22;36m": "", + "\x1b[0;22;37m": "", + "\x1b[0;1;30m": "", + "\x1b[0;1;31m": "", + "\x1b[0;1;32m": "", + "\x1b[0;1;33m": "", + "\x1b[0;1;34m": "", + "\x1b[0;1;35m": "", + "\x1b[0;1;36m": "", + "\x1b[0;1;37m": "", + "\x1b[1;32m": "", + "\x1b[101;1;33m": "", + "\x07": "", + "\x1b[2~": "", + "\x1b[?2004h": "", + "\x1b[?2004l": "" +} +colRe = re.compile( "\\x1b\\[(\\d+)G" ) +upRe = re.compile( "\\x1b\\[(\\d+)A" ) +downRe = re.compile( "\\x1b\\[(\\d+)B" ) + +def sym_to_raw( str_ ): + for sym, seq in keytab.items(): + if isinstance( str_, Rapid ): + str_ = Rapid( str_.replace( sym, seq ) ) + else: + str_ = str_.replace( sym, seq ) + return str_ + +def seq_to_sym( str_ ): + for seq, sym in termseq.items(): + str_ = str_.replace( seq, sym ) + str_ = colRe.sub( "", str_ ) + str_ = upRe.sub( "", str_ ) + str_ = downRe.sub( "", str_ ) + return str_ + +_words_ = [ + "ada", "algol" + "bash", "basic", + "clojure", "cobol", "csharp", + "eiffel", "erlang", + "forth", "fortran", "fsharp", + "go", "groovy", + "haskell", "huginn", + "java", "javascript", "julia", + "kotlin", + "lisp", "lua", + "modula", + "nemerle", + "ocaml", + "perl", "php", "prolog", "python", + "rebol", "ruby", "rust", + "scala", "scheme", "sql", "swift", + "typescript" +] + +def skip( test_ ): + return "SKIP" in os.environ and os.environ["SKIP"].find( test_ ) >= 0 + +verbosity = None + +class Rapid( str ): pass + +def rapid( item ): + if isinstance( item, str ): + r = Rapid( item ) + return r + return list( map( Rapid, item ) ) + +class ReplxxTests( unittest.TestCase ): + _prompt_ = "\033\\[1;32mreplxx\033\\[0m> " + _cxxSample_ = "./build/debug/replxx-example-cxx-api" + _cSample_ = "./build/debug/replxx-example-c-api" + _end_ = "\r\nExiting Replxx\r\n" + def send_str( self_, str_, intraKeyDelay_ ): + if isinstance(str_, Rapid): + self_._replxx.send( str_ ) + return + for char in str_: + self_._replxx.send( char ) + time.sleep( intraKeyDelay_ ) + + def check_scenario( + self_, seq_, expected_, + history = "one\ntwo\nthree\n", + term = "xterm", + command = _cxxSample_, + dimensions = ( 25, 80 ), + prompt = _prompt_, + end = _prompt_ + _end_, + encoding = "utf-8", + pause = 0.25, + intraKeyDelay = 0.002 + ): + with open( "replxx_history.txt", "wb" ) as f: + f.write( history.encode( encoding ) ) + f.close() + os.environ["TERM"] = term + if isinstance( command, str ): + command = command.replace( "\n", "~" ) + if verbosity >= 2: + print( "\nTERM: {}, SIZE: {}, CMD: {}".format( term, dimensions, command ) ) + prompt = prompt.replace( "\n", "\r\n" ).replace( "\r\r", "\r" ) + end = end.replace( "\n", "\r\n" ).replace( "\r\r", "\r" ) + if isinstance( command, str ): + self_._replxx = pexpect.spawn( command, maxread = 1, encoding = encoding, dimensions = dimensions ) + else: + self_._replxx = pexpect.spawn( command[0], args = command[1:], maxread = 1, encoding = encoding, dimensions = dimensions ) + self_._replxx.expect( prompt ) + self_.maxDiff = None + if isinstance( seq_, str ): + if isinstance( seq_, Rapid ): + seqs = rapid( seq_.split( "" ) ) + else: + seqs = seq_.split( "" ) + for seq in seqs: + last = seq is seqs[-1] + if not last: + seq += "" + self_.send_str( sym_to_raw( seq ), intraKeyDelay ) + if not last: + time.sleep( pause ) + self_._replxx.kill( signal.SIGCONT ) + else: + for seq in seq_: + last = seq is seq_[-1] + self_.send_str( sym_to_raw( seq ), intraKeyDelay ) + if not last: + time.sleep( pause ) + self_._replxx.expect( end ) + if isinstance( expected_, str ): + self_.assertSequenceEqual( seq_to_sym( self_._replxx.before ), expected_ ) + else: + try: + self_.assertIn( seq_to_sym( self_._replxx.before ), expected_ ) + except: + self_.assertSequenceEqual( seq_to_sym( self_._replxx.before ), "" ) + def test_unicode( self_ ): + self_.check_scenario( + "", + "aóą Ϩ ð“¢€ 󃔀 " + "aóą Ϩ ð“¢€ 󃔀 \r\n" + "aóą Ϩ ð“¢€ 󃔀 \r\n", + "aóą Ϩ ð“¢€ 󃔀 \n" + ) + self_.check_scenario( + "aóą Ϩ ð“¢€ 󃔀 ", + "aaóaóąaóą " + "aóą Ϩaóą Ϩ " + "aóą Ϩ ð“¢€aóą Ϩ ð“¢€ " + "aóą Ϩ ð“¢€ " + "aóą Ϩ ð“¢€ 󃔀aóą Ϩ ð“¢€ 󃔀 " + "aóą Ϩ ð“¢€ 󃔀 " + "aóą Ϩ ð“¢€ 󃔀 \r\n" + "aóą Ϩ ð“¢€ 󃔀 \r\n" + ) + @unittest.skipIf( skip( "8bit_encoding" ), "broken platform" ) + def test_8bit_encoding( self_ ): + LC_CTYPE = "LC_CTYPE" + exists = LC_CTYPE in os.environ + lcCtype = None + if exists: + lcCtype = os.environ[LC_CTYPE] + os.environ[LC_CTYPE] = "pl_PL.ISO-8859-2" + self_.check_scenario( + "", + "text ~ó~text ~ó~\r\ntext ~ó~\r\n", + "text ~ó~\n", + encoding = "iso-8859-2" + ) + if exists: + os.environ[LC_CTYPE] = lcCtype + else: + del os.environ[LC_CTYPE] + def test_bad_term( self_ ): + self_.check_scenario( + "a line of text", + "a line of text\r\na line of text\r\n", + term = "dumb" + ) + def test_ctrl_c( self_ ): + self_.check_scenario( + "abc", + "aababcabc^C\r" + "\r\n" + ) + def test_ctrl_z( self_ ): + self_.check_scenario( + "", + "threereplxx> " + "threethree\r\n" + "three\r\n" + ) + self_.check_scenario( + "w", + "(reverse-i-search)`': " + "(reverse-i-search)`w': " + "two(reverse-i-search)`w': " + "tworeplxx> " + "two\r\n" + "two\r\n" + ) + def test_ctrl_l( self_ ): + self_.check_scenario( + "", + "\r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> replxx> " + "", + end = "\r\nExiting Replxx\r\n" + ) + self_.check_scenario( + "", + "\r\n" + "replxx> first " + "secondfirst " + "secondreplxx> " + "first secondfirst second\r\n" + "first second\r\n", + "first second\n" + ) + def test_backspace( self_ ): + self_.check_scenario( + "", + "one two threeone two " + "threeone two threeone two " + "threeone tw threeone t " + "threeone threeone " + "threeone three\r\n" + "one three\r\n", + "one two three\n" + ) + def test_delete( self_ ): + self_.check_scenario( + "", + "one two threeone two " + "threeone two threeone wo " + "threeone o threeone " + "threeone threeone three\r\n" + "one three\r\n", + "one two three\n" + ) + def test_home_key( self_ ): + self_.check_scenario( + "abcz", + "aababcabczabczabc\r\n" + "zabc\r\n" + ) + def test_end_key( self_ ): + self_.check_scenario( + "abczq", + "aababcabczabczabczabcqzabcq\r\n" + "zabcq\r\n" + ) + def test_left_key( self_ ): + self_.check_scenario( + "abcxy", + "aababcabcabxcabxcabxcaybxcaybxc\r\n" + "aybxc\r\n" + ) + def test_right_key( self_ ): + self_.check_scenario( + "abcxy", + "aababcabcabcaxbcaxbcaxbycaxbyc\r\n" + "axbyc\r\n" + ) + def test_prev_word_key( self_ ): + self_.check_scenario( + "abc def ghix", + "aababcabc " + "abc dabc " + "deabc defabc " + "def abc def " + "gabc def ghabc " + "def ghiabc def ghiabc def " + "ghiabc xdef ghiabc xdef " + "ghi\r\n" + "abc xdef ghi\r\n" + ) + def test_next_word_key( self_ ): + self_.check_scenario( + "abc def ghix", + "aababcabc " + "abc dabc " + "deabc defabc " + "def abc def " + "gabc def ghabc " + "def ghiabc def ghiabc def " + "ghiabc def ghiabc defx " + "ghiabc defx ghi\r\n" + "abc defx ghi\r\n" + ) + def test_hint_show( self_ ): + self_.check_scenario( + "co\r", + "cco\r\n" + " color_black\r\n" + " color_red\r\n" + " color_greenco\r\n" + "co\r\n" + ) + self_.check_scenario( + "", + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz " + "color_brightgreenzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz " + "color_brightgreen\r\n" + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz color_brightgreen\r\n", + "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz color_brightgreen\n", + dimensions = ( 64, 16 ) + ) + def test_hint_scroll_down( self_ ): + self_.check_scenario( + "co", + "cco\r\n" + " color_black\r\n" + " color_red\r\n" + " " + "color_greencolor_black\r\n" + " color_red\r\n" + " color_green\r\n" + " " + "color_browncolor_red\r\n" + " color_green\r\n" + " color_brown\r\n" + " " + "color_bluecolor_redcolor_red\r\n" + "color_red\r\n" + ) + def test_hint_scroll_up( self_ ): + self_.check_scenario( + "co", + "cco\r\n" + " color_black\r\n" + " color_red\r\n" + " " + "color_greencolor_normal\r\n" + " co\r\n" + " color_black\r\n" + " " + "color_redcolor_white\r\n" + " color_normal\r\n" + " co\r\n" + " " + "color_blackcolor_whitecolor_white\r\n" + "color_white\r\n" + ) + def test_history( self_ ): + self_.check_scenario( + "four", + "threetwoonetwothreeffofoufourfour\r\n" + "four\r\n" + ) + with open( "replxx_history.txt", "rb" ) as f: + data = f.read().decode() + self_.assertSequenceEqual( data[:-33], "### 0000-00-00 00:00:00.000\none\n### 0000-00-00 00:00:00.000\ntwo\n### 0000-00-00 00:00:00.000\nthree\n" ) + self_.assertSequenceEqual( data[-5:], "four\n" ) + def test_paren_matching( self_ ): + self_.check_scenario( + "ab(cd)ef", + "aabab(ab(cab(cdab(cd)ab(cd)eab(cd)efab(cd)efab(cd)efab(cd)efab(cd)efab(cd)efab(cd)efab(cd)efab(cd)ef\r\n" + "ab(cd)ef\r\n" + ) + def test_paren_not_matched( self_ ): + self_.check_scenario( + "a(b[c)d", + "aa(a(ba(b[a(b[ca(b[c)a(b[c)da(b[c)da(b[c)da(b[c)da(b[c)da(b[c)da(b[c)da(b[c)da(b[c)d\r\n" + "a(b[c)d\r\n" + ) + def test_tab_completion( self_ ): + self_.check_scenario( + "cobrib", + "cco\r\n" + " color_black\r\n" + " color_red\r\n" + " color_greencolor_\r\n" + " color_black\r\n" + " color_red\r\n" + " color_greencolor_\r\n" + "color_black " + "color_cyan " + "color_brightblue\r\n" + "color_red " + "color_lightgray " + "color_brightmagenta\r\n" + "color_green " + "color_gray " + "color_brightcyan\r\n" + "color_brown " + "color_brightred color_white\r\n" + "color_blue " + "color_brightgreen color_normal\r\n" + "color_magenta color_yellow\r\n" + "replxx> color_\r\n" + " color_black\r\n" + " color_red\r\n" + " color_greencolor_b\r\n" + " color_black\r\n" + " color_brown\r\n" + " color_bluecolor_br\r\n" + " color_brown\r\n" + " color_brightred\r\n" + " " + "color_brightgreencolor_bri\r\n" + " color_brightred\r\n" + " color_brightgreen\r\n" + " " + "color_brightbluecolor_bright\r\n" + " color_brightred\r\n" + " color_brightgreen\r\n" + " " + "color_brightbluecolor_brightbluecolor_brightbluecolor_brightblue\r\n" + "color_brightblue\r\n" + ) + self_.check_scenario( + "n", + "nn\r\nn\r\n", + dimensions = ( 4, 32 ), + command = [ ReplxxTests._cSample_, "q1", "e0" ] + ) + self_.check_scenario( + "n", + "\r\n" + "db\r\n" + "hello\r\n" + "hallo\r\n" + "--More--\r" + "\t\t\t\t\r" + "replxx> " + "\r\n", + dimensions = ( 4, 24 ), + command = ReplxxTests._cSample_ + " q1 e1" + ) + self_.check_scenario( + "co", + "abcd()" + "abcd()" + "cabcd()" + "coabcd()" + "color_abcd()" + "color_abcd()\r\n" + "color_abcd()\r\n", + "abcd()\n" + ) + def test_completion_shorter_result( self_ ): + self_.check_scenario( + "", + "\\piππ\r\n" + "Ï€\r\n", + "\\pi\n" + ) + def test_completion_pager( self_ ): + cmd = ReplxxTests._cSample_ + " q1 x" + ",".join( _words_ ) + self_.check_scenario( + "py", + "\r\n" + "ada groovy perl\r\n" + "algolbash haskell php\r\n" + "basic huginn prolog\r\n" + "clojure java python\r\n" + "cobol javascript rebol\r\n" + "csharp julia ruby\r\n" + "eiffel kotlin rust\r\n" + "erlang lisp scala\r\n" + "forth lua scheme\r\n" + "--More--\r" + "\t\t\t\t\r" + "fortran modula sql\r\n" + "fsharp nemerle swift\r\n" + "go ocaml typescript\r\n" + "replxx> " + "\r\n", + dimensions = ( 10, 40 ), + command = cmd + ) + self_.check_scenario( + "", + "\r\n" + "ada groovy perl\r\n" + "algolbash haskell php\r\n" + "basic huginn prolog\r\n" + "clojure java python\r\n" + "cobol javascript rebol\r\n" + "csharp julia ruby\r\n" + "eiffel kotlin rust\r\n" + "erlang lisp scala\r\n" + "forth lua scheme\r\n" + "--More--\r" + "\t\t\t\t\r" + "fortran modula sql\r\n" + "--More--\r" + "\t\t\t\t\r" + "fsharp nemerle swift\r\n" + "--More--\r" + "\t\t\t\t\r" + "go ocaml typescript\r\n" + "replxx> " + "\r\n", + dimensions = ( 10, 40 ), + command = cmd + ) + self_.check_scenario( + "", + "\r\n" + "ada kotlin\r\n" + "algolbash lisp\r\n" + "basic lua\r\n" + "clojure modula\r\n" + "cobol nemerle\r\n" + "csharp ocaml\r\n" + "eiffel perl\r\n" + "--More--^C\r\n" + "replxx> " + "\r\n", + dimensions = ( 8, 32 ), + command = cmd + ) + self_.check_scenario( + "q", + "\r\n" + "ada kotlin\r\n" + "algolbash lisp\r\n" + "basic lua\r\n" + "clojure modula\r\n" + "cobol nemerle\r\n" + "csharp ocaml\r\n" + "eiffel perl\r\n" + "--More--\r" + "\t\t\t\t\r" + "replxx> " + "\r\n", + dimensions = ( 8, 32 ), + command = cmd + ) + def test_double_tab_completion( self_ ): + cmd = ReplxxTests._cSample_ + " d1 q1 x" + ",".join( _words_ ) + self_.check_scenario( + "for", + "f\r\n" + " forth\r\n" + " fortran\r\n" + " fsharpfo\r\n" + " forth\r\n" + " fortranfort\r\n" + " forth\r\n" + " " + "fortranfortranfortranfortran\r\n" + "fortran\r\n", + command = cmd + ) + def test_beep_on_ambiguous_completion( self_ ): + cmd = ReplxxTests._cSample_ + " b1 d1 q1 x" + ",".join( _words_ ) + self_.check_scenario( + "for", + "f\r\n" + " forth\r\n" + " fortran\r\n" + " fsharpfo\r\n" + " forth\r\n" + " fortranfort\r\n" + " forth\r\n" + " " + "fortranfortranfortranfortran\r\n" + "fortran\r\n", + command = cmd + ) + def test_history_search_backward( self_ ): + self_.check_scenario( + "repl", + "(reverse-i-search)`': " + "(reverse-i-search)`r': echo repl " + "golf(reverse-i-search)`re': echo repl " + "golf(reverse-i-search)`rep': echo repl " + "golf(reverse-i-search)`repl': echo repl " + "golf(reverse-i-search)`repl': charlie repl " + "deltareplxx> charlie repl " + "delta\r\n" + "charlie repl delta\r\n", + "some command\n" + "alfa repl bravo\n" + "other request\n" + "charlie repl delta\n" + "misc input\n" + "echo repl golf\n" + "final thoughts\n" + ) + self_.check_scenario( + "fors", + "(reverse-i-search)`': " + "(reverse-i-search)`f': " + "swift(reverse-i-search)`fo': " + "fortran(reverse-i-search)`for': " + "fortran(reverse-i-search)`fo': " + "fortran(reverse-i-search)`f': " + "swift(reverse-i-search)`fs': " + "fsharpreplxx> " + "fsharp\r\n" + "fsharp\r\n", + "\n".join( _words_ ) + "\n" + ) + self_.check_scenario( + "mod", + "(reverse-i-search)`': " + "(reverse-i-search)`m': " + "scheme(reverse-i-search)`mo': " + "modula(reverse-i-search)`mod': " + "modulareplxx> " + "replxx> " + "\r\n", + "\n".join( _words_ ) + "\n" + ) + def test_history_search_forward( self_ ): + self_.check_scenario( + "repl", + "(i-search)`': (i-search)`r': " + "(i-search)`re': (i-search)`rep': " + "(i-search)`repl': " + "(i-search)`repl': " + "replxx> \r\n", + "charlie repl delta\r\n", + "some command\n" + "alfa repl bravo\n" + "other request\n" + "charlie repl delta\n" + "misc input\n" + "echo repl golf\n" + "final thoughts\n" + ) + self_.check_scenario( + "repl", + "final thoughts(i-search)`': final " + "thoughts(i-search)`r': echo repl " + "golf(i-search)`re': echo repl " + "golf(i-search)`rep': echo repl " + "golf(i-search)`repl': echo repl " + "golf(i-search)`repl': alfa repl " + "bravoreplxx> alfa repl bravofinal " + "thoughts\r\n" + "alfa repl bravo\r\n", + "final thoughts\n" + "echo repl golf\n" + "misc input\n" + "charlie repl delta\n" + "other request\n" + "alfa repl bravo\n" + "some command\n" + "charlie repl delta\r\n", + ) + self_.check_scenario( + "fors", + "(i-search)`': (i-search)`f': " + "(i-search)`fo': (i-search)`for': " + "(i-search)`fo': (i-search)`f': " + "(i-search)`fs': " + "replxx> \r\n", + "\n".join( _words_[::-1] ) + "\n" + ) + self_.check_scenario( + "fors", + "typescript(i-search)`': " + "typescript(i-search)`f': swift(i-search)`fo': " + "fortran(i-search)`for': fortran(i-search)`fo': " + "fortran(i-search)`f': swift(i-search)`fs': " + "fsharpreplxx> " + "fsharptypescript\r\n" + "fsharp\r\n", + "\n".join( _words_[::-1] ) + "\n" + ) + self_.check_scenario( + "mod", + "(i-search)`': (i-search)`m': " + "(i-search)`mo': (i-search)`mod': " + "replxx> " + "replxx> " + "\r\n", + "\n".join( _words_[::-1] ) + "\n" + ) + self_.check_scenario( + "mod", + "typescript(i-search)`': " + "typescript(i-search)`m': scheme(i-search)`mo': " + "modula(i-search)`mod': " + "modulareplxx> " + "typescriptreplxx> " + "typescripttypescript\r\n" + "typescript\r\n", + "\n".join( _words_[::-1] ) + "\n" + ) + def test_history_search_backward_position( self_ ): + self_.check_scenario( + "req", + "(reverse-i-search)`': " + "(reverse-i-search)`r': echo repl " + "golf(reverse-i-search)`re': echo repl " + "golf(reverse-i-search)`req': other " + "requestreplxx> other requestalfa " + "repl bravoalfa repl bravo\r\n" + "alfa repl bravo\r\n", + "some command\n" + "alfa repl bravo\n" + "other request\n" + "charlie repl delta\n" + "misc input\n" + "echo repl golf\n" + "final thoughts\n" + ) + def test_history_search_overlong_line( self_ ): + self_.check_scenario( + "lo", + "(reverse-i-search)`': " + "(reverse-i-search)`l': some very long line of text, much " + "longer then a witdth of a terminal, " + "seriously(reverse-i-search)`lo': some very long line of " + "text, much longer then a witdth of a terminal, " + "seriouslyreplxx> some very long line of " + "text, much longer then a witdth of a terminal, " + "seriously\r\n" + "some very long line of text, much longer then a witdth of a terminal, " + "seriously\r\n", + "fake\nsome very long line of text, much longer then a witdth of a terminal, seriously\nanother fake", + dimensions = ( 24, 64 ) + ) + def test_history_prefix_search_backward( self_ ): + self_.check_scenario( + "repl", + "rrerepreplrepl_echo " + "golfrepl_charlie " + "deltarepl_charlie delta\r\n" + "repl_charlie delta\r\n", + "some command\n" + "repl_alfa bravo\n" + "other request\n" + "repl_charlie delta\n" + "misc input\n" + "repl_echo golf\n" + "final thoughts\n" + ) + def test_history_prefix_search_backward_position( self_ ): + self_.check_scenario( + "repl", + "rrerepreplrepl_echo " + "golfmisc inputmisc " + "input\r\n" + "misc input\r\n", + "some command\n" + "repl_alfa bravo\n" + "other request\n" + "repl_charlie delta\n" + "misc input\n" + "repl_echo golf\n" + "final thoughts\n" + ) + def test_history_listing( self_ ): + self_.check_scenario( + "", + ".history.history\r\n" + " 0: some command\r\n" + " 1: repl_alfa bravo\r\n" + " 2: other request\r\n" + " 3: repl_charlie delta\r\n" + " 4: misc input\r\n" + " 5: repl_echo golf\r\n" + " 6: .history\r\n", + "some command\n" + "repl_alfa bravo\n" + "other request\n" + "repl_charlie delta\n" + "misc input\n" + "repl_echo golf\n" + ".history\n" + ) + self_.check_scenario( + "", + "/history/history\r\n" + " 0: some command\r\n" + " 1: repl_alfa bravo\r\n" + " 2: other request\r\n" + " 3: repl_charlie delta\r\n" + " 4: misc input\r\n" + " 5: repl_echo golf\r\n" + " 6: /history\r\n" + "/history\r\n", + "some command\n" + "repl_alfa bravo\n" + "other request\n" + "repl_charlie delta\n" + "misc input\n" + "repl_echo golf\n" + "/history\n", + command = ReplxxTests._cSample_ + " q1" + ) + def test_history_browse( self_ ): + self_.check_scenario( + "", + "twelve" + "eleven" + "one" + "two" + "one" + "two" + "" + "twelve" + "" + "twelve" + "twelve\r\n" + "twelve\r\n", + "one\n" + "two\n" + "three\n" + "four\n" + "five\n" + "six\n" + "seven\n" + "eight\n" + "nine\n" + "ten\n" + "eleven\n" + "twelve\n" + ) + def test_history_max_size( self_ ): + self_.check_scenario( + "a", + "threeaa\r\n" + "a\r\n" + "replxx> " + "fourfour\r\n" + "four\r\n", + "one\n" + "two\n" + "three\n" + "four\n" + "five\n", + command = ReplxxTests._cSample_ + " q1 s3" + ) + def test_history_unique( self_ ): + self_.check_scenario( + "abab", + "aa\r\n" + "a\r\n" + "replxx> bb\r\n" + "b\r\n" + "replxx> aa\r\n" + "a\r\n" + "replxx> bb\r\n" + "b\r\n" + "replxx> " + "bacc\r\n" + "c\r\n", + "a\nb\nc\n", + command = ReplxxTests._cSample_ + " u1 q1" + ) + self_.check_scenario( + "abab", + "aa\r\n" + "a\r\n" + "replxx> bb\r\n" + "b\r\n" + "replxx> aa\r\n" + "a\r\n" + "replxx> bb\r\n" + "b\r\n" + "replxx> " + "babb\r\n" + "b\r\n", + "a\nb\nc\n", + command = ReplxxTests._cSample_ + " u0 q1" + ) + self_.check_scenario( + rapid( "/history\n/unique\n/history\n" ), + "//history\r\n" + " 0: a\r\n" + " 1: b\r\n" + " 2: c\r\n" + " 3: b\r\n" + " 4: c\r\n" + " 5: d\r\n" + " 6: a\r\n" + " 7: c\r\n" + " 8: c\r\n" + " 9: a\r\n" + "/history\r\n" + "replxx> /unique\r\n" + "/unique\r\n" + "replxx> /history\r\n" + " 0: b\r\n" + " 1: d\r\n" + " 2: c\r\n" + " 3: a\r\n" + " 4: /history\r\n" + " 5: /unique\r\n" + "/history\r\n", + "a\nb\nc\nb\nc\nd\na\nc\nc\na\n", + command = ReplxxTests._cSample_ + " u0 q1" + ) + def test_history_recall_most_recent( self_ ): + self_.check_scenario( + "", + "aaaabbbbbbbb\r\n" + "bbbb\r\n" + "replxx> " + "cccccccc\r\n" + "cccc\r\n", + "aaaa\nbbbb\ncccc\ndddd\n" + ) + def test_history_abort_incremental_history_search_position( self_ ): + self_.check_scenario( + "cc", + "hhhhgggg(reverse-i-search)`': " + "gggg(reverse-i-search)`c': " + "cccc(reverse-i-search)`cc': " + "ccccreplxx> " + "ggggggggffffffff\r\n" + "ffff\r\n", + "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\n" + ) + def test_capitalize( self_ ): + self_.check_scenario( + "", + "abc defg ijklmn zzxqabc defg ijklmn " + "zzxqabc defg ijklmn zzxqaBc defg " + "ijklmn zzxqaBc Defg ijklmn zzxqaBc " + "Defg ijklmn zzxqaBc Defg ijklmn " + "zzxqaBc Defg iJklmn zzxqaBc Defg " + "iJklmn ZzxqaBc Defg iJklmn Zzxq\r\n" + "aBc Defg iJklmn Zzxq\r\n", + "abc defg ijklmn zzxq\n" + ) + def test_make_upper_case( self_ ): + self_.check_scenario( + "", + "abcdefg hijklmno pqrstuvwabcdefg " + "hijklmno pqrstuvwabcdefg hijklmno " + "pqrstuvwabcdefg hijklmno " + "pqrstuvwabcdefg hijklmno " + "pqrstuvwabcDEFG hijklmno " + "pqrstuvwabcDEFG HIJKLMNO " + "pqrstuvwabcDEFG HIJKLMNO " + "pqrstuvwabcDEFG HIJKLMNO " + "PQRSTUVWabcDEFG HIJKLMNO " + "PQRSTUVW\r\n" + "abcDEFG HIJKLMNO PQRSTUVW\r\n", + "abcdefg hijklmno pqrstuvw\n" + ) + def test_make_lower_case( self_ ): + self_.check_scenario( + "", + "ABCDEFG HIJKLMNO PQRSTUVWABCDEFG " + "HIJKLMNO PQRSTUVWABCDEFG HIJKLMNO " + "PQRSTUVWABCDEFG HIJKLMNO " + "PQRSTUVWABCDEFG HIJKLMNO " + "PQRSTUVWABCdefg HIJKLMNO " + "PQRSTUVWABCdefg hijklmno " + "PQRSTUVWABCdefg hijklmno " + "PQRSTUVWABCdefg hijklmno " + "pqrstuvwABCdefg hijklmno " + "pqrstuvw\r\n" + "ABCdefg hijklmno pqrstuvw\r\n", + "ABCDEFG HIJKLMNO PQRSTUVW\n" + ) + def test_transpose( self_ ): + self_.check_scenario( + "", + "abcd" + "abcd" + "abcd" + "bacd" + "bcad" + "bcda" + "bcad" + "bcda" + "bcda\r\n" + "bcda\r\n", + "abcd\n" + ) + def test_kill_to_beginning_of_line( self_ ): + self_.check_scenario( + "", + "+abc defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq+-ijklmn " + "zzxq+-ijklmn " + "zzxq+-ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc defg-\r\n" + "-ijklmn zzxq++abc defg-\r\n", + "+abc defg--ijklmn zzxq+\n" + ) + def test_kill_to_end_of_line( self_ ): + self_.check_scenario( + "", + "+abc defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc " + "defg-+abc " + "defg--ijklmn " + "zzxq++abc " + "defg--ijklmn " + "zzxq++abc defg-\r\n" + "-ijklmn zzxq++abc defg-\r\n", + "+abc defg--ijklmn zzxq+\n" + ) + def test_kill_next_word( self_ ): + self_.check_scenario( + "", + "alpha charlie bravo deltaalpha " + "charlie bravo deltaalpha charlie bravo " + "deltaalpha bravo deltaalpha bravo " + "deltaalpha bravo charlie deltaalpha " + "bravo charlie delta\r\n" + "alpha bravo charlie delta\r\n", + "alpha charlie bravo delta\n" + ) + def test_kill_prev_word_to_white_space( self_ ): + self_.check_scenario( + "", + "alpha charlie bravo deltaalpha " + "charlie bravo deltaalpha charlie " + "deltaalpha charlie deltaalpha bravo " + "charlie deltaalpha bravo charlie delta\r\n" + "alpha bravo charlie delta\r\n", + "alpha charlie bravo delta\n" + ) + def test_kill_prev_word( self_ ): + self_.check_scenario( + "", + "alpha.charlie " + "bravo.deltaalpha.charlie " + "bravo.deltaalpha.charlie " + "deltaalpha.charlie " + "deltaalpha.bravo.charlie " + "deltaalpha.bravo.charlie " + "delta\r\n" + "alpha.bravo.charlie delta\r\n", + "alpha.charlie bravo.delta\n" + ) + def test_kill_ring( self_ ): + self_.check_scenario( + " ", + "delta charlie bravo alphadelta " + "charlie bravo delta charlie " + "bravodelta charlie " + "delta " + "charliedelta " + "" + "delta" + "" + "delta" + "charlie" + "bravo" + "alpha" + "alpha " + "alpha " + "alphaalpha " + "deltaalpha " + "charliealpha " + "bravoalpha bravo " + "alpha bravo " + "bravoalpha bravo " + "alphaalpha bravo " + "deltaalpha bravo " + "charliealpha bravo charlie " + "alpha bravo charlie " + "charliealpha bravo charlie " + "bravoalpha bravo charlie " + "alphaalpha bravo charlie " + "deltaalpha bravo charlie delta\r\n" + "alpha bravo charlie delta\r\n", + "delta charlie bravo alpha\n" + ) + self_.check_scenario( + " ", + "charlie delta alpha bravocharlie " + "delta alpha charlie delta " + "charlie " + "deltacharlie deltaalpha " + "bravocharlie deltaalpha bravo charlie " + "deltaalpha bravo charlie delta\r\n" + "alpha bravo charlie delta\r\n", + "charlie delta alpha bravo\n" + ) + self_.check_scenario( + " ", + "charlie delta alpha bravocharlie " + "delta alpha bravo delta alpha bravo " + "alpha bravoalpha bravoalpha " + "bravoalpha bravo " + "alpha bravo charlie " + "deltaalpha bravo charlie delta\r\n" + "alpha bravo charlie delta\r\n", + "charlie delta alpha bravo\n" + ) + self_.check_scenario( + "" + "" + "", + "a b c d e f g h i j ka b c d e f g " + "h i j a b c d e f g h i " + "ja b c d e f g h i " + "a b c d e f g h " + "ia b c d e f g h " + "a b c d e f g " + "ha b c d e f g " + "a b c d e f ga " + "b c d e f a b c d e " + "fa b c d e a b " + "c d ea b c d a " + "b c da b c a b " + "ca b a " + "ba " + "" + "a" + "" + "a" + "b" + "c" + "d" + "e" + "f" + "g" + "h" + "i" + "j" + "a" + "a\r\n" + "a\r\n", + "a b c d e f g h i j k\n" + ) + def test_yank_last_arg( self_ ): + self_.check_scenario( + "0123", + "0" + "01" + "012" + "0123" + "0123" + "0123" + "01cat23" + "01trillion23" + "01twelve23" + "01twelve23\r\n" + "01twelve23\r\n", + "one two three\nten eleven twelve\nmillion trillion\ndog cat\n" + ) + self_.check_scenario( + " ", + "dog catmillion trillionten " + "eleven twelveten eleven twelve ten " + "eleven twelve catten eleven twelve " + "trillionten eleven twelve trillion\r\n" + "ten eleven twelve trillion\r\n", + "one two three\nten eleven twelve\nmillion trillion\ndog cat\n" + ) + def test_tab_completion_cutoff( self_ ): + self_.check_scenario( + "ny", + "\r\n" + "Display all 9 possibilities? (y or n)\r\n" + "replxx> " + "\r\n" + "Display all 9 possibilities? (y or n)\r\n" + "db hallo hansekogge quetzalcoatl power\r\n" + "hello hans seamann quit\r\n" + "replxx> " + "\r\n", + command = ReplxxTests._cSample_ + " q1 c3" + ) + self_.check_scenario( + "n", + "\r\n" + "Display all 9 possibilities? (y or n)\r\n" + "replxx> " + "\r\n", + command = ReplxxTests._cSample_ + " q1 c3" + ) + self_.check_scenario( + "", + "\r\n" + "Display all 9 possibilities? (y or n)^C\r\n" + "replxx> " + "\r\n", + command = ReplxxTests._cSample_ + " q1 c3" + ) + self_.check_scenario( + ["", ""], + "\r\n" + "Display all 9 possibilities? (y or n)^C\r\n" + "replxx> " + "\r\n", + command = ReplxxTests._cSample_ + " q1 c3 H200" + ) + def test_preload( self_ ): + self_.check_scenario( + "", + "Alice has a cat." + "Alice has a cat.\r\n" + "Alice has a cat.\r\n", + command = ReplxxTests._cSample_ + " q1 'iAlice has a cat.'" + ) + self_.check_scenario( + "", + "Cat eats mice. " + "Cat eats mice. " + "\r\n" + "Cat eats mice. " + "\r\n", + command = ReplxxTests._cSample_ + " q1 'iCat\teats\tmice.\r\n'" + ) + self_.check_scenario( + "", + "Cat eats mice. " + "Cat eats mice. " + "\r\n" + "Cat eats mice. " + "\r\n", + command = ReplxxTests._cSample_ + " q1 'iCat\teats\tmice.\r\n\r\n\n\n'" + ) + self_.check_scenario( + "", + "M Alice has a cat." + "M Alice has a cat.\r\n" + "M Alice has a cat.\r\n", + command = ReplxxTests._cSample_ + " q1 'iMAlice has a cat.'" + ) + self_.check_scenario( + "", + "M Alice has a cat." + "M Alice has a cat.\r\n" + "M Alice has a cat.\r\n", + command = ReplxxTests._cSample_ + " q1 'iM\t\t\t\tAlice has a cat.'" + ) + def test_prompt( self_ ): + prompt = "date: now\nrepl> " + self_.check_scenario( + "", + "threethree\r\n" + "three\r\n" + "date: now\r\n" + "repl> " + "threetwotwo\r\n" + "two\r\n", + command = ReplxxTests._cSample_ + " q1 'p{}'".format( prompt ), + prompt = prompt, + end = prompt + ReplxxTests._end_ + ) + prompt = "repl>\n" + self_.check_scenario( + "a", + "aa\r\na\r\n", + command = ReplxxTests._cSample_ + " q1 'p{}'".format( prompt ), + prompt = prompt, + end = prompt + ReplxxTests._end_ + ) + def test_long_line( self_ ): + self_.check_scenario( + "~~~~~~~~~~~~", + "ada clojure eiffel fortran groovy java kotlin modula perl python " + "rust sqlada clojure eiffel fortran groovy " + "java kotlin modula perl python rust sqlada " + "clojure eiffel fortran groovy java kotlin modula perl python rust " + "~sqlada clojure eiffel fortran groovy java " + "kotlin modula perl python rust ~sqlada clojure " + "eiffel fortran groovy java kotlin modula perl python ~rust " + "~sqlada clojure eiffel fortran groovy java " + "kotlin modula perl python ~rust ~sqlada clojure " + "eiffel fortran groovy java kotlin modula perl ~python ~rust " + "~sqlada clojure eiffel fortran groovy java " + "kotlin modula perl ~python ~rust ~sqlada clojure " + "eiffel fortran groovy java kotlin modula ~perl ~python ~rust " + "~sqlada clojure eiffel fortran groovy java " + "kotlin modula ~perl ~python ~rust ~sqlada " + "clojure eiffel fortran groovy java kotlin ~modula ~perl ~python ~rust " + "~sqlada clojure eiffel fortran groovy java " + "kotlin ~modula ~perl ~python ~rust ~sqlada " + "clojure eiffel fortran groovy java ~kotlin ~modula ~perl ~python ~rust " + "~sqlada clojure eiffel fortran groovy java " + "~kotlin ~modula ~perl ~python ~rust ~sqlada " + "clojure eiffel fortran groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sqlada clojure eiffel fortran groovy ~java " + "~kotlin ~modula ~perl ~python ~rust ~sqlada clojure " + "eiffel fortran ~groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sqlada clojure eiffel fortran ~groovy ~java ~kotlin " + "~modula ~perl ~python ~rust ~sqlada clojure eiffel " + "~fortran ~groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sqlada clojure eiffel ~fortran ~groovy ~java " + "~kotlin ~modula ~perl ~python ~rust ~sqlada clojure " + "~eiffel ~fortran ~groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sqlada clojure ~eiffel ~fortran ~groovy ~java " + "~kotlin ~modula ~perl ~python ~rust ~sqlada ~clojure " + "~eiffel ~fortran ~groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sqlada ~clojure ~eiffel ~fortran ~groovy ~java " + "~kotlin ~modula ~perl ~python ~rust ~sql~ada ~clojure " + "~eiffel ~fortran ~groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sql~ada ~clojure ~eiffel ~fortran ~groovy ~java " + "~kotlin ~modula ~perl ~python ~rust ~sql~ada ~clojure " + "~eiffel ~fortran ~groovy ~java ~kotlin ~modula ~perl ~python ~rust " + "~sql\r\n" + "~ada ~clojure ~eiffel ~fortran ~groovy ~java ~kotlin ~modula ~perl ~python " + "~rust ~sql\r\n", + " ".join( _words_[::3] ) + "\n", + dimensions = ( 10, 40 ) + ) + def test_colors( self_ ): + self_.check_scenario( + "", + "color_black color_red " + "color_green color_brown color_blue " + "color_magenta color_cyan " + "color_lightgray color_gray " + "color_brightred color_brightgreen " + "color_yellow color_brightblue " + "color_brightmagenta color_brightcyan " + "color_whitecolor_black " + "color_red color_green color_brown " + "color_blue color_magenta color_cyan " + "color_lightgray color_gray " + "color_brightred color_brightgreen " + "color_yellow color_brightblue " + "color_brightmagenta color_brightcyan " + "color_white\r\n" + "color_black color_red color_green color_brown color_blue color_magenta " + "color_cyan color_lightgray color_gray color_brightred color_brightgreen " + "color_yellow color_brightblue color_brightmagenta color_brightcyan " + "color_white\r\n", + "color_black color_red color_green color_brown color_blue color_magenta color_cyan color_lightgray" + " color_gray color_brightred color_brightgreen color_yellow color_brightblue color_brightmagenta color_brightcyan color_white\n" + ) + def test_word_break_characters( self_ ): + self_.check_scenario( + "xxxxxx", + "one_two three-four five_six " + "seven-eightone_two three-four five_six " + "seven-eightone_two three-four five_six " + "seven-xeightone_two three-four five_six " + "seven-xeightone_two three-four five_six " + "seven-xeightone_two three-four five_six " + "xseven-xeightone_two three-four five_six " + "xseven-xeightone_two three-four five_six " + "xseven-xeightone_two three-four xfive_six " + "xseven-xeightone_two three-four xfive_six " + "xseven-xeightone_two three-four xfive_six " + "xseven-xeightone_two three-xfour xfive_six " + "xseven-xeightone_two three-xfour xfive_six " + "xseven-xeightone_two three-xfour xfive_six " + "xseven-xeightone_two xthree-xfour xfive_six " + "xseven-xeightone_two xthree-xfour xfive_six " + "xseven-xeightone_two xthree-xfour xfive_six " + "xseven-xeightxone_two xthree-xfour xfive_six " + "xseven-xeightxone_two xthree-xfour xfive_six " + "xseven-xeight\r\n" + "xone_two xthree-xfour xfive_six xseven-xeight\r\n", + "one_two three-four five_six seven-eight\n", + command = ReplxxTests._cSample_ + " q1 'w \t-'" + ) + self_.check_scenario( + "xxxxxx", + "one_two three-four five_six " + "seven-eightone_two three-four five_six " + "seven-eightone_two three-four five_six " + "xseven-eightone_two three-four five_six " + "xseven-eightone_two three-four five_six " + "xseven-eightone_two three-four five_xsix " + "xseven-eightone_two three-four five_xsix " + "xseven-eightone_two three-four five_xsix " + "xseven-eightone_two three-four xfive_xsix " + "xseven-eightone_two three-four xfive_xsix " + "xseven-eightone_two three-four xfive_xsix " + "xseven-eightone_two xthree-four xfive_xsix " + "xseven-eightone_two xthree-four xfive_xsix " + "xseven-eightone_two xthree-four xfive_xsix " + "xseven-eightone_xtwo xthree-four xfive_xsix " + "xseven-eightone_xtwo xthree-four xfive_xsix " + "xseven-eightone_xtwo xthree-four xfive_xsix " + "xseven-eightxone_xtwo xthree-four xfive_xsix " + "xseven-eightxone_xtwo xthree-four xfive_xsix " + "xseven-eight\r\n" + "xone_xtwo xthree-four xfive_xsix xseven-eight\r\n", + "one_two three-four five_six seven-eight\n", + command = ReplxxTests._cSample_ + " q1 'w \t_'" + ) + def test_no_color( self_ ): + self_.check_scenario( + " X", + "color_black color_red color_green color_brown color_blue " + "color_magenta color_cyan color_lightgray color_gray color_brightred " + "color_brightgreen color_yellow color_brightblue color_brightmagenta " + "color_brightcyan color_whitecolor_black color_red " + "color_green color_brown color_blue color_magenta color_cyan color_lightgray " + "color_gray color_brightred color_brightgreen color_yellow color_brightblue " + "color_brightmagenta color_brightcyan color_white " + "color_black color_red color_green color_brown color_blue " + "color_magenta color_cyan color_lightgray color_gray color_brightred " + "color_brightgreen color_yellow color_brightblue color_brightmagenta " + "color_brightcyan color_white Xcolor_black color_red " + "color_green color_brown color_blue color_magenta color_cyan color_lightgray " + "color_gray color_brightred color_brightgreen color_yellow color_brightblue " + "color_brightmagenta color_brightcyan color_white X\r\n" + "color_black color_red color_green color_brown color_blue color_magenta " + "color_cyan color_lightgray color_gray color_brightred color_brightgreen " + "color_yellow color_brightblue color_brightmagenta color_brightcyan " + "color_white X\r\n", + "color_black color_red color_green color_brown color_blue color_magenta color_cyan color_lightgray" + " color_gray color_brightred color_brightgreen color_yellow color_brightblue color_brightmagenta color_brightcyan color_white\n", + command = ReplxxTests._cSample_ + " q1 m1" + ) + def test_backspace_long_line_on_small_term( self_ ): + self_.check_scenario( + "", + "\r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> " + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", + dimensions = ( 10, 40 ) + ) + self_.check_scenario( + "", + "\r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> a qu ite lo ng li ne of sh ort wo rds wi " + "ll te st cu rs or mo ve me nta qu ite lo " + "ng li ne of sh ort wo rds wi ll te st cu rs or mo ve me " + "na qu ite lo ng li ne of sh ort wo rds wi " + "ll te st cu rs or mo ve me a qu ite lo ng " + "li ne of sh ort wo rds wi ll te st cu rs or mo ve " + "mea qu ite lo ng li ne of sh ort wo rds " + "wi ll te st cu rs or mo ve ma qu ite lo " + "ng li ne of sh ort wo rds wi ll te st cu rs or mo ve " + "a qu ite lo ng li ne of sh ort wo rds wi " + "ll te st cu rs or mo vea qu ite lo ng li " + "ne of sh ort wo rds wi ll te st cu rs or mo ve\r\n" + "a qu ite lo ng li ne of sh ort wo rds wi ll te st cu rs or mo ve\r\n", + "a qu ite lo ng li ne of sh ort wo rds wi ll te st cu rs or mo ve me nt\n", + dimensions = ( 10, 40 ) + ) + def test_reverse_history_search_on_max_match( self_ ): + self_.check_scenario( + "", + "aaaaaaaaaaaaaaaaaaaaa(reverse-i-search)`': " + "aaaaaaaaaaaaaaaaaaaaareplxx> " + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n" + "aaaaaaaaaaaaaaaaaaaaa\r\n", + "aaaaaaaaaaaaaaaaaaaaa\n" + ) + def test_no_terminal( self_ ): + res = subprocess.run( [ ReplxxTests._cSample_, "q1" ], input = b"replxx FTW!\n", stdout = subprocess.PIPE, stderr = subprocess.PIPE ) + self_.assertSequenceEqual( res.stdout, b"starting...\nreplxx FTW!\n\nExiting Replxx\n" ) + def test_async_print( self_ ): + self_.check_scenario( + [ "a", "b", "c", "d", "e", "f" ], [ + "0\r\n" + "replxx> " + "aab1\r\n" + "replxx> " + "ababcabcd2\r\n" + "replxx> " + "abcdabcdeabcdefabcdef\r\n" + "abcdef\r\n", + "0\r\n" + "replxx> " + "aab1\r\n" + "replxx> " + "ababcabcd2\r\n" + "replxx> " + "abcdabcdeabcdefabcdef\r\n" + "abcdef\r\n", + ], + command = [ ReplxxTests._cxxSample_, "" ], + pause = 0.5 + ) + self_.check_scenario( + [ "", "a", "b", "c", "d", "e", "f" ], [ + "0\r\n" + "replxx> a very long line of " + "user input, wider then current terminal, the line is wrapped: " + "a very long line of user input, wider then current " + "terminal, the line is wrapped: a1\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, " + "the line is wrapped: aa very long line of user " + "input, wider then current terminal, the line is wrapped: " + "aba very long line of user input, wider then current " + "terminal, the line is wrapped: abc2\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, " + "the line is wrapped: abca very long line of user " + "input, wider then current terminal, the line is wrapped: " + "abcda very long line of user input, wider then " + "current terminal, the line is wrapped: abcde3\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, " + "the line is wrapped: abcdea very long line of user " + "input, wider then current terminal, the line is wrapped: " + "abcdefa very long line of user input, wider then " + "current terminal, the line is wrapped: abcdef\r\n" + "a very long line of user input, wider then current terminal, the line is " + "wrapped: abcdef\r\n", + "0\r\n" + "replxx> a very long line of user input, " + "wider then current terminal, the line is wrapped: a " + "very long line of user input, wider then current terminal, the line is " + "wrapped: a1\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, the " + "line is wrapped: aa very long line of user input, " + "wider then current terminal, the line is wrapped: " + "aba very long line of user input, wider then current " + "terminal, the line is wrapped: abc2\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, the " + "line is wrapped: abca very long line of user input, " + "wider then current terminal, the line is wrapped: " + "abcd3\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, the " + "line is wrapped: abcda very long line of user input, " + "wider then current terminal, the line is wrapped: " + "abcdea very long line of user input, wider then " + "current terminal, the line is wrapped: abcdefa very " + "long line of user input, wider then current terminal, the line is wrapped: " + "abcdef\r\n" + "a very long line of user input, wider then current terminal, the line is " + "wrapped: abcdef\r\n", + "0\r\n" + "replxx> a very long line of user input, wider then " + "current terminal, the line is wrapped: a very long " + "line of user input, wider then current terminal, the line is wrapped: " + "a1\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, the " + "line is wrapped: aa very long line of user input, " + "wider then current terminal, the line is wrapped: " + "aba very long line of user input, wider then current " + "terminal, the line is wrapped: abc2\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, the " + "line is wrapped: abca very long line of user input, " + "wider then current terminal, the line is wrapped: " + "abcd3\r\n" + "replxx> \r\n" + "\r\n" + "a very long line of user input, wider then current terminal, the " + "line is wrapped: abcda very long line of user input, " + "wider then current terminal, the line is wrapped: " + "abcdea very long line of user input, wider then " + "current terminal, the line is wrapped: abcdefa very " + "long line of user input, wider then current terminal, the line is wrapped: " + "abcdef\r\n" + "a very long line of user input, wider then current terminal, the line is " + "wrapped: abcdef\r\n" + ], + "a very long line of user input, wider then current terminal, the line is wrapped: \n", + command = [ ReplxxTests._cxxSample_, "" ], + dimensions = ( 10, 40 ), + pause = 0.5 + ) + def test_async_emulate_key_press( self_ ): + self_.check_scenario( + [ "a", "b", "c", "d", "e", "f" ], [ + "11a" + "1ab1ab" + "21ab2" + "c1ab2" + "cd1ab2cd3" + "1ab2cd3e" + "1ab2cd3ef" + "1ab2cd3ef\r\n" + "1ab2cd3ef\r\n", + "1a1ab" + "1ab21ab" + "2c1ab2cd" + "1ab2cd31ab" + "2cd3e1ab2cd" + "3ef1ab2cd3ef\r\n" + "1ab2cd3ef\r\n" + ], + command = [ ReplxxTests._cxxSample_, "123456" ], + pause = 0.5 + ) + def test_special_keys( self_ ): + self_.check_scenario( + "" + "" + "" + "" + "" + "", + "\r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + "replxx> \r\n" + ) + def test_overwrite_mode( self_ ): + self_.check_scenario( + "XYZ012345", + "abcdefghabcdefgh" + "abcdefghabcdefgh" + "abXcdefghabXYcdefgh" + "abXYZcdefghabXYZ0defgh" + "abXYZ01efghabXYZ012fgh" + "abXYZ0123fghabXYZ01234fgh" + "abXYZ012345fghabXYZ012345fgh\r\n" + "abXYZ012345fgh\r\n", + "abcdefgh\n" + ) + def test_verbatim_insert( self_ ): + self_.check_scenario( + ["", rapid( "" ), ""], + "^[[2~^[[2~\r\n" + "\r\n" + ) + def test_hint_delay( self_ ): + self_.check_scenario( + ["han", ""], + "hhahanhan\r\n" + " hans\r\n" + " hansekoggehan\r\n" + "han\r\n", + command = [ ReplxxTests._cSample_, "q1", "H200" ] + ) + def test_complete_next( self_ ): + self_.check_scenario( + "", + "color_\r\n" + " color_black\r\n" + " color_red\r\n" + " " + "color_greencolor_black" + "color_redcolor_blackcolor_\r\n" + + " color_black\r\n" + " color_red\r\n" + " " + "color_greencolor_normalcolor_normal\r\n" + "color_normal\r\n", + "color_\n" + ) + self_.check_scenario( + "l", + "l\r\n" + " lc_ctype\r\n" + " lc_time\r\n" + " lc_messageslc_\r\n" + " lc_ctype\r\n" + " lc_time\r\n" + " " + "lc_messageslc_ctypelc_timelc_ctypelc_\r\n" + " lc_ctype\r\n" + " lc_time\r\n" + " lc_messageslc_\r\n" + "lc_\r\n", + command = [ ReplxxTests._cSample_, "xlc_ctype,lc_time,lc_messages,zoom", "I1", "q1" ] + ) + self_.check_scenario( + "l", + "l\r\n" + " lc_ctype\r\n" + " lc_time\r\n" + " lc_messageslc_\r\n" + " lc_ctype\r\n" + " lc_time\r\n" + " " + "lc_messageslc_ctypelc_\r\n" + " lc_ctype\r\n" + " lc_time\r\n" + " " + "lc_messageslc_messageslc_messages\r\n" + "lc_messages\r\n", + command = [ ReplxxTests._cSample_, "xlc_ctype,lc_time,lc_messages,zoom", "I0", "q1" ] + ) + def test_disabled_handlers( self_ ): + self_.check_scenario( + "4", + "(+ 1 2)(+ 1 " + "2)(+ 1 " + ")(+ 1 " + "4)(+ 1 4)\r\n" + "thanks for the input: (+ 1 4)\r\n", + "(+ 1 2)\r\n", + command = [ ReplxxTests._cSample_, "N", "S" ] + ) + def test_state_manipulation( self_ ): + self_.check_scenario( + "~", + "replxxREPLXXREP~LXXREP~LXX\r\n" + "REP~LXX\r\n", + "replxx\n", + command = [ ReplxxTests._cSample_, "q1" ] + ) + def test_modify_callback( self_ ): + self_.check_scenario( + "*", + "abcd12abcd12" + "abcd12abcd12" + "ababcd12cd12" + "ababcd12cd12\r\n" + "ababcd12cd12\r\n", + "abcd12\n", + command = [ ReplxxTests._cSample_, "q1", "M1" ] + ) + def test_paste( self_ ): + self_.check_scenario( + rapid( "abcdef" ), + "aabcdef\r\nabcdef\r\n" + ) + def test_history_merge( self_ ): + with open( "replxx_history_alt.txt", "w" ) as f: + f.write( + "### 0000-00-00 00:00:00.001\n" + "one\n" + "### 0000-00-00 00:00:00.003\n" + "three\n" + "### 0000-00-00 00:00:00.005\n" + "other\n" + "### 0000-00-00 00:00:00.009\n" + "same\n" + "### 0000-00-00 00:00:00.017\n" + "seven\n" + ) + f.close() + self_.check_scenario( + "", + ".merge.merge\r\n", + "### 0000-00-00 00:00:00.002\n" + "two\n" + "### 0000-00-00 00:00:00.004\n" + "four\n" + "### 0000-00-00 00:00:00.006\n" + "same\n" + "### 0000-00-00 00:00:00.008\n" + "other\n" + "### 0000-00-00 00:00:00.018\n" + ".merge\n" + ) + with open( "replxx_history_alt.txt", "r" ) as f: + data = f.read() + expected = ( + "### 0000-00-00 00:00:00.001\n" + "one\n" + "### 0000-00-00 00:00:00.002\n" + "two\n" + "### 0000-00-00 00:00:00.003\n" + "three\n" + "### 0000-00-00 00:00:00.004\n" + "four\n" + "### 0000-00-00 00:00:00.008\n" + "other\n" + "### 0000-00-00 00:00:00.009\n" + "same\n" + "### 0000-00-00 00:00:00.017\n" + "seven\n" + "### " + ) + self_.assertSequenceEqual( data[:-31], expected ) + self_.assertSequenceEqual( data[-7:], ".merge\n" ) + def test_history_save( self_ ): + with open( "replxx_history_alt.txt", "w" ) as f: + f.write( + "### 0000-00-00 00:00:00.001\n" + "one\n" + "### 0000-00-00 00:00:00.003\n" + "three\n" + "### 3000-00-00 00:00:00.005\n" + "other\n" + "### 3000-00-00 00:00:00.009\n" + "same\n" + "### 3000-00-00 00:00:00.017\n" + "seven\n" + ) + f.close() + self_.check_scenario( + "zoom.save", + "zzozoozoomzoom\r\n" + "zoom\r\n" + "replxx> " + "..s.sa.sav.save.save\r\n" + "replxx> " + "zoomzoom\r\n" + "zoom\r\n" + ) + def test_bracketed_paste( self_ ): + self_.check_scenario( + "a0b1c2d3e4f", + "a" + "a0" + "a0b1c2d3e" + "a0b1c2d3e4" + "a0b1c2d3e4f" + "a0b1c2d3e4f\r\n" + "a0b1c2d3e4f\r\n", + command = [ ReplxxTests._cSample_, "q1" ] + ) + self_.check_scenario( + "a0b1c2d3e4f", + "a" + "a0" + "a0b1c2d3e" + "a0b1c2d3e4" + "a0b1c2d3e4f" + "a0b1c2d3e4f\r\n" + "a0b1c2d3e4f\r\n", + command = [ ReplxxTests._cSample_, "q1", "B" ] + ) + self_.check_scenario( + "a0/eb/dbx", + "aa0a0" + "a/eb0a/eb0\r\n" + "a/eb0\r\n" + "replxx> /db/db\r\n" + "/db\r\n" + "replxx> xx\r\n" + "x\r\n", + command = [ ReplxxTests._cSample_, "q1" ] + ) + +def parseArgs( self, func, argv ): + global verbosity + res = func( self, argv ) + verbosity = self.verbosity + return res + +if __name__ == "__main__": + pa = unittest.TestProgram.parseArgs + unittest.TestProgram.parseArgs = lambda self, argv: parseArgs( self, pa, argv ) + unittest.main() +