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
This commit is contained in:
Tyler Wilding
2021-03-07 20:41:21 -08:00
committed by GitHub
parent 9074a35b9b
commit 8bba3d7fd7
58 changed files with 12141 additions and 2569 deletions
+5
View File
@@ -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)
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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)
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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<EnvironmentObject>& env);
bool get_global_variable_by_name(const std::string& name, Object* dest);
+5 -9
View File
@@ -13,7 +13,7 @@
#include "common/util/FileUtil.h"
#include "third-party/fmt/core.h"
#include <filesystem>
#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
+3 -1
View File
@@ -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<std::string>& file_path);
std::string get_source_dir();
-38
View File
@@ -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<std::string>& ReplHistory::repl_get_history() {
return linenoise::GetHistory();
}
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <string>
#include <vector>
// 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<std::string>& repl_get_history();
}; // namespace ReplHistory
+138
View File
@@ -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<std::string, bool> 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");
}
+33
View File
@@ -0,0 +1,33 @@
#pragma once
#include <functional>
#include <string>
#include <vector>
#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<std::string, bool> get_current_repl_token(std::string const& context);
std::vector<std::string> examples{};
using cl = Replxx::Color;
std::vector<std::pair<std::string, cl>> regex_colors{};
};
+21 -12
View File
@@ -9,7 +9,8 @@
using namespace goos;
Compiler::Compiler() : m_debugger(&m_listener) {
Compiler::Compiler(std::unique_ptr<ReplWrapper> 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<GlobalEnv>();
@@ -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);
}
}
+14 -1
View File
@@ -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<ReplWrapper> 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<std::string> const& user_data);
Replxx::hints_t find_hints_by_prefix(std::string const& context,
int& contextLen,
Replxx::Color& color,
std::vector<std::string> const& user_data);
void repl_coloring(std::string const& str,
Replxx::colors_t& colors,
std::vector<std::pair<std::string, Replxx::Color>> 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<ReplWrapper> 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);
+1
View File
@@ -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},
+116 -50
View File
@@ -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 <regex>
#include <stack>
/*!
* 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<std::string> 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<std::string> 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<std::pair<std::string, Replxx::Color>> const& regex_color) {
using cl = Replxx::Color;
// TODO - a proper circular queue would be cleaner to use
std::deque<cl> paren_colors = {cl::GREEN, cl::CYAN, cl::MAGENTA};
std::stack<std::pair<char, cl>> expression_stack;
std::pair<int, std::string> 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<SymbolInfo>* 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();
}
}
+3 -21
View File
@@ -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> compiler = std::make_unique<Compiler>();
// 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>();
compiler = std::make_unique<Compiler>(std::make_unique<ReplWrapper>());
status = compiler->execute_repl();
if (status == ReplStatus::WANT_RELOAD) {
fmt::print("Reloading compiler...\n");
-2415
View File
File diff suppressed because it is too large Load Diff
+11
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = tab
indent_size = 2
+14
View File
@@ -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
+14
View File
@@ -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
+220
View File
@@ -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 $<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>>)
set(is-msvc $<CXX_COMPILER_ID:MSVC>)
set(is-gnu $<CXX_COMPILER_ID:GNU>)
set(compiler-id-clang-or-gnu $<OR:${is-clang},${is-gnu}>)
set(coverage-config $<AND:$<CONFIG:Coverage>,$<OR:${is-gnu},${is-clang}>>)
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
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>
PRIVATE
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src>
)
target_compile_definitions(
replxx
PUBLIC
$<$<NOT:$<BOOL:${BUILD_SHARED_LIBS}>>:REPLXX_STATIC>
$<$<BOOL:${BUILD_SHARED_LIBS}>:REPLXX_BUILDING_DLL>
PRIVATE
$<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS=1 /ignore:4503>
)
target_compile_options(
replxx
PRIVATE
$<$<AND:$<CONFIG:RelWithDebInfo>,${compiler-id-clang-or-gnu}>:-fomit-frame-pointer>
$<$<AND:$<CONFIG:MinSizeRel>,${compiler-id-clang-or-gnu}>:-Os>
$<$<AND:$<CONFIG:Debug>,$<CXX_COMPILER_ID:GNU>>:-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>
$<$<CXX_COMPILER_ID:GNU>:-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 $<$<CXX_COMPILER_ID:MSVC>:_CRT_SECURE_NO_WARNINGS=1>)
target_compile_definitions(replxx-example-c-api PRIVATE REPLXX_STATIC $<$<CXX_COMPILER_ID:MSVC>:_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")
+63
View File
@@ -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.
+119
View File
@@ -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.
+93
View File
@@ -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
+235
View File
@@ -0,0 +1,235 @@
#ifndef __sun
#define _POSIX_C_SOURCE 200809L
#else
#define __EXTENSIONS__ 1
#endif
#include <ctype.h>
#include <string.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#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 );
}
+453
View File
@@ -0,0 +1,453 @@
#include <regex>
#include <string>
#include <vector>
#include <cerrno>
#include <cctype>
#include <cstdlib>
#include <utility>
#include <iomanip>
#include <iostream>
#include <thread>
#include <chrono>
#include "replxx.hxx"
#include "util.h"
using Replxx = replxx::Replxx;
class Tick {
typedef std::vector<char32_t> 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<int>( _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<std::string> const& user_data);
Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector<std::string> const& user_data);
void hook_color(std::string const& str, Replxx::colors_t& colors, std::vector<std::pair<std::string, Replxx::Color>> const& user_data);
Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector<std::string> const& examples) {
Replxx::completions_t completions;
int utf8ContextLen( context_len( context.c_str() ) );
int prefixLen( static_cast<int>( 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<std::string> 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<int>( 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<std::pair<std::string, Replxx::Color>> 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<int>( prefix.length() ) );
int len( utf8str_codepoint_len( c.c_str(), static_cast<int>( 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<std::string> 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<std::pair<std::string, cl>> 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 ), "<F1>", _1 ) );
rx.bind_key( Replxx::KEY::F2, std::bind( &message, std::ref( rx ), "<F2>", _1 ) );
rx.bind_key( Replxx::KEY::F3, std::bind( &message, std::ref( rx ), "<F3>", _1 ) );
rx.bind_key( Replxx::KEY::F4, std::bind( &message, std::ref( rx ), "<F4>", _1 ) );
rx.bind_key( Replxx::KEY::F5, std::bind( &message, std::ref( rx ), "<F5>", _1 ) );
rx.bind_key( Replxx::KEY::F6, std::bind( &message, std::ref( rx ), "<F6>", _1 ) );
rx.bind_key( Replxx::KEY::F7, std::bind( &message, std::ref( rx ), "<F7>", _1 ) );
rx.bind_key( Replxx::KEY::F8, std::bind( &message, std::ref( rx ), "<F8>", _1 ) );
rx.bind_key( Replxx::KEY::F9, std::bind( &message, std::ref( rx ), "<F9>", _1 ) );
rx.bind_key( Replxx::KEY::F10, std::bind( &message, std::ref( rx ), "<F10>", _1 ) );
rx.bind_key( Replxx::KEY::F11, std::bind( &message, std::ref( rx ), "<F11>", _1 ) );
rx.bind_key( Replxx::KEY::F12, std::bind( &message, std::ref( rx ), "<F12>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "<S-F1>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "<S-F2>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "<S-F3>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "<S-F4>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "<S-F5>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "<S-F6>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "<S-F7>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "<S-F8>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "<S-F9>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "<S-F10>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "<S-F11>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "<S-F12>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "<C-F1>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "<C-F2>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "<C-F3>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "<C-F4>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "<C-F5>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "<C-F6>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "<C-F7>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "<C-F8>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "<C-F9>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "<C-F10>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "<C-F11>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "<C-F12>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::TAB ), std::bind( &message, std::ref( rx ), "<S-Tab>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "<C-Home>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "<S-Home>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "<C-End>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "<S-End>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_UP ), std::bind( &message, std::ref( rx ), "<C-PgUp>", _1 ) );
rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_DOWN ), std::bind( &message, std::ref( rx ), "<C-PgDn>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::LEFT ), std::bind( &message, std::ref( rx ), "<S-Left>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::RIGHT ), std::bind( &message, std::ref( rx ), "<S-Right>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::UP ), std::bind( &message, std::ref( rx ), "<S-Up>", _1 ) );
rx.bind_key( Replxx::KEY::shift( Replxx::KEY::DOWN ), std::bind( &message, std::ref( rx ), "<S-Down>", _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 <str>\n\tset the repl prompt to <str>\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;
}
+34
View File
@@ -0,0 +1,34 @@
#include <string.h>
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 );
}
+13
View File
@@ -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
+22
View File
@@ -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
+567
View File
@@ -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 <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.
*/
#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 */
+607
View File
@@ -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 <memory>
#include <vector>
#include <string>
#include <functional>
/*
* 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<Color> 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<Completion> 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<HistoryScanImpl, void (*)( HistoryScanImpl* )> 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<std::string> 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<void ( std::string& line, int& cursorPosition )> 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<completions_t ( std::string const& input, int& contextLen )> 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<void ( std::string const& input, colors_t& colors )> 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<hints_t ( std::string const& input, int& contextLen, Color& color )> 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<ACTION_RESULT ( char32_t code )> 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<ReplxxImpl, void (*)( ReplxxImpl* )> 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 */
+80
View File
@@ -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 "$_"
}
+3
View File
@@ -0,0 +1,3 @@
@PACKAGE_INIT@
include("${CMAKE_CURRENT_LIST_DIR}/replxx-targets.cmake")
+271
View File
@@ -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 <stdio.h>
#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.
--------------------------------------------------------------------- */
+139
View File
@@ -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: <F4 90 80 80>, <C0 80>,
or <A0> 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 <stdint.h>
#include <string>
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 */
/* --------------------------------------------------------------------- */
+108
View File
@@ -0,0 +1,108 @@
#include <algorithm>
#include <string>
#include <cstring>
#include <cctype>
#include <locale.h>
#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<int(*)(int)>( &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<const UTF8*>(src);
const UTF8* sourceEnd = sourceStart + strlen(src);
UTF32* targetStart = reinterpret_cast<UTF32*>(dst);
UTF32* targetEnd = targetStart + dstSize;
res = ConvertUTF8toUTF32(
&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion);
if (res == conversionOK) {
dstCount = static_cast<int>( targetStart - reinterpret_cast<UTF32*>( 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<const char*>(src)
);
}
int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) {
int resCount( 0 );
if ( ! locale::is8BitEncoding ) {
const UTF32* sourceStart = reinterpret_cast<const UTF32*>(src);
const UTF32* sourceEnd = sourceStart + srcSize;
UTF8* targetStart = reinterpret_cast<UTF8*>(dst);
UTF8* targetEnd = targetStart + dstSize;
ConversionResult res = ConvertUTF32toUTF8(
&sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion
);
if ( res == conversionOK ) {
resCount = static_cast<int>( targetStart - reinterpret_cast<UTF8*>( dst ) );
if ( resCount < dstSize ) {
*targetStart = 0;
}
}
} else {
int i( 0 );
for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) {
dst[i] = static_cast<char>( src[i] );
}
resCount = i;
if ( i < dstSize ) {
dst[i] = 0;
}
}
return ( resCount );
}
}
+30
View File
@@ -0,0 +1,30 @@
#ifndef REPLXX_CONVERSION_HXX_INCLUDED
#define REPLXX_CONVERSION_HXX_INCLUDED 1
#include "ConvertUTF.h"
#ifdef __has_include
#if __has_include( <version> )
#include <version>
#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
+890
View File
@@ -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<unsigned char>(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) <more stuff> 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 ; <more stuff> 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 ; <more stuff> 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 <more stuff> escape sequences
//
static CharacterDispatchRoutine escLeftBracket1Routines[] = {
homeKeyRoutine, escLeftBracket1SemicolonRoutine,
escLeftBracket15Routine,
escLeftBracket17Routine,
escLeftBracket18Routine,
escLeftBracket19Routine,
escFailureRoutine
};
static CharacterDispatch escLeftBracket1Dispatch = {
6, "~;5789", escLeftBracket1Routines
};
// Handle ESC [ 2 <more stuff> 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 <more stuff> escape sequences
//
static CharacterDispatchRoutine escLeftBracket2Routines[] = {
insertKeyRoutine,
escLeftBracket20Routine,
escLeftBracket21Routine,
escLeftBracket23Routine,
escLeftBracket24Routine,
escFailureRoutine
};
static CharacterDispatch escLeftBracket2Dispatch = {
5, "~0134", escLeftBracket2Routines
};
// Handle ESC [ 3 <more stuff> escape sequences
//
static CharacterDispatchRoutine escLeftBracket3Routines[] = {
deleteKeyRoutine, escFailureRoutine
};
static CharacterDispatch escLeftBracket3Dispatch = {
1, "~", escLeftBracket3Routines
};
// Handle ESC [ 4 <more stuff> escape sequences
//
static CharacterDispatchRoutine escLeftBracket4Routines[] = {
endKeyRoutine, escFailureRoutine
};
static CharacterDispatch escLeftBracket4Dispatch = {
1, "~", escLeftBracket4Routines
};
// Handle ESC [ 5 <more stuff> 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 <more stuff> 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 <more stuff> escape sequences
//
static CharacterDispatchRoutine escLeftBracket7Routines[] = {
homeKeyRoutine, escFailureRoutine
};
static CharacterDispatch escLeftBracket7Dispatch = {
1, "~", escLeftBracket7Routines
};
// Handle ESC [ 8 <more stuff> escape sequences
//
static CharacterDispatchRoutine escLeftBracket8Routines[] = {
endKeyRoutine, escFailureRoutine
};
static CharacterDispatch escLeftBracket8Dispatch = {
1, "~", escLeftBracket8Routines
};
// Handle ESC [ <digit> 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 [ <more stuff> 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 <char> 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 */
+37
View File
@@ -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
+402
View File
@@ -0,0 +1,402 @@
#include <algorithm>
#include <memory>
#include <fstream>
#include <cstring>
#ifndef _WIN32
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#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<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
}
~FileLock( void ) {
static_cast<void>( ::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<Entry> 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_t::iterator, bool> 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();
}
}
+141
View File
@@ -0,0 +1,141 @@
#ifndef REPLXX_HISTORY_HXX_INCLUDED
#define REPLXX_HISTORY_HXX_INCLUDED 1
#include <list>
#include <unordered_map>
#include "unicodestring.hxx"
#include "utf8string.hxx"
#include "conversion.hxx"
#include "util.hxx"
namespace std {
template<>
struct hash<replxx::UnicodeString> {
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<Entry> entries_t;
typedef std::unordered_map<UnicodeString, entries_t::const_iterator> 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<int>( _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
+78
View File
@@ -0,0 +1,78 @@
#ifndef REPLXX_KILLRING_HXX_INCLUDED
#define REPLXX_KILLRING_HXX_INCLUDED 1
#include <vector>
#include "unicodestring.hxx"
namespace replxx {
class KillRing {
static const int capacity = 10;
int size;
int index;
char indexToSlot[10];
std::vector<UnicodeString> 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<int>(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
+154
View File
@@ -0,0 +1,154 @@
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#include <io.h>
#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 <unistd.h>
#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<int>(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<int>(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<int>(promptStartLength + _searchText.length() +
endSearchBasePrompt.length());
_byteCount = _characterCount;
_text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt );
}
}
+47
View File
@@ -0,0 +1,47 @@
#ifndef REPLXX_PROMPT_HXX_INCLUDED
#define REPLXX_PROMPT_HXX_INCLUDED 1
#include <cstdlib>
#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
+648
View File
@@ -0,0 +1,648 @@
/*
* 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.
*
* 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+<char> 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 <algorithm>
#include <cstdarg>
#ifdef _WIN32
#include <io.h>
#define STDIN_FILENO 0
#else /* _WIN32 */
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#endif /* _WIN32 */
#include "replxx.h"
#include "replxx.hxx"
#include "replxx_impl.hxx"
#include "history.hxx"
static_assert(
static_cast<int>( replxx::Replxx::ACTION::SEND_EOF ) == static_cast<int>( REPLXX_ACTION_SEND_EOF ),
"C and C++ `ACTION` APIs are missaligned!"
);
static_assert(
static_cast<int>( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast<int>( 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<int>( vsnprintf( nullptr, 0, format_, ap ) );
va_end( ap );
va_start( ap, format_ );
unique_ptr<char[]> buf( new char[size + 1] );
vsnprintf( buf.get(), static_cast<size_t>( 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<replxx_data_t>( new replxx::Replxx::ReplxxImpl( nullptr, nullptr, nullptr ) ) );
}
void replxx_end( ::Replxx* replxx_ ) {
delete reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ );
}
void replxx_clear_screen( ::Replxx* replxx_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->clear_screen( 0 );
}
void replxx_emulate_key_press( ::Replxx* replxx_, int unsigned keyPress_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->emulate_key_press( keyPress_ );
}
ReplxxActionResult replxx_invoke( ::Replxx* replxx_, ReplxxAction action_, int unsigned keyPress_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
return ( static_cast<ReplxxActionResult>( replxx->invoke( static_cast<replxx::Replxx::ACTION>( action_ ), keyPress_ ) ) );
}
replxx::Replxx::ACTION_RESULT key_press_handler_forwarder( key_press_handler_t handler_, char32_t code_, void* userData_ ) {
return ( static_cast<replxx::Replxx::ACTION_RESULT>( 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::ReplxxImpl*>( 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::Replxx::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::Replxx::ReplxxImpl*>( replxx_ ) );
return ( replxx->input( prompt ) );
}
int replxx_print( ::Replxx* replxx_, char const* format_, ... ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
::std::va_list ap;
va_start( ap, format_ );
int size = static_cast<int>( vsnprintf( nullptr, 0, format_, ap ) );
va_end( ap );
va_start( ap, format_ );
unique_ptr<char[]> buf( new char[size + 1] );
vsnprintf( buf.get(), static_cast<size_t>( 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::Replxx::ReplxxImpl*>( replxx_ ) );
try {
replxx->print( str, length );
} catch ( ... ) {
return ( -1 );
}
return static_cast<int>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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<ReplxxColor> colorsTmp( colors.size() );
std::transform(
colors.begin(),
colors.end(),
colorsTmp.begin(),
[]( replxx::Replxx::Color c ) {
return ( static_cast<ReplxxColor>( c ) );
}
);
fn( input.c_str(), colorsTmp.data(), static_cast<int>( colors.size() ), userData );
std::transform(
colorsTmp.begin(),
colorsTmp.end(),
colors.begin(),
[]( ReplxxColor c ) {
return ( static_cast<replxx::Replxx::Color>( c ) );
}
);
}
void replxx_set_highlighter_callback( ::Replxx* replxx_, replxx_highlighter_callback_t* fn, void* userData ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( 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<ReplxxColor>( 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::ReplxxImpl*>( 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<replxx::Replxx::Color>( color ) );
}
void replxx_history_add( ::Replxx* replxx_, const char* line ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->history_add( line );
}
void replxx_set_max_history_size( ::Replxx* replxx_, int len ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( 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::ReplxxImpl*>( replxx_ ) );
replxx->set_max_hint_rows( count );
}
void replxx_set_hint_delay( ::Replxx* replxx_, int milliseconds ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->set_hint_delay( milliseconds );
}
void replxx_set_completion_count_cutoff( ::Replxx* replxx_, int count ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( 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::ReplxxImpl*>( replxx_ ) );
replxx->set_unique_history( val ? true : false );
}
void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->enable_bracketed_paste();
}
void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->disable_bracketed_paste();
}
ReplxxHistoryScan* replxx_history_scan_start( ::Replxx* replxx_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
return ( reinterpret_cast<ReplxxHistoryScan*>( replxx->history_scan().release() ) );
}
void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) {
delete reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ );
}
int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) {
replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( 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::Replxx::ReplxxImpl*>( 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::Replxx::ReplxxImpl*>( 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::Replxx::ReplxxImpl*>( replxx_ ) );
return ( replxx->history_load( filename ) ? 0 : -1 );
}
void replxx_history_clear( ::Replxx* replxx_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
replxx->history_clear();
}
int replxx_history_size( ::Replxx* replxx_ ) {
replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( 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::Replxx::ReplxxImpl*>( replxx_ ) );
return ( replxx->install_window_change_handler() );
}
File diff suppressed because it is too large Load Diff
+270
View File
@@ -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 <vector>
#include <deque>
#include <memory>
#include <string>
#include <unordered_map>
#include <thread>
#include <mutex>
#include <chrono>
#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<Completion> completions_t;
typedef std::vector<UnicodeString> hints_t;
typedef std::unique_ptr<char[]> utf8_buffer_t;
typedef std::unique_ptr<char32_t[]> input_buffer_t;
typedef std::vector<char> char_widths_t;
typedef std::vector<char32_t> display_t;
typedef std::deque<char32_t> key_presses_t;
typedef std::deque<std::string> messages_t;
enum class HINT_ACTION {
REGENERATE,
REPAINT,
TRIM,
SKIP
};
typedef std::unordered_map<std::string, Replxx::key_press_handler_t> named_actions_t;
typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t );
typedef std::unordered_map<int, Replxx::key_press_handler_t> 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
+742
View File
@@ -0,0 +1,742 @@
#include <memory>
#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <array>
#include <stdexcept>
#ifdef _WIN32
#include <conio.h>
#include <windows.h>
#include <io.h>
#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 <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <fcntl.h>
#include <signal.h>
#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<void>( ::pipe( _interrupt ) == 0 );
#endif
}
Terminal::~Terminal( void ) {
if ( _rawMode ) {
disable_raw_mode();
}
#ifdef _WIN32
CloseHandle( _interrupt );
#else
static_cast<void>( ::close( _interrupt[0] ) == 0 );
static_cast<void>( ::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<char32_t>(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<char*>("space");
} else if (keyCopy == 27) {
friendlyTextPtr = const_cast<char*>("ESC");
} else if (keyCopy == 0) {
friendlyTextPtr = const_cast<char*>("NUL");
} else if (keyCopy == 127) {
friendlyTextPtr = const_cast<char*>("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<HANDLE,2> handles = { _consoleIn, _interrupt };
while ( true ) {
DWORD event( WaitForMultipleObjects( static_cast<DWORD>( 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<suseconds_t>( ( 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<void>( 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<void>( 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<void>( write(1, clearCode, sizeof ( clearCode ) - 1) >= 0 );
} else {
char const clearCode[] = "\033[J";
static_cast<void>( 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
}
+94
View File
@@ -0,0 +1,94 @@
#ifndef REPLXX_IO_HXX_INCLUDED
#define REPLXX_IO_HXX_INCLUDED 1
#include <deque>
#ifdef _WIN32
#include <vector>
#include <windows.h>
#else
#include <termios.h>
#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<EVENT_TYPE> events_t;
events_t _events;
std::vector<char> _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
+191
View File
@@ -0,0 +1,191 @@
#ifndef REPLXX_UNICODESTRING_HXX_INCLUDED
#define REPLXX_UNICODESTRING_HXX_INCLUDED
#include <vector>
#include <cstring>
#include "conversion.hxx"
namespace replxx {
class UnicodeString {
public:
typedef std::vector<char32_t> 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<const char*>( 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<int>( str_.length() ) );
int len( 0 );
copyString8to32( _data.data(), static_cast<int>( str_.length() ), len, str_.c_str() );
_data.resize( len );
return *this;
}
UnicodeString& assign( char const* str_ ) {
int byteCount( static_cast<int>( 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<int>( _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<int>( 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
+94
View File
@@ -0,0 +1,94 @@
#ifndef REPLXX_UTF8STRING_HXX_INCLUDED
#define REPLXX_UTF8STRING_HXX_INCLUDED
#include <memory>
#include "unicodestring.hxx"
namespace replxx {
class Utf8String {
private:
typedef std::unique_ptr<char[]> 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<int>( str_.length() ) );
strncpy( _data.get(), str_.c_str(), str_.length() );
_len = static_cast<int>( 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
+170
View File
@@ -0,0 +1,170 @@
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <ctime>
#include <wctype.h>
#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::milliseconds>( 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<int>( ms.count() % 1000 ) );
return ( str );
}
}
+26
View File
@@ -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
+296
View File
@@ -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 <wchar.h>
#include <string>
#include <memory>
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 );
}
}
+144
View File
@@ -0,0 +1,144 @@
#ifdef _WIN32
#include <iostream>
#include "windows.hxx"
#include "conversion.hxx"
#include "terminal.hxx"
using namespace std;
namespace replxx {
WinAttributes WIN_ATTR;
template<typename T>
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<int>( str_ - s ) );
WriteConsoleA( out_, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr );
count += nWritten;
if ( nWritten != toWrite ) {
s = str_ = nullptr;
break;
}
}
s = HandleEsc( out_, str_ + 1, e );
int escaped( static_cast<int>( s - str_ ) );
count += escaped;
str_ = s;
} else {
++ str_;
}
}
if ( s < str_ ) {
WriteConsoleA( out_, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr );
count += nWritten;
}
}
} else {
count = _write( 1, str_, size_ );
}
return ( count );
}
}
#endif
+44
View File
@@ -0,0 +1,44 @@
#ifndef REPLXX_WINDOWS_HXX_INCLUDED
#define REPLXX_WINDOWS_HXX_INCLUDED 1
#include <conio.h>
#include <windows.h>
#include <io.h>
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
+2029
View File
File diff suppressed because it is too large Load Diff