From 4893fc8dc5b54968be8949fe3b45fc7326cbb90f Mon Sep 17 00:00:00 2001 From: Vsevolod Stakhov Date: Tue, 24 Aug 2021 15:47:07 +0100 Subject: [PATCH] [Minor] Update replxx library --- contrib/replxx/include/replxx.h | 142 ++++- contrib/replxx/include/replxx.hxx | 174 +++++- contrib/replxx/src/conversion.cxx | 88 +-- contrib/replxx/src/conversion.hxx | 21 +- contrib/replxx/src/escape.cxx | 36 +- contrib/replxx/src/history.cxx | 390 ++++++++++-- contrib/replxx/src/history.hxx | 134 +++- contrib/replxx/src/killring.hxx | 6 +- contrib/replxx/src/prompt.cxx | 64 +- contrib/replxx/src/prompt.hxx | 12 +- contrib/replxx/src/replxx.cxx | 159 ++++- contrib/replxx/src/replxx_impl.cxx | 896 +++++++++++++++++---------- contrib/replxx/src/replxx_impl.hxx | 73 ++- contrib/replxx/src/unicodestring.hxx | 28 +- contrib/replxx/src/utf8string.hxx | 33 +- contrib/replxx/src/util.cxx | 30 +- contrib/replxx/src/util.hxx | 6 +- contrib/replxx/src/windows.cxx | 38 +- contrib/replxx/src/windows.hxx | 2 +- 19 files changed, 1692 insertions(+), 640 deletions(-) diff --git a/contrib/replxx/include/replxx.h b/contrib/replxx/include/replxx.h index 4bdad5127..5127ac2ae 100644 --- a/contrib/replxx/include/replxx.h +++ b/contrib/replxx/include/replxx.h @@ -126,6 +126,8 @@ 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 ) @@ -139,19 +141,25 @@ enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) }; */ 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_HISTORY_NEXT, @@ -165,12 +173,16 @@ typedef enum { 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, @@ -196,12 +208,17 @@ typedef struct ReplxxStateTag { } ReplxxState; typedef struct Replxx Replxx; +typedef struct ReplxxHistoryScan ReplxxHistoryScan; +typedef struct ReplxxHistoryEntryTag { + char const* timestamp; + char const* text; +} ReplxxHistoryEntry; -/*! \brief Create Replxx library resouce holder. +/*! \brief Create Replxx library resource holder. * - * Use replxx_end() to free resoiurce acquired with this function. + * Use replxx_end() to free resources acquired with this function. * - * \return Replxx library resouce holder. + * \return Replxx library resource holder. */ REPLXX_IMPEXP Replxx* replxx_init( void ); @@ -211,6 +228,28 @@ REPLXX_IMPEXP Replxx* replxx_init( void ); */ 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. @@ -247,8 +286,8 @@ typedef struct replxx_completions replxx_completions; * 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 orther non-negative - * number not greated than the number of code points in input) if it makes better sense + * 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. @@ -292,8 +331,8 @@ typedef struct replxx_hints replxx_hints; * 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 orther non-negative - * number not greated than the number of code points in input) if it makes better sense + * 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. @@ -314,7 +353,7 @@ REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn /*! \brief Key press handler type definition. * * \param code - the key code replxx got from terminal. - * \return Decition on how should input() behave after this key handler returns. + * \return Decision on how should input() behave after this key handler returns. */ typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); @@ -326,6 +365,8 @@ typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); 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). @@ -356,11 +397,17 @@ REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state ); * * \param fmt - printf style format. */ -#ifdef __GNUC__ -__attribute__((format(printf, 2, 3))) -#endif 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. @@ -385,6 +432,19 @@ REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, in */ 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 ); @@ -430,6 +490,23 @@ REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val ); */ 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. @@ -439,15 +516,56 @@ 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 char const* replxx_history_line( Replxx*, int index ); +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 } diff --git a/contrib/replxx/include/replxx.hxx b/contrib/replxx/include/replxx.hxx index 1401ea27c..5362312e5 100644 --- a/contrib/replxx/include/replxx.hxx +++ b/contrib/replxx/include/replxx.hxx @@ -131,6 +131,8 @@ public: 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 ); } @@ -148,19 +150,25 @@ public: */ 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, HISTORY_NEXT, @@ -174,12 +182,16 @@ public: 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, @@ -222,8 +234,60 @@ public: } }; 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!). @@ -234,8 +298,8 @@ public: * 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 orther non-negative - * number not greated than the number of code points in input) if it makes better sense + * 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. @@ -252,7 +316,7 @@ public: * 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()`! + * 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. @@ -269,8 +333,8 @@ public: * 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 orther non-negative - * number not greated than the number of code points in input) if it makes better sense + * 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. @@ -283,7 +347,7 @@ public: /*! \brief Key press handler type definition. * * \param code - the key code replxx got from terminal. - * \return Decition on how should input() behave after this key handler returns. + * \return Decision on how should input() behave after this key handler returns. */ typedef std::function key_press_handler_t; @@ -307,12 +371,12 @@ public: class ReplxxImpl; private: typedef std::unique_ptr impl_t; -#ifdef _WIN32 +#ifdef _MSC_VER #pragma warning(push) #pragma warning(disable:4251) #endif impl_t _impl; -#ifdef _WIN32 +#ifdef _MSC_VER #pragma warning(pop) #endif @@ -321,6 +385,12 @@ public: 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. @@ -340,6 +410,8 @@ public: 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). @@ -370,11 +442,17 @@ public: * * \param fmt - printf style format. */ -#ifdef __GNUC__ - __attribute__((format(printf, 2, 3))) -#endif 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. @@ -398,11 +476,60 @@ public: */ 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 ); - int history_save( std::string const& filename ); - int history_load( std::string const& filename ); + + /*! \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; - std::string history_line( int index ); + HistoryScan history_scan( void ) const; void set_preload_buffer( std::string const& preloadText ); @@ -446,6 +573,23 @@ public: */ 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. @@ -457,6 +601,8 @@ public: 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; diff --git a/contrib/replxx/src/conversion.cxx b/contrib/replxx/src/conversion.cxx index ce9bd932b..bcdbe048e 100644 --- a/contrib/replxx/src/conversion.cxx +++ b/contrib/replxx/src/conversion.cxx @@ -2,10 +2,7 @@ #include #include #include -#include - -#include "unicode/utf8.h" - +#include #include "conversion.hxx" @@ -47,38 +44,20 @@ bool is8BitEncoding( is_8bit_encoding() ); ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, const char* src) { ConversionResult res = ConversionResult::conversionOK; if ( ! locale::is8BitEncoding ) { - auto sourceStart = reinterpret_cast(src); - auto slen = strlen(src); - auto targetStart = reinterpret_cast(dst); - int i = 0, j = 0; - - while (i < slen && j < dstSize) { - UChar32 uc; - auto prev_i = i; - U8_NEXT (sourceStart, i, slen, uc); - - if (uc <= 0) { - if (U8_IS_LEAD (sourceStart[prev_i])) { - auto lead_byte = sourceStart[prev_i]; - auto trailing_bytes = (((uint8_t)(lead_byte)>=0xc2)+ - ((uint8_t)(lead_byte)>=0xe0)+ - ((uint8_t)(lead_byte)>=0xf0)); - - if (trailing_bytes + i > slen) { - return ConversionResult::sourceExhausted; - } - } - - /* Replace with 0xFFFD */ - uc = 0x0000FFFD; - } - targetStart[j++] = uc; - } + const UTF8* sourceStart = reinterpret_cast(src); + const UTF8* sourceEnd = sourceStart + strlen(src); + UTF32* targetStart = reinterpret_cast(dst); + UTF32* targetEnd = targetStart + dstSize; - dstCount = j; + res = ConvertUTF8toUTF32( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion); - if (j < dstSize) { - targetStart[j] = 0; + if (res == conversionOK) { + dstCount = static_cast( targetStart - reinterpret_cast( dst ) ); + + if (dstCount < dstSize) { + *targetStart = 0; + } } } else { for ( dstCount = 0; ( dstCount < dstSize ) && src[dstCount]; ++ dstCount ) { @@ -94,28 +73,22 @@ ConversionResult copyString8to32(char32_t* dst, int dstSize, int& dstCount, cons ); } -void copyString32to8( - char* dst, int dstSize, const char32_t* src, int srcSize, int* dstCount -) { +int copyString32to8( char* dst, int dstSize, const char32_t* src, int srcSize ) { + int resCount( 0 ); if ( ! locale::is8BitEncoding ) { - int j = 0; - UBool is_error = 0; - - for (auto i = 0; i < srcSize; i ++) { - U8_APPEND ((uint8_t *)dst, j, dstSize, src[i], is_error); - - if (is_error) { - break; - } - } - - if (!is_error) { - if (dstCount) { - *dstCount = j; - } - - if (j < dstSize) { - dst[j] = '\0'; + const UTF32* sourceStart = reinterpret_cast(src); + const UTF32* sourceEnd = sourceStart + srcSize; + UTF8* targetStart = reinterpret_cast(dst); + UTF8* targetEnd = targetStart + dstSize; + + ConversionResult res = ConvertUTF32toUTF8( + &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion + ); + + if ( res == conversionOK ) { + resCount = static_cast( targetStart - reinterpret_cast( dst ) ); + if ( resCount < dstSize ) { + *targetStart = 0; } } } else { @@ -123,13 +96,12 @@ void copyString32to8( for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) { dst[i] = static_cast( src[i] ); } - if ( dstCount ) { - *dstCount = i; - } + resCount = i; if ( i < dstSize ) { dst[i] = 0; } } + return ( resCount ); } } diff --git a/contrib/replxx/src/conversion.hxx b/contrib/replxx/src/conversion.hxx index 1cb2d450d..6587ad0e2 100644 --- a/contrib/replxx/src/conversion.hxx +++ b/contrib/replxx/src/conversion.hxx @@ -1,20 +1,25 @@ #ifndef REPLXX_CONVERSION_HXX_INCLUDED #define REPLXX_CONVERSION_HXX_INCLUDED 1 -namespace replxx { +#include "ConvertUTF.h" + +#ifdef __has_include +#if __has_include( ) +#include +#endif +#endif +#if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) ) +namespace replxx { typedef unsigned char char8_t; +} +#endif -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; +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 ); -void copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize, int* dstCount = nullptr ); +int copyString32to8( char* dst, int dstSize, char32_t const* src, int srcSize ); namespace locale { extern bool is8BitEncoding; diff --git a/contrib/replxx/src/escape.cxx b/contrib/replxx/src/escape.cxx index 3edc4c1ec..dda1ab0be 100644 --- a/contrib/replxx/src/escape.cxx +++ b/contrib/replxx/src/escape.cxx @@ -1,5 +1,5 @@ #include "escape.hxx" -#include "io.hxx" +#include "terminal.hxx" #include "replxx.hxx" #ifndef _WIN32 @@ -115,6 +115,12 @@ static char32_t ctrlRightArrowKeyRoutine(char32_t) { 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; @@ -442,11 +448,35 @@ static char32_t escLeftBracket20SemicolonRoutine(char32_t c) { 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, escFailureRoutine + f9KeyRoutine, escLeftBracket20SemicolonRoutine, escLeftBracket200Routine, escLeftBracket201Routine, escFailureRoutine }; static CharacterDispatch escLeftBracket20Dispatch = { - 2, "~;", escLeftBracket20Routines + 4, "~;01", escLeftBracket20Routines }; static char32_t escLeftBracket20Routine(char32_t c) { c = read_unicode_character(); diff --git a/contrib/replxx/src/history.cxx b/contrib/replxx/src/history.cxx index 6c6eff346..fe691df08 100644 --- a/contrib/replxx/src/history.cxx +++ b/contrib/replxx/src/history.cxx @@ -1,147 +1,401 @@ +#include +#include #include #include #ifndef _WIN32 #include +#include #include #endif /* _WIN32 */ +#include "replxx.hxx" #include "history.hxx" -#include "utf8string.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 ) - : _data() + : _entries() , _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN ) - , _maxLineLength( 0 ) - , _index( 0 ) - , _previousIndex( -2 ) - , _recallMostRecent( false ) { -} - -void History::add( UnicodeString const& line ) { - if ( ( _maxSize > 0 ) && ( _data.empty() || ( line != _data.back() ) ) ) { - if ( size() > _maxSize ) { - _data.erase( _data.begin() ); - if ( -- _previousIndex < -1 ) { - _previousIndex = -2; - } - } - if ( static_cast( line.length() ) > _maxLineLength ) { - _maxLineLength = static_cast( line.length() ); - } - _data.push_back( line ); + , _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(); } -int History::save( std::string const& filename ) { #ifndef _WIN32 - mode_t old_umask = umask( S_IXUSR | S_IRWXG| S_IRWXO ); +class FileLock { + std::string _path; + int _lockFd; +public: + FileLock( std::string const& name_ ) + : _path( name_ + ".lock" ) + , _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) { + static_cast( ::lockf( _lockFd, F_LOCK, 0 ) == 0 ); + } + ~FileLock( void ) { + static_cast( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 ); + ::close( _lockFd ); + ::unlink( _path.c_str() ); + return; + } +}; #endif + +bool History::save( std::string const& filename, bool sync_ ) { +#ifndef _WIN32 + mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO ); + FileLock fileLock( filename ); +#endif + entries_t entries; + locations_t locations; + if ( ! sync_ ) { + entries.swap( _entries ); + locations.swap( _locations ); + _entries = entries; + reset_iters(); + } + do_load( filename ); + sort(); + remove_duplicates(); + trim_to_max_size(); ofstream histFile( filename ); if ( ! histFile ) { - return ( -1 ); + return ( false ); } #ifndef _WIN32 umask( old_umask ); chmod( filename.c_str(), S_IRUSR | S_IWUSR ); #endif Utf8String utf8; - for ( UnicodeString const& h : _data ) { - if ( ! h.is_empty() ) { - utf8.assign( h ); - histFile << utf8.get() << endl; + 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 ( 0 ); + return ( true ); +} + } -int History::load( std::string const& filename ) { +bool History::do_load( std::string const& filename ) { ifstream histFile( filename ); if ( ! histFile ) { - return ( -1 ); + 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() ) { - add( UnicodeString( line ) ); + _entries.emplace_back( when, UnicodeString( line ) ); } } - return 0; + return ( true ); +} + +bool History::load( std::string const& filename ) { + clear(); + bool success( do_load( filename ) ); + sort(); + remove_duplicates(); + trim_to_max_size(); + _previous = _current = last(); + _yankPos = _entries.end(); + return ( success ); +} + +void History::sort( void ) { + typedef std::vector sortable_entries_t; + _locations.clear(); + sortable_entries_t sortableEntries( _entries.begin(), _entries.end() ); + std::stable_sort( sortableEntries.begin(), sortableEntries.end() ); + _entries.clear(); + _entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() ); +} + +void History::clear( void ) { + _locations.clear(); + _entries.clear(); + _current = _entries.begin(); + _recallMostRecent = false; } void History::set_max_size( int size_ ) { if ( size_ >= 0 ) { _maxSize = size_; - int curSize( size() ); - if ( _maxSize < curSize ) { - _data.erase( _data.begin(), _data.begin() + ( curSize - _maxSize ) ); - } + trim_to_max_size(); } } -void History::reset_pos( int pos_ ) { - if ( pos_ == -1 ) { - _index = size() - 1; - _recallMostRecent = false; +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 { - _index = pos_; + _yankPos = moved( _entries.end(), -2 ); } + return ( resetYankSize ); } bool History::move( bool up_ ) { - if (_previousIndex != -2 && ! up_ ) { - _index = 1 + _previousIndex; // emulate Windows down-arrow + 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 { - _index += up_ ? -1 : 1; + _current = last(); } - _previousIndex = -2; - if (_index < 0) { - _index = 0; - return ( false ); - } else if ( _index >= size() ) { - _index = size() - 1; - return ( false ); + if ( reset_ ) { + _recallMostRecent = false; } - _recallMostRecent = true; - return ( true ); } -void History::jump( bool start_ ) { - _index = start_ ? 0 : size() - 1; - _previousIndex = -2; - _recallMostRecent = true; +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 direct( size() + ( back_ ? -1 : 1 ) ); - int i( ( _index + direct ) % _data.size() ); - while ( i != _index ) { - if ( _data[i].starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) { - _index = i; - _previousIndex = -2; - _recallMostRecent = true; + 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 ); } - i += direct; - i %= _data.size(); + move( it, step, true ); } return ( false ); } -UnicodeString const& History::operator[] ( int idx_ ) const { - return ( _data[ idx_ ] ); +bool History::move( entries_t::const_iterator& it_, int by_, bool wrapped_ ) const { + if ( by_ > 0 ) { + for ( int i( 0 ); i < by_; ++ i ) { + ++ it_; + if ( it_ != _entries.end() ) { + } else if ( wrapped_ ) { + it_ = _entries.begin(); + } else { + -- it_; + return ( false ); + } + } + } else { + for ( int i( 0 ); i > by_; -- i ) { + if ( it_ != _entries.begin() ) { + -- it_; + } else if ( wrapped_ ) { + it_ = last(); + } else { + return ( false ); + } + } + } + return ( true ); +} + +History::entries_t::const_iterator History::moved( entries_t::const_iterator it_, int by_, bool wrapped_ ) const { + move( it_, by_, wrapped_ ); + return ( it_ ); +} + +void History::erase( entries_t::const_iterator it_ ) { + bool invalidated( it_ == _current ); + _locations.erase( it_->text() ); + it_ = _entries.erase( it_ ); + if ( invalidated ) { + _current = it_; + } + if ( ( _current == _entries.end() ) && ! _entries.empty() ) { + -- _current; + } + _yankPos = _entries.end(); + _previous = _current; +} + +void History::trim_to_max_size( void ) { + while ( size() > _maxSize ) { + erase( _entries.begin() ); + } +} + +void History::remove_duplicate( UnicodeString const& line_ ) { + if ( ! _unique ) { + return; + } + locations_t::iterator it( _locations.find( line_ ) ); + if ( it == _locations.end() ) { + return; + } + erase( it->second ); +} + +void History::remove_duplicates( void ) { + if ( ! _unique ) { + return; + } + _locations.clear(); + typedef std::pair locations_insertion_result_t; + for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) { + locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) ); + if ( ! locationsInsertionResult.second ) { + _entries.erase( locationsInsertionResult.first->second ); + locationsInsertionResult.first->second = it; + } + } +} + +void History::update_last( UnicodeString const& line_ ) { + if ( _unique ) { + _locations.erase( _entries.back().text() ); + remove_duplicate( line_ ); + _locations.insert( make_pair( line_, last() ) ); + } + _entries.back() = Entry( now_ms_str(), line_ ); +} + +void History::drop_last( void ) { + erase( last() ); +} + +bool History::is_last( void ) const { + return ( _current == last() ); +} + +History::entries_t::const_iterator History::last( void ) const { + return ( moved( _entries.end(), -1 ) ); +} + +void History::reset_iters( void ) { + _previous = _current = last(); + _yankPos = _entries.end(); } } diff --git a/contrib/replxx/src/history.hxx b/contrib/replxx/src/history.hxx index 33aed148a..4e72c0367 100644 --- a/contrib/replxx/src/history.hxx +++ b/contrib/replxx/src/history.hxx @@ -1,70 +1,138 @@ #ifndef REPLXX_HISTORY_HXX_INCLUDED #define REPLXX_HISTORY_HXX_INCLUDED 1 -#include +#include +#include #include "unicodestring.hxx" +#include "utf8string.hxx" #include "conversion.hxx" +#include "util.hxx" + +namespace std { +template<> +struct hash { + std::size_t operator()( replxx::UnicodeString const& us_ ) const { + std::size_t h( 0 ); + char32_t const* p( us_.get() ); + char32_t const* e( p + us_.length() ); + while ( p != e ) { + h *= 31; + h += *p; + ++ p; + } + return ( h ); + } +}; +} namespace replxx { class History { public: - typedef std::vector lines_t; + class Entry { + std::string _timestamp; + UnicodeString _text; + public: + Entry( std::string const& timestamp_, UnicodeString const& text_ ) + : _timestamp( timestamp_ ) + , _text( text_ ) { + } + std::string const& timestamp( void ) const { + return ( _timestamp ); + } + UnicodeString const& text( void ) const { + return ( _text ); + } + bool operator < ( Entry const& other_ ) const { + return ( _timestamp < other_._timestamp ); + } + }; + typedef std::list entries_t; + typedef std::unordered_map locations_t; private: - lines_t _data; + entries_t _entries; + locations_t _locations; int _maxSize; - int _maxLineLength; - int _index; - int _previousIndex; + 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 ); - int save( std::string const& filename ); - int load( std::string const& filename ); + 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 reset_pos( int = -1 ); - UnicodeString const& operator[] ( int ) const; - void set_recall_most_recent( void ) { - _recallMostRecent = true; + 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 drop_last( void ) { - _data.pop_back(); - } void commit_index( void ) { - _previousIndex = _recallMostRecent ? _index : -2; - } - int current_pos( void ) const { - return ( _index ); - } - bool is_last( void ) const { - return ( _index == ( size() - 1 ) ); + _previous = _current; + _recallMostRecent = true; } bool is_empty( void ) const { - return ( _data.empty() ); - } - void update_last( UnicodeString const& line_ ) { - _data.back() = line_; + 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 ( _data[_index] ); + return ( _current->text() ); } - void jump( bool ); + 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( _data.size() ) ); - } - int max_line_length( void ) { - return ( _maxLineLength ); + return ( static_cast( _entries.size() ) ); } + Replxx::HistoryScan::impl_t scan( void ) const; + void save_pos( void ); + void restore_pos( void ); private: History( History const& ) = delete; History& operator = ( History const& ) = delete; + bool move( entries_t::const_iterator&, int, bool = false ) const; + entries_t::const_iterator moved( entries_t::const_iterator, int, bool = false ) const; + void erase( entries_t::const_iterator ); + void trim_to_max_size( void ); + void remove_duplicate( UnicodeString const& ); + void remove_duplicates( void ); + bool do_load( std::string const& ); + entries_t::const_iterator last( void ) const; + void sort( void ); + void reset_iters( void ); +}; + +class Replxx::HistoryScanImpl { + History::entries_t const& _entries; + History::entries_t::const_iterator _it; + mutable Utf8String _utf8Cache; + mutable Replxx::HistoryEntry _entryCache; + mutable bool _cacheValid; +public: + HistoryScanImpl( History::entries_t const& ); + bool next( void ); + Replxx::HistoryEntry const& get( void ) const; }; } diff --git a/contrib/replxx/src/killring.hxx b/contrib/replxx/src/killring.hxx index 9eca23a8e..0baf108e7 100644 --- a/contrib/replxx/src/killring.hxx +++ b/contrib/replxx/src/killring.hxx @@ -17,9 +17,11 @@ class KillRing { public: enum action { actionOther, actionKill, actionYank }; action lastAction; - size_t lastYankSize; - KillRing() : size(0), index(0), lastAction(actionOther) { + KillRing() + : size(0) + , index(0) + , lastAction(actionOther) { theRing.reserve(capacity); } diff --git a/contrib/replxx/src/prompt.cxx b/contrib/replxx/src/prompt.cxx index 391d3745b..c13ea808b 100644 --- a/contrib/replxx/src/prompt.cxx +++ b/contrib/replxx/src/prompt.cxx @@ -25,14 +25,13 @@ namespace replxx { Prompt::Prompt( Terminal& terminal_ ) : _extraLines( 0 ) , _lastLinePosition( 0 ) - , _previousInputLen( 0 ) - , _previousLen( 0 ) + , _cursorRowOffset( 0 ) , _screenColumns( 0 ) , _terminal( terminal_ ) { } void Prompt::write() { - _terminal.write32( _text.get(), _byteCount ); + _terminal.write32( _text.get(), _text.length() ); } void Prompt::update_screen_columns( void ) { @@ -40,28 +39,36 @@ void Prompt::update_screen_columns( void ) { } void Prompt::set_text( UnicodeString const& text_ ) { + _text = text_; + update_state(); +} + +void Prompt::update_state() { + _cursorRowOffset -= _extraLines; + _extraLines = 0; + _lastLinePosition = 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::const_iterator in( _text.begin() ); UnicodeString::iterator out( _text.begin() ); - int len = 0; + int visibleCount = 0; int x = 0; bool const strip = !tty::out; - while (in != text_.end()) { + while (in != _text.end()) { char32_t c = *in; if ('\n' == c || !is_control_code(c)) { *out = c; ++out; ++in; - ++len; + ++visibleCount; if ('\n' == c || ++x >= _screenColumns) { x = 0; ++_extraLines; - _lastLinePosition = len; + _lastLinePosition = visibleCount; } } else if (c == '\x1b') { if ( strip ) { @@ -69,7 +76,7 @@ void Prompt::set_text( UnicodeString const& text_ ) { ++in; if (*in == '[') { ++in; - while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { + while ( ( in != _text.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { ++in; } if (*in == 'm') { @@ -85,7 +92,7 @@ void Prompt::set_text( UnicodeString const& text_ ) { *out = *in; ++out; ++in; - while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { + while ( ( in != _text.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) { *out = *in; ++out; ++in; @@ -101,11 +108,15 @@ void Prompt::set_text( UnicodeString const& text_ ) { ++in; } } - _characterCount = len; - _byteCount = static_cast(out - _text.begin()); + _characterCount = visibleCount; + int charCount( static_cast( out - _text.begin() ) ); + _text.erase( charCount, _text.length() - charCount ); + + _cursorRowOffset += _extraLines; +} - _indentation = len - _lastLinePosition; - _cursorRowOffset = _extraLines; +int Prompt::indentation() const { + return _characterCount - _lastLinePosition; } // Used with DynamicPrompt (history search) @@ -113,37 +124,20 @@ void Prompt::set_text( UnicodeString const& text_ ) { const UnicodeString forwardSearchBasePrompt("(i-search)`"); const UnicodeString reverseSearchBasePrompt("(reverse-i-search)`"); const UnicodeString endSearchBasePrompt("': "); -UnicodeString previousSearchText; // remembered across invocations of replxx_input() DynamicPrompt::DynamicPrompt( Terminal& terminal_, int initialDirection ) : Prompt( terminal_ ) , _searchText() , _direction( initialDirection ) { - update_screen_columns(); - _cursorRowOffset = 0; - const UnicodeString* basePrompt = - (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; - size_t promptStartLength = basePrompt->length(); - _characterCount = static_cast(promptStartLength + endSearchBasePrompt.length()); - _byteCount = _characterCount; - _lastLinePosition = _characterCount; // TODO fix this, we are asssuming - // that the history prompt won't wrap (!) - _previousLen = _characterCount; - _text.assign( *basePrompt ).append( endSearchBasePrompt ); - calculate_screen_position( - 0, 0, screen_columns(), _characterCount, - _indentation, _extraLines - ); + updateSearchPrompt(); } void DynamicPrompt::updateSearchPrompt(void) { + update_screen_columns(); const UnicodeString* basePrompt = (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt; - size_t promptStartLength = basePrompt->length(); - _characterCount = static_cast(promptStartLength + _searchText.length() + - endSearchBasePrompt.length()); - _byteCount = _characterCount; _text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt ); + update_state(); } } diff --git a/contrib/replxx/src/prompt.hxx b/contrib/replxx/src/prompt.hxx index aabff0ab0..9ed3f5fd9 100644 --- a/contrib/replxx/src/prompt.hxx +++ b/contrib/replxx/src/prompt.hxx @@ -4,36 +4,32 @@ #include #include "unicodestring.hxx" -#include "io.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 _characterCount; // visible characters 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_state(); void update_screen_columns( void ); int screen_columns() const { return ( _screenColumns ); } void write(); + int indentation() const; }; -extern UnicodeString previousSearchText; // remembered across invocations of replxx_input() - // changing prompt for "(reverse-i-search)`text':" etc. // struct DynamicPrompt : public Prompt { diff --git a/contrib/replxx/src/replxx.cxx b/contrib/replxx/src/replxx.cxx index 7803d873a..29d35a231 100644 --- a/contrib/replxx/src/replxx.cxx +++ b/contrib/replxx/src/replxx.cxx @@ -102,7 +102,17 @@ #include "replxx.h" #include "replxx.hxx" #include "replxx_impl.hxx" -#include "io.hxx" +#include "history.hxx" + +static_assert( + static_cast( replxx::Replxx::ACTION::SEND_EOF ) == static_cast( REPLXX_ACTION_SEND_EOF ), + "C and C++ `ACTION` APIs are missaligned!" +); + +static_assert( + static_cast( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast( REPLXX_KEY_PASTE_FINISH ), + "C and C++ `KEY` APIs are missaligned!" +); using namespace std; using namespace std::placeholders; @@ -124,6 +134,10 @@ 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 ); } @@ -140,20 +154,28 @@ void Replxx::history_add( std::string const& line ) { _impl->history_add( line ); } -int Replxx::history_save( std::string const& filename ) { +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 ) ); } -int Replxx::history_load( std::string const& 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() ); } -std::string Replxx::history_line( int index ) { - return ( _impl->history_line( index ) ); +Replxx::HistoryScan Replxx::history_scan( void ) const { + return ( _impl->history_scan() ); } void Replxx::set_preload_buffer( std::string const& preloadText ) { @@ -188,6 +210,14 @@ 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 ); } @@ -212,6 +242,10 @@ 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() ); } @@ -224,6 +258,14 @@ 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_ ); @@ -236,6 +278,10 @@ void Replxx::print( char const* format_, ... ) { return ( _impl->print( buf.get(), size ) ); } +void Replxx::write( char const* str, int length ) { + return ( _impl->print( str, length ) ); +} + } ::Replxx* replxx_init() { @@ -271,6 +317,16 @@ void replxx_bind_key( ::Replxx* replxx_, int code_, key_press_handler_t handler_ replxx->bind_key( code_, std::bind( key_press_handler_forwarder, handler_, _1, userData_ ) ); } +int replxx_bind_key_internal( ::Replxx* replxx_, int code_, char const* actionName_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + try { + replxx->bind_key_internal( code_, actionName_ ); + } catch ( ... ) { + return ( -1 ); + } + return ( 0 ); +} + void replxx_get_state( ::Replxx* replxx_, ReplxxState* state ) { replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); replxx::Replxx::State s( replxx->get_state() ); @@ -303,8 +359,8 @@ void replxx_set_preload_buffer(::Replxx* replxx_, const char* preloadText) { * user * * @param prompt text of prompt to display to the user - * @return the returned string belongs to the caller on return and must be - * freed to prevent memory leaks + * @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_ ) ); @@ -329,6 +385,16 @@ int replxx_print( ::Replxx* replxx_, char const* format_, ... ) { return ( size ); } +int replxx_write( ::Replxx* replxx_, char const* str, int length ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + try { + replxx->print( str, length ); + } catch ( ... ) { + return ( -1 ); + } + return static_cast( length ); +} + struct replxx_completions { replxx::Replxx::completions_t data; }; @@ -337,6 +403,23 @@ struct replxx_hints { replxx::Replxx::hints_t data; }; +void modify_fwd( replxx_modify_callback_t fn, std::string& line_, int& cursorPosition_, void* userData_ ) { +#ifdef _WIN32 +#define strdup _strdup +#endif + char* s( strdup( line_.c_str() ) ); +#undef strdup + fn( &s, &cursorPosition_, userData_ ); + line_ = s; + free( s ); + return; +} + +void replxx_set_modify_callback(::Replxx* replxx_, replxx_modify_callback_t* fn, void* userData) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_modify_callback( std::bind( &modify_fwd, fn, _1, _2, userData ) ); +} + replxx::Replxx::completions_t completions_fwd( replxx_completion_callback_t fn, std::string const& input_, int& contextLen_, void* userData ) { replxx_completions completions; fn( input_.c_str(), &completions, &contextLen_, userData ); @@ -359,7 +442,7 @@ void highlighter_fwd( replxx_highlighter_callback_t fn, std::string const& input return ( static_cast( c ) ); } ); - fn( input.c_str(), colorsTmp.data(), colors.size(), userData ); + fn( input.c_str(), colorsTmp.data(), static_cast( colors.size() ), userData ); std::transform( colorsTmp.begin(), colorsTmp.end(), @@ -395,7 +478,7 @@ 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 ) { +void replxx_add_color_completion( replxx_completions* lc, const char* str, ReplxxColor color ) { lc->data.emplace_back( str, static_cast( color ) ); } @@ -449,19 +532,58 @@ void replxx_set_beep_on_ambiguous_completion( ::Replxx* replxx_, int val ) { replxx->set_beep_on_ambiguous_completion( val ? true : false ); } -/* Fetch a line of the history by (zero-based) index. If the requested - * line does not exist, NULL is returned. The return value is a heap-allocated - * copy of the line. */ -char const* replxx_history_line( ::Replxx* replxx_, int index ) { +void replxx_set_immediate_completion( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_immediate_completion( val ? true : false ); +} + +void replxx_set_unique_history( ::Replxx* replxx_, int val ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->set_unique_history( val ? true : false ); +} + +void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->enable_bracketed_paste(); +} + +void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) { replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); - return ( replxx->history_line( index ).c_str() ); + replxx->disable_bracketed_paste(); +} + +ReplxxHistoryScan* replxx_history_scan_start( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( reinterpret_cast( replxx->history_scan().release() ) ); +} + +void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) { + delete reinterpret_cast( historyScan_ ); +} + +int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) { + replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast( historyScan_ ) ); + bool hasNext( historyScan->next() ); + if ( hasNext ) { + replxx::Replxx::HistoryEntry const& historyEntry( historyScan->get() ); + historyEntry_->timestamp = historyEntry.timestamp().c_str(); + historyEntry_->text = historyEntry.text().c_str(); + } + return ( hasNext ? 0 : -1 ); +} + +/* Save the history in the specified file. On success 0 is returned + * otherwise -1 is returned. */ +int replxx_history_sync( ::Replxx* replxx_, const char* filename ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + return ( replxx->history_sync( filename ) ? 0 : -1 ); } /* Save the history in the specified file. On success 0 is returned * otherwise -1 is returned. */ int replxx_history_save( ::Replxx* replxx_, const char* filename ) { replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); - return ( replxx->history_save( filename ) ); + return ( replxx->history_save( filename ) ? 0 : -1 ); } /* Load the history from the specified file. If the file does not exist @@ -471,7 +593,12 @@ int replxx_history_save( ::Replxx* replxx_, const char* filename ) { * on error -1 is returned. */ int replxx_history_load( ::Replxx* replxx_, const char* filename ) { replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); - return ( replxx->history_load( filename ) ); + return ( replxx->history_load( filename ) ? 0 : -1 ); +} + +void replxx_history_clear( ::Replxx* replxx_ ) { + replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast( replxx_ ) ); + replxx->history_clear(); } int replxx_history_size( ::Replxx* replxx_ ) { diff --git a/contrib/replxx/src/replxx_impl.cxx b/contrib/replxx/src/replxx_impl.cxx index 14f3cbd4d..28152d58d 100644 --- a/contrib/replxx/src/replxx_impl.cxx +++ b/contrib/replxx/src/replxx_impl.cxx @@ -2,6 +2,7 @@ #include #include #include +#include #ifdef _WIN32 @@ -29,7 +30,7 @@ #include "utf8string.hxx" #include "prompt.hxx" #include "util.hxx" -#include "io.hxx" +#include "terminal.hxx" #include "history.hxx" #include "replxx.hxx" @@ -37,30 +38,69 @@ using namespace std; namespace replxx { -#ifndef _WIN32 - -bool gotResize = false; - -#endif - namespace { +namespace action_names { + +char const INSERT_CHARACTER[] = "insert_character"; +char const NEW_LINE[] = "new_line"; +char const MOVE_CURSOR_TO_BEGINING_OF_LINE[] = "move_cursor_to_begining_of_line"; +char const MOVE_CURSOR_TO_END_OF_LINE[] = "move_cursor_to_end_of_line"; +char const MOVE_CURSOR_LEFT[] = "move_cursor_left"; +char const MOVE_CURSOR_RIGHT[] = "move_cursor_right"; +char const MOVE_CURSOR_ONE_WORD_LEFT[] = "move_cursor_one_word_left"; +char const MOVE_CURSOR_ONE_WORD_RIGHT[] = "move_cursor_one_word_right"; +char const MOVE_CURSOR_ONE_SUBWORD_LEFT[] = "move_cursor_one_subword_left"; +char const MOVE_CURSOR_ONE_SUBWORD_RIGHT[] = "move_cursor_one_subword_right"; +char const KILL_TO_WHITESPACE_ON_LEFT[] = "kill_to_whitespace_on_left"; +char const KILL_TO_END_OF_WORD[] = "kill_to_end_of_word"; +char const KILL_TO_END_OF_SUBWORD[] = "kill_to_end_of_subword"; +char const KILL_TO_BEGINING_OF_WORD[] = "kill_to_begining_of_word"; +char const KILL_TO_BEGINING_OF_SUBWORD[] = "kill_to_begining_of_subword"; +char const KILL_TO_BEGINING_OF_LINE[] = "kill_to_begining_of_line"; +char const KILL_TO_END_OF_LINE[] = "kill_to_end_of_line"; +char const YANK[] = "yank"; +char const YANK_CYCLE[] = "yank_cycle"; +char const YANK_LAST_ARG[] = "yank_last_arg"; +char const CAPITALIZE_WORD[] = "capitalize_word"; +char const LOWERCASE_WORD[] = "lowercase_word"; +char const UPPERCASE_WORD[] = "uppercase_word"; +char const CAPITALIZE_SUBWORD[] = "capitalize_subword"; +char const LOWERCASE_SUBWORD[] = "lowercase_subword"; +char const UPPERCASE_SUBWORD[] = "uppercase_subword"; +char const TRANSPOSE_CHARACTERS[] = "transpose_characters"; +char const ABORT_LINE[] = "abort_line"; +char const SEND_EOF[] = "send_eof"; +char const TOGGLE_OVERWRITE_MODE[] = "toggle_overwrite_mode"; +char const DELETE_CHARACTER_UNDER_CURSOR[] = "delete_character_under_cursor"; +char const DELETE_CHARACTER_LEFT_OF_CURSOR[] = "delete_character_left_of_cursor"; +char const COMMIT_LINE[] = "commit_line"; +char const CLEAR_SCREEN[] = "clear_screen"; +char const COMPLETE_NEXT[] = "complete_next"; +char const COMPLETE_PREVIOUS[] = "complete_previous"; +char const HISTORY_NEXT[] = "history_next"; +char const HISTORY_PREVIOUS[] = "history_previous"; +char const HISTORY_LAST[] = "history_last"; +char const HISTORY_FIRST[] = "history_first"; +char const HINT_PREVIOUS[] = "hint_previous"; +char const HINT_NEXT[] = "hint_next"; +char const VERBATIM_INSERT[] = "verbatim_insert"; +char const SUSPEND[] = "suspend"; +char const COMPLETE_LINE[] = "complete_line"; +char const HISTORY_INCREMENTAL_SEARCH[] = "history_incremental_search"; +char const HISTORY_COMMON_PREFIX_SEARCH[] = "history_common_prefix_search"; +} + static int const REPLXX_MAX_HINT_ROWS( 4 ); /* * All whitespaces and all non-alphanumerical characters from ASCII range * with an exception of an underscore ('_'). */ -char const defaultBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?"; - -#ifndef _WIN32 - -static void WindowSizeChanged(int) { - // do nothing here but setting this flag - gotResize = true; -} - -#endif - +char const defaultWordBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?"; +/* + * All whitespaces and all non-alphanumerical characters from ASCII range + */ +char const defaultSubwordBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?_"; static const char* unsupported_term[] = {"dumb", "cons25", "emacs", NULL}; static bool isUnsupportedTerm(void) { @@ -76,29 +116,57 @@ static bool isUnsupportedTerm(void) { return false; } +int long long RAPID_REFRESH_MS = 1; +int long long RAPID_REFRESH_US = RAPID_REFRESH_MS * 1000; + +inline int long long now_us( void ) { + return ( std::chrono::duration_cast( std::chrono::high_resolution_clock::now().time_since_epoch() ).count() ); +} + +class IOModeGuard { + Terminal& _terminal; +public: + IOModeGuard( Terminal& terminal_ ) + : _terminal( terminal_ ) { + _terminal.disable_raw_mode(); + } + ~IOModeGuard( void ) { + try { + _terminal.enable_raw_mode(); + } catch ( ... ) { + } + } +}; + } Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* ) : _utf8Buffer() , _data() - , _charWidths() + , _pos( 0 ) , _display() , _displayInputLength( 0 ) , _hint() - , _pos( 0 ) , _prefix( 0 ) , _hintSelection( -1 ) , _history() , _killRing() + , _lastRefreshTime( now_us() ) + , _refreshSkipped( false ) + , _lastYankSize( 0 ) , _maxHintRows( REPLXX_MAX_HINT_ROWS ) , _hintDelay( 0 ) - , _breakChars( defaultBreakChars ) + , _wordBreakChars( defaultWordBreakChars ) + , _subwordBreakChars( defaultSubwordBreakChars ) , _completionCountCutoff( 100 ) , _overwrite( false ) , _doubleTabCompletion( false ) , _completeOnEmpty( true ) , _beepOnAmbiguousCompletion( false ) + , _immediateCompletion( true ) + , _bracketedPaste( false ) , _noColor( false ) + , _namedActions() , _keyPressHandlers() , _terminal() , _currentThread() @@ -113,89 +181,159 @@ Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* ) , _completionSelection( -1 ) , _preloadedBuffer() , _errorMessage() + , _previousSearchText() , _modifiedState( false ) + , _hintColor( Replxx::Color::GRAY ) + , _hintsCache() + , _hintContextLenght( -1 ) + , _hintSeed() , _mutex() { using namespace std::placeholders; - bind_key( Replxx::KEY::control( 'A' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ) ); - bind_key( Replxx::KEY::HOME + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'E' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 ) ); - bind_key( Replxx::KEY::END + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'B' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 ) ); - bind_key( Replxx::KEY::LEFT + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 ) ); - bind_key( Replxx::KEY::control( 'F' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 ) ); - bind_key( Replxx::KEY::RIGHT + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 ) ); - bind_key( Replxx::KEY::meta( 'b' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) ); - bind_key( Replxx::KEY::meta( 'B' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) ); - bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) ); - bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ) ); // Emacs allows Meta, readline don't - bind_key( Replxx::KEY::meta( 'f' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) ); - bind_key( Replxx::KEY::meta( 'F' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) ); - bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) ); - bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ) ); // Emacs allows Meta, readline don't - bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT, _1 ) ); - bind_key( Replxx::KEY::meta( 'd' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 ) ); - bind_key( Replxx::KEY::meta( 'D' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 ) ); - bind_key( Replxx::KEY::control( 'W' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD, _1 ) ); - bind_key( Replxx::KEY::control( 'U' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'K' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'Y' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK, _1 ) ); - bind_key( Replxx::KEY::meta( 'y' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 ) ); - bind_key( Replxx::KEY::meta( 'Y' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 ) ); - bind_key( Replxx::KEY::meta( 'c' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 ) ); - bind_key( Replxx::KEY::meta( 'C' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 ) ); - bind_key( Replxx::KEY::meta( 'l' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 ) ); - bind_key( Replxx::KEY::meta( 'L' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 ) ); - bind_key( Replxx::KEY::meta( 'u' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 ) ); - bind_key( Replxx::KEY::meta( 'U' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 ) ); - bind_key( Replxx::KEY::control( 'T' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS, _1 ) ); - bind_key( Replxx::KEY::control( 'C' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'D' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF, _1 ) ); - bind_key( Replxx::KEY::INSERT + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE, _1 ) ); - bind_key( 127, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 ) ); - bind_key( Replxx::KEY::DELETE + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 ) ); - bind_key( Replxx::KEY::BACKSPACE + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 ) ); - bind_key( Replxx::KEY::control( 'J' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 ) ); - bind_key( Replxx::KEY::ENTER + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'L' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN, _1 ) ); - bind_key( Replxx::KEY::control( 'N' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT, _1 ) ); - bind_key( Replxx::KEY::control( 'P' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS, _1 ) ); - bind_key( Replxx::KEY::DOWN + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 ) ); - bind_key( Replxx::KEY::UP + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 ) ); - bind_key( Replxx::KEY::meta( '>' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ) ); - bind_key( Replxx::KEY::meta( '<' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ) ); - bind_key( Replxx::KEY::PAGE_DOWN + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ) ); - bind_key( Replxx::KEY::PAGE_UP + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ) ); - bind_key( Replxx::KEY::control( Replxx::KEY::UP ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS, _1 ) ); - bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT, _1 ) ); + _namedActions[action_names::INSERT_CHARACTER] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::INSERT_CHARACTER, _1 ); + _namedActions[action_names::NEW_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::NEW_LINE, _1 ); + _namedActions[action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 ); + _namedActions[action_names::MOVE_CURSOR_TO_END_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE, _1 ); + _namedActions[action_names::MOVE_CURSOR_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT, _1 ); + _namedActions[action_names::MOVE_CURSOR_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT, _1 ); + _namedActions[action_names::MOVE_CURSOR_ONE_WORD_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT, _1 ); + _namedActions[action_names::MOVE_CURSOR_ONE_WORD_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT, _1 ); + _namedActions[action_names::MOVE_CURSOR_ONE_SUBWORD_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT, _1 ); + _namedActions[action_names::MOVE_CURSOR_ONE_SUBWORD_RIGHT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT, _1 ); + _namedActions[action_names::KILL_TO_WHITESPACE_ON_LEFT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT, _1 ); + _namedActions[action_names::KILL_TO_END_OF_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD, _1 ); + _namedActions[action_names::KILL_TO_BEGINING_OF_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD, _1 ); + _namedActions[action_names::KILL_TO_END_OF_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_SUBWORD, _1 ); + _namedActions[action_names::KILL_TO_BEGINING_OF_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_SUBWORD, _1 ); + _namedActions[action_names::KILL_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE, _1 ); + _namedActions[action_names::KILL_TO_END_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE, _1 ); + _namedActions[action_names::YANK] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK, _1 ); + _namedActions[action_names::YANK_CYCLE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE, _1 ); + _namedActions[action_names::YANK_LAST_ARG] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_LAST_ARG, _1 ); + _namedActions[action_names::CAPITALIZE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD, _1 ); + _namedActions[action_names::LOWERCASE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD, _1 ); + _namedActions[action_names::UPPERCASE_WORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD, _1 ); + _namedActions[action_names::CAPITALIZE_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_SUBWORD, _1 ); + _namedActions[action_names::LOWERCASE_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_SUBWORD, _1 ); + _namedActions[action_names::UPPERCASE_SUBWORD] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_SUBWORD, _1 ); + _namedActions[action_names::TRANSPOSE_CHARACTERS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS, _1 ); + _namedActions[action_names::ABORT_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE, _1 ); + _namedActions[action_names::SEND_EOF] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF, _1 ); + _namedActions[action_names::TOGGLE_OVERWRITE_MODE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE, _1 ); + _namedActions[action_names::DELETE_CHARACTER_UNDER_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR, _1 ); + _namedActions[action_names::DELETE_CHARACTER_LEFT_OF_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 ); + _namedActions[action_names::COMMIT_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE, _1 ); + _namedActions[action_names::CLEAR_SCREEN] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN, _1 ); + _namedActions[action_names::COMPLETE_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT, _1 ); + _namedActions[action_names::COMPLETE_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS, _1 ); + _namedActions[action_names::HISTORY_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT, _1 ); + _namedActions[action_names::HISTORY_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS, _1 ); + _namedActions[action_names::HISTORY_LAST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST, _1 ); + _namedActions[action_names::HISTORY_FIRST] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST, _1 ); + _namedActions[action_names::HINT_PREVIOUS] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS, _1 ); + _namedActions[action_names::HINT_NEXT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT, _1 ); +#ifndef _WIN32 + _namedActions[action_names::VERBATIM_INSERT] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT, _1 ); + _namedActions[action_names::SUSPEND] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND, _1 ); +#else + _namedActions[action_names::VERBATIM_INSERT] = _namedActions[action_names::SUSPEND] = Replxx::key_press_handler_t(); +#endif + _namedActions[action_names::COMPLETE_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE, _1 ); + _namedActions[action_names::HISTORY_INCREMENTAL_SEARCH] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 ); + _namedActions[action_names::HISTORY_COMMON_PREFIX_SEARCH] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ); + + bind_key( Replxx::KEY::control( 'A' ), _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) ); + bind_key( Replxx::KEY::HOME + 0, _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'E' ), _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) ); + bind_key( Replxx::KEY::END + 0, _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'B' ), _namedActions.at( action_names::MOVE_CURSOR_LEFT ) ); + bind_key( Replxx::KEY::LEFT + 0, _namedActions.at( action_names::MOVE_CURSOR_LEFT ) ); + bind_key( Replxx::KEY::control( 'F' ), _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) ); + bind_key( Replxx::KEY::RIGHT + 0, _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) ); + bind_key( Replxx::KEY::meta( 'b' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); + bind_key( Replxx::KEY::meta( 'B' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_SUBWORD_LEFT ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); + bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); // Emacs allows Meta, readline don't + bind_key( Replxx::KEY::meta( 'f' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); + bind_key( Replxx::KEY::meta( 'F' ), _namedActions.at( action_names::MOVE_CURSOR_ONE_SUBWORD_RIGHT ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); + bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ), _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); // Emacs allows Meta, readline don't + bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), _namedActions.at( action_names::KILL_TO_WHITESPACE_ON_LEFT ) ); + bind_key( Replxx::KEY::meta( 'd' ), _namedActions.at( action_names::KILL_TO_END_OF_WORD ) ); + bind_key( Replxx::KEY::meta( 'D' ), _namedActions.at( action_names::KILL_TO_END_OF_SUBWORD ) ); + bind_key( Replxx::KEY::control( 'W' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_WORD ) ); + bind_key( Replxx::KEY::meta( 'W' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_SUBWORD ) ); + bind_key( Replxx::KEY::control( 'U' ), _namedActions.at( action_names::KILL_TO_BEGINING_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'K' ), _namedActions.at( action_names::KILL_TO_END_OF_LINE ) ); + bind_key( Replxx::KEY::control( 'Y' ), _namedActions.at( action_names::YANK ) ); + bind_key( Replxx::KEY::meta( 'y' ), _namedActions.at( action_names::YANK_CYCLE ) ); + bind_key( Replxx::KEY::meta( 'Y' ), _namedActions.at( action_names::YANK_CYCLE ) ); + bind_key( Replxx::KEY::meta( '.' ), _namedActions.at( action_names::YANK_LAST_ARG ) ); + bind_key( Replxx::KEY::meta( 'c' ), _namedActions.at( action_names::CAPITALIZE_WORD ) ); + bind_key( Replxx::KEY::meta( 'C' ), _namedActions.at( action_names::CAPITALIZE_SUBWORD ) ); + bind_key( Replxx::KEY::meta( 'l' ), _namedActions.at( action_names::LOWERCASE_WORD ) ); + bind_key( Replxx::KEY::meta( 'L' ), _namedActions.at( action_names::LOWERCASE_SUBWORD ) ); + bind_key( Replxx::KEY::meta( 'u' ), _namedActions.at( action_names::UPPERCASE_WORD ) ); + bind_key( Replxx::KEY::meta( 'U' ), _namedActions.at( action_names::UPPERCASE_SUBWORD ) ); + bind_key( Replxx::KEY::control( 'T' ), _namedActions.at( action_names::TRANSPOSE_CHARACTERS ) ); + bind_key( Replxx::KEY::control( 'C' ), _namedActions.at( action_names::ABORT_LINE ) ); + bind_key( Replxx::KEY::control( 'D' ), _namedActions.at( action_names::SEND_EOF ) ); + bind_key( Replxx::KEY::INSERT + 0, _namedActions.at( action_names::TOGGLE_OVERWRITE_MODE ) ); + bind_key( 127, _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) ); + bind_key( Replxx::KEY::DELETE + 0, _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) ); + bind_key( Replxx::KEY::BACKSPACE + 0, _namedActions.at( action_names::DELETE_CHARACTER_LEFT_OF_CURSOR ) ); + bind_key( Replxx::KEY::control( 'J' ), _namedActions.at( action_names::NEW_LINE ) ); + bind_key( Replxx::KEY::ENTER + 0, _namedActions.at( action_names::COMMIT_LINE ) ); + bind_key( Replxx::KEY::control( 'L' ), _namedActions.at( action_names::CLEAR_SCREEN ) ); + bind_key( Replxx::KEY::control( 'N' ), _namedActions.at( action_names::COMPLETE_NEXT ) ); + bind_key( Replxx::KEY::control( 'P' ), _namedActions.at( action_names::COMPLETE_PREVIOUS ) ); + bind_key( Replxx::KEY::DOWN + 0, _namedActions.at( action_names::HISTORY_NEXT ) ); + bind_key( Replxx::KEY::UP + 0, _namedActions.at( action_names::HISTORY_PREVIOUS ) ); + bind_key( Replxx::KEY::meta( '<' ), _namedActions.at( action_names::HISTORY_FIRST ) ); + bind_key( Replxx::KEY::PAGE_UP + 0, _namedActions.at( action_names::HISTORY_FIRST ) ); + bind_key( Replxx::KEY::meta( '>' ), _namedActions.at( action_names::HISTORY_LAST ) ); + bind_key( Replxx::KEY::PAGE_DOWN + 0, _namedActions.at( action_names::HISTORY_LAST ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::UP ), _namedActions.at( action_names::HINT_PREVIOUS ) ); + bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ), _namedActions.at( action_names::HINT_NEXT ) ); #ifndef _WIN32 - bind_key( Replxx::KEY::control( 'V' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT, _1 ) ); - bind_key( Replxx::KEY::control( 'Z' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND, _1 ) ); + bind_key( Replxx::KEY::control( 'V' ), _namedActions.at( action_names::VERBATIM_INSERT ) ); + bind_key( Replxx::KEY::control( 'Z' ), _namedActions.at( action_names::SUSPEND ) ); #endif - bind_key( Replxx::KEY::TAB + 0, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE, _1 ) ); - bind_key( Replxx::KEY::control( 'R' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 ) ); - bind_key( Replxx::KEY::control( 'S' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH, _1 ) ); - bind_key( Replxx::KEY::meta( 'p' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) ); - bind_key( Replxx::KEY::meta( 'P' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) ); - bind_key( Replxx::KEY::meta( 'n' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) ); - bind_key( Replxx::KEY::meta( 'N' ), std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH, _1 ) ); + bind_key( Replxx::KEY::TAB + 0, _namedActions.at( action_names::COMPLETE_LINE ) ); + bind_key( Replxx::KEY::control( 'R' ), _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) ); + bind_key( Replxx::KEY::control( 'S' ), _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'p' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'P' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'n' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::meta( 'N' ), _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) ); + bind_key( Replxx::KEY::PASTE_START, std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::BRACKETED_PASTE, _1 ) ); +} + +Replxx::ReplxxImpl::~ReplxxImpl( void ) { + disable_bracketed_paste(); } Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32_t code ) { switch ( action_ ) { - case ( Replxx::ACTION::INSERT_CHARACTER ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::insert_character, code ) ); - case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::delete_character, code ) ); - case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::backspace_character, code ) ); - case ( Replxx::ACTION::KILL_TO_END_OF_LINE ): return ( action( WANT_REFRESH | SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_end_of_line, code ) ); - case ( Replxx::ACTION::KILL_TO_BEGINING_OF_LINE ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_begining_of_line, code ) ); - case ( Replxx::ACTION::KILL_TO_END_OF_WORD ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_word_to_right, code ) ); - case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_word_to_left, code ) ); - case ( Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT ): return ( action( SET_KILL_ACTION, &Replxx::ReplxxImpl::kill_to_whitespace_to_left, code ) ); - case ( Replxx::ACTION::YANK ): return ( action( NOOP, &Replxx::ReplxxImpl::yank, code ) ); - case ( Replxx::ACTION::YANK_CYCLE ): return ( action( NOOP, &Replxx::ReplxxImpl::yank_cycle, code ) ); + case ( Replxx::ACTION::INSERT_CHARACTER ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, code ) ); + case ( Replxx::ACTION::NEW_LINE ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::new_line, code ) ); + case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::delete_character, code ) ); + case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::backspace_character, code ) ); + case ( Replxx::ACTION::KILL_TO_END_OF_LINE ): return ( action( WANT_REFRESH | SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_end_of_line, code ) ); + case ( Replxx::ACTION::KILL_TO_BEGINING_OF_LINE ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_begining_of_line, code ) ); + case ( Replxx::ACTION::KILL_TO_END_OF_WORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_right, code ) ); + case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left, code ) ); + case ( Replxx::ACTION::KILL_TO_END_OF_SUBWORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_right, code ) ); + case ( Replxx::ACTION::KILL_TO_BEGINING_OF_SUBWORD ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left, code ) ); + case ( Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT ): return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_to_whitespace_to_left, code ) ); + case ( Replxx::ACTION::YANK ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank, code ) ); + case ( Replxx::ACTION::YANK_CYCLE ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank_cycle, code ) ); + case ( Replxx::ACTION::YANK_LAST_ARG ): return ( action( HISTORY_RECALL_MOST_RECENT | DONT_RESET_HIST_YANK_INDEX, &Replxx::ReplxxImpl::yank_last_arg, code ) ); case ( Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_begining_of_line, code ) ); case ( Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_end_of_line, code ) ); - case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left, code ) ); - case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left, code ) ); + case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right, code ) ); case ( Replxx::ACTION::MOVE_CURSOR_LEFT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_left, code ) ); case ( Replxx::ACTION::MOVE_CURSOR_RIGHT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_right, code ) ); case ( Replxx::ACTION::HISTORY_NEXT ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_next, code ) ); @@ -206,10 +344,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32 case ( Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH ): return ( action( RESET_KILL_ACTION | DONT_RESET_PREFIX, &Replxx::ReplxxImpl::common_prefix_search, code ) ); case ( Replxx::ACTION::HINT_NEXT ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_next, code ) ); case ( Replxx::ACTION::HINT_PREVIOUS ): return ( action( NOOP, &Replxx::ReplxxImpl::hint_previous, code ) ); - case ( Replxx::ACTION::CAPITALIZE_WORD ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::capitalize_word, code ) ); - case ( Replxx::ACTION::LOWERCASE_WORD ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::lowercase_word, code ) ); - case ( Replxx::ACTION::UPPERCASE_WORD ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::uppercase_word, code ) ); - case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::transpose_characters, code ) ); + case ( Replxx::ACTION::CAPITALIZE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word, code ) ); + case ( Replxx::ACTION::LOWERCASE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word, code ) ); + case ( Replxx::ACTION::UPPERCASE_WORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word, code ) ); + case ( Replxx::ACTION::CAPITALIZE_SUBWORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word, code ) ); + case ( Replxx::ACTION::LOWERCASE_SUBWORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word, code ) ); + case ( Replxx::ACTION::UPPERCASE_SUBWORD ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word, code ) ); + case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::transpose_characters, code ) ); case ( Replxx::ACTION::TOGGLE_OVERWRITE_MODE ): return ( action( NOOP, &Replxx::ReplxxImpl::toggle_overwrite_mode, code ) ); #ifndef _WIN32 case ( Replxx::ACTION::VERBATIM_INSERT ): return ( action( WANT_REFRESH | RESET_KILL_ACTION, &Replxx::ReplxxImpl::verbatim_insert, code ) ); @@ -218,12 +359,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32 case ( Replxx::ACTION::CLEAR_SCREEN ): return ( action( NOOP, &Replxx::ReplxxImpl::clear_screen, code ) ); case ( Replxx::ACTION::CLEAR_SELF ): clear_self_to_end_of_screen(); return ( Replxx::ACTION_RESULT::CONTINUE ); case ( Replxx::ACTION::REPAINT ): repaint(); return ( Replxx::ACTION_RESULT::CONTINUE ); - case ( Replxx::ACTION::COMPLETE_LINE ): return ( action( NOOP, &Replxx::ReplxxImpl::complete_line, code ) ); - case ( Replxx::ACTION::COMPLETE_NEXT ): return ( action( DONT_RESET_COMPLETIONS, &Replxx::ReplxxImpl::complete_next, code ) ); - case ( Replxx::ACTION::COMPLETE_PREVIOUS ): return ( action( DONT_RESET_COMPLETIONS, &Replxx::ReplxxImpl::complete_previous, code ) ); + case ( Replxx::ACTION::COMPLETE_LINE ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_line, code ) ); + case ( Replxx::ACTION::COMPLETE_NEXT ): return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_next, code ) ); + case ( Replxx::ACTION::COMPLETE_PREVIOUS ): return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_previous, code ) ); case ( Replxx::ACTION::COMMIT_LINE ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::commit_line, code ) ); - case ( Replxx::ACTION::ABORT_LINE ): return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::abort_line, code ) ); - case ( Replxx::ACTION::SEND_EOF ): return ( action( NOOP, &Replxx::ReplxxImpl::send_eof, code ) ); + case ( Replxx::ACTION::ABORT_LINE ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::abort_line, code ) ); + case ( Replxx::ACTION::SEND_EOF ): return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::send_eof, code ) ); + case ( Replxx::ACTION::BRACKETED_PASTE ): return ( action( WANT_REFRESH | RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::bracketed_paste, code ) ); } return ( Replxx::ACTION_RESULT::BAIL ); } @@ -232,6 +374,16 @@ void Replxx::ReplxxImpl::bind_key( char32_t code_, Replxx::key_press_handler_t h _keyPressHandlers[code_] = handler_; } +void Replxx::ReplxxImpl::bind_key_internal( char32_t code_, char const* actionName_ ) { + named_actions_t::const_iterator it( _namedActions.find( actionName_ ) ); + if ( it == _namedActions.end() ) { + throw std::runtime_error( std::string( "replxx: Unknown action name: " ).append( actionName_ ) ); + } + if ( !! it->second ) { + bind_key( code_, it->second ); + } +} + Replxx::State Replxx::ReplxxImpl::get_state( void ) const { _utf8Buffer.assign( _data ); return ( Replxx::State( _utf8Buffer.get(), _pos ) ); @@ -254,22 +406,35 @@ char32_t Replxx::ReplxxImpl::read_char( HINT_ACTION hintAction_ ) { return ( keyPress ); } } - int hintDelay( hintAction_ != HINT_ACTION::SKIP ? _hintDelay : 0 ); + int hintDelay( + _refreshSkipped + ? static_cast( RAPID_REFRESH_MS * 2 ) + : ( hintAction_ != HINT_ACTION::SKIP ? _hintDelay : 0 ) + ); while ( true ) { Terminal::EVENT_TYPE eventType( _terminal.wait_for_input( hintDelay ) ); if ( eventType == Terminal::EVENT_TYPE::TIMEOUT ) { - refresh_line( HINT_ACTION::REPAINT ); + refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::REPAINT ); hintDelay = 0; + _refreshSkipped = false; continue; } if ( eventType == Terminal::EVENT_TYPE::KEY_PRESS ) { break; } + if ( eventType == Terminal::EVENT_TYPE::RESIZE ) { + // caught a window resize event + // now redraw the prompt and line + _prompt.update_screen_columns(); + // redraw the original prompt with current input + refresh_line( HINT_ACTION::REPAINT ); + continue; + } std::lock_guard l( _mutex ); clear_self_to_end_of_screen(); while ( ! _messages.empty() ) { string const& message( _messages.front() ); - _terminal.write8( message.data(), message.length() ); + _terminal.write8( message.data(), static_cast( message.length() ) ); _messages.pop_front(); } repaint(); @@ -298,6 +463,25 @@ void Replxx::ReplxxImpl::clear( void ) { _displayInputLength = 0; } +void Replxx::ReplxxImpl::call_modify_callback( void ) { + if ( ! _modifyCallback ) { + return; + } + _utf8Buffer.assign( _data ); + std::string origLine( _utf8Buffer.get() ); + int pos( _pos ); + std::string line( origLine ); + /* IOModeGuard scope */ { + IOModeGuard ioModeGuard( _terminal ); + _modifyCallback( line, pos ); + } + if ( ( pos != _pos ) || ( line != origLine ) ) { + _data.assign( line.c_str() ); + _pos = min( pos, _data.length() ); + _modifiedState = true; + } +} + Replxx::ReplxxImpl::completions_t Replxx::ReplxxImpl::call_completer( std::string const& input, int& contextLen_ ) const { Replxx::completions_t completionsIntermediary( !! _completionCallback @@ -396,9 +580,6 @@ void Replxx::ReplxxImpl::emulate_key_press( char32_t keyCode_ ) { } char const* Replxx::ReplxxImpl::input( std::string const& prompt ) { -#ifndef _WIN32 - gotResize = false; -#endif try { errno = 0; if ( ! tty::in ) { // input not from a terminal, we should work with piped input, i.e. redirected stdin @@ -427,7 +608,7 @@ char const* Replxx::ReplxxImpl::input( std::string const& prompt ) { if ( get_input_line() == -1 ) { return ( finalize_input( nullptr ) ); } - printf("\n"); + _terminal.write8( "\n", 1 ); _utf8Buffer.assign( _data ); return ( finalize_input( _utf8Buffer.get() ) ); } catch ( std::exception const& ) { @@ -443,16 +624,26 @@ char const* Replxx::ReplxxImpl::finalize_input( char const* retVal_ ) { int Replxx::ReplxxImpl::install_window_change_handler( void ) { #ifndef _WIN32 - struct sigaction sa; - sigemptyset(&sa.sa_mask); - sa.sa_flags = 0; - sa.sa_handler = &WindowSizeChanged; + return ( _terminal.install_window_change_handler() ); +#else + return 0; +#endif +} - if (sigaction(SIGWINCH, &sa, nullptr) == -1) { - return errno; +void Replxx::ReplxxImpl::enable_bracketed_paste( void ) { + if ( _bracketedPaste ) { + return; } -#endif - return 0; + _terminal.enable_bracketed_paste(); + _bracketedPaste = true; +} + +void Replxx::ReplxxImpl::disable_bracketed_paste( void ) { + if ( ! _bracketedPaste ) { + return; + } + _terminal.disable_bracketed_paste(); + _bracketedPaste = false; } void Replxx::ReplxxImpl::print( char const* str_, int size_ ) { @@ -468,8 +659,6 @@ void Replxx::ReplxxImpl::print( char const* str_, int size_ ) { void Replxx::ReplxxImpl::preload_puffer(const char* preloadText) { _data.assign( preloadText ); - _charWidths.resize( _data.length() ); - recompute_character_widths( _data.get(), _charWidths.data(), _data.length() ); _prefix = _pos = _data.length(); } @@ -485,9 +674,9 @@ void Replxx::ReplxxImpl::render( char32_t ch ) { if ( ch == Replxx::KEY::ESCAPE ) { _display.push_back( '^' ); _display.push_back( '[' ); - } else if ( is_control_code( ch ) ) { + } else if ( is_control_code( ch ) && ( ch != '\n' ) ) { _display.push_back( '^' ); - _display.push_back( ch + 0x40 ); + _display.push_back( control_to_human( ch ) ); } else { _display.push_back( ch ); } @@ -497,6 +686,7 @@ void Replxx::ReplxxImpl::render( char32_t ch ) { void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) { if ( hintAction_ == HINT_ACTION::TRIM ) { _display.erase( _display.begin() + _displayInputLength, _display.end() ); + _modifiedState = false; return; } if ( hintAction_ == HINT_ACTION::SKIP ) { @@ -507,12 +697,14 @@ void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) { for ( char32_t ch : _data ) { render( ch ); } - _displayInputLength = _display.size(); + _displayInputLength = static_cast( _display.size() ); + _modifiedState = false; return; } Replxx::colors_t colors( _data.length(), Replxx::Color::DEFAULT ); _utf8Buffer.assign( _data ); if ( !! _highlighterCallback ) { + IOModeGuard ioModeGuard( _terminal ); _highlighterCallback( _utf8Buffer.get(), colors ); } paren_info_t pi( matching_paren() ); @@ -528,7 +720,7 @@ void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) { render( _data[i] ); } set_color( Replxx::Color::DEFAULT ); - _displayInputLength = _display.size(); + _displayInputLength = static_cast( _display.size() ); _modifiedState = false; return; } @@ -555,23 +747,27 @@ int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) { if ( hintAction_ == HINT_ACTION::REGENERATE ) { _hintSelection = -1; } - Replxx::Color c( Replxx::Color::GRAY ); _utf8Buffer.assign( _data, _pos ); - int contextLen( context_length() ); - Replxx::ReplxxImpl::hints_t hints( call_hinter( _utf8Buffer.get(), contextLen, c ) ); - int hintCount( hints.size() ); + if ( ( _utf8Buffer != _hintSeed ) || ( _hintContextLenght < 0 ) ) { + _hintSeed.assign( _utf8Buffer ); + _hintContextLenght = context_length(); + _hintColor = Replxx::Color::GRAY; + IOModeGuard ioModeGuard( _terminal ); + _hintsCache = call_hinter( _utf8Buffer.get(), _hintContextLenght, _hintColor ); + } + int hintCount( static_cast( _hintsCache.size() ) ); if ( hintCount == 1 ) { - _hint = hints.front(); - len = _hint.length() - contextLen; + _hint = _hintsCache.front(); + len = _hint.length() - _hintContextLenght; if ( len > 0 ) { - set_color( c ); + set_color( _hintColor ); for ( int i( 0 ); i < len; ++ i ) { - _display.push_back( _hint[i + contextLen] ); + _display.push_back( _hint[i + _hintContextLenght] ); } set_color( Replxx::Color::DEFAULT ); } } else if ( ( _maxHintRows > 0 ) && ( hintCount > 0 ) ) { - int startCol( _prompt._indentation + _pos - contextLen ); + int startCol( _prompt.indentation() + _pos ); int maxCol( _prompt.screen_columns() ); #ifdef _WIN32 -- maxCol; @@ -582,16 +778,17 @@ int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) { _hintSelection = -1; } if ( _hintSelection != -1 ) { - _hint = hints[_hintSelection]; - len = min( _hint.length(), maxCol - startCol - _data.length() ); - if ( contextLen < len ) { - set_color( c ); - for ( int i( contextLen ); i < len; ++ i ) { + _hint = _hintsCache[_hintSelection]; + len = min( _hint.length(), maxCol - startCol ); + if ( _hintContextLenght < len ) { + set_color( _hintColor ); + for ( int i( _hintContextLenght ); i < len; ++ i ) { _display.push_back( _hint[i] ); } set_color( Replxx::Color::DEFAULT ); } } + startCol -= _hintContextLenght; for ( int hintRow( 0 ); hintRow < min( hintCount, _maxHintRows ); ++ hintRow ) { #ifdef _WIN32 _display.push_back( '\r' ); @@ -601,8 +798,8 @@ int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) { for ( int i( 0 ); ( i < startCol ) && ( col < maxCol ); ++ i, ++ col ) { _display.push_back( ' ' ); } - set_color( c ); - for ( int i( _pos - contextLen ); ( i < _pos ) && ( col < maxCol ); ++ i, ++ col ) { + set_color( _hintColor ); + for ( int i( _pos - _hintContextLenght ); ( i < _pos ) && ( col < maxCol ); ++ i, ++ col ) { _display.push_back( _data[i] ); } int hintNo( hintRow + _hintSelection + 1 ); @@ -611,8 +808,8 @@ int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) { } else if ( hintNo > hintCount ) { -- hintNo; } - UnicodeString const& h( hints[hintNo % hintCount] ); - for ( int i( contextLen ); ( i < h.length() ) && ( col < maxCol ); ++ i, ++ col ) { + UnicodeString const& h( _hintsCache[hintNo % hintCount] ); + for ( int i( _hintContextLenght ); ( i < h.length() ) && ( col < maxCol ); ++ i, ++ col ) { _display.push_back( h[i] ); } set_color( Replxx::Color::DEFAULT ); @@ -686,35 +883,44 @@ Replxx::ReplxxImpl::paren_info_t Replxx::ReplxxImpl::matching_paren( void ) { * redrawn here screen position */ void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) { + int long long now( now_us() ); + int long long duration( now - _lastRefreshTime ); + if ( duration < RAPID_REFRESH_US ) { + _lastRefreshTime = now; + _refreshSkipped = true; + return; + } + _refreshSkipped = false; // check for a matching brace/bracket/paren, remember its position if found render( hintAction_ ); int hintLen( handle_hints( hintAction_ ) ); // calculate the position of the end of the input line int xEndOfInput( 0 ), yEndOfInput( 0 ); calculate_screen_position( - _prompt._indentation, 0, _prompt.screen_columns(), + _prompt.indentation(), 0, _prompt.screen_columns(), calculate_displayed_length( _data.get(), _data.length() ) + hintLen, xEndOfInput, yEndOfInput ); - yEndOfInput += count( _display.begin(), _display.end(), '\n' ); + yEndOfInput += static_cast( count( _display.begin(), _display.end(), '\n' ) ); // calculate the desired position of the cursor int xCursorPos( 0 ), yCursorPos( 0 ); calculate_screen_position( - _prompt._indentation, 0, _prompt.screen_columns(), + _prompt.indentation(), 0, _prompt.screen_columns(), calculate_displayed_length( _data.get(), _pos ), xCursorPos, yCursorPos ); // position at the end of the prompt, clear to end of previous input + _terminal.set_cursor_visible( false ); _terminal.jump_cursor( - _prompt._indentation, // 0-based on Win32 + _prompt.indentation(), // 0-based on Win32 -( _prompt._cursorRowOffset - _prompt._extraLines ) ); - _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END ); - _prompt._previousInputLen = _data.length(); // display the input line - _terminal.write32( _display.data(), _display.size() ); + _terminal.write32( _display.data(), _displayInputLength ); + _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END ); + _terminal.write32( _display.data() + _displayInputLength, static_cast( _display.size() ) - _displayInputLength ); #ifndef _WIN32 // we have to generate our own newline on line wrap if ( ( xEndOfInput == 0 ) && ( yEndOfInput > 0 ) ) { @@ -723,13 +929,15 @@ void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) { #endif // position the cursor _terminal.jump_cursor( xCursorPos, -( yEndOfInput - yCursorPos ) ); + _terminal.set_cursor_visible( true ); _prompt._cursorRowOffset = _prompt._extraLines + yCursorPos; // remember row for next pass + _lastRefreshTime = now_us(); } int Replxx::ReplxxImpl::context_length() { int prefixLength = _pos; while ( prefixLength > 0 ) { - if ( is_word_break_character( _data[prefixLength - 1] ) ) { + if ( is_word_break_character( _data[prefixLength - 1] ) ) { break; } -- prefixLength; @@ -745,16 +953,16 @@ void Replxx::ReplxxImpl::repaint( void ) { refresh_line( HINT_ACTION::SKIP ); } -void Replxx::ReplxxImpl::clear_self_to_end_of_screen( void ) { +void Replxx::ReplxxImpl::clear_self_to_end_of_screen( Prompt const* prompt_ ) { // position at the start of the prompt, clear to end of previous input - _terminal.jump_cursor( 0, -_prompt._cursorRowOffset ); + _terminal.jump_cursor( 0, prompt_ ? -prompt_->_cursorRowOffset : -_prompt._cursorRowOffset ); _terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END ); return; } namespace { int longest_common_prefix( Replxx::ReplxxImpl::completions_t const& completions ) { - int completionsCount( completions.size() ); + int completionsCount( static_cast( completions.size() ) ); if ( completionsCount < 1 ) { return ( 0 ); } @@ -801,7 +1009,10 @@ char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) { // get a list of completions _completionSelection = -1; _completionContextLength = context_length(); - _completions = call_completer( _utf8Buffer.get(), _completionContextLength ); + /* IOModeGuard scope */ { + IOModeGuard ioModeGuard( _terminal ); + _completions = call_completer( _utf8Buffer.get(), _completionContextLength ); + } // if no completions, we are done if ( _completions.empty() ) { @@ -811,7 +1022,7 @@ char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) { // at least one completion int longestCommonPrefix = 0; - int completionsCount( _completions.size() ); + int completionsCount( static_cast( _completions.size() ) ); int selectedCompletion( 0 ); if ( _hintSelection != -1 ) { selectedCompletion = _hintSelection; @@ -950,7 +1161,7 @@ char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) { break; } } else { - printf("\n"); + _terminal.write8( "\n", 1 ); } if (stopList) { break; @@ -999,12 +1210,6 @@ char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) { _terminal.write8( "\n", 1 ); } _prompt.write(); -#ifndef _WIN32 - // we have to generate our own newline on line wrap on Linux - if (_prompt._indentation == 0 && _prompt._extraLines > 0) { - _terminal.write8( "\n", 1 ); - } -#endif _prompt._cursorRowOffset = _prompt._extraLines; refresh_line(); return 0; @@ -1017,18 +1222,11 @@ int Replxx::ReplxxImpl::get_input_line( void ) { } else { _history.add( UnicodeString() ); } - _history.reset_pos(); + _history.jump( false, false ); // display the prompt _prompt.write(); -#ifndef _WIN32 - // we have to generate our own newline on line wrap on Linux - if ( ( _prompt._indentation == 0 ) && ( _prompt._extraLines > 0 ) ) { - _terminal.write8( "\n", 1 ); - } -#endif - // the cursor starts out at the end of the prompt _prompt._cursorRowOffset = _prompt._extraLines; @@ -1044,17 +1242,6 @@ int Replxx::ReplxxImpl::get_input_line( void ) { Replxx::ACTION_RESULT next( Replxx::ACTION_RESULT::CONTINUE ); while ( next == Replxx::ACTION_RESULT::CONTINUE ) { int c( read_char( HINT_ACTION::REPAINT ) ); // get a new keystroke -#ifndef _WIN32 - if (c == 0 && gotResize) { - // caught a window resize event - // now redraw the prompt and line - gotResize = false; - _prompt.update_screen_columns(); - // redraw the original prompt with current input - dynamicRefresh( _prompt, _data.get(), _data.length(), _pos ); - continue; - } -#endif if (c == 0) { return _data.length(); @@ -1078,7 +1265,7 @@ int Replxx::ReplxxImpl::get_input_line( void ) { refresh_line(); } } else { - next = action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::insert_character, c ); + next = action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, c ); } } return ( next == Replxx::ACTION_RESULT::RETURN ? _data.length() : -1 ); @@ -1086,6 +1273,10 @@ int Replxx::ReplxxImpl::get_input_line( void ) { Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, key_press_handler_raw_t const& handler_, char32_t code_ ) { Replxx::ACTION_RESULT res( ( this->*handler_ )( code_ ) ); + call_modify_callback(); + if ( actionTrait_ & HISTORY_RECALL_MOST_RECENT ) { + _history.reset_recall_most_recent(); + } if ( actionTrait_ & RESET_KILL_ACTION ) { _killRing.lastAction = KillRing::actionOther; } @@ -1100,6 +1291,9 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, k _completionSelection = -1; _completionContextLength = 0; } + if ( ! ( actionTrait_ & DONT_RESET_HIST_YANK_INDEX ) ) { + _history.reset_yank_iterator(); + } if ( actionTrait_ & WANT_REFRESH ) { _modifiedState = true; } @@ -1107,12 +1301,11 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, k } Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) { - _history.reset_recall_most_recent(); /* * beep on unknown Ctrl and/or Meta keys * don't insert control characters */ - if ( ( c >= static_cast( Replxx::KEY::BASE ) ) || is_control_code( c ) ) { + if ( ( c >= static_cast( Replxx::KEY::BASE ) ) || ( is_control_code( c ) && ( c != '\n' ) ) ) { beep(); return ( Replxx::ACTION_RESULT::CONTINUE ); } @@ -1122,26 +1315,38 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) { _data[_pos] = c; } ++ _pos; + call_modify_callback(); + int long long now( now_us() ); + int long long duration( now - _lastRefreshTime ); + if ( duration < RAPID_REFRESH_US ) { + _lastRefreshTime = now; + _refreshSkipped = true; + return ( Replxx::ACTION_RESULT::CONTINUE ); + } int inputLen = calculate_displayed_length( _data.get(), _data.length() ); if ( ( _pos == _data.length() ) + && ! _modifiedState && ( _noColor || ! ( !! _highlighterCallback || !! _hintCallback ) ) - && ( _prompt._indentation + inputLen < _prompt.screen_columns() ) + && ( _prompt.indentation() + inputLen < _prompt.screen_columns() ) ) { /* Avoid a full assign of the line in the * trivial case. */ - if (inputLen > _prompt._previousInputLen) { - _prompt._previousInputLen = inputLen; - } render( c ); - _displayInputLength = _display.size(); - _terminal.write32(reinterpret_cast(&c), 1); + _displayInputLength = static_cast( _display.size() ); + _terminal.write32( reinterpret_cast( &c ), 1 ); } else { refresh_line(); } + _lastRefreshTime = now_us(); return ( Replxx::ACTION_RESULT::CONTINUE ); } +// ctrl-J/linefeed/newline +Replxx::ACTION_RESULT Replxx::ReplxxImpl::new_line( char32_t ) { + return ( insert_character( '\n' ) ); +} + // ctrl-A, HOME: move cursor to start of line Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_begining_of_line( char32_t ) { _pos = 0; @@ -1172,12 +1377,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_right( char32_t ) { } // meta-B, move cursor left by one word +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) { if (_pos > 0) { - while (_pos > 0 && is_word_break_character( _data[_pos - 1] ) ) { + while (_pos > 0 && is_word_break_character( _data[_pos - 1] ) ) { --_pos; } - while (_pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) { + while (_pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) { --_pos; } refresh_line(); @@ -1185,13 +1391,14 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) { return ( Replxx::ACTION_RESULT::CONTINUE ); } -// meta-F, move cursor right by one word +// meta-f, move cursor right by one word +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) { if ( _pos < _data.length() ) { - while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { ++_pos; } - while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { ++_pos; } refresh_line(); @@ -1200,14 +1407,14 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) { } // meta-Backspace, kill word to left of cursor +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) { if ( _pos > 0 ) { - _history.reset_recall_most_recent(); int startingPos = _pos; - while ( _pos > 0 && is_word_break_character( _data[_pos - 1] ) ) { + while ( _pos > 0 && is_word_break_character( _data[_pos - 1] ) ) { -- _pos; } - while ( _pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) { + while ( _pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) { -- _pos; } _killRing.kill( _data.get() + _pos, startingPos - _pos, false); @@ -1218,14 +1425,14 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) { } // meta-D, kill word to right of cursor +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) { if ( _pos < _data.length() ) { - _history.reset_recall_most_recent(); int endingPos = _pos; - while ( endingPos < _data.length() && is_word_break_character( _data[endingPos] ) ) { + while ( endingPos < _data.length() && is_word_break_character( _data[endingPos] ) ) { ++ endingPos; } - while ( endingPos < _data.length() && !is_word_break_character( _data[endingPos] ) ) { + while ( endingPos < _data.length() && !is_word_break_character( _data[endingPos] ) ) { ++ endingPos; } _killRing.kill( _data.get() + _pos, endingPos - _pos, true ); @@ -1238,12 +1445,11 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) { // ctrl-W, kill to whitespace (not word) to left of cursor Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_whitespace_to_left( char32_t ) { if ( _pos > 0 ) { - _history.reset_recall_most_recent(); int startingPos = _pos; - while ( _pos > 0 && _data[_pos - 1] == ' ' ) { + while ( ( _pos > 0 ) && isspace( _data[_pos - 1] ) ) { --_pos; } - while ( _pos > 0 && _data[_pos - 1] != ' ' ) { + while ( ( _pos > 0 ) && ! isspace( _data[_pos - 1] ) ) { -- _pos; } _killRing.kill( _data.get() + _pos, startingPos - _pos, false ); @@ -1257,14 +1463,12 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_whitespace_to_left( char32_t ) Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_end_of_line( char32_t ) { _killRing.kill( _data.get() + _pos, _data.length() - _pos, true ); _data.erase( _pos, _data.length() - _pos ); - _history.reset_recall_most_recent(); return ( Replxx::ACTION_RESULT::CONTINUE ); } // ctrl-U, kill all characters to the left of the cursor Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_begining_of_line( char32_t ) { if (_pos > 0) { - _history.reset_recall_most_recent(); _killRing.kill( _data.get(), _pos, false ); _data.erase( 0, _pos ); _pos = 0; @@ -1275,14 +1479,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_begining_of_line( char32_t ) { // ctrl-Y, yank killed text Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank( char32_t ) { - _history.reset_recall_most_recent(); UnicodeString* restoredText( _killRing.yank() ); if ( restoredText ) { _data.insert( _pos, *restoredText, 0, restoredText->length() ); _pos += restoredText->length(); refresh_line(); _killRing.lastAction = KillRing::actionYank; - _killRing.lastYankSize = restoredText->length(); + _lastYankSize = restoredText->length(); } else { beep(); } @@ -1295,35 +1498,60 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_cycle( char32_t ) { beep(); return ( Replxx::ACTION_RESULT::CONTINUE ); } - _history.reset_recall_most_recent(); UnicodeString* restoredText = _killRing.yankPop(); if ( !restoredText ) { beep(); return ( Replxx::ACTION_RESULT::CONTINUE ); } - _pos -= _killRing.lastYankSize; - _data.erase( _pos, _killRing.lastYankSize ); + _pos -= _lastYankSize; + _data.erase( _pos, _lastYankSize ); _data.insert( _pos, *restoredText, 0, restoredText->length() ); _pos += restoredText->length(); - _killRing.lastYankSize = restoredText->length(); + _lastYankSize = restoredText->length(); + refresh_line(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +// meta-., "yank-last-arg", on consecutive uses move back in history for popped text +Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_last_arg( char32_t ) { + if ( _history.size() < 2 ) { + return ( Replxx::ACTION_RESULT::CONTINUE ); + } + if ( _history.next_yank_position() ) { + _lastYankSize = 0; + } + UnicodeString const& histLine( _history.yank_line() ); + int endPos( histLine.length() ); + while ( ( endPos > 0 ) && isspace( histLine[endPos - 1] ) ) { + -- endPos; + } + int startPos( endPos ); + while ( ( startPos > 0 ) && ! isspace( histLine[startPos - 1] ) ) { + -- startPos; + } + _pos -= _lastYankSize; + _data.erase( _pos, _lastYankSize ); + _lastYankSize = endPos - startPos; + _data.insert( _pos, histLine, startPos, _lastYankSize ); + _pos += _lastYankSize; refresh_line(); return ( Replxx::ACTION_RESULT::CONTINUE ); } // meta-C, give word initial Cap +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) { - _history.reset_recall_most_recent(); if (_pos < _data.length()) { - while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { ++_pos; } - if (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + if (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { if ( _data[_pos] >= 'a' && _data[_pos] <= 'z' ) { _data[_pos] += 'A' - 'a'; } ++_pos; } - while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) { _data[_pos] += 'a' - 'A'; } @@ -1335,13 +1563,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) { } // meta-L, lowercase word +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) { if (_pos < _data.length()) { - _history.reset_recall_most_recent(); - while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { ++ _pos; } - while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) { _data[_pos] += 'a' - 'A'; } @@ -1353,13 +1581,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) { } // meta-U, uppercase word +template Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) { if (_pos < _data.length()) { - _history.reset_recall_most_recent(); - while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { + while ( _pos < _data.length() && is_word_break_character( _data[_pos] ) ) { ++ _pos; } - while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { + while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) { if ( _data[_pos] >= 'a' && _data[_pos] <= 'z') { _data[_pos] += 'A' - 'a'; } @@ -1373,7 +1601,6 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) { // ctrl-T, transpose characters Replxx::ACTION_RESULT Replxx::ReplxxImpl::transpose_characters( char32_t ) { if ( _pos > 0 && _data.length() > 1 ) { - _history.reset_recall_most_recent(); size_t leftCharPos = ( _pos == _data.length() ) ? _pos - 2 : _pos - 1; char32_t aux = _data[leftCharPos]; _data[leftCharPos] = _data[leftCharPos + 1]; @@ -1388,13 +1615,13 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::transpose_characters( char32_t ) { // ctrl-C, abort this line Replxx::ACTION_RESULT Replxx::ReplxxImpl::abort_line( char32_t ) { - _history.reset_recall_most_recent(); errno = EAGAIN; _history.drop_last(); // we need one last refresh with the cursor at the end of the line // so we don't display the next prompt over the previous input line _pos = _data.length(); // pass _data.length() as _pos for EOL - refresh_line( HINT_ACTION::TRIM ); + _lastRefreshTime = 0; + refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM ); _terminal.write8( "^C\r\n", 4 ); return ( Replxx::ACTION_RESULT::BAIL ); } @@ -1402,7 +1629,6 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::abort_line( char32_t ) { // DEL, delete the character under the cursor Replxx::ACTION_RESULT Replxx::ReplxxImpl::delete_character( char32_t ) { if ( ( _data.length() > 0 ) && ( _pos < _data.length() ) ) { - _history.reset_recall_most_recent(); _data.erase( _pos ); refresh_line(); } @@ -1422,7 +1648,6 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::send_eof( char32_t key_ ) { // backspace/ctrl-H, delete char to left of cursor Replxx::ACTION_RESULT Replxx::ReplxxImpl::backspace_character( char32_t ) { if ( _pos > 0 ) { - _history.reset_recall_most_recent(); -- _pos; _data.erase( _pos ); refresh_line(); @@ -1430,24 +1655,24 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::backspace_character( char32_t ) { return ( Replxx::ACTION_RESULT::CONTINUE ); } -// ctrl-J/linefeed/newline, accept line -// ctrl-M/return/enter +// ctrl-M/return/enter, accept line Replxx::ACTION_RESULT Replxx::ReplxxImpl::commit_line( char32_t ) { // we need one last refresh with the cursor at the end of the line // so we don't display the next prompt over the previous input line _pos = _data.length(); // pass _data.length() as _pos for EOL - refresh_line( HINT_ACTION::TRIM ); + _lastRefreshTime = 0; + refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM ); _history.commit_index(); _history.drop_last(); return ( Replxx::ACTION_RESULT::RETURN ); } -// ctrl-N, recall next line in history +// Down, recall next line in history Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) { return ( history_move( false ) ); } -// ctrl-P, recall previous line in history +// Up, recall previous line in history Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) { return ( history_move( true ) ); } @@ -1537,9 +1762,10 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::verbatim_insert( char32_t ) { // ctrl-Z, job control Replxx::ACTION_RESULT Replxx::ReplxxImpl::suspend( char32_t ) { - _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode - raise(SIGSTOP); // Break out in mid-line - _terminal.enable_raw_mode(); // Back from Linux shell, re-enter raw mode + /* IOModeGuard scope */ { + IOModeGuard ioModeGuard( _terminal ); + raise( SIGSTOP ); // Break out in mid-line + } // Redraw prompt _prompt.write(); return ( Replxx::ACTION_RESULT::CONTINUE ); @@ -1548,9 +1774,6 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::suspend( char32_t ) { Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_line( char32_t c ) { if ( !! _completionCallback && ( _completeOnEmpty || ( _pos > 0 ) ) ) { - _killRing.lastAction = KillRing::actionOther; - _history.reset_recall_most_recent(); - // complete_line does the actual completion and replacement c = do_complete_line( c != 0 ); @@ -1569,8 +1792,9 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_line( char32_t c ) { Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete( bool previous_ ) { if ( _completions.empty() ) { bool first( _completions.empty() ); - complete_line( first ? '\t' : 0 ); - if ( first ) { + int dataLen( _data.length() ); + complete_line( 0 ); + if ( ! _immediateCompletion && first && ( _data.length() > dataLen ) ) { return ( Replxx::ACTION_RESULT::CONTINUE ); } } @@ -1581,12 +1805,12 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete( bool previous_ ) { newSelection = static_cast( _completions.size() ) - 1; } if ( _completionSelection != -1 ) { - int oldCompletionLength( _completions[_completionSelection].text().length() - _completionContextLength ); + int oldCompletionLength( max( _completions[_completionSelection].text().length() - _completionContextLength, 0 ) ); _pos -= oldCompletionLength; _data.erase( _pos, oldCompletionLength ); } if ( newSelection != -1 ) { - int newCompletionLength( _completions[newSelection].text().length() - _completionContextLength ); + int newCompletionLength( max( _completions[newSelection].text().length() - _completionContextLength, 0 ) ); _data.insert( _pos, _completions[newSelection].text(), _completionContextLength, newCompletionLength ); _pos += newCompletionLength; } @@ -1636,15 +1860,14 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t s if ( _history.is_last() ) { _history.update_last( _data ); } + _history.save_pos(); int historyLinePosition( _pos ); clear_self_to_end_of_screen(); DynamicPrompt dp( _terminal, (startChar == Replxx::KEY::control('R')) ? -1 : 1 ); - dp._previousLen = _prompt._previousLen; - dp._previousInputLen = _prompt._previousInputLen; // draw user's text with our prompt - dynamicRefresh(dp, _data.get(), _data.length(), historyLinePosition); + dynamicRefresh(_prompt, dp, _data.get(), _data.length(), historyLinePosition); // loop until we get an exit character char32_t c( 0 ); @@ -1694,63 +1917,69 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t s case Replxx::KEY::meta( '<' ): // start of history case Replxx::KEY::PAGE_UP: case Replxx::KEY::meta( '>' ): // end of history - case Replxx::KEY::PAGE_DOWN: + case Replxx::KEY::PAGE_DOWN: { keepLooping = false; - break; + } break; // these characters revert the input line to its previous state case Replxx::KEY::control('C'): // ctrl-C, abort this line case Replxx::KEY::control('G'): - case Replxx::KEY::control('L'): // ctrl-L, clear screen and redisplay line + case Replxx::KEY::control('L'): { // ctrl-L, clear screen and redisplay line keepLooping = false; useSearchedLine = false; if (c != Replxx::KEY::control('L')) { c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else } - break; + } break; // these characters stay in search mode and assign the display case Replxx::KEY::control('S'): - case Replxx::KEY::control('R'): + case Replxx::KEY::control('R'): { if ( dp._searchText.length() == 0 ) { // if no current search text, recall previous text - if ( previousSearchText.length() > 0 ) { - dp._searchText = previousSearchText; + if ( _previousSearchText.length() > 0 ) { + dp._searchText = _previousSearchText; } } - if ((dp._direction == 1 && c == Replxx::KEY::control('R')) || - (dp._direction == -1 && c == Replxx::KEY::control('S'))) { - dp._direction = 0 - dp._direction; // reverse _direction - dp.updateSearchPrompt(); // change the prompt + if ( + ( ( dp._direction == 1 ) && ( c == Replxx::KEY::control( 'R' ) ) ) + || ( ( dp._direction == -1 ) && ( c == Replxx::KEY::control( 'S' ) ) ) + ) { + dp._direction = 0 - dp._direction; // reverse direction + dp.updateSearchPrompt(); // change the prompt } else { - searchAgain = true; // same _direction, search again + searchAgain = true; // same direction, search again } - break; + } break; // job control is its own thing #ifndef _WIN32 case Replxx::KEY::control('Z'): { // ctrl-Z, job control - _terminal.disable_raw_mode(); // Returning to Linux (whatever) shell, leave raw mode - raise(SIGSTOP); // Break out in mid-line - _terminal.enable_raw_mode(); // Back from Linux shell, re-enter raw mode - dynamicRefresh(dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition); + /* IOModeGuard scope */ { + IOModeGuard ioModeGuard( _terminal ); + // Returning to Linux (whatever) shell, leave raw mode + // Break out in mid-line + // Back from Linux shell, re-enter raw mode + raise( SIGSTOP ); + } + dynamicRefresh( dp, dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition ); continue; } break; #endif - // these characters assign the search string, and hence the selected input - // line - case Replxx::KEY::BACKSPACE: // backspace/ctrl-H, delete char to left of cursor + // these characters assign the search string, and hence the selected input line + case Replxx::KEY::BACKSPACE: { // backspace/ctrl-H, delete char to left of cursor if ( dp._searchText.length() > 0 ) { dp._searchText.erase( dp._searchText.length() - 1 ); dp.updateSearchPrompt(); - _history.reset_pos( dp._direction == -1 ? _history.size() - 1 : 0 ); + _history.restore_pos(); + historyLinePosition = _pos; } else { beep(); } - break; + } break; - case Replxx::KEY::control('Y'): // ctrl-Y, yank killed text - break; + case Replxx::KEY::control('Y'): { // ctrl-Y, yank killed text + } break; default: { if ( ! is_control_code( c ) && ( c < static_cast( Replxx::KEY::BASE ) ) ) { // not an action character @@ -1769,61 +1998,66 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t s activeHistoryLine.assign( _history.current() ); if ( dp._searchText.length() > 0 ) { bool found = false; - int historySearchIndex = _history.current_pos(); int lineSearchPos = historyLinePosition; if ( searchAgain ) { lineSearchPos += dp._direction; } searchAgain = false; while ( true ) { - while ( ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) && ( lineSearchPos >= 0 ) ) { - if ( std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos ) ) { + while ( + dp._direction < 0 + ? ( lineSearchPos >= 0 ) + : ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) + ) { + if ( + ( lineSearchPos >= 0 ) + && ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() ) + && std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos ) + ) { found = true; break; } lineSearchPos += dp._direction; } if ( found ) { - _history.reset_pos( historySearchIndex ); historyLinePosition = lineSearchPos; break; - } else if ( ( dp._direction > 0 ) ? ( historySearchIndex < _history.size() ) : ( historySearchIndex > 0 ) ) { - historySearchIndex += dp._direction; - activeHistoryLine.assign( _history[historySearchIndex] ); + } else if ( _history.move( dp._direction < 0 ) ) { + activeHistoryLine.assign( _history.current() ); lineSearchPos = ( dp._direction > 0 ) ? 0 : ( activeHistoryLine.length() - dp._searchText.length() ); } else { + historyLinePosition = _pos; beep(); break; } } // while + if ( ! found ) { + _history.restore_pos(); + } + } else { + _history.restore_pos(); + historyLinePosition = _pos; } activeHistoryLine.assign( _history.current() ); - dynamicRefresh(dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition); // draw user's text with our prompt + dynamicRefresh( dp, dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition ); // draw user's text with our prompt } // while // leaving history search, restore previous prompt, maybe make searched line // current Prompt pb( _terminal ); - pb._characterCount = _prompt._indentation; - pb._byteCount = _prompt._byteCount; - UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], pb._byteCount - _prompt._lastLinePosition ); - pb._text = tempUnicode; - pb._extraLines = 0; - pb._indentation = _prompt._indentation; - pb._lastLinePosition = 0; - pb._previousInputLen = activeHistoryLine.length(); - pb._cursorRowOffset = dp._cursorRowOffset; + UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], _prompt._text.length() - _prompt._lastLinePosition ); + pb.set_text( tempUnicode ); pb.update_screen_columns(); - pb._previousLen = dp._characterCount; if ( useSearchedLine && ( activeHistoryLine.length() > 0 ) ) { - _history.set_recall_most_recent(); + _history.commit_index(); _data.assign( activeHistoryLine ); _pos = historyLinePosition; + _modifiedState = true; + } else if ( ! useSearchedLine ) { + _history.restore_pos(); } - dynamicRefresh(pb, _data.get(), _data.length(), _pos); // redraw the original prompt with current input - _prompt._previousInputLen = _data.length(); - _prompt._cursorRowOffset = _prompt._extraLines + pb._cursorRowOffset; - previousSearchText = dp._searchText; // save search text for possible reuse on ctrl-R ctrl-R + dynamicRefresh(pb, _prompt, _data.get(), _data.length(), _pos); // redraw the original prompt with current input + _previousSearchText = dp._searchText; // save search text for possible reuse on ctrl-R ctrl-R emulate_key_press( c ); // pass a character or -1 back to main loop return ( Replxx::ACTION_RESULT::CONTINUE ); } @@ -1833,22 +2067,33 @@ Replxx::ACTION_RESULT Replxx::ReplxxImpl::clear_screen( char32_t c ) { _terminal.clear_screen( Terminal::CLEAR_SCREEN::WHOLE ); if ( c ) { _prompt.write(); -#ifndef _WIN32 - // we have to generate our own newline on line wrap on Linux - if (_prompt._indentation == 0 && _prompt._extraLines > 0) { - _terminal.write8( "\n", 1 ); - } -#endif _prompt._cursorRowOffset = _prompt._extraLines; refresh_line(); } return ( Replxx::ACTION_RESULT::CONTINUE ); } +Replxx::ACTION_RESULT Replxx::ReplxxImpl::bracketed_paste( char32_t ) { + UnicodeString buf; + while ( char32_t c = _terminal.read_char() ) { + if ( c == KEY::PASTE_FINISH ) { + break; + } + if ( ( c == '\r' ) || ( c == KEY::control( 'M' ) ) ) { + c = '\n'; + } + buf.push_back( c ); + } + _data.insert( _pos, buf, 0, buf.length() ); + _pos += buf.length(); + return ( Replxx::ACTION_RESULT::CONTINUE ); +} + +template bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const { bool wbc( false ); if ( char_ < 128 ) { - wbc = strchr( _breakChars, static_cast( char_ ) ) != nullptr; + wbc = strchr( subword ? _subwordBreakChars.c_str() : _wordBreakChars.c_str(), static_cast( char_ ) ) != nullptr; } return ( wbc ); } @@ -1857,21 +2102,32 @@ void Replxx::ReplxxImpl::history_add( std::string const& line ) { _history.add( UnicodeString( line ) ); } -int Replxx::ReplxxImpl::history_save( std::string const& filename ) { - return ( _history.save( filename ) ); +bool Replxx::ReplxxImpl::history_save( std::string const& filename ) { + return ( _history.save( filename, false ) ); } -int Replxx::ReplxxImpl::history_load( std::string const& filename ) { +bool Replxx::ReplxxImpl::history_sync( std::string const& filename ) { + return ( _history.save( filename, true ) ); +} + +bool Replxx::ReplxxImpl::history_load( std::string const& filename ) { return ( _history.load( filename ) ); } +void Replxx::ReplxxImpl::history_clear( void ) { + _history.clear(); +} + int Replxx::ReplxxImpl::history_size( void ) const { return ( _history.size() ); } -std::string Replxx::ReplxxImpl::history_line( int index ) { - _utf8Buffer.assign( _history[index] ); - return ( _utf8Buffer.get() ); +Replxx::HistoryScan::impl_t Replxx::ReplxxImpl::history_scan( void ) const { + return ( _history.scan() ); +} + +void Replxx::ReplxxImpl::set_modify_callback( Replxx::modify_callback_t const& fn ) { + _modifyCallback = fn; } void Replxx::ReplxxImpl::set_completion_callback( Replxx::completion_callback_t const& fn ) { @@ -1903,7 +2159,11 @@ void Replxx::ReplxxImpl::set_hint_delay( int hintDelay_ ) { } void Replxx::ReplxxImpl::set_word_break_characters( char const* wordBreakers ) { - _breakChars = wordBreakers; + _wordBreakChars = wordBreakers; +} + +void Replxx::ReplxxImpl::set_subword_break_characters( char const* subwordBreakers ) { + _subwordBreakChars = subwordBreakers; } void Replxx::ReplxxImpl::set_double_tab_completion( bool val ) { @@ -1918,6 +2178,14 @@ void Replxx::ReplxxImpl::set_beep_on_ambiguous_completion( bool val ) { _beepOnAmbiguousCompletion = val; } +void Replxx::ReplxxImpl::set_immediate_completion( bool val ) { + _immediateCompletion = val; +} + +void Replxx::ReplxxImpl::set_unique_history( bool val ) { + _history.set_unique( val ); +} + void Replxx::ReplxxImpl::set_no_color( bool val ) { _noColor = val; } @@ -1931,20 +2199,19 @@ void Replxx::ReplxxImpl::set_no_color( bool val ) { * @param len count of characters in the buffer * @param pos current cursor position within the buffer (0 <= pos <= len) */ -void Replxx::ReplxxImpl::dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos) { - clear_self_to_end_of_screen(); +void Replxx::ReplxxImpl::dynamicRefresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos) { + clear_self_to_end_of_screen( &oldPrompt ); // calculate the position of the end of the prompt int xEndOfPrompt, yEndOfPrompt; calculate_screen_position( - 0, 0, pi.screen_columns(), pi._characterCount, + 0, 0, newPrompt.screen_columns(), newPrompt._characterCount, xEndOfPrompt, yEndOfPrompt ); - pi._indentation = xEndOfPrompt; // calculate the position of the end of the input line int xEndOfInput, yEndOfInput; calculate_screen_position( - xEndOfPrompt, yEndOfPrompt, pi.screen_columns(), + xEndOfPrompt, yEndOfPrompt, newPrompt.screen_columns(), calculate_displayed_length(buf32, len), xEndOfInput, yEndOfInput ); @@ -1952,16 +2219,13 @@ void Replxx::ReplxxImpl::dynamicRefresh(Prompt& pi, char32_t* buf32, int len, in // calculate the desired position of the cursor int xCursorPos, yCursorPos; calculate_screen_position( - xEndOfPrompt, yEndOfPrompt, pi.screen_columns(), + xEndOfPrompt, yEndOfPrompt, newPrompt.screen_columns(), calculate_displayed_length(buf32, pos), xCursorPos, yCursorPos ); - pi._previousLen = pi._indentation; - pi._previousInputLen = len; - // display the prompt - pi.write(); + newPrompt.write(); // display the input line _terminal.write32( buf32, len ); @@ -1977,7 +2241,7 @@ void Replxx::ReplxxImpl::dynamicRefresh(Prompt& pi, char32_t* buf32, int len, in xCursorPos, // 0-based on Win32 -( yEndOfInput - yCursorPos ) ); - pi._cursorRowOffset = pi._extraLines + yCursorPos; // remember row for next pass + newPrompt._cursorRowOffset = newPrompt._extraLines + yCursorPos; // remember row for next pass } } diff --git a/contrib/replxx/src/replxx_impl.hxx b/contrib/replxx/src/replxx_impl.hxx index 3cf1e8203..bec9383c1 100644 --- a/contrib/replxx/src/replxx_impl.hxx +++ b/contrib/replxx/src/replxx_impl.hxx @@ -38,13 +38,13 @@ #include #include #include +#include #include "replxx.hxx" #include "history.hxx" #include "killring.hxx" #include "utf8string.hxx" #include "prompt.hxx" -#include "io.hxx" namespace replxx { @@ -74,10 +74,10 @@ public: } }; typedef std::vector completions_t; + typedef std::vector data_t; typedef std::vector hints_t; typedef std::unique_ptr utf8_buffer_t; typedef std::unique_ptr input_buffer_t; - typedef std::vector char_widths_t; typedef std::vector display_t; typedef std::deque key_presses_t; typedef std::deque messages_t; @@ -87,41 +87,51 @@ public: TRIM, SKIP }; + typedef std::unordered_map named_actions_t; typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t ); typedef std::unordered_map key_press_handlers_t; private: typedef int long long unsigned action_trait_t; - static action_trait_t const NOOP = 0; - static action_trait_t const WANT_REFRESH = 1; - static action_trait_t const RESET_KILL_ACTION = 2; - static action_trait_t const SET_KILL_ACTION = 4; - static action_trait_t const DONT_RESET_PREFIX = 8; - static action_trait_t const DONT_RESET_COMPLETIONS = 16; + static action_trait_t const 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() + int _pos; // character position in buffer ( 0 <= _pos <= _data[_line].length() ) 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; - char const* _breakChars; + std::string _wordBreakChars; + std::string _subwordBreakChars; 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; @@ -132,37 +142,50 @@ private: 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 ); - int history_save( std::string const& filename ); - int history_load( std::string const& filename ); - std::string history_line( int index ); + 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_subword_break_characters( char const* subwordBreakers ); 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 ); - completions_t call_completer( std::string const& input, int& ) const; - hints_t call_hinter( std::string const& input, int&, Replxx::Color& color ) const; + 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: @@ -173,21 +196,30 @@ private: 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 new_line( 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 ); + template Replxx::ACTION_RESULT move_one_word_left( char32_t ); + template Replxx::ACTION_RESULT move_one_word_right( char32_t ); + template Replxx::ACTION_RESULT kill_word_to_left( char32_t ); + template 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 ); + template Replxx::ACTION_RESULT capitalize_word( char32_t ); + template Replxx::ACTION_RESULT lowercase_word( char32_t ); + template Replxx::ACTION_RESULT uppercase_word( char32_t ); Replxx::ACTION_RESULT transpose_characters( char32_t ); Replxx::ACTION_RESULT abort_line( char32_t ); @@ -215,9 +247,13 @@ private: 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 ); @@ -226,10 +262,11 @@ private: int context_length( void ); void clear( void ); void repaint( void ); + template bool is_word_break_character( char32_t ) const; - void dynamicRefresh(Prompt& pi, char32_t* buf32, int len, int pos); + void dynamicRefresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos); char const* finalize_input( char const* ); - void clear_self_to_end_of_screen( void ); + void clear_self_to_end_of_screen( Prompt const* = nullptr ); typedef struct { int index; bool error; diff --git a/contrib/replxx/src/unicodestring.hxx b/contrib/replxx/src/unicodestring.hxx index 1607ede66..22f3e4695 100644 --- a/contrib/replxx/src/unicodestring.hxx +++ b/contrib/replxx/src/unicodestring.hxx @@ -2,7 +2,6 @@ #define REPLXX_UNICODESTRING_HXX_INCLUDED #include -#include #include #include "conversion.hxx" @@ -26,6 +25,15 @@ public: assign( src ); } + explicit UnicodeString( UnicodeString const& other, int offset, int len = -1 ) + : _data() { + _data.insert( + _data.end(), + other._data.begin() + offset, + len > 0 ? other._data.begin() + offset + len : other._data.end() + ); + } + explicit UnicodeString( char const* src ) : _data() { assign( src ); @@ -55,15 +63,15 @@ public: } UnicodeString& assign( std::string const& str_ ) { - _data.resize( str_.length() ); + _data.resize( static_cast( str_.length() ) ); int len( 0 ); - copyString8to32( _data.data(), str_.length(), len, str_.c_str() ); + copyString8to32( _data.data(), static_cast( str_.length() ), len, str_.c_str() ); _data.resize( len ); return *this; } UnicodeString& assign( char const* str_ ) { - size_t byteCount( strlen( str_ ) ); + int byteCount( static_cast( strlen( str_ ) ) ); _data.resize( byteCount ); int len( 0 ); copyString8to32( _data.data(), byteCount, len, str_ ); @@ -93,6 +101,10 @@ public: 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; @@ -149,6 +161,14 @@ public: ); } + bool ends_with( data_buffer_t::const_iterator first_, data_buffer_t::const_iterator last_ ) const { + int len( static_cast( std::distance( first_, last_ ) ) ); + return ( + ( len <= length() ) + && ( std::equal( first_, last_, _data.end() - len ) ) + ); + } + bool is_empty( void ) const { return ( _data.size() == 0 ); } diff --git a/contrib/replxx/src/utf8string.hxx b/contrib/replxx/src/utf8string.hxx index 3adf17a34..29effa2ce 100644 --- a/contrib/replxx/src/utf8string.hxx +++ b/contrib/replxx/src/utf8string.hxx @@ -12,20 +12,24 @@ private: typedef std::unique_ptr buffer_t; buffer_t _data; int _bufSize; + int _len; public: Utf8String( void ) : _data() - , _bufSize( 0 ) { + , _bufSize( 0 ) + , _len( 0 ) { } explicit Utf8String( UnicodeString const& src ) : _data() - , _bufSize( 0 ) { + , _bufSize( 0 ) + , _len( 0 ) { assign( src, src.length() ); } Utf8String( UnicodeString const& src_, int len_ ) : _data() - , _bufSize( 0 ) { + , _bufSize( 0 ) + , _len( 0 ) { assign( src_, len_ ); } @@ -34,20 +38,39 @@ public: } 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 ); - copyString32to8( _data.get(), len, str_.get(), len_ ); + _len = copyString32to8( _data.get(), len, str_, len_ ); } void assign( std::string const& str_ ) { - realloc( str_.length() ); + realloc( static_cast( str_.length() ) ); strncpy( _data.get(), str_.c_str(), str_.length() ); + _len = static_cast( str_.length() ); + } + + void assign( Utf8String const& other_ ) { + realloc( other_._len ); + strncpy( _data.get(), other_._data.get(), other_._len ); + _len = other_._len; } char const* get() const { return _data.get(); } + int size( void ) const { + return ( _len ); + } + + bool operator != ( Utf8String const& other_ ) { + return ( ( other_._len != _len ) || ( memcmp( other_._data.get(), _data.get(), _len ) != 0 ) ); + } + private: void realloc( int reqLen ) { if ( ( reqLen + 1 ) > _bufSize ) { diff --git a/contrib/replxx/src/util.cxx b/contrib/replxx/src/util.cxx index 9beb96b80..719d7073d 100644 --- a/contrib/replxx/src/util.cxx +++ b/contrib/replxx/src/util.cxx @@ -1,5 +1,7 @@ +#include #include #include +#include #include #include "util.hxx" @@ -8,18 +10,6 @@ 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 @@ -148,5 +138,21 @@ char const* ansi_color( Replxx::Color color_ ) { return ( code ); } +std::string now_ms_str( void ) { + std::chrono::milliseconds ms( std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch() ) ); + time_t t( ms.count() / 1000 ); + tm broken; +#ifdef _WIN32 +#define localtime_r( t, b ) localtime_s( ( b ), ( t ) ) +#endif + localtime_r( &t, &broken ); +#undef localtime_r + static int const BUFF_SIZE( 32 ); + char str[BUFF_SIZE]; + strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken ); + snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast( ms.count() % 1000 ) ); + return ( str ); +} + } diff --git a/contrib/replxx/src/util.hxx b/contrib/replxx/src/util.hxx index 8afa0fa96..17c108680 100644 --- a/contrib/replxx/src/util.hxx +++ b/contrib/replxx/src/util.hxx @@ -10,10 +10,14 @@ inline bool is_control_code(char32_t testChar) { (testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls } -void recompute_character_widths( char32_t const* text, char* widths, int charCount ); +inline char32_t control_to_human( char32_t key ) { + return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) ); +} + 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 ); } diff --git a/contrib/replxx/src/windows.cxx b/contrib/replxx/src/windows.cxx index e5b6de428..715292c0c 100644 --- a/contrib/replxx/src/windows.cxx +++ b/contrib/replxx/src/windows.cxx @@ -4,11 +4,7 @@ #include "windows.hxx" #include "conversion.hxx" -#include "io.hxx" - -#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4; -#endif +#include "terminal.hxx" using namespace std; @@ -17,7 +13,7 @@ namespace replxx { WinAttributes WIN_ATTR; template -T* HandleEsc(T* p, T* end) { +T* HandleEsc(HANDLE out_, T* p, T* end) { if (*p == '[') { int code = 0; @@ -93,45 +89,37 @@ T* HandleEsc(T* p, T* end) { ++p; } - auto handle = GetStdHandle(STD_OUTPUT_HANDLE); SetConsoleTextAttribute( - handle, + out_, WIN_ATTR._consoleAttribute | WIN_ATTR._consoleColor ); return p; } -int win_write( char const* str_, int size_ ) { +int win_write( HANDLE out_, bool autoEscape_, char const* str_, int size_ ) { int count( 0 ); - DWORD currentMode( 0 ); - HANDLE consoleOut( GetStdHandle( STD_OUTPUT_HANDLE ) ); - if ( tty::out && GetConsoleMode( consoleOut, ¤tMode ) ) { - UINT inputCodePage( GetConsoleCP() ); - UINT outputCodePage( GetConsoleOutputCP() ); - SetConsoleCP( 65001 ); - SetConsoleOutputCP( 65001 ); + if ( tty::out ) { DWORD nWritten( 0 ); - if ( SetConsoleMode( consoleOut, currentMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) ) { - WriteConsoleA( consoleOut, str_, size_, &nWritten, nullptr ); + if ( autoEscape_ ) { + WriteConsoleA( out_, str_, size_, &nWritten, nullptr ); count = nWritten; - SetConsoleMode( consoleOut, currentMode ); } else { char const* s( str_ ); char const* e( str_ + size_ ); while ( str_ < e ) { if ( *str_ == 27 ) { if ( s < str_ ) { - int toWrite( str_ - s ); - WriteConsoleA( consoleOut, s, static_cast( toWrite ), &nWritten, nullptr ); + int toWrite( static_cast( str_ - s ) ); + WriteConsoleA( out_, s, static_cast( toWrite ), &nWritten, nullptr ); count += nWritten; if ( nWritten != toWrite ) { s = str_ = nullptr; break; } } - s = HandleEsc( str_ + 1, e ); - int escaped( s - str_); + s = HandleEsc( out_, str_ + 1, e ); + int escaped( static_cast( s - str_ ) ); count += escaped; str_ = s; } else { @@ -140,12 +128,10 @@ int win_write( char const* str_, int size_ ) { } if ( s < str_ ) { - WriteConsoleA( consoleOut, s, static_cast( str_ - s ), &nWritten, nullptr ); + WriteConsoleA( out_, s, static_cast( str_ - s ), &nWritten, nullptr ); count += nWritten; } } - SetConsoleCP( inputCodePage ); - SetConsoleOutputCP( outputCodePage ); } else { count = _write( 1, str_, size_ ); } diff --git a/contrib/replxx/src/windows.hxx b/contrib/replxx/src/windows.hxx index d49484fd8..243f41cb7 100644 --- a/contrib/replxx/src/windows.hxx +++ b/contrib/replxx/src/windows.hxx @@ -35,7 +35,7 @@ class WinAttributes { int _consoleColor; }; -int win_write( char const*, int ); +int win_write( HANDLE, bool, char const*, int ); extern WinAttributes WIN_ATTR; -- 2.39.5