summaryrefslogtreecommitdiffstats
path: root/contrib
diff options
context:
space:
mode:
authorVsevolod Stakhov <vsevolod@highsecure.ru>2021-08-24 15:47:07 +0100
committerVsevolod Stakhov <vsevolod@highsecure.ru>2021-08-24 15:47:07 +0100
commit4893fc8dc5b54968be8949fe3b45fc7326cbb90f (patch)
treef766d309525eb9f9544d9e5c5262aabf063e084f /contrib
parentaf4fca4b2a5543bc3838897fa603af2e0cdc210c (diff)
downloadrspamd-4893fc8dc5b54968be8949fe3b45fc7326cbb90f.tar.gz
rspamd-4893fc8dc5b54968be8949fe3b45fc7326cbb90f.zip
[Minor] Update replxx library
Diffstat (limited to 'contrib')
-rw-r--r--contrib/replxx/include/replxx.h142
-rw-r--r--contrib/replxx/include/replxx.hxx174
-rw-r--r--contrib/replxx/src/conversion.cxx88
-rw-r--r--contrib/replxx/src/conversion.hxx21
-rw-r--r--contrib/replxx/src/escape.cxx36
-rw-r--r--contrib/replxx/src/history.cxx390
-rw-r--r--contrib/replxx/src/history.hxx134
-rw-r--r--contrib/replxx/src/killring.hxx6
-rw-r--r--contrib/replxx/src/prompt.cxx64
-rw-r--r--contrib/replxx/src/prompt.hxx12
-rw-r--r--contrib/replxx/src/replxx.cxx159
-rw-r--r--contrib/replxx/src/replxx_impl.cxx896
-rw-r--r--contrib/replxx/src/replxx_impl.hxx73
-rw-r--r--contrib/replxx/src/unicodestring.hxx28
-rw-r--r--contrib/replxx/src/utf8string.hxx33
-rw-r--r--contrib/replxx/src/util.cxx30
-rw-r--r--contrib/replxx/src/util.hxx6
-rw-r--r--contrib/replxx/src/windows.cxx38
-rw-r--r--contrib/replxx/src/windows.hxx2
19 files changed, 1692 insertions, 640 deletions
diff --git a/contrib/replxx/include/replxx.h b/contrib/replxx/include/replxx.h
index 4bdad5127..5127ac2ae 100644
--- a/contrib/replxx/include/replxx.h
+++ b/contrib/replxx/include/replxx.h
@@ -126,6 +126,8 @@ enum { REPLXX_KEY_F22 = REPLXX_KEY_F21 + 1 };
enum { REPLXX_KEY_F23 = REPLXX_KEY_F22 + 1 };
enum { REPLXX_KEY_F24 = REPLXX_KEY_F23 + 1 };
enum { REPLXX_KEY_MOUSE = REPLXX_KEY_F24 + 1 };
+enum { REPLXX_KEY_PASTE_START = REPLXX_KEY_MOUSE + 1 };
+enum { REPLXX_KEY_PASTE_FINISH = REPLXX_KEY_PASTE_START + 1 };
#define REPLXX_KEY_SHIFT( key ) ( ( key ) | REPLXX_KEY_BASE_SHIFT )
#define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL )
@@ -139,19 +141,25 @@ enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) };
*/
typedef enum {
REPLXX_ACTION_INSERT_CHARACTER,
+ REPLXX_ACTION_NEW_LINE,
REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR,
REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR,
REPLXX_ACTION_KILL_TO_END_OF_LINE,
REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE,
REPLXX_ACTION_KILL_TO_END_OF_WORD,
REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD,
+ REPLXX_ACTION_KILL_TO_END_OF_SUBWORD,
+ REPLXX_ACTION_KILL_TO_BEGINING_OF_SUBWORD,
REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT,
REPLXX_ACTION_YANK,
REPLXX_ACTION_YANK_CYCLE,
+ REPLXX_ACTION_YANK_LAST_ARG,
REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE,
REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE,
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT,
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT,
+ REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_LEFT,
+ REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT,
REPLXX_ACTION_MOVE_CURSOR_LEFT,
REPLXX_ACTION_MOVE_CURSOR_RIGHT,
REPLXX_ACTION_HISTORY_NEXT,
@@ -165,12 +173,16 @@ typedef enum {
REPLXX_ACTION_CAPITALIZE_WORD,
REPLXX_ACTION_LOWERCASE_WORD,
REPLXX_ACTION_UPPERCASE_WORD,
+ REPLXX_ACTION_CAPITALIZE_SUBWORD,
+ REPLXX_ACTION_LOWERCASE_SUBWORD,
+ REPLXX_ACTION_UPPERCASE_SUBWORD,
REPLXX_ACTION_TRANSPOSE_CHARACTERS,
REPLXX_ACTION_TOGGLE_OVERWRITE_MODE,
#ifndef _WIN32
REPLXX_ACTION_VERBATIM_INSERT,
REPLXX_ACTION_SUSPEND,
#endif
+ REPLXX_ACTION_BRACKETED_PASTE,
REPLXX_ACTION_CLEAR_SCREEN,
REPLXX_ACTION_CLEAR_SELF,
REPLXX_ACTION_REPAINT,
@@ -196,12 +208,17 @@ typedef struct ReplxxStateTag {
} ReplxxState;
typedef struct Replxx Replxx;
+typedef struct ReplxxHistoryScan ReplxxHistoryScan;
+typedef struct ReplxxHistoryEntryTag {
+ char const* timestamp;
+ char const* text;
+} ReplxxHistoryEntry;
-/*! \brief Create Replxx library resouce holder.
+/*! \brief Create Replxx library resource holder.
*
- * Use replxx_end() to free resoiurce acquired with this function.
+ * Use replxx_end() to free resources acquired with this function.
*
- * \return Replxx library resouce holder.
+ * \return Replxx library resource holder.
*/
REPLXX_IMPEXP Replxx* replxx_init( void );
@@ -211,6 +228,28 @@ REPLXX_IMPEXP Replxx* replxx_init( void );
*/
REPLXX_IMPEXP void replxx_end( Replxx* replxx );
+/*! \brief Line modification callback type definition.
+ *
+ * User can observe and modify line contents (and cursor position)
+ * in response to changes to both introduced by the user through
+ * normal interactions.
+ *
+ * When callback returns Replxx updates current line content
+ * and current cursor position to the ones updated by the callback.
+ *
+ * \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far.
+ * \param cursorPosition[in,out] - a R/W reference to current cursor position.
+ * \param userData - pointer to opaque user data block.
+ */
+typedef void (replxx_modify_callback_t)(char** input, int* contextLen, void* userData);
+
+/*! \brief Register modify callback.
+ *
+ * \param fn - user defined callback function.
+ * \param userData - pointer to opaque user data block to be passed into each invocation of the callback.
+ */
+REPLXX_IMPEXP void replxx_set_modify_callback( Replxx*, replxx_modify_callback_t* fn, void* userData );
+
/*! \brief Highlighter callback type definition.
*
* If user want to have colorful input she must simply install highlighter callback.
@@ -247,8 +286,8 @@ typedef struct replxx_completions replxx_completions;
* input == "if ( obj.me"
* contextLen == 2 (depending on \e replxx_set_word_break_characters())
*
- * Client application is free to update \e contextLen to be 6 (or any orther non-negative
- * number not greated than the number of code points in input) if it makes better sense
+ * Client application is free to update \e contextLen to be 6 (or any other non-negative
+ * number not greater than the number of code points in input) if it makes better sense
* for given client application semantics.
*
* \param input - UTF-8 encoded input entered by the user until current cursor position.
@@ -292,8 +331,8 @@ typedef struct replxx_hints replxx_hints;
* input == "if ( obj.me"
* contextLen == 2 (depending on \e replxx_set_word_break_characters())
*
- * Client application is free to update \e contextLen to be 6 (or any orther non-negative
- * number not greated than the number of code points in input) if it makes better sense
+ * Client application is free to update \e contextLen to be 6 (or any other non-negative
+ * number not greater than the number of code points in input) if it makes better sense
* for given client application semantics.
*
* \param input - UTF-8 encoded input entered by the user until current cursor position.
@@ -314,7 +353,7 @@ REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn
/*! \brief Key press handler type definition.
*
* \param code - the key code replxx got from terminal.
- * \return Decition on how should input() behave after this key handler returns.
+ * \return Decision on how should input() behave after this key handler returns.
*/
typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData );
@@ -327,6 +366,8 @@ REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str );
/*! \brief Read line of user input.
*
+ * Returned pointer is managed by the library and is not to be freed in the client.
+ *
* \param prompt - prompt to be displayed before getting user input.
* \return An UTF-8 encoded input given by the user (or nullptr on EOF).
*/
@@ -356,11 +397,17 @@ REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state );
*
* \param fmt - printf style format.
*/
-#ifdef __GNUC__
-__attribute__((format(printf, 2, 3)))
-#endif
REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... );
+/*! \brief Prints a char array with the given length to standard output.
+ *
+ * \copydetails print
+ *
+ * \param str - The char array to print.
+ * \param length - The length of the array.
+ */
+REPLXX_IMPEXP int replxx_write( Replxx*, char const* str, int length );
+
/*! \brief Schedule an emulated key press event.
*
* \param code - key press code to be emulated.
@@ -385,6 +432,19 @@ REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, in
*/
REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData );
+/*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
+ *
+ * Action names are the same as unique part of names of ReplxxAction enumerations
+ * but in lower case, e.g.: an action for recalling previous history line
+ * is \e REPLXX_ACTION_HISTORY_PREVIOUS so action name to be used in this
+ * interface for the same effect is "history_previous".
+ *
+ * \param code - handle this key-press event with following handler.
+ * \param actionName - name of internal action to be invoked on key press.
+ * \return -1 if invalid action name was used, 0 otherwise.
+ */
+int replxx_bind_key_internal( Replxx*, int code, char const* actionName );
+
REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText );
REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line );
@@ -430,6 +490,23 @@ REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val );
*/
REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val );
+/*! \brief Set complete next/complete previous behavior.
+ *
+ * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
+ * in case when a partial completion is possible complete only partial part (`false` setting)
+ * or complete first proposed completion fully (`true` setting).
+ * The default is to complete fully (a `true` setting - complete immediately).
+ *
+ * \param val - complete immediately.
+ */
+REPLXX_IMPEXP void replxx_set_immediate_completion( Replxx*, int val );
+
+/*! \brief Set history duplicate entries behaviour.
+ *
+ * \param val - should history contain only unique entries?
+ */
+REPLXX_IMPEXP void replxx_set_unique_history( Replxx*, int val );
+
/*! \brief Disable output coloring.
*
* \param val - if set to non-zero disable output colors.
@@ -439,15 +516,56 @@ REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val );
/*! \brief Set maximum number of entries in history list.
*/
REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len );
-REPLXX_IMPEXP char const* replxx_history_line( Replxx*, int index );
+REPLXX_IMPEXP ReplxxHistoryScan* replxx_history_scan_start( Replxx* );
+REPLXX_IMPEXP void replxx_history_scan_stop( Replxx*, ReplxxHistoryScan* );
+REPLXX_IMPEXP int replxx_history_scan_next( Replxx*, ReplxxHistoryScan*, ReplxxHistoryEntry* );
+
+/*! \brief Synchronize REPL's history with given file.
+ *
+ * Synchronizing means loading existing history from given file,
+ * merging it with current history sorted by timestamps,
+ * saving merged version to given file,
+ * keeping merged version as current REPL's history.
+ *
+ * This call is an equivalent of calling:
+ * replxx_history_save( rx, "some-file" );
+ * replxx_history_load( rx, "some-file" );
+ *
+ * \param filename - a path to the file with which REPL's current history should be synchronized.
+ * \return 0 iff history file was successfully created, -1 otherwise.
+ */
+REPLXX_IMPEXP int replxx_history_sync( Replxx*, const char* filename );
+
+/*! \brief Save REPL's history into given file.
+ *
+ * Saving means loading existing history from given file,
+ * merging it with current history sorted by timestamps,
+ * saving merged version to given file,
+ * keeping original (NOT merged) version as current REPL's history.
+ *
+ * \param filename - a path to the file where REPL's history should be saved.
+ * \return 0 iff history file was successfully created, -1 otherwise.
+ */
REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename );
+
+/*! \brief Load REPL's history from given file.
+ *
+ * \param filename - a path to the file which contains REPL's history that should be loaded.
+ * \return 0 iff history file was successfully opened, -1 otherwise.
+ */
REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename );
+
+/*! \brief Clear REPL's in-memory history.
+ */
+REPLXX_IMPEXP void replxx_history_clear( Replxx* );
REPLXX_IMPEXP void replxx_clear_screen( Replxx* );
#ifdef __REPLXX_DEBUG__
void replxx_debug_dump_print_codes(void);
#endif
/* the following is extension to the original linenoise API */
REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* );
+REPLXX_IMPEXP void replxx_enable_bracketed_paste( Replxx* );
+REPLXX_IMPEXP void replxx_disable_bracketed_paste( Replxx* );
#ifdef __cplusplus
}
diff --git a/contrib/replxx/include/replxx.hxx b/contrib/replxx/include/replxx.hxx
index 1401ea27c..5362312e5 100644
--- a/contrib/replxx/include/replxx.hxx
+++ b/contrib/replxx/include/replxx.hxx
@@ -131,6 +131,8 @@ public:
static char32_t const F23 = F22 + 1;
static char32_t const F24 = F23 + 1;
static char32_t const MOUSE = F24 + 1;
+ static char32_t const PASTE_START = MOUSE + 1;
+ static char32_t const PASTE_FINISH = PASTE_START + 1;
static constexpr char32_t shift( char32_t key_ ) {
return ( key_ | BASE_SHIFT );
}
@@ -148,19 +150,25 @@ public:
*/
enum class ACTION {
INSERT_CHARACTER,
+ NEW_LINE,
DELETE_CHARACTER_UNDER_CURSOR,
DELETE_CHARACTER_LEFT_OF_CURSOR,
KILL_TO_END_OF_LINE,
KILL_TO_BEGINING_OF_LINE,
KILL_TO_END_OF_WORD,
KILL_TO_BEGINING_OF_WORD,
+ KILL_TO_END_OF_SUBWORD,
+ KILL_TO_BEGINING_OF_SUBWORD,
KILL_TO_WHITESPACE_ON_LEFT,
YANK,
YANK_CYCLE,
+ YANK_LAST_ARG,
MOVE_CURSOR_TO_BEGINING_OF_LINE,
MOVE_CURSOR_TO_END_OF_LINE,
MOVE_CURSOR_ONE_WORD_LEFT,
MOVE_CURSOR_ONE_WORD_RIGHT,
+ MOVE_CURSOR_ONE_SUBWORD_LEFT,
+ MOVE_CURSOR_ONE_SUBWORD_RIGHT,
MOVE_CURSOR_LEFT,
MOVE_CURSOR_RIGHT,
HISTORY_NEXT,
@@ -174,12 +182,16 @@ public:
CAPITALIZE_WORD,
LOWERCASE_WORD,
UPPERCASE_WORD,
+ CAPITALIZE_SUBWORD,
+ LOWERCASE_SUBWORD,
+ UPPERCASE_SUBWORD,
TRANSPOSE_CHARACTERS,
TOGGLE_OVERWRITE_MODE,
#ifndef _WIN32
VERBATIM_INSERT,
SUSPEND,
#endif
+ BRACKETED_PASTE,
CLEAR_SCREEN,
CLEAR_SELF,
REPAINT,
@@ -222,8 +234,60 @@ public:
}
};
typedef std::vector<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.
@@ -341,6 +411,8 @@ public:
/*! \brief Read line of user input.
*
+ * Returned pointer is managed by the library and is not to be freed in the client.
+ *
* \param prompt - prompt to be displayed before getting user input.
* \return An UTF-8 encoded input given by the user (or nullptr on EOF).
*/
@@ -370,11 +442,17 @@ public:
*
* \param fmt - printf style format.
*/
-#ifdef __GNUC__
- __attribute__((format(printf, 2, 3)))
-#endif
void print( char const* fmt, ... );
+ /*! \brief Prints a char array with the given length to standard output.
+ *
+ * \copydetails print
+ *
+ * \param str - The char array to print.
+ * \param length - The length of the array.
+ */
+ void write( char const* str, int length );
+
/*! \brief Schedule an emulated key press event.
*
* \param code - key press code to be emulated.
@@ -398,11 +476,60 @@ public:
*/
void bind_key( char32_t code, key_press_handler_t handler );
+ /*! \brief Bind internal `replxx` action (by name) to handle given key-press event.
+ *
+ * Action names are the same as names of Replxx::ACTION enumerations
+ * but in lower case, e.g.: an action for recalling previous history line
+ * is \e Replxx::ACTION::HISTORY_PREVIOUS so action name to be used in this
+ * interface for the same effect is "history_previous".
+ *
+ * \param code - handle this key-press event with following handler.
+ * \param actionName - name of internal action to be invoked on key press.
+ */
+ void bind_key_internal( char32_t code, char const* actionName );
+
void history_add( std::string const& line );
- int history_save( std::string const& filename );
- int history_load( std::string const& filename );
+
+ /*! \brief Synchronize REPL's history with given file.
+ *
+ * Synchronizing means loading existing history from given file,
+ * merging it with current history sorted by timestamps,
+ * saving merged version to given file,
+ * keeping merged version as current REPL's history.
+ *
+ * This call is an equivalent of calling:
+ * history_save( "some-file" );
+ * history_load( "some-file" );
+ *
+ * \param filename - a path to the file with which REPL's current history should be synchronized.
+ * \return True iff history file was successfully created.
+ */
+ bool history_sync( std::string const& filename );
+
+ /*! \brief Save REPL's history into given file.
+ *
+ * Saving means loading existing history from given file,
+ * merging it with current history sorted by timestamps,
+ * saving merged version to given file,
+ * keeping original (NOT merged) version as current REPL's history.
+ *
+ * \param filename - a path to the file where REPL's history should be saved.
+ * \return True iff history file was successfully created.
+ */
+ bool history_save( std::string const& filename );
+
+ /*! \brief Load REPL's history from given file.
+ *
+ * \param filename - a path to the file which contains REPL's history that should be loaded.
+ * \return True iff history file was successfully opened.
+ */
+ bool history_load( std::string const& filename );
+
+ /*! \brief Clear REPL's in-memory history.
+ */
+ void history_clear( void );
int history_size( void ) const;
- std::string history_line( int index );
+ HistoryScan history_scan( void ) const;
void set_preload_buffer( std::string const& preloadText );
@@ -446,6 +573,23 @@ public:
*/
void set_beep_on_ambiguous_completion( bool val );
+ /*! \brief Set complete next/complete previous behavior.
+ *
+ * COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations,
+ * in case when a partial completion is possible complete only partial part (`false` setting)
+ * or complete first proposed completion fully (`true` setting).
+ * The default is to complete fully (a `true` setting - complete immediately).
+ *
+ * \param val - complete immediately.
+ */
+ void set_immediate_completion( bool val );
+
+ /*! \brief Set history duplicate entries behaviour.
+ *
+ * \param val - should history contain only unique entries?
+ */
+ void set_unique_history( bool val );
+
/*! \brief Disable output coloring.
*
* \param val - if set to non-zero disable output colors.
@@ -457,6 +601,8 @@ public:
void set_max_history_size( int len );
void clear_screen( void );
int install_window_change_handler( void );
+ void enable_bracketed_paste( void );
+ void disable_bracketed_paste( void );
private:
Replxx( Replxx const& ) = delete;
diff --git a/contrib/replxx/src/conversion.cxx b/contrib/replxx/src/conversion.cxx
index ce9bd932b..bcdbe048e 100644
--- a/contrib/replxx/src/conversion.cxx
+++ b/contrib/replxx/src/conversion.cxx
@@ -2,10 +2,7 @@
#include <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 );
}
}
diff --git a/contrib/replxx/src/conversion.hxx b/contrib/replxx/src/conversion.hxx
index 1cb2d450d..6587ad0e2 100644
--- a/contrib/replxx/src/conversion.hxx
+++ b/contrib/replxx/src/conversion.hxx
@@ -1,20 +1,25 @@
#ifndef REPLXX_CONVERSION_HXX_INCLUDED
#define REPLXX_CONVERSION_HXX_INCLUDED 1
-namespace replxx {
+#include "ConvertUTF.h"
+
+#ifdef __has_include
+#if __has_include( <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;
diff --git a/contrib/replxx/src/escape.cxx b/contrib/replxx/src/escape.cxx
index 3edc4c1ec..dda1ab0be 100644
--- a/contrib/replxx/src/escape.cxx
+++ b/contrib/replxx/src/escape.cxx
@@ -1,5 +1,5 @@
#include "escape.hxx"
-#include "io.hxx"
+#include "terminal.hxx"
#include "replxx.hxx"
#ifndef _WIN32
@@ -115,6 +115,12 @@ static char32_t ctrlRightArrowKeyRoutine(char32_t) {
static char32_t ctrlLeftArrowKeyRoutine(char32_t) {
return thisKeyMetaCtrl | Replxx::KEY::BASE_CONTROL | Replxx::KEY::LEFT;
}
+static char32_t bracketPasteStartKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::PASTE_START;
+}
+static char32_t bracketPasteFinishKeyRoutine(char32_t) {
+ return thisKeyMetaCtrl | Replxx::KEY::PASTE_FINISH;
+}
static char32_t escFailureRoutine(char32_t) {
beep();
return -1;
@@ -442,11 +448,35 @@ static char32_t escLeftBracket20SemicolonRoutine(char32_t c) {
return doDispatch(c, escLeftBracket20SemicolonDispatch);
}
+static CharacterDispatchRoutine escLeftBracket200Routines[] = {
+ bracketPasteStartKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket200Dispatch = {
+ 1, "~", escLeftBracket200Routines
+};
+static char32_t escLeftBracket200Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket200Dispatch);
+}
+
+static CharacterDispatchRoutine escLeftBracket201Routines[] = {
+ bracketPasteFinishKeyRoutine, escFailureRoutine
+};
+static CharacterDispatch escLeftBracket201Dispatch = {
+ 1, "~", escLeftBracket201Routines
+};
+static char32_t escLeftBracket201Routine(char32_t c) {
+ c = read_unicode_character();
+ if (c == 0) return 0;
+ return doDispatch(c, escLeftBracket201Dispatch);
+}
+
static CharacterDispatchRoutine escLeftBracket20Routines[] = {
- f9KeyRoutine, escLeftBracket20SemicolonRoutine, escFailureRoutine
+ f9KeyRoutine, escLeftBracket20SemicolonRoutine, escLeftBracket200Routine, escLeftBracket201Routine, escFailureRoutine
};
static CharacterDispatch escLeftBracket20Dispatch = {
- 2, "~;", escLeftBracket20Routines
+ 4, "~;01", escLeftBracket20Routines
};
static char32_t escLeftBracket20Routine(char32_t c) {
c = read_unicode_character();
diff --git a/contrib/replxx/src/history.cxx b/contrib/replxx/src/history.cxx
index 6c6eff346..fe691df08 100644
--- a/contrib/replxx/src/history.cxx
+++ b/contrib/replxx/src/history.cxx
@@ -1,147 +1,401 @@
+#include <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();
}
}
diff --git a/contrib/replxx/src/history.hxx b/contrib/replxx/src/history.hxx
index 33aed148a..4e72c0367 100644
--- a/contrib/replxx/src/history.hxx
+++ b/contrib/replxx/src/history.hxx
@@ -1,70 +1,138 @@
#ifndef REPLXX_HISTORY_HXX_INCLUDED
#define REPLXX_HISTORY_HXX_INCLUDED 1
-#include <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;
};
}
diff --git a/contrib/replxx/src/killring.hxx b/contrib/replxx/src/killring.hxx
index 9eca23a8e..0baf108e7 100644
--- a/contrib/replxx/src/killring.hxx
+++ b/contrib/replxx/src/killring.hxx
@@ -17,9 +17,11 @@ class KillRing {
public:
enum action { actionOther, actionKill, actionYank };
action lastAction;
- size_t lastYankSize;
- KillRing() : size(0), index(0), lastAction(actionOther) {
+ KillRing()
+ : size(0)
+ , index(0)
+ , lastAction(actionOther) {
theRing.reserve(capacity);
}
diff --git a/contrib/replxx/src/prompt.cxx b/contrib/replxx/src/prompt.cxx
index 391d3745b..c13ea808b 100644
--- a/contrib/replxx/src/prompt.cxx
+++ b/contrib/replxx/src/prompt.cxx
@@ -25,14 +25,13 @@ namespace replxx {
Prompt::Prompt( Terminal& terminal_ )
: _extraLines( 0 )
, _lastLinePosition( 0 )
- , _previousInputLen( 0 )
- , _previousLen( 0 )
+ , _cursorRowOffset( 0 )
, _screenColumns( 0 )
, _terminal( terminal_ ) {
}
void Prompt::write() {
- _terminal.write32( _text.get(), _byteCount );
+ _terminal.write32( _text.get(), _text.length() );
}
void Prompt::update_screen_columns( void ) {
@@ -40,28 +39,36 @@ void Prompt::update_screen_columns( void ) {
}
void Prompt::set_text( UnicodeString const& text_ ) {
+ _text = text_;
+ update_state();
+}
+
+void Prompt::update_state() {
+ _cursorRowOffset -= _extraLines;
+ _extraLines = 0;
+ _lastLinePosition = 0;
+ _screenColumns = 0;
update_screen_columns();
// strip control characters from the prompt -- we do allow newline
- _text = text_;
- UnicodeString::const_iterator in( text_.begin() );
+ UnicodeString::const_iterator in( _text.begin() );
UnicodeString::iterator out( _text.begin() );
- int len = 0;
+ int visibleCount = 0;
int x = 0;
bool const strip = !tty::out;
- while (in != text_.end()) {
+ while (in != _text.end()) {
char32_t c = *in;
if ('\n' == c || !is_control_code(c)) {
*out = c;
++out;
++in;
- ++len;
+ ++visibleCount;
if ('\n' == c || ++x >= _screenColumns) {
x = 0;
++_extraLines;
- _lastLinePosition = len;
+ _lastLinePosition = visibleCount;
}
} else if (c == '\x1b') {
if ( strip ) {
@@ -69,7 +76,7 @@ void Prompt::set_text( UnicodeString const& text_ ) {
++in;
if (*in == '[') {
++in;
- while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) {
+ while ( ( in != _text.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) {
++in;
}
if (*in == 'm') {
@@ -85,7 +92,7 @@ void Prompt::set_text( UnicodeString const& text_ ) {
*out = *in;
++out;
++in;
- while ( ( in != text_.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) {
+ while ( ( in != _text.end() ) && ( ( *in == ';' ) || ( ( ( *in >= '0' ) && ( *in <= '9' ) ) ) ) ) {
*out = *in;
++out;
++in;
@@ -101,11 +108,15 @@ void Prompt::set_text( UnicodeString const& text_ ) {
++in;
}
}
- _characterCount = len;
- _byteCount = static_cast<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();
}
}
diff --git a/contrib/replxx/src/prompt.hxx b/contrib/replxx/src/prompt.hxx
index aabff0ab0..9ed3f5fd9 100644
--- a/contrib/replxx/src/prompt.hxx
+++ b/contrib/replxx/src/prompt.hxx
@@ -4,36 +4,32 @@
#include <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 {
diff --git a/contrib/replxx/src/replxx.cxx b/contrib/replxx/src/replxx.cxx
index 7803d873a..29d35a231 100644
--- a/contrib/replxx/src/replxx.cxx
+++ b/contrib/replxx/src/replxx.cxx
@@ -102,7 +102,17 @@
#include "replxx.h"
#include "replxx.hxx"
#include "replxx_impl.hxx"
-#include "io.hxx"
+#include "history.hxx"
+
+static_assert(
+ static_cast<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_ ) {
diff --git a/contrib/replxx/src/replxx_impl.cxx b/contrib/replxx/src/replxx_impl.cxx
index 14f3cbd4d..28152d58d 100644
--- a/contrib/replxx/src/replxx_impl.cxx
+++ b/contrib/replxx/src/replxx_impl.cxx
@@ -2,6 +2,7 @@
#include <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
}
}
diff --git a/contrib/replxx/src/replxx_impl.hxx b/contrib/replxx/src/replxx_impl.hxx
index 3cf1e8203..bec9383c1 100644
--- a/contrib/replxx/src/replxx_impl.hxx
+++ b/contrib/replxx/src/replxx_impl.hxx
@@ -38,13 +38,13 @@
#include <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;
diff --git a/contrib/replxx/src/unicodestring.hxx b/contrib/replxx/src/unicodestring.hxx
index 1607ede66..22f3e4695 100644
--- a/contrib/replxx/src/unicodestring.hxx
+++ b/contrib/replxx/src/unicodestring.hxx
@@ -2,7 +2,6 @@
#define REPLXX_UNICODESTRING_HXX_INCLUDED
#include <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 );
}
diff --git a/contrib/replxx/src/utf8string.hxx b/contrib/replxx/src/utf8string.hxx
index 3adf17a34..29effa2ce 100644
--- a/contrib/replxx/src/utf8string.hxx
+++ b/contrib/replxx/src/utf8string.hxx
@@ -12,20 +12,24 @@ private:
typedef std::unique_ptr<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 ) {
diff --git a/contrib/replxx/src/util.cxx b/contrib/replxx/src/util.cxx
index 9beb96b80..719d7073d 100644
--- a/contrib/replxx/src/util.cxx
+++ b/contrib/replxx/src/util.cxx
@@ -1,5 +1,7 @@
+#include <chrono>
#include <cstdlib>
#include <cstring>
+#include <ctime>
#include <wctype.h>
#include "util.hxx"
@@ -9,18 +11,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
* @param x - initial x position (zero-based)
@@ -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 );
+}
+
}
diff --git a/contrib/replxx/src/util.hxx b/contrib/replxx/src/util.hxx
index 8afa0fa96..17c108680 100644
--- a/contrib/replxx/src/util.hxx
+++ b/contrib/replxx/src/util.hxx
@@ -10,10 +10,14 @@ inline bool is_control_code(char32_t testChar) {
(testChar >= 0x7F && testChar <= 0x9F); // DEL and C1 controls
}
-void recompute_character_widths( char32_t const* text, char* widths, int charCount );
+inline char32_t control_to_human( char32_t key ) {
+ return ( key < 27 ? ( key + 0x40 ) : ( key + 0x18 ) );
+}
+
void calculate_screen_position( int x, int y, int screenColumns, int charCount, int& xOut, int& yOut );
int calculate_displayed_length( char32_t const* buf32, int size );
char const* ansi_color( Replxx::Color );
+std::string now_ms_str( void );
}
diff --git a/contrib/replxx/src/windows.cxx b/contrib/replxx/src/windows.cxx
index e5b6de428..715292c0c 100644
--- a/contrib/replxx/src/windows.cxx
+++ b/contrib/replxx/src/windows.cxx
@@ -4,11 +4,7 @@
#include "windows.hxx"
#include "conversion.hxx"
-#include "io.hxx"
-
-#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING
-static DWORD const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 4;
-#endif
+#include "terminal.hxx"
using namespace std;
@@ -17,7 +13,7 @@ namespace replxx {
WinAttributes WIN_ATTR;
template<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_ );
}
diff --git a/contrib/replxx/src/windows.hxx b/contrib/replxx/src/windows.hxx
index d49484fd8..243f41cb7 100644
--- a/contrib/replxx/src/windows.hxx
+++ b/contrib/replxx/src/windows.hxx
@@ -35,7 +35,7 @@ class WinAttributes {
int _consoleColor;
};
-int win_write( char const*, int );
+int win_write( HANDLE, bool, char const*, int );
extern WinAttributes WIN_ATTR;