mirror of
https://github.com/open-goal/jak-project
synced 2026-08-10 11:14:50 -04:00
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:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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
@@ -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");
|
||||
|
||||
Vendored
-2415
File diff suppressed because it is too large
Load Diff
Vendored
+11
@@ -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
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
[*]
|
||||
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
|
||||
@@ -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
|
||||
Vendored
+14
@@ -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
|
||||
Vendored
+220
@@ -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")
|
||||
|
||||
Vendored
+63
@@ -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.
|
||||
Vendored
+119
@@ -0,0 +1,119 @@
|
||||
# Read Evaluate Print Loop ++
|
||||
|
||||

|
||||
|
||||
[](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.
|
||||
|
||||
Vendored
+93
@@ -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
|
||||
|
||||
Vendored
+235
@@ -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
@@ -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;
|
||||
}
|
||||
Vendored
+34
@@ -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 );
|
||||
}
|
||||
|
||||
Vendored
+13
@@ -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
|
||||
Vendored
+22
@@ -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
|
||||
|
||||
Vendored
+567
@@ -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
@@ -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 */
|
||||
|
||||
Vendored
+80
@@ -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
@@ -0,0 +1,3 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
include("${CMAKE_CURRENT_LIST_DIR}/replxx-targets.cmake")
|
||||
+271
@@ -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.
|
||||
|
||||
--------------------------------------------------------------------- */
|
||||
Vendored
+139
@@ -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
@@ -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
@@ -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
|
||||
Vendored
+890
@@ -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 */
|
||||
|
||||
Vendored
+37
@@ -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
|
||||
|
||||
Vendored
+402
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+141
@@ -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
|
||||
|
||||
Vendored
+78
@@ -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
|
||||
|
||||
Vendored
+154
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+47
@@ -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
|
||||
Vendored
+648
@@ -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() );
|
||||
}
|
||||
|
||||
+2195
File diff suppressed because it is too large
Load Diff
+270
@@ -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
|
||||
|
||||
Vendored
+742
@@ -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
|
||||
|
||||
}
|
||||
|
||||
Vendored
+94
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Vendored
+170
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+26
@@ -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
|
||||
|
||||
Vendored
+296
@@ -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 );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Vendored
+144
@@ -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
|
||||
|
||||
Vendored
+44
@@ -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
|
||||
Vendored
+2029
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user