]> git.ipfire.org Git - thirdparty/rspamd.git/commitdiff
[Minor] Update replxx library
authorVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 24 Aug 2021 14:47:07 +0000 (15:47 +0100)
committerVsevolod Stakhov <vsevolod@highsecure.ru>
Tue, 24 Aug 2021 14:47:07 +0000 (15:47 +0100)
19 files changed:
contrib/replxx/include/replxx.h
contrib/replxx/include/replxx.hxx
contrib/replxx/src/conversion.cxx
contrib/replxx/src/conversion.hxx
contrib/replxx/src/escape.cxx
contrib/replxx/src/history.cxx
contrib/replxx/src/history.hxx
contrib/replxx/src/killring.hxx
contrib/replxx/src/prompt.cxx
contrib/replxx/src/prompt.hxx
contrib/replxx/src/replxx.cxx
contrib/replxx/src/replxx_impl.cxx
contrib/replxx/src/replxx_impl.hxx
contrib/replxx/src/unicodestring.hxx
contrib/replxx/src/utf8string.hxx
contrib/replxx/src/util.cxx
contrib/replxx/src/util.hxx
contrib/replxx/src/windows.cxx
contrib/replxx/src/windows.hxx

index 4bdad512775e4d66dd11421fdd2c77a668bb6451..5127ac2aef514d8e7cffee64a4e03f57c62140df 100644 (file)
@@ -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
 }
index 1401ea27c91ada666965a5e917ab2e70161cd091..5362312e57db73e83723ce19f5629a7218a4e44b 100644 (file)
@@ -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<Completion> completions_t;
+       class HistoryEntry {
+               std::string _timestamp;
+               std::string _text;
+       public:
+               HistoryEntry( std::string const& timestamp_, std::string const& text_ )
+                       : _timestamp( timestamp_ )
+                       , _text( text_ ) {
+               }
+               std::string const& timestamp( void ) const {
+                       return ( _timestamp );
+               }
+               std::string const& text( void ) const {
+                       return ( _text );
+               }
+       };
+       class HistoryScanImpl;
+       class HistoryScan {
+       public:
+               typedef std::unique_ptr<HistoryScanImpl, void (*)( HistoryScanImpl* )> impl_t;
+       private:
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable:4251)
+#endif
+               impl_t _impl;
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+       public:
+               HistoryScan( impl_t );
+               HistoryScan( HistoryScan&& ) = default;
+               HistoryScan& operator = ( HistoryScan&& ) = default;
+               bool next( void );
+               HistoryEntry const& get( void ) const;
+       private:
+               HistoryScan( HistoryScan const& ) = delete;
+               HistoryScan& operator = ( HistoryScan const& ) = delete;
+       };
        typedef std::vector<std::string> hints_t;
 
+       /*! \brief Line modification callback type definition.
+        *
+        * User can observe and modify line contents (and cursor position)
+        * in response to changes to both introduced by the user through
+        * normal interactions.
+        *
+        * When callback returns Replxx updates current line content
+        * and current cursor position to the ones updated by the callback.
+        *
+        * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
+        * \param cursorPosition[in,out] - a R/W reference to current cursor position.
+        */
+       typedef std::function<void ( std::string& line, int& cursorPosition )> modify_callback_t;
+
        /*! \brief Completions callback type definition.
         *
         * \e contextLen is counted in Unicode code points (not in bytes!).
@@ -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<ACTION_RESULT ( char32_t code )> key_press_handler_t;
 
@@ -307,12 +371,12 @@ public:
        class ReplxxImpl;
 private:
        typedef std::unique_ptr<ReplxxImpl, void (*)( ReplxxImpl* )> 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;
index ce9bd932b11081d8c6b01b7bc519b12a0fdd3b25..bcdbe048ec878f130fd72a35268306a24caca9ff 100644 (file)
@@ -2,10 +2,7 @@
 #include <string>
 #include <cstring>
 #include <cctype>
-#include <clocale>
-
-#include "unicode/utf8.h"
-
+#include <locale.h>
 
 #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<const unsigned char*>(src);
-               auto slen = strlen(src);
-               auto targetStart = reinterpret_cast<UChar32*>(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<const UTF8*>(src);
+               const UTF8* sourceEnd = sourceStart + strlen(src);
+               UTF32* targetStart = reinterpret_cast<UTF32*>(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<int>( targetStart - reinterpret_cast<UTF32*>( 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<const UTF32*>(src);
+               const UTF32* sourceEnd = sourceStart + srcSize;
+               UTF8* targetStart = reinterpret_cast<UTF8*>(dst);
+               UTF8* targetEnd = targetStart + dstSize;
+
+               ConversionResult res = ConvertUTF32toUTF8(
+                       &sourceStart, sourceEnd, &targetStart, targetEnd, lenientConversion
+               );
+
+               if ( res == conversionOK ) {
+                       resCount = static_cast<int>( targetStart - reinterpret_cast<UTF8*>( dst ) );
+                       if ( resCount < dstSize ) {
+                               *targetStart = 0;
                        }
                }
        } else {
@@ -123,13 +96,12 @@ void copyString32to8(
                for ( i = 0; ( i < dstSize ) && ( i < srcSize ) && src[i]; ++ i ) {
                        dst[i] = static_cast<char>( src[i] );
                }
-               if ( dstCount ) {
-                       *dstCount = i;
-               }
+               resCount = i;
                if ( i < dstSize ) {
                        dst[i] = 0;
                }
        }
+       return ( resCount );
 }
 
 }
index 1cb2d450d99961c6940c9fb9a574dba754b6f9ab..6587ad0e2f1a19adc1232f402537ee55a60fb31e 100644 (file)
@@ -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( <version> )
+#include <version>
+#endif
+#endif
 
+#if ! ( defined( __cpp_lib_char8_t ) || ( defined( __clang_major__ ) && ( __clang_major__ >= 8 ) && ( __cplusplus > 201703L ) ) )
+namespace replxx {
 typedef unsigned char char8_t;
+}
+#endif
 
-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;
index 3edc4c1ec471f96a6f757e1260cd1d32391abd6c..dda1ab0be091092ffa674cce679d6963f32d5ee3 100644 (file)
@@ -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();
index 6c6eff346c93b0de84fc6ef5eb9ee5d2e8caac46..fe691df089abd62761dc8ca5d18927e705043567 100644 (file)
+#include <algorithm>
+#include <memory>
 #include <fstream>
 #include <cstring>
 
 #ifndef _WIN32
 
 #include <unistd.h>
+#include <fcntl.h>
 #include <sys/stat.h>
 
 #endif /* _WIN32 */
 
+#include "replxx.hxx"
 #include "history.hxx"
-#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<int>( line.length() ) > _maxLineLength ) {
-                       _maxLineLength = static_cast<int>( 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<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
+       }
+       ~FileLock( void ) {
+               static_cast<void>( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 );
+               ::close( _lockFd );
+               ::unlink( _path.c_str() );
+               return;
+       }
+};
 #endif
+
+bool History::save( std::string const& filename, bool sync_ ) {
+#ifndef _WIN32
+       mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO );
+       FileLock fileLock( filename );
+#endif
+       entries_t entries;
+       locations_t locations;
+       if ( ! sync_ ) {
+               entries.swap( _entries );
+               locations.swap( _locations );
+               _entries = entries;
+               reset_iters();
+       }
+       do_load( filename );
+       sort();
+       remove_duplicates();
+       trim_to_max_size();
        ofstream histFile( filename );
        if ( ! histFile ) {
-               return ( -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<Entry> sortable_entries_t;
+       _locations.clear();
+       sortable_entries_t sortableEntries( _entries.begin(), _entries.end() );
+       std::stable_sort( sortableEntries.begin(), sortableEntries.end() );
+       _entries.clear();
+       _entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() );
+}
+
+void History::clear( void ) {
+       _locations.clear();
+       _entries.clear();
+       _current = _entries.begin();
+       _recallMostRecent = false;
 }
 
 void History::set_max_size( int size_ ) {
        if ( size_ >= 0 ) {
                _maxSize = size_;
-               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_t::iterator, bool> locations_insertion_result_t;
+       for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) {
+               locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) );
+               if ( ! locationsInsertionResult.second ) {
+                       _entries.erase( locationsInsertionResult.first->second );
+                       locationsInsertionResult.first->second = it;
+               }
+       }
+}
+
+void History::update_last( UnicodeString const& line_ ) {
+       if ( _unique ) {
+               _locations.erase( _entries.back().text() );
+               remove_duplicate( line_ );
+               _locations.insert( make_pair( line_, last() ) );
+       }
+       _entries.back() = Entry( now_ms_str(), line_ );
+}
+
+void History::drop_last( void ) {
+       erase( last() );
+}
+
+bool History::is_last( void ) const {
+       return ( _current == last() );
+}
+
+History::entries_t::const_iterator History::last( void ) const {
+       return ( moved( _entries.end(), -1 ) );
+}
+
+void History::reset_iters( void ) {
+       _previous = _current = last();
+       _yankPos = _entries.end();
 }
 
 }
index 33aed148ab42c2deede5f8fb7b3a78f6f17ac7b0..4e72c036763ddfec6e9e7b851c203df1a7989283 100644 (file)
 #ifndef REPLXX_HISTORY_HXX_INCLUDED
 #define REPLXX_HISTORY_HXX_INCLUDED 1
 
-#include <vector>
+#include <list>
+#include <unordered_map>
 
 #include "unicodestring.hxx"
+#include "utf8string.hxx"
 #include "conversion.hxx"
+#include "util.hxx"
+
+namespace std {
+template<>
+struct hash<replxx::UnicodeString> {
+       std::size_t operator()( replxx::UnicodeString const& us_ ) const {
+               std::size_t h( 0 );
+               char32_t const* p( us_.get() );
+               char32_t const* e( p + us_.length() );
+               while ( p != e ) {
+                       h *= 31;
+                       h += *p;
+                       ++ p;
+               }
+               return ( h );
+       }
+};
+}
 
 namespace replxx {
 
 class History {
 public:
-       typedef std::vector<UnicodeString> 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<Entry> entries_t;
+       typedef std::unordered_map<UnicodeString, entries_t::const_iterator> 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<int>( _data.size() ) );
-       }
-       int max_line_length( void ) {
-               return ( _maxLineLength );
+               return ( static_cast<int>( _entries.size() ) );
        }
+       Replxx::HistoryScan::impl_t scan( void ) const;
+       void save_pos( void );
+       void restore_pos( void );
 private:
        History( History const& ) = delete;
        History& operator = ( History const& ) = delete;
+       bool move( entries_t::const_iterator&, int, bool = false ) const;
+       entries_t::const_iterator moved( entries_t::const_iterator, int, bool = false ) const;
+       void erase( entries_t::const_iterator );
+       void trim_to_max_size( void );
+       void remove_duplicate( UnicodeString const& );
+       void remove_duplicates( void );
+       bool do_load( std::string const& );
+       entries_t::const_iterator last( void ) const;
+       void sort( void );
+       void reset_iters( void );
+};
+
+class Replxx::HistoryScanImpl {
+       History::entries_t const& _entries;
+       History::entries_t::const_iterator _it;
+       mutable Utf8String _utf8Cache;
+       mutable Replxx::HistoryEntry _entryCache;
+       mutable bool _cacheValid;
+public:
+       HistoryScanImpl( History::entries_t const& );
+       bool next( void );
+       Replxx::HistoryEntry const& get( void ) const;
 };
 
 }
index 9eca23a8ef7b6cf7cab83dba0e95a9f05b4549e8..0baf108e73e534f5b1b9c4760e21bd48994ff86f 100644 (file)
@@ -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);
        }
 
index 391d3745b246d1ef5d2c75dea3c3bbabd16812d3..c13ea808bf43dc81b4a726d8bfa1af84da2ff67e 100644 (file)
@@ -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<int>(out - _text.begin());
+       _characterCount = visibleCount;
+       int charCount( static_cast<int>( 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<int>(promptStartLength + endSearchBasePrompt.length());
-       _byteCount = _characterCount;
-       _lastLinePosition = _characterCount; // TODO fix this, we are asssuming
-                                               // that the history prompt won't wrap (!)
-       _previousLen = _characterCount;
-       _text.assign( *basePrompt ).append( endSearchBasePrompt );
-       calculate_screen_position(
-               0, 0, screen_columns(), _characterCount,
-               _indentation, _extraLines
-       );
+       updateSearchPrompt();
 }
 
 void DynamicPrompt::updateSearchPrompt(void) {
+       update_screen_columns();
        const UnicodeString* basePrompt =
                        (_direction > 0) ? &forwardSearchBasePrompt : &reverseSearchBasePrompt;
-       size_t promptStartLength = basePrompt->length();
-       _characterCount = static_cast<int>(promptStartLength + _searchText.length() +
-                                                                                                                                endSearchBasePrompt.length());
-       _byteCount = _characterCount;
        _text.assign( *basePrompt ).append( _searchText ).append( endSearchBasePrompt );
+       update_state();
 }
 
 }
index aabff0ab05ce502bf1b64bdc4a2b8e0c392923f9..9ed3f5fd9520032f59a8b65ca91e11511d553a19 100644 (file)
@@ -4,36 +4,32 @@
 #include <cstdlib>
 
 #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 {
index 7803d873a9f38554dfd14184bdfb89aa45ddbd05..29d35a2313268887d1aec7478e5e3170af3951c4 100644 (file)
 #include "replxx.h"
 #include "replxx.hxx"
 #include "replxx_impl.hxx"
-#include "io.hxx"
+#include "history.hxx"
+
+static_assert(
+       static_cast<int>( replxx::Replxx::ACTION::SEND_EOF ) == static_cast<int>( REPLXX_ACTION_SEND_EOF ),
+       "C and C++ `ACTION` APIs are missaligned!"
+);
+
+static_assert(
+       static_cast<int>( replxx::Replxx::KEY::PASTE_FINISH ) == static_cast<int>( REPLXX_KEY_PASTE_FINISH ),
+       "C and C++ `KEY` APIs are missaligned!"
+);
 
 using namespace std;
 using namespace std::placeholders;
@@ -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::Replxx::ReplxxImpl*>( replxx_ ) );
+       try {
+               replxx->bind_key_internal( code_, actionName_ );
+       } catch ( ... ) {
+               return ( -1 );
+       }
+       return ( 0 );
+}
+
 void replxx_get_state( ::Replxx* replxx_, ReplxxState* state ) {
        replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
        replxx::Replxx::State s( replxx->get_state() );
@@ -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::Replxx::ReplxxImpl*>( 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::Replxx::ReplxxImpl*>( replxx_ ) );
+       try {
+               replxx->print( str, length );
+       } catch ( ... ) {
+               return ( -1 );
+       }
+       return static_cast<int>( 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::ReplxxImpl*>( replxx_ ) );
+       replxx->set_modify_callback( std::bind( &modify_fwd, fn, _1, _2, userData ) );
+}
+
 replxx::Replxx::completions_t completions_fwd( replxx_completion_callback_t fn, std::string const& input_, int& contextLen_, void* userData ) {
        replxx_completions completions;
        fn( input_.c_str(), &completions, &contextLen_, userData );
@@ -359,7 +442,7 @@ void highlighter_fwd( replxx_highlighter_callback_t fn, std::string const& input
                        return ( static_cast<ReplxxColor>( c ) );
                }
        );
-       fn( input.c_str(), colorsTmp.data(), colors.size(), userData );
+       fn( input.c_str(), colorsTmp.data(), static_cast<int>( 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<replxx::Replxx::Color>( 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::ReplxxImpl*>( replxx_ ) );
+       replxx->set_immediate_completion( val ? true : false );
+}
+
+void replxx_set_unique_history( ::Replxx* replxx_, int val ) {
+       replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+       replxx->set_unique_history( val ? true : false );
+}
+
+void replxx_enable_bracketed_paste( ::Replxx* replxx_ ) {
+       replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+       replxx->enable_bracketed_paste();
+}
+
+void replxx_disable_bracketed_paste( ::Replxx* replxx_ ) {
        replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
-       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::Replxx::ReplxxImpl*>( replxx_ ) );
+       return ( reinterpret_cast<ReplxxHistoryScan*>( replxx->history_scan().release() ) );
+}
+
+void replxx_history_scan_stop( ::Replxx*, ReplxxHistoryScan* historyScan_ ) {
+       delete reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ );
+}
+
+int replxx_history_scan_next( ::Replxx*, ReplxxHistoryScan* historyScan_, ReplxxHistoryEntry* historyEntry_ ) {
+       replxx::Replxx::HistoryScanImpl* historyScan( reinterpret_cast<replxx::Replxx::HistoryScanImpl*>( historyScan_ ) );
+       bool hasNext( historyScan->next() );
+       if ( hasNext ) {
+               replxx::Replxx::HistoryEntry const& historyEntry( historyScan->get() );
+               historyEntry_->timestamp = historyEntry.timestamp().c_str();
+               historyEntry_->text = historyEntry.text().c_str();
+       }
+       return ( hasNext ? 0 : -1 );
+}
+
+/* Save the history in the specified file. On success 0 is returned
+ * otherwise -1 is returned. */
+int replxx_history_sync( ::Replxx* replxx_, const char* filename ) {
+       replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
+       return ( replxx->history_sync( filename ) ? 0 : -1 );
 }
 
 /* Save the history in the specified file. On success 0 is returned
  * otherwise -1 is returned. */
 int replxx_history_save( ::Replxx* replxx_, const char* filename ) {
        replxx::Replxx::ReplxxImpl* replxx( reinterpret_cast<replxx::Replxx::ReplxxImpl*>( replxx_ ) );
-       return ( replxx->history_save( filename ) );
+       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::Replxx::ReplxxImpl*>( 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::ReplxxImpl*>( replxx_ ) );
+       replxx->history_clear();
 }
 
 int replxx_history_size( ::Replxx* replxx_ ) {
index 14f3cbd4d5bc3497326aa9f6f40702b4061c9104..28152d58d51e600e0a4483f4d343eb440c2589ba 100644 (file)
@@ -2,6 +2,7 @@
 #include <memory>
 #include <cerrno>
 #include <iostream>
+#include <chrono>
 
 #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::microseconds>( 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<false>, code ) );
+               case ( Replxx::ACTION::KILL_TO_BEGINING_OF_WORD ):        return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left<false>, code ) );
+               case ( Replxx::ACTION::KILL_TO_END_OF_SUBWORD ):          return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_right<true>, code ) );
+               case ( Replxx::ACTION::KILL_TO_BEGINING_OF_SUBWORD ):     return ( action( SET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::kill_word_to_left<true>, 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<false>, code ) );
+               case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ):      return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right<false>, code ) );
+               case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT ):    return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left<true>, code ) );
+               case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT ):   return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right<true>, 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<false>, code ) );
+               case ( Replxx::ACTION::LOWERCASE_WORD ):                  return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word<false>, code ) );
+               case ( Replxx::ACTION::UPPERCASE_WORD ):                  return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word<false>, code ) );
+               case ( Replxx::ACTION::CAPITALIZE_SUBWORD ):              return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word<true>, code ) );
+               case ( Replxx::ACTION::LOWERCASE_SUBWORD ):               return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word<true>, code ) );
+               case ( Replxx::ACTION::UPPERCASE_SUBWORD ):               return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word<true>, 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<int>( 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<std::mutex> 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<int>( 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<int>( _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<int>( _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<int>( _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<int>( _hint.length(), maxCol - startCol - _data.length() );
-                       if ( contextLen < len ) {
-                               set_color( c );
-                               for ( int i( contextLen ); i < len; ++ i ) {
+                       _hint = _hintsCache[_hintSelection];
+                       len = min<int>( _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<int>( 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<int>( _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<false>( _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<int>( 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<int>( _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<int>( Replxx::KEY::BASE ) ) || is_control_code( c ) ) {
+       if ( ( c >= static_cast<int>( 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<char32_t*>(&c), 1);
+               _displayInputLength = static_cast<int>( _display.size() );
+               _terminal.write32( reinterpret_cast<char32_t*>( &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 <bool subword>
 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<subword>( _data[_pos - 1] ) ) {
                        --_pos;
                }
-               while (_pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) {
+               while (_pos > 0 && !is_word_break_character<subword>( _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 <bool subword>
 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<subword>( _data[_pos] ) ) {
                        ++_pos;
                }
-               while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
+               while ( _pos < _data.length() && !is_word_break_character<subword>( _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 <bool subword>
 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<subword>( _data[_pos - 1] ) ) {
                        -- _pos;
                }
-               while ( _pos > 0 && !is_word_break_character( _data[_pos - 1] ) ) {
+               while ( _pos > 0 && !is_word_break_character<subword>( _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 <bool subword>
 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<subword>( _data[endingPos] ) ) {
                        ++ endingPos;
                }
-               while ( endingPos < _data.length() && !is_word_break_character( _data[endingPos] ) ) {
+               while ( endingPos < _data.length() && !is_word_break_character<subword>( _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 <bool subword>
 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<subword>( _data[_pos] ) ) {
                        ++_pos;
                }
-               if (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
+               if (_pos < _data.length() && !is_word_break_character<subword>( _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<subword>( _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 <bool subword>
 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<subword>( _data[_pos] ) ) {
                        ++ _pos;
                }
-               while (_pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
+               while (_pos < _data.length() && !is_word_break_character<subword>( _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 <bool subword>
 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<subword>( _data[_pos] ) ) {
                        ++ _pos;
                }
-               while ( _pos < _data.length() && !is_word_break_character( _data[_pos] ) ) {
+               while ( _pos < _data.length() && !is_word_break_character<subword>( _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<int>( _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<int>( 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 subword>
 bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const {
        bool wbc( false );
        if ( char_ < 128 ) {
-               wbc = strchr( _breakChars, static_cast<char>( char_ ) ) != nullptr;
+               wbc = strchr( subword ? _subwordBreakChars.c_str() : _wordBreakChars.c_str(), static_cast<char>( 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
 }
 
 }
index 3cf1e82033f24ef5d9dc9af73fd99fbeb6b4e6a4..bec9383c15fb0dd3a9951ea1f2e48eddc673614d 100644 (file)
 #include <unordered_map>
 #include <thread>
 #include <mutex>
+#include <chrono>
 
 #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<Completion> completions_t;
+       typedef std::vector<UnicodeString> data_t;
        typedef std::vector<UnicodeString> hints_t;
        typedef std::unique_ptr<char[]> utf8_buffer_t;
        typedef std::unique_ptr<char32_t[]> input_buffer_t;
-       typedef std::vector<char> char_widths_t;
        typedef std::vector<char32_t> display_t;
        typedef std::deque<char32_t> key_presses_t;
        typedef std::deque<std::string> messages_t;
@@ -87,41 +87,51 @@ public:
                TRIM,
                SKIP
        };
+       typedef std::unordered_map<std::string, Replxx::key_press_handler_t> named_actions_t;
        typedef Replxx::ACTION_RESULT ( ReplxxImpl::* key_press_handler_raw_t )( char32_t );
        typedef std::unordered_map<int, Replxx::key_press_handler_t> key_press_handlers_t;
 private:
        typedef int long long unsigned action_trait_t;
-       static action_trait_t const NOOP                   =  0;
-       static action_trait_t const WANT_REFRESH           =  1;
-       static action_trait_t const RESET_KILL_ACTION      =  2;
-       static action_trait_t const SET_KILL_ACTION        =  4;
-       static action_trait_t const DONT_RESET_PREFIX      =  8;
-       static action_trait_t const DONT_RESET_COMPLETIONS = 16;
+       static action_trait_t const 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 <bool subword>
        Replxx::ACTION_RESULT move_one_word_left( char32_t );
+       template <bool subword>
        Replxx::ACTION_RESULT move_one_word_right( char32_t );
+       template <bool subword>
        Replxx::ACTION_RESULT kill_word_to_left( char32_t );
+       template <bool subword>
        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 <bool subword>
        Replxx::ACTION_RESULT capitalize_word( char32_t );
+       template <bool subword>
        Replxx::ACTION_RESULT lowercase_word( char32_t );
+       template <bool subword>
        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 subword>
        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;
index 1607ede66bb44869138cdf6b4352daf29ace8a44..22f3e46956dde3ee5c3b4199e20f013f1ed02833 100644 (file)
@@ -2,7 +2,6 @@
 #define REPLXX_UNICODESTRING_HXX_INCLUDED
 
 #include <vector>
-#include <string>
 #include <cstring>
 
 #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<int>( str_.length() ) );
                int len( 0 );
-               copyString8to32( _data.data(), str_.length(), len, str_.c_str() );
+               copyString8to32( _data.data(), static_cast<int>( 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<int>( 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<int>( std::distance( first_, last_ ) ) );
+               return (
+                       ( len <= length() )
+                       && ( std::equal( first_, last_, _data.end() - len ) )
+               );
+       }
+
        bool is_empty( void ) const {
                return ( _data.size() == 0 );
        }
index 3adf17a3492dfe24a064f69d8e946a0acb268cbe..29effa2ce58dabc8f55835c6dd0ff4711dd78a2d 100644 (file)
@@ -12,20 +12,24 @@ private:
        typedef std::unique_ptr<char[]> 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<int>( str_.length() ) );
                strncpy( _data.get(), str_.c_str(), str_.length() );
+               _len = static_cast<int>( str_.length() );
+       }
+
+       void assign( Utf8String const& other_ ) {
+               realloc( other_._len );
+               strncpy( _data.get(), other_._data.get(), other_._len );
+               _len = other_._len;
        }
 
        char const* get() const {
                return _data.get();
        }
 
+       int size( void ) const {
+               return ( _len );
+       }
+
+       bool operator != ( Utf8String const& other_ ) {
+               return ( ( other_._len != _len ) || ( memcmp( other_._data.get(), _data.get(), _len ) != 0 ) );
+       }
+
 private:
        void realloc( int reqLen ) {
                if ( ( reqLen + 1 ) > _bufSize ) {
index 9beb96b80fe64d60a80d89c14d2874d5253eafc4..719d7073d8f8dab64074e16a0a1590efe93a4d87 100644 (file)
@@ -1,5 +1,7 @@
+#include <chrono>
 #include <cstdlib>
 #include <cstring>
+#include <ctime>
 #include <wctype.h>
 
 #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::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ) );
+       time_t t( ms.count() / 1000 );
+       tm broken;
+#ifdef _WIN32
+#define localtime_r( t, b ) localtime_s( ( b ), ( t ) )
+#endif
+       localtime_r( &t, &broken );
+#undef localtime_r
+       static int const BUFF_SIZE( 32 );
+       char str[BUFF_SIZE];
+       strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken );
+       snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast<int>( ms.count() % 1000 ) );
+       return ( str );
+}
+
 }
 
index 8afa0fa96d8ddd26f857eeeb4c4b145c125137d2..17c1086800b0cfe4b7e32334ca48accc613d31ee 100644 (file)
@@ -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 );
 
 }
 
index e5b6de428d4327f387192f233d709230f7b692a4..715292c0c5e2323e21d787fb54f33d1e8530c65e 100644 (file)
@@ -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<typename T>
-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, &currentMode ) ) {
-               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<DWORD>( toWrite ), &nWritten, nullptr );
+                                               int toWrite( static_cast<int>( str_ - s ) );
+                                               WriteConsoleA( out_, s, static_cast<DWORD>( toWrite ), &nWritten, nullptr );
                                                count += nWritten;
                                                if ( nWritten != toWrite ) {
                                                        s = str_ = nullptr;
                                                        break;
                                                }
                                        }
-                                       s = HandleEsc( str_ + 1, e );
-                                       int escaped( s - str_);
+                                       s = HandleEsc( out_, str_ + 1, e );
+                                       int escaped( static_cast<int>( 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<DWORD>( str_ - s ), &nWritten, nullptr );
+                               WriteConsoleA( out_, s, static_cast<DWORD>( str_ - s ), &nWritten, nullptr );
                                count += nWritten;
                        }
                }
-               SetConsoleCP( inputCodePage );
-               SetConsoleOutputCP( outputCodePage );
        } else {
                count = _write( 1, str_, size_ );
        }
index d49484fd86188ddd07ce0312c163f4f31f49c60a..243f41cb7e8b6c60fd4f2217abda097db05e0ec1 100644 (file)
@@ -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;