From ca85d7b1489aea3235f43d405d0b8cd272d0a341 Mon Sep 17 00:00:00 2001 From: AndreiCherniaev Date: Wed, 3 Jan 2024 20:44:22 +0900 Subject: [PATCH 1/3] add cmake example with FetchContent --- examples/replxx_cmake/CMakeLists.txt | 35 ++ examples/replxx_cmake/cxx-api.cxx | 656 +++++++++++++++++++++ examples/replxx_cmake/include/replxx.h | 646 +++++++++++++++++++++ examples/replxx_cmake/include/replxx.hxx | 710 +++++++++++++++++++++++ examples/replxx_cmake/util.c | 34 ++ examples/replxx_cmake/util.h | 13 + 6 files changed, 2094 insertions(+) create mode 100644 examples/replxx_cmake/CMakeLists.txt create mode 100644 examples/replxx_cmake/cxx-api.cxx create mode 100644 examples/replxx_cmake/include/replxx.h create mode 100644 examples/replxx_cmake/include/replxx.hxx create mode 100644 examples/replxx_cmake/util.c create mode 100644 examples/replxx_cmake/util.h diff --git a/examples/replxx_cmake/CMakeLists.txt b/examples/replxx_cmake/CMakeLists.txt new file mode 100644 index 0000000..ee7e793 --- /dev/null +++ b/examples/replxx_cmake/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.14) + +project(replxx_example LANGUAGES CXX C DESCRIPTION "replxx CMake example with FetchContent" VERSION 1.0.0.0) + +include(GNUInstallDirs) +include(FetchContent) + +FetchContent_Declare( + replxx + GIT_REPOSITORY https://github.com/AmokHuginnsson/replxx + GIT_TAG master +) +FetchContent_MakeAvailable(replxx) + +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +add_executable(${PROJECT_NAME} + cxx-api.cxx + util.c util.h + include/replxx.hxx +) + +target_include_directories( ${PROJECT_NAME} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include +) + +target_link_libraries(${PROJECT_NAME} PRIVATE + replxx::replxx +) + +install(TARGETS ${PROJECT_NAME} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) diff --git a/examples/replxx_cmake/cxx-api.cxx b/examples/replxx_cmake/cxx-api.cxx new file mode 100644 index 0000000..fee8fa0 --- /dev/null +++ b/examples/replxx_cmake/cxx-api.cxx @@ -0,0 +1,656 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "replxx.hxx" +#include "util.h" + +using Replxx = replxx::Replxx; +using namespace replxx::color; + +class Tick { + typedef std::vector keys_t; + std::thread _thread; + int _tick; + int _promptState; + bool _alive; + keys_t _keys; + bool _tickMessages; + bool _promptFan; + Replxx& _replxx; +public: + Tick( Replxx& replxx_, std::string const& keys_, bool tickMessages_, bool promptFan_ ) + : _thread() + , _tick( 0 ) + , _promptState( 0 ) + , _alive( false ) + , _keys( keys_.begin(), keys_.end() ) + , _tickMessages( tickMessages_ ) + , _promptFan( promptFan_ ) + , _replxx( replxx_ ) { + } + void start() { + _alive = true; + _thread = std::thread( &Tick::run, this ); + } + void stop() { + _alive = false; + _thread.join(); + } + void run() { + std::string s; + static char const PROMPT_STATES[] = "-\\|/"; + while ( _alive ) { + if ( _tickMessages ) { + _replxx.print( "%d\n", _tick ); + } + if ( _tick < static_cast( _keys.size() ) ) { + _replxx.emulate_key_press( _keys[_tick] ); + } + if ( ! _tickMessages && ! _promptFan && ( _tick >= static_cast( _keys.size() ) ) ) { + break; + } + if ( _promptFan ) { + for ( int i( 0 ); i < 4; ++ i ) { + char prompt[] = "\x1b[1;32mreplxx\x1b[0m[ ]> "; + prompt[18] = PROMPT_STATES[_promptState % 4]; + ++ _promptState; + _replxx.set_prompt( prompt ); + std::this_thread::sleep_for( std::chrono::milliseconds( 250 ) ); + } + } else { + std::this_thread::sleep_for( std::chrono::seconds( 1 ) ); + } + ++ _tick; + } + } +}; + +// prototypes +Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& user_data, bool); +Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& user_data, bool); +typedef std::vector> syntax_highlight_t; +typedef std::unordered_map keyword_highlight_t; +void hook_color( std::string const& str, Replxx::colors_t& colors, syntax_highlight_t const&, keyword_highlight_t const& ); +void hook_modify( std::string& line, int& cursorPosition, Replxx* ); + +bool eq( std::string const& l, std::string const& r, int s, bool ic ) { + if ( static_cast( l.length() ) < s ) { + return false; + } + if ( static_cast( r.length() ) < s ) { + return false; + } + bool same( true ); + for ( int i( 0 ); same && ( i < s ); ++ i ) { + same = ( ic && ( towlower( l[i] ) == towlower( r[i] ) ) ) || ( l[i] == r[i] ); + } + return same; +} + +Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector const& examples, bool ignoreCase) { + Replxx::completions_t completions; + int utf8ContextLen( context_len( context.c_str() ) ); + int prefixLen( static_cast( context.length() ) - utf8ContextLen ); + if ( ( prefixLen > 0 ) && ( context[prefixLen - 1] == '\\' ) ) { + -- prefixLen; + ++ utf8ContextLen; + } + contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen ); + + std::string prefix { context.substr(prefixLen) }; + if ( prefix == "\\pi" ) { + completions.push_back( "π" ); + } else { + for (auto const& e : examples) { + bool lowerCasePrefix( std::none_of( prefix.begin(), prefix.end(), iswupper ) ); + if ( eq( e, prefix, static_cast( prefix.size() ), ignoreCase && lowerCasePrefix ) ) { + Replxx::Color c( Replxx::Color::DEFAULT ); + if ( e.find( "brightred" ) != std::string::npos ) { + c = Replxx::Color::BRIGHTRED; + } else if ( e.find( "red" ) != std::string::npos ) { + c = Replxx::Color::RED; + } + completions.emplace_back(e.c_str(), c); + } + } + } + + return completions; +} + +Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector const& examples, bool ignoreCase) { + Replxx::hints_t hints; + + // only show hint if prefix is at least 'n' chars long + // or if prefix begins with a specific character + + int utf8ContextLen( context_len( context.c_str() ) ); + int prefixLen( static_cast( context.length() ) - utf8ContextLen ); + contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen ); + std::string prefix { context.substr(prefixLen) }; + + if (prefix.size() >= 2 || (! prefix.empty() && prefix.at(0) == '.')) { + bool lowerCasePrefix( std::none_of( prefix.begin(), prefix.end(), iswupper ) ); + for (auto const& e : examples) { + if ( eq( e, prefix, prefix.size(), ignoreCase && lowerCasePrefix ) ) { + 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; +} + +inline bool is_kw( char ch ) { + return isalnum( ch ) || ( ch == '_' ); +} + +void hook_color( std::string const& context, Replxx::colors_t& colors, syntax_highlight_t const& regex_color, keyword_highlight_t const& word_color ) { + // highlight matching regex sequences + for (auto const& e : regex_color) { + size_t pos {0}; + std::string str = context; + std::smatch match; + + while(std::regex_search(str, match, std::regex(e.first))) { + std::string c{ match[0] }; + std::string prefix( match.prefix().str() ); + pos += utf8str_codepoint_len( prefix.c_str(), static_cast( prefix.length() ) ); + int len( utf8str_codepoint_len( c.c_str(), static_cast( c.length() ) ) ); + + for (int i = 0; i < len; ++i) { + colors.at(pos + i) = e.second; + } + + pos += len; + str = match.suffix(); + } + } + bool inWord( false ); + int wordStart( 0 ); + int wordEnd( 0 ); + int colorOffset( 0 ); + auto dohl = [&](int i) { + inWord = false; + std::string intermission( context.substr( wordEnd, wordStart - wordEnd ) ); + colorOffset += utf8str_codepoint_len( intermission.c_str(), intermission.length() ); + int wordLen( i - wordStart ); + std::string keyword( context.substr( wordStart, wordLen ) ); + bool bold( false ); + if ( keyword.substr( 0, 5 ) == "bold_" ) { + keyword = keyword.substr( 5 ); + bold = true; + } + bool underline( false ); + if ( keyword.substr( 0, 10 ) == "underline_" ) { + keyword = keyword.substr( 10 ); + underline = true; + } + keyword_highlight_t::const_iterator it( word_color.find( keyword ) ); + Replxx::Color color = Replxx::Color::DEFAULT; + if ( it != word_color.end() ) { + color = it->second; + } + if ( bold ) { + color = replxx::color::bold( color ); + } + if ( underline ) { + color = replxx::color::underline( color ); + } + for ( int k( 0 ); k < wordLen; ++ k ) { + Replxx::Color& c( colors.at( colorOffset + k ) ); + if ( color != Replxx::Color::DEFAULT ) { + c = color; + } + } + colorOffset += wordLen; + wordEnd = i; + }; + for ( int i( 0 ); i < static_cast( context.length() ); ++ i ) { + if ( !inWord ) { + if ( is_kw( context[i] ) ) { + inWord = true; + wordStart = i; + } + } else if ( inWord && !is_kw( context[i] ) ) { + dohl(i); + } + if ( ( context[i] != '_' ) && ispunct( context[i] ) ) { + wordStart = i; + dohl( i + 1 ); + } + } + if ( inWord ) { + dohl(context.length()); + } +} + +void hook_modify( std::string& currentInput_, int&, Replxx* rx ) { + char prompt[64]; + snprintf( prompt, 64, "\x1b[1;32mreplxx\x1b[0m[%lu]> ", currentInput_.length() ); + rx->set_prompt( prompt ); +} + +Replxx::ACTION_RESULT message( Replxx& replxx, std::string s, char32_t ) { + replxx.invoke( Replxx::ACTION::CLEAR_SELF, 0 ); + replxx.print( "%s\n", s.c_str() ); + replxx.invoke( Replxx::ACTION::REPAINT, 0 ); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +int main( int argc_, char** argv_ ) { + // words to be completed + std::vector examples { + ".help", ".history", ".quit", ".exit", ".clear", ".prompt ", + "hello", "world", "db", "data", "drive", "print", "put", + "color_black", "color_red", "color_green", "color_brown", "color_blue", + "color_magenta", "color_cyan", "color_lightgray", "color_gray", + "color_brightred", "color_brightgreen", "color_yellow", "color_brightblue", + "color_brightmagenta", "color_brightcyan", "color_white", + "determinANT", "determiNATION", "deterMINE", "deteRMINISM", "detERMINISTIC", "deTERMINED", + "star", "star_galaxy_cluser_supercluster_observable_universe", + }; + + // highlight specific words + // a regex string, and a color + // the order matters, the last match will take precedence + using cl = Replxx::Color; + keyword_highlight_t word_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}, + + // commands + {"help", cl::BRIGHTMAGENTA}, + {"history", cl::BRIGHTMAGENTA}, + {"quit", cl::BRIGHTMAGENTA}, + {"exit", cl::BRIGHTMAGENTA}, + {"clear", cl::BRIGHTMAGENTA}, + {"prompt", cl::BRIGHTMAGENTA}, + }; + syntax_highlight_t regex_color { + // 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 + }; + static int const MAX_LABEL_NAME( 32 ); + char label[MAX_LABEL_NAME]; + for ( int r( 0 ); r < 6; ++ r ) { + for ( int g( 0 ); g < 6; ++ g ) { + for ( int b( 0 ); b < 6; ++ b ) { + snprintf( label, MAX_LABEL_NAME, "rgb%d%d%d", r, g, b ); + word_color.insert( std::make_pair( label, replxx::color::rgb666( r, g, b ) ) ); + for ( int br( 0 ); br < 6; ++ br ) { + for ( int bg( 0 ); bg < 6; ++ bg ) { + for ( int bb( 0 ); bb < 6; ++ bb ) { + snprintf( label, MAX_LABEL_NAME, "fg%d%d%dbg%d%d%d", r, g, b, br, bg, bb ); + word_color.insert( + std::make_pair( + label, + rgb666( r, g, b ) | replxx::color::bg( rgb666( br, bg, bb ) ) + ) + ); + } + } + } + } + } + } + for ( int gs( 0 ); gs < 24; ++ gs ) { + snprintf( label, MAX_LABEL_NAME, "gs%d", gs ); + word_color.insert( std::make_pair( label, grayscale( gs ) ) ); + for ( int bgs( 0 ); bgs < 24; ++ bgs ) { + snprintf( label, MAX_LABEL_NAME, "gs%dgs%d", gs, bgs ); + word_color.insert( std::make_pair( label, grayscale( gs ) | bg( grayscale( bgs ) ) ) ); + } + } + Replxx::Color colorCodes[] = { + Replxx::Color::BLACK, Replxx::Color::RED, Replxx::Color::GREEN, Replxx::Color::BROWN, Replxx::Color::BLUE, + Replxx::Color::CYAN, Replxx::Color::MAGENTA, Replxx::Color::LIGHTGRAY, Replxx::Color::GRAY, Replxx::Color::BRIGHTRED, + Replxx::Color::BRIGHTGREEN, Replxx::Color::YELLOW, Replxx::Color::BRIGHTBLUE, Replxx::Color::BRIGHTCYAN, + Replxx::Color::BRIGHTMAGENTA, Replxx::Color::WHITE + }; + for ( Replxx::Color bg : colorCodes ) { + for ( Replxx::Color fg : colorCodes ) { + snprintf( label, MAX_LABEL_NAME, "c_%d_%d", static_cast( fg ), static_cast( bg ) ); + word_color.insert( std::make_pair( label, fg | replxx::color::bg( bg ) ) ); + } + } + + bool tickMessages( false ); + bool promptFan( false ); + bool promptInCallback( false ); + bool indentMultiline( false ); + bool bracketedPaste( false ); + bool ignoreCase( false ); + std::string keys; + std::string prompt; + int hintDelay( 0 ); + while ( argc_ > 1 ) { + -- argc_; + ++ argv_; + switch ( (*argv_)[0] ) { + case ( 'm' ): tickMessages = true; break; + case ( 'F' ): promptFan = true; break; + case ( 'P' ): promptInCallback = true; break; + case ( 'I' ): indentMultiline = true; break; + case ( 'i' ): ignoreCase = true; break; + case ( 'k' ): keys = (*argv_) + 1; break; + case ( 'd' ): hintDelay = std::stoi( (*argv_) + 1 ); break; + case ( 'h' ): examples.push_back( (*argv_) + 1 ); break; + case ( 'p' ): prompt = (*argv_) + 1; break; + case ( 'B' ): bracketedPaste = true; break; + } + } + + // init the repl + Replxx rx; + Tick tick( rx, keys, tickMessages, promptFan ); + rx.install_window_change_handler(); + + // the path to the history file + std::string history_file_path {"./replxx_history.txt"}; + + // load the history file if it exists + /* scope for ifstream object for auto-close */ { + std::ifstream history_file( history_file_path.c_str() ); + 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 ), ignoreCase ) ); + rx.set_highlighter_callback( std::bind( &hook_color, _1, _2, cref( regex_color ), cref( word_color ) ) ); + rx.set_hint_callback( std::bind( &hook_hint, _1, _2, _3, cref( examples ), ignoreCase ) ); + if ( promptInCallback ) { + rx.set_modify_callback( std::bind( &hook_modify, _1, _2, &rx ) ); + } + + // other api calls + rx.set_word_break_characters( " \n\t.,-%!;:=*~^'\"/?<>|[](){}" ); + rx.set_completion_count_cutoff( 128 ); + rx.set_hint_delay( hintDelay ); + rx.set_double_tab_completion( false ); + rx.set_complete_on_empty( true ); + rx.set_beep_on_ambiguous_completion( false ); + rx.set_no_color( false ); + rx.set_indent_multiline( indentMultiline ); + if ( bracketedPaste ) { + rx.enable_bracketed_paste(); + } + rx.set_ignore_case( ignoreCase ); + + // 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, "line_previous" ); + rx.bind_key_internal( Replxx::KEY::DOWN, "line_next" ); + rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::UP ), "history_previous" ); + rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::DOWN ), "history_next" ); + rx.bind_key_internal( Replxx::KEY::PAGE_UP, "history_first" ); + rx.bind_key_internal( Replxx::KEY::PAGE_DOWN, "history_last" ); + rx.bind_key_internal( Replxx::KEY::HOME, "move_cursor_to_begining_of_line" ); + rx.bind_key_internal( Replxx::KEY::END, "move_cursor_to_end_of_line" ); + rx.bind_key_internal( Replxx::KEY::TAB, "complete_line" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::LEFT ), "move_cursor_one_word_left" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::RIGHT ), "move_cursor_one_word_right" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::UP ), "hint_previous" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::DOWN ), "hint_next" ); + rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::ENTER ), "commit_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'R' ), "history_incremental_search" ); + rx.bind_key_internal( Replxx::KEY::control( 'W' ), "kill_to_begining_of_word" ); + rx.bind_key_internal( Replxx::KEY::control( 'U' ), "kill_to_begining_of_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'K' ), "kill_to_end_of_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'Y' ), "yank" ); + rx.bind_key_internal( Replxx::KEY::control( 'L' ), "clear_screen" ); + rx.bind_key_internal( Replxx::KEY::control( 'D' ), "send_eof" ); + rx.bind_key_internal( Replxx::KEY::control( 'C' ), "abort_line" ); + rx.bind_key_internal( Replxx::KEY::control( 'T' ), "transpose_characters" ); +#ifndef _WIN32 + rx.bind_key_internal( Replxx::KEY::control( 'V' ), "verbatim_insert" ); + rx.bind_key_internal( Replxx::KEY::control( 'Z' ), "suspend" ); +#endif + rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), "kill_to_whitespace_on_left" ); + rx.bind_key_internal( Replxx::KEY::meta( 'p' ), "history_common_prefix_search" ); + rx.bind_key_internal( Replxx::KEY::meta( 'n' ), "history_common_prefix_search" ); + rx.bind_key_internal( Replxx::KEY::meta( 'd' ), "kill_to_end_of_word" ); + rx.bind_key_internal( Replxx::KEY::meta( 'y' ), "yank_cycle" ); + rx.bind_key_internal( Replxx::KEY::meta( 'u' ), "uppercase_word" ); + rx.bind_key_internal( Replxx::KEY::meta( 'l' ), "lowercase_word" ); + rx.bind_key_internal( Replxx::KEY::meta( 'c' ), "capitalize_word" ); + rx.bind_key_internal( 'a', "insert_character" ); + rx.bind_key_internal( Replxx::KEY::INSERT, "toggle_overwrite_mode" ); + rx.bind_key( Replxx::KEY::F1, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F2, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F3, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F4, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F5, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F6, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F7, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F8, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F9, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F10, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F11, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::F12, std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::TAB ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_UP ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_DOWN ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::LEFT ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::RIGHT ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::UP ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::shift( Replxx::KEY::DOWN ), std::bind( &message, std::ref( rx ), "", _1 ) ); + rx.bind_key( Replxx::KEY::meta( '\r' ), std::bind( &message, std::ref( rx ), "", _1 ) ); + + // display initial welcome message + std::cout + << "Welcome to Replxx\n" + << "Press 'tab' to view autocompletions\n" + << "Type '.help' for help\n" + << "Type '.quit' or '.exit' to exit\n\n"; + + // set the repl prompt + if ( prompt.empty() ) { + prompt = "\x1b[1;32mreplxx\x1b[0m> "; + } + + // main repl loop + if ( ! keys.empty() || tickMessages || promptFan ) { + tick.start(); + } + for (;;) { + // display the prompt and retrieve input from the user + char const* cinput{ nullptr }; + + do { + cinput = rx.input(prompt); + } while ( ( cinput == nullptr ) && ( errno == EAGAIN ) ); + + if (cinput == nullptr) { + break; + } + + // change cinput into a std::string + // easier to manipulate + std::string input {cinput}; + + if (input.empty()) { + // user hit enter on an empty line + + continue; + + } else if (input.compare(0, 5, ".quit") == 0 || input.compare(0, 5, ".exit") == 0) { + // exit the repl + + rx.history_add(input); + break; + + } else if (input.compare(0, 5, ".help") == 0) { + // display the help output + std::cout + << ".help\n\tdisplays the help output\n" + << ".quit\n\texit the repl\n" + << ".exit\n\texit the repl\n" + << ".clear\n\tclears the screen\n" + << ".history\n\tdisplays the history output\n" + << ".prompt \n\tset the repl prompt to \n"; + + rx.history_add(input); + continue; + + } else if (input.compare(0, 7, ".prompt") == 0) { + // set the repl prompt text + auto pos = input.find(" "); + if (pos == std::string::npos) { + std::cout << "Error: '.prompt' missing argument\n"; + } else { + prompt = input.substr(pos + 1) + " "; + } + + rx.history_add(input); + continue; + + } else if (input.compare(0, 8, ".history") == 0) { + // display the current history + Replxx::HistoryScan hs( rx.history_scan() ); + for ( int i( 0 ); hs.next(); ++ i ) { + std::cout << std::setw(4) << i << ": " << hs.get().text() << "\n"; + } + + rx.history_add(input); + continue; + + } else if (input.compare(0, 6, ".merge") == 0) { + history_file_path = "replxx_history_alt.txt"; + + rx.history_add(input); + continue; + + } else if (input.compare(0, 5, ".save") == 0) { + history_file_path = "replxx_history_alt.txt"; + std::ofstream history_file( history_file_path.c_str() ); + 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 ( ! keys.empty() || tickMessages || promptFan ) { + tick.stop(); + } + + // save the history + rx.history_sync( history_file_path ); + if ( bracketedPaste ) { + rx.disable_bracketed_paste(); + } + if ( bracketedPaste || promptInCallback || promptFan ) { + std::cout << "\n" << prompt; + } + + std::cout << "\nExiting Replxx\n"; + + return 0; +} diff --git a/examples/replxx_cmake/include/replxx.h b/examples/replxx_cmake/include/replxx.h new file mode 100644 index 0000000..b02baa3 --- /dev/null +++ b/examples/replxx_cmake/include/replxx.h @@ -0,0 +1,646 @@ +/* linenoise.h -- guerrilla line editing library against the idea that a + * line editing lib needs to be 20,000 lines of C code. + * + * See linenoise.c for more information. + * + * Copyright (c) 2010, Salvatore Sanfilippo + * Copyright (c) 2010, Pieter Noordhuis + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef __REPLXX_H +#define __REPLXX_H + +#define REPLXX_VERSION "0.0.2" +#define REPLXX_VERSION_MAJOR 0 +#define REPLXX_VERSION_MINOR 0 + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * For use in Windows DLLs: + * + * If you are building replxx into a DLL, + * unless you are using supplied CMake based build, + * ensure that 'REPLXX_BUILDING_DLL' is defined when + * building the DLL so that proper symbols are exported. + */ +#if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) +# ifdef REPLXX_BUILDING_DLL +# define REPLXX_IMPEXP __declspec( dllexport ) +# else +# define REPLXX_IMPEXP __declspec( dllimport ) +# endif +#else +# define REPLXX_IMPEXP /**/ +#endif + +/*! \brief Color definitions to use in highlighter callbacks. + */ +typedef enum { + REPLXX_COLOR_BLACK = 0, + REPLXX_COLOR_RED = 1, + REPLXX_COLOR_GREEN = 2, + REPLXX_COLOR_BROWN = 3, + REPLXX_COLOR_BLUE = 4, + REPLXX_COLOR_MAGENTA = 5, + REPLXX_COLOR_CYAN = 6, + REPLXX_COLOR_LIGHTGRAY = 7, + REPLXX_COLOR_GRAY = 8, + REPLXX_COLOR_BRIGHTRED = 9, + REPLXX_COLOR_BRIGHTGREEN = 10, + REPLXX_COLOR_YELLOW = 11, + REPLXX_COLOR_BRIGHTBLUE = 12, + REPLXX_COLOR_BRIGHTMAGENTA = 13, + REPLXX_COLOR_BRIGHTCYAN = 14, + REPLXX_COLOR_WHITE = 15, + REPLXX_COLOR_DEFAULT = 1u << 16u +} 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' ) }; +enum { REPLXX_KEY_ABORT = REPLXX_KEY_META( REPLXX_KEY_CONTROL( 'M' ) ) }; + +/*! \brief List of built-in actions that act upon user input. + */ +typedef enum { + REPLXX_ACTION_INSERT_CHARACTER, + REPLXX_ACTION_NEW_LINE, + 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_END_OF_SUBWORD, + REPLXX_ACTION_KILL_TO_BEGINING_OF_SUBWORD, + 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_ONE_SUBWORD_LEFT, + REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT, + REPLXX_ACTION_MOVE_CURSOR_LEFT, + REPLXX_ACTION_MOVE_CURSOR_RIGHT, + REPLXX_ACTION_LINE_NEXT, + REPLXX_ACTION_LINE_PREVIOUS, + REPLXX_ACTION_HISTORY_MOVE_NEXT, + REPLXX_ACTION_HISTORY_MOVE_PREVIOUS, + REPLXX_ACTION_HISTORY_FIRST, + REPLXX_ACTION_HISTORY_LAST, + REPLXX_ACTION_HISTORY_RESTORE, + REPLXX_ACTION_HISTORY_RESTORE_CURRENT, + REPLXX_ACTION_HISTORY_INCREMENTAL_SEARCH, + REPLXX_ACTION_HISTORY_SEEDED_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_CAPITALIZE_SUBWORD, + REPLXX_ACTION_LOWERCASE_SUBWORD, + REPLXX_ACTION_UPPERCASE_SUBWORD, + 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 Enable/disable case insensitive history search and completion. + * + * \param val - if set to non-zero then history search and completion will be case insensitive. + */ +REPLXX_IMPEXP void replxx_set_ignore_case( Replxx*, int val ); + +/*! \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 Asynchronously change the prompt while replxx_input() call is in efect. + * + * Can be used to change the prompt from callbacks or other threads. + * + * \param prompt - The prompt string to change to. + */ +REPLXX_IMPEXP void replxx_set_prompt( Replxx*, const char* prompt ); + +/*! \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_LINE_PREVIOUS so action name to be used in this + * interface for the same effect is "line_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 Enable/disable (prompt width) indent for multiline entry. + * + * \param val - if set to non-zero then multiline indent will be enabled. + */ +REPLXX_IMPEXP void replxx_set_indent_multiline( 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* ); + +/*! \brief Combine two color definitions to get encompassing color definition. + * + * To be used only for combining foreground and background colors. + * + * \param color1 - first input color. + * \param color2 - second input color. + * \return A new color definition that represent combined input colors. + */ +ReplxxColor replxx_color_combine( ReplxxColor color1, ReplxxColor color2 ); + +/*! \brief Transform foreground color definition into a background color definition. + * + * \param color - an input foreground color definition. + * \return A background color definition that is a transformed input \e color. + */ +ReplxxColor replxx_color_bg( ReplxxColor color ); + +/*! \brief Add `bold` attribute to color definition. + * + * \param color - an input color definition. + * \return A new color definition with bold attribute set. + */ +ReplxxColor replxx_color_bold( ReplxxColor color ); + +/*! \brief Add `underline` attribute to color definition. + * + * \param color - an input color definition. + * \return A new color definition with underline attribute set. + */ +ReplxxColor replxx_color_underline( ReplxxColor color ); + +/*! \brief Create a new grayscale color of given brightness level. + * + * \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest). + * \return A new grayscale color of a given brightest \e level. + */ +ReplxxColor replxx_color_grayscale( int level ); + +/*! \brief Create a new color in 6×6×6 RGB color space from base component levels. + * + * \param red - a red (of RGB) component level, must be 0 and 5. + * \param green - a green (of RGB) component level, must be 0 and 5. + * \param blue - a blue (of RGB) component level, must be 0 and 5. + * \return A new color in 6×6×6 RGB color space. + */ +ReplxxColor replxx_color_rgb666( int red, int green, int blue ); + +#ifdef __cplusplus +} +#endif + +#endif /* __REPLXX_H */ + diff --git a/examples/replxx_cmake/include/replxx.hxx b/examples/replxx_cmake/include/replxx.hxx new file mode 100644 index 0000000..4928cb2 --- /dev/null +++ b/examples/replxx_cmake/include/replxx.hxx @@ -0,0 +1,710 @@ +/* + * Copyright (c) 2017-2018, Marcin Konarski (amok at codestation.org) + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Redis nor the names of its contributors may be used + * to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef HAVE_REPLXX_HXX_INCLUDED +#define HAVE_REPLXX_HXX_INCLUDED 1 + +#include +#include +#include +#include +#include + +/* + * For use in Windows DLLs: + * + * If you are building replxx into a DLL, + * unless you are using supplied CMake based build, + * ensure that 'REPLXX_BUILDING_DLL' is defined when + * building the DLL so that proper symbols are exported. + */ +#if defined( _WIN32 ) && ! defined( REPLXX_STATIC ) +# ifdef REPLXX_BUILDING_DLL +# define REPLXX_IMPEXP __declspec( dllexport ) +# else +# define REPLXX_IMPEXP __declspec( dllimport ) +# endif +#else +# define REPLXX_IMPEXP /**/ +#endif + +#ifdef ERROR +enum { ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 = ERROR }; +#undef ERROR +enum { ERROR = ERROR_BB1CA97EC761FC37101737BA0AA2E7C5 }; +#endif +#ifdef ABORT +enum { ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 = ABORT }; +#undef ABORT +enum { ABORT = ABORT_8D12A2CA7E5A64036D7251A3EDA51A38 }; +#endif +#ifdef DELETE +enum { DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E = DELETE }; +#undef DELETE +enum { DELETE = DELETE_32F68A60CEF40FAEDBC6AF20298C1A1E }; +#endif + +namespace replxx { + +class REPLXX_IMPEXP Replxx { +public: + enum class Color : int { + 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, + DEFAULT = 1u << 16u + }; + 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; + static char32_t const ABORT = 'C' | BASE_CONTROL | BASE_META; + }; + /*! \brief List of built-in actions that act upon user input. + */ + enum class ACTION { + INSERT_CHARACTER, + NEW_LINE, + 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_END_OF_SUBWORD, + KILL_TO_BEGINING_OF_SUBWORD, + 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_ONE_SUBWORD_LEFT, + MOVE_CURSOR_ONE_SUBWORD_RIGHT, + MOVE_CURSOR_LEFT, + MOVE_CURSOR_RIGHT, + LINE_NEXT, + LINE_PREVIOUS, + HISTORY_NEXT, + HISTORY_PREVIOUS, + HISTORY_FIRST, + HISTORY_LAST, + HISTORY_RESTORE, + HISTORY_RESTORE_CURRENT, + HISTORY_INCREMENTAL_SEARCH, + HISTORY_SEEDED_INCREMENTAL_SEARCH, + HISTORY_COMMON_PREFIX_SEARCH, + HINT_NEXT, + HINT_PREVIOUS, + CAPITALIZE_WORD, + LOWERCASE_WORD, + UPPERCASE_WORD, + CAPITALIZE_SUBWORD, + LOWERCASE_SUBWORD, + UPPERCASE_SUBWORD, + TRANSPOSE_CHARACTERS, + TOGGLE_OVERWRITE_MODE, +#ifndef _WIN32 + VERBATIM_INSERT, + SUSPEND, +#endif + BRACKETED_PASTE, + CLEAR_SCREEN, + CLEAR_SELF, + REPAINT, + COMPLETE_LINE, + COMPLETE_NEXT, + COMPLETE_PREVIOUS, + COMMIT_LINE, + ABORT_LINE, + SEND_EOF + }; + /*! \brief Possible results of key-press handler actions. + */ + enum class ACTION_RESULT { + CONTINUE, /*!< Continue processing user input. */ + RETURN, /*!< Return user input entered so far. */ + BAIL /*!< Stop processing user input, returns nullptr from the \e input() call. */ + }; + typedef std::vector colors_t; + class Completion { + std::string _text; + Color _color; + public: + Completion( char const* text_ ) + : _text( text_ ) + , _color( Color::DEFAULT ) { + } + Completion( std::string const& text_ ) + : _text( text_ ) + , _color( Color::DEFAULT ) { + } + Completion( std::string const& text_, Color color_ ) + : _text( text_ ) + , _color( color_ ) { + } + std::string const& text( void ) const { + return ( _text ); + } + Color color( void ) const { + return ( _color ); + } + }; + typedef std::vector completions_t; + class HistoryEntry { + std::string _timestamp; + std::string _text; + public: + HistoryEntry( std::string const& timestamp_, std::string const& text_ ) + : _timestamp( timestamp_ ) + , _text( text_ ) { + } + std::string const& timestamp( void ) const { + return ( _timestamp ); + } + std::string const& text( void ) const { + return ( _text ); + } + }; + class HistoryScanImpl; + class HistoryScan { + public: + typedef std::unique_ptr impl_t; + private: +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4251) +#endif + impl_t _impl; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + public: + HistoryScan( impl_t ); + HistoryScan( HistoryScan&& ) = default; + HistoryScan& operator = ( HistoryScan&& ) = default; + bool next( void ); + HistoryEntry const& get( void ) const; + private: + HistoryScan( HistoryScan const& ) = delete; + HistoryScan& operator = ( HistoryScan const& ) = delete; + }; + typedef std::vector hints_t; + + /*! \brief Line modification callback type definition. + * + * User can observe and modify line contents (and cursor position) + * in response to changes to both introduced by the user through + * normal interactions. + * + * When callback returns Replxx updates current line content + * and current cursor position to the ones updated by the callback. + * + * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. + * \param cursorPosition[in,out] - a R/W reference to current cursor position. + */ + typedef std::function modify_callback_t; + + /*! \brief Completions callback type definition. + * + * \e contextLen is counted in Unicode code points (not in bytes!). + * + * For user input: + * if ( obj.me + * + * input == "if ( obj.me" + * contextLen == 2 (depending on \e set_word_break_characters()) + * + * Client application is free to update \e contextLen to be 6 (or any other non-negative + * number not greater than the number of code points in input) if it makes better sense + * for given client application semantics. + * + * \param input - UTF-8 encoded input entered by the user until current cursor position. + * \param[in,out] contextLen - length of the additional context to provide while displaying completions. + * \return A list of user completions. + */ + typedef std::function completion_callback_t; + + /*! \brief Highlighter callback type definition. + * + * If user want to have colorful input she must simply install highlighter callback. + * The callback would be invoked by the library after each change to the input done by + * the user. After callback returns library uses data from colors buffer to colorize + * displayed user input. + * + * Size of \e colors buffer is equal to number of code points in user \e input + * which will be different from simple `input.length()`! + * + * \param input - an UTF-8 encoded input entered by the user so far. + * \param colors - output buffer for color information. + */ + typedef std::function highlighter_callback_t; + + /*! \brief Hints callback type definition. + * + * \e contextLen is counted in Unicode code points (not in bytes!). + * + * For user input: + * if ( obj.me + * + * input == "if ( obj.me" + * contextLen == 2 (depending on \e set_word_break_characters()) + * + * Client application is free to update \e contextLen to be 6 (or any other non-negative + * number not greater than the number of code points in input) if it makes better sense + * for given client application semantics. + * + * \param input - UTF-8 encoded input entered by the user until current cursor position. + * \param contextLen[in,out] - length of the additional context to provide while displaying hints. + * \param color - a color used for displaying hints. + * \return A list of possible hints. + */ + typedef std::function hint_callback_t; + + /*! \brief Key press handler type definition. + * + * \param code - the key code replxx got from terminal. + * \return Decision on how should input() behave after this key handler returns. + */ + typedef std::function key_press_handler_t; + + struct State { + char const* _text; + int _cursorPosition; + State( char const* text_, int cursorPosition_ = -1 ) + : _text( text_ ) + , _cursorPosition( cursorPosition_ ) { + } + State( State const& ) = default; + State& operator = ( State const& ) = default; + char const* text( void ) const { + return ( _text ); + } + int cursor_position( void ) const { + return ( _cursorPosition ); + } + }; + + class ReplxxImpl; +private: + typedef std::unique_ptr impl_t; +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable:4251) +#endif + impl_t _impl; +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +public: + Replxx( void ); + Replxx( Replxx&& ) = default; + Replxx& operator = ( Replxx&& ) = default; + + /*! \brief Register modify callback. + * + * \param fn - user defined callback function. + */ + void set_modify_callback( modify_callback_t const& fn ); + + /*! \brief Register completion callback. + * + * \param fn - user defined callback function. + */ + void set_completion_callback( completion_callback_t const& fn ); + + /*! \brief Register highlighter callback. + * + * \param fn - user defined callback function. + */ + void set_highlighter_callback( highlighter_callback_t const& fn ); + + /*! \brief Register hints callback. + * + * \param fn - user defined callback function. + */ + void set_hint_callback( hint_callback_t const& fn ); + + /*! \brief Read line of user input. + * + * Returned pointer is managed by the library and is not to be freed in the client. + * + * \param prompt - prompt to be displayed before getting user input. + * \return An UTF-8 encoded input given by the user (or nullptr on EOF). + */ + char const* input( std::string const& prompt ); + + /*! \brief Get current state data. + * + * This call is intended to be used in handlers. + * + * \return Current state of the model. + */ + State get_state( void ) const; + + /*! \brief Set new state data. + * + * This call is intended to be used in handlers. + * + * \param state - new state of the model. + */ + void set_state( State const& state ); + + /*! \brief Enable/disable case insensitive history search and completion. + * + * \param val - if set to non-zero then history search and completion will be case insensitive. + */ + void set_ignore_case( bool val ); + + /*! \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 Asynchronously change the prompt while replxx_input() call is in efect. + * + * Can be used to change the prompt from callbacks or other threads. + * + * \param prompt - The prompt string to change to. + */ + void set_prompt( std::string prompt ); + + /*! \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::LINE_PREVIOUS so action name to be used in this + * interface for the same effect is "line_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 ); + + /*! + * \copydoc history_save + */ + void history_save( std::ostream& out ); + + /*! \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 ); + + /*! + * \copydoc history_load + */ + void history_load( std::istream& in ); + + /*! \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 Enable/disable (prompt width) indent for multiline entry. + * + * \param val - if set to true then multiline indent will be enabled. + */ + void set_indent_multiline( 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; +}; + +/*! \brief Color definition related helper function. + * + * To be used to leverage 256 color terminal capabilities. + */ +namespace color { + +/*! \brief Combine two color definitions to get encompassing color definition. + * + * To be used only for combining foreground and background colors. + * + * \param color1 - first input color. + * \param color2 - second input color. + * \return A new color definition that represent combined input colors. + */ +Replxx::Color operator | ( Replxx::Color color1, Replxx::Color color2 ); + +/*! \brief Transform foreground color definition into a background color definition. + * + * \param color - an input foreground color definition. + * \return A background color definition that is a transformed input \e color. + */ +Replxx::Color bg( Replxx::Color color ); + +/*! \brief Add `bold` attribute to color definition. + * + * \param color - an input color definition. + * \return A new color definition with bold attribute set. + */ +Replxx::Color bold( Replxx::Color color ); + +/*! \brief Add `underline` attribute to color definition. + * + * \param color - an input color definition. + * \return A new color definition with underline attribute set. + */ +Replxx::Color underline( Replxx::Color color ); + +/*! \brief Create a new grayscale color of given brightness level. + * + * \param level - a brightness level for new color, must be between 0 (darkest) and 23 (brightest). + * \return A new grayscale color of a given brightest \e level. + */ +Replxx::Color grayscale( int level ); + +/*! \brief Create a new color in 6×6×6 RGB color space from base component levels. + * + * \param red - a red (of RGB) component level, must be 0 and 5. + * \param green - a green (of RGB) component level, must be 0 and 5. + * \param blue - a blue (of RGB) component level, must be 0 and 5. + * \return A new color in 6×6×6 RGB color space. + */ +Replxx::Color rgb666( int red, int green, int blue ); + +} + +} + +#endif /* HAVE_REPLXX_HXX_INCLUDED */ + diff --git a/examples/replxx_cmake/util.c b/examples/replxx_cmake/util.c new file mode 100644 index 0000000..6fb18c0 --- /dev/null +++ b/examples/replxx_cmake/util.c @@ -0,0 +1,34 @@ +#include + +int utf8str_codepoint_len( char const* s, int utf8len ) { + int codepointLen = 0; + unsigned char m4 = 128 + 64 + 32 + 16; + unsigned char m3 = 128 + 64 + 32; + unsigned char m2 = 128 + 64; + for ( int i = 0; i < utf8len; ++ i, ++ codepointLen ) { + char c = s[i]; + if ( ( c & m4 ) == m4 ) { + i += 3; + } else if ( ( c & m3 ) == m3 ) { + i += 2; + } else if ( ( c & m2 ) == m2 ) { + i += 1; + } + } + return ( codepointLen ); +} + +int context_len( char const* prefix ) { + char const wb[] = " \t\n\r\v\f-=+*&^%$#@!,./?<>;:`~'\"[]{}()\\|"; + int i = (int)strlen( prefix ) - 1; + int cl = 0; + while ( i >= 0 ) { + if ( strchr( wb, prefix[i] ) != NULL ) { + break; + } + ++ cl; + -- i; + } + return ( cl ); +} + diff --git a/examples/replxx_cmake/util.h b/examples/replxx_cmake/util.h new file mode 100644 index 0000000..c13bcf2 --- /dev/null +++ b/examples/replxx_cmake/util.h @@ -0,0 +1,13 @@ +#pragma once + +// strrpbrk would suffice but is not portable. +#ifdef __cplusplus +extern "C" { +#endif + +int context_len( char const* ); +int utf8str_codepoint_len( char const*, int ); + +#ifdef __cplusplus +} +#endif From 6cd1c3604d484e4ccc5f070b7b193a0f5f326b40 Mon Sep 17 00:00:00 2001 From: AndreiCherniaev Date: Wed, 3 Jan 2024 20:45:08 +0900 Subject: [PATCH 2/3] add to .gitignore any build*/ folders and files like CMakeLists.txt.user --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 19487f6..643ed07 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ replxx_history_alt.txt *.o *~ .vs +build*/ +*.user From 3de5a5998c05505ce13755d418216c1a59443e03 Mon Sep 17 00:00:00 2001 From: AndreiCherniaev Date: Mon, 15 Apr 2024 01:30:28 +0900 Subject: [PATCH 3/3] add unit of measurement for hintDelay --- examples/replxx_cmake/cxx-api.cxx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/replxx_cmake/cxx-api.cxx b/examples/replxx_cmake/cxx-api.cxx index fee8fa0..6e501a9 100644 --- a/examples/replxx_cmake/cxx-api.cxx +++ b/examples/replxx_cmake/cxx-api.cxx @@ -378,7 +378,7 @@ int main( int argc_, char** argv_ ) { bool ignoreCase( false ); std::string keys; std::string prompt; - int hintDelay( 0 ); + int hintDelay( 0 ); // [ms] while ( argc_ > 1 ) { -- argc_; ++ argv_;