@@ -126,6 +126,8 @@ enum { REPLXX_KEY_F22 = REPLXX_KEY_F21 + 1 }; | |||
enum { REPLXX_KEY_F23 = REPLXX_KEY_F22 + 1 }; | |||
enum { REPLXX_KEY_F24 = REPLXX_KEY_F23 + 1 }; | |||
enum { REPLXX_KEY_MOUSE = REPLXX_KEY_F24 + 1 }; | |||
enum { REPLXX_KEY_PASTE_START = REPLXX_KEY_MOUSE + 1 }; | |||
enum { REPLXX_KEY_PASTE_FINISH = REPLXX_KEY_PASTE_START + 1 }; | |||
#define REPLXX_KEY_SHIFT( key ) ( ( key ) | REPLXX_KEY_BASE_SHIFT ) | |||
#define REPLXX_KEY_CONTROL( key ) ( ( key ) | REPLXX_KEY_BASE_CONTROL ) | |||
@@ -139,19 +141,25 @@ enum { REPLXX_KEY_ENTER = REPLXX_KEY_CONTROL( 'M' ) }; | |||
*/ | |||
typedef enum { | |||
REPLXX_ACTION_INSERT_CHARACTER, | |||
REPLXX_ACTION_NEW_LINE, | |||
REPLXX_ACTION_DELETE_CHARACTER_UNDER_CURSOR, | |||
REPLXX_ACTION_DELETE_CHARACTER_LEFT_OF_CURSOR, | |||
REPLXX_ACTION_KILL_TO_END_OF_LINE, | |||
REPLXX_ACTION_KILL_TO_BEGINING_OF_LINE, | |||
REPLXX_ACTION_KILL_TO_END_OF_WORD, | |||
REPLXX_ACTION_KILL_TO_BEGINING_OF_WORD, | |||
REPLXX_ACTION_KILL_TO_END_OF_SUBWORD, | |||
REPLXX_ACTION_KILL_TO_BEGINING_OF_SUBWORD, | |||
REPLXX_ACTION_KILL_TO_WHITESPACE_ON_LEFT, | |||
REPLXX_ACTION_YANK, | |||
REPLXX_ACTION_YANK_CYCLE, | |||
REPLXX_ACTION_YANK_LAST_ARG, | |||
REPLXX_ACTION_MOVE_CURSOR_TO_BEGINING_OF_LINE, | |||
REPLXX_ACTION_MOVE_CURSOR_TO_END_OF_LINE, | |||
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_LEFT, | |||
REPLXX_ACTION_MOVE_CURSOR_ONE_WORD_RIGHT, | |||
REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_LEFT, | |||
REPLXX_ACTION_MOVE_CURSOR_ONE_SUBWORD_RIGHT, | |||
REPLXX_ACTION_MOVE_CURSOR_LEFT, | |||
REPLXX_ACTION_MOVE_CURSOR_RIGHT, | |||
REPLXX_ACTION_HISTORY_NEXT, | |||
@@ -165,12 +173,16 @@ typedef enum { | |||
REPLXX_ACTION_CAPITALIZE_WORD, | |||
REPLXX_ACTION_LOWERCASE_WORD, | |||
REPLXX_ACTION_UPPERCASE_WORD, | |||
REPLXX_ACTION_CAPITALIZE_SUBWORD, | |||
REPLXX_ACTION_LOWERCASE_SUBWORD, | |||
REPLXX_ACTION_UPPERCASE_SUBWORD, | |||
REPLXX_ACTION_TRANSPOSE_CHARACTERS, | |||
REPLXX_ACTION_TOGGLE_OVERWRITE_MODE, | |||
#ifndef _WIN32 | |||
REPLXX_ACTION_VERBATIM_INSERT, | |||
REPLXX_ACTION_SUSPEND, | |||
#endif | |||
REPLXX_ACTION_BRACKETED_PASTE, | |||
REPLXX_ACTION_CLEAR_SCREEN, | |||
REPLXX_ACTION_CLEAR_SELF, | |||
REPLXX_ACTION_REPAINT, | |||
@@ -196,12 +208,17 @@ typedef struct ReplxxStateTag { | |||
} ReplxxState; | |||
typedef struct Replxx Replxx; | |||
typedef struct ReplxxHistoryScan ReplxxHistoryScan; | |||
typedef struct ReplxxHistoryEntryTag { | |||
char const* timestamp; | |||
char const* text; | |||
} ReplxxHistoryEntry; | |||
/*! \brief Create Replxx library resouce holder. | |||
/*! \brief Create Replxx library resource holder. | |||
* | |||
* Use replxx_end() to free resoiurce acquired with this function. | |||
* Use replxx_end() to free resources acquired with this function. | |||
* | |||
* \return Replxx library resouce holder. | |||
* \return Replxx library resource holder. | |||
*/ | |||
REPLXX_IMPEXP Replxx* replxx_init( void ); | |||
@@ -211,6 +228,28 @@ REPLXX_IMPEXP Replxx* replxx_init( void ); | |||
*/ | |||
REPLXX_IMPEXP void replxx_end( Replxx* replxx ); | |||
/*! \brief Line modification callback type definition. | |||
* | |||
* User can observe and modify line contents (and cursor position) | |||
* in response to changes to both introduced by the user through | |||
* normal interactions. | |||
* | |||
* When callback returns Replxx updates current line content | |||
* and current cursor position to the ones updated by the callback. | |||
* | |||
* \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. | |||
* \param cursorPosition[in,out] - a R/W reference to current cursor position. | |||
* \param userData - pointer to opaque user data block. | |||
*/ | |||
typedef void (replxx_modify_callback_t)(char** input, int* contextLen, void* userData); | |||
/*! \brief Register modify callback. | |||
* | |||
* \param fn - user defined callback function. | |||
* \param userData - pointer to opaque user data block to be passed into each invocation of the callback. | |||
*/ | |||
REPLXX_IMPEXP void replxx_set_modify_callback( Replxx*, replxx_modify_callback_t* fn, void* userData ); | |||
/*! \brief Highlighter callback type definition. | |||
* | |||
* If user want to have colorful input she must simply install highlighter callback. | |||
@@ -247,8 +286,8 @@ typedef struct replxx_completions replxx_completions; | |||
* input == "if ( obj.me" | |||
* contextLen == 2 (depending on \e replxx_set_word_break_characters()) | |||
* | |||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||
* number not greated than the number of code points in input) if it makes better sense | |||
* Client application is free to update \e contextLen to be 6 (or any other non-negative | |||
* number not greater than the number of code points in input) if it makes better sense | |||
* for given client application semantics. | |||
* | |||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||
@@ -292,8 +331,8 @@ typedef struct replxx_hints replxx_hints; | |||
* input == "if ( obj.me" | |||
* contextLen == 2 (depending on \e replxx_set_word_break_characters()) | |||
* | |||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||
* number not greated than the number of code points in input) if it makes better sense | |||
* Client application is free to update \e contextLen to be 6 (or any other non-negative | |||
* number not greater than the number of code points in input) if it makes better sense | |||
* for given client application semantics. | |||
* | |||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||
@@ -314,7 +353,7 @@ REPLXX_IMPEXP void replxx_set_hint_callback( Replxx*, replxx_hint_callback_t* fn | |||
/*! \brief Key press handler type definition. | |||
* | |||
* \param code - the key code replxx got from terminal. | |||
* \return Decition on how should input() behave after this key handler returns. | |||
* \return Decision on how should input() behave after this key handler returns. | |||
*/ | |||
typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); | |||
@@ -326,6 +365,8 @@ typedef ReplxxActionResult (key_press_handler_t)( int code, void* userData ); | |||
REPLXX_IMPEXP void replxx_add_hint( replxx_hints* hints, const char* str ); | |||
/*! \brief Read line of user input. | |||
* | |||
* Returned pointer is managed by the library and is not to be freed in the client. | |||
* | |||
* \param prompt - prompt to be displayed before getting user input. | |||
* \return An UTF-8 encoded input given by the user (or nullptr on EOF). | |||
@@ -356,11 +397,17 @@ REPLXX_IMPEXP void replxx_set_state( Replxx*, ReplxxState* state ); | |||
* | |||
* \param fmt - printf style format. | |||
*/ | |||
#ifdef __GNUC__ | |||
__attribute__((format(printf, 2, 3))) | |||
#endif | |||
REPLXX_IMPEXP int replxx_print( Replxx*, char const* fmt, ... ); | |||
/*! \brief Prints a char array with the given length to standard output. | |||
* | |||
* \copydetails print | |||
* | |||
* \param str - The char array to print. | |||
* \param length - The length of the array. | |||
*/ | |||
REPLXX_IMPEXP int replxx_write( Replxx*, char const* str, int length ); | |||
/*! \brief Schedule an emulated key press event. | |||
* | |||
* \param code - key press code to be emulated. | |||
@@ -385,6 +432,19 @@ REPLXX_IMPEXP ReplxxActionResult replxx_invoke( Replxx*, ReplxxAction action, in | |||
*/ | |||
REPLXX_IMPEXP void replxx_bind_key( Replxx*, int code, key_press_handler_t handler, void* userData ); | |||
/*! \brief Bind internal `replxx` action (by name) to handle given key-press event. | |||
* | |||
* Action names are the same as unique part of names of ReplxxAction enumerations | |||
* but in lower case, e.g.: an action for recalling previous history line | |||
* is \e REPLXX_ACTION_HISTORY_PREVIOUS so action name to be used in this | |||
* interface for the same effect is "history_previous". | |||
* | |||
* \param code - handle this key-press event with following handler. | |||
* \param actionName - name of internal action to be invoked on key press. | |||
* \return -1 if invalid action name was used, 0 otherwise. | |||
*/ | |||
int replxx_bind_key_internal( Replxx*, int code, char const* actionName ); | |||
REPLXX_IMPEXP void replxx_set_preload_buffer( Replxx*, const char* preloadText ); | |||
REPLXX_IMPEXP void replxx_history_add( Replxx*, const char* line ); | |||
@@ -430,6 +490,23 @@ REPLXX_IMPEXP void replxx_set_complete_on_empty( Replxx*, int val ); | |||
*/ | |||
REPLXX_IMPEXP void replxx_set_beep_on_ambiguous_completion( Replxx*, int val ); | |||
/*! \brief Set complete next/complete previous behavior. | |||
* | |||
* COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations, | |||
* in case when a partial completion is possible complete only partial part (`false` setting) | |||
* or complete first proposed completion fully (`true` setting). | |||
* The default is to complete fully (a `true` setting - complete immediately). | |||
* | |||
* \param val - complete immediately. | |||
*/ | |||
REPLXX_IMPEXP void replxx_set_immediate_completion( Replxx*, int val ); | |||
/*! \brief Set history duplicate entries behaviour. | |||
* | |||
* \param val - should history contain only unique entries? | |||
*/ | |||
REPLXX_IMPEXP void replxx_set_unique_history( Replxx*, int val ); | |||
/*! \brief Disable output coloring. | |||
* | |||
* \param val - if set to non-zero disable output colors. | |||
@@ -439,15 +516,56 @@ REPLXX_IMPEXP void replxx_set_no_color( Replxx*, int val ); | |||
/*! \brief Set maximum number of entries in history list. | |||
*/ | |||
REPLXX_IMPEXP void replxx_set_max_history_size( Replxx*, int len ); | |||
REPLXX_IMPEXP char const* replxx_history_line( Replxx*, int index ); | |||
REPLXX_IMPEXP ReplxxHistoryScan* replxx_history_scan_start( Replxx* ); | |||
REPLXX_IMPEXP void replxx_history_scan_stop( Replxx*, ReplxxHistoryScan* ); | |||
REPLXX_IMPEXP int replxx_history_scan_next( Replxx*, ReplxxHistoryScan*, ReplxxHistoryEntry* ); | |||
/*! \brief Synchronize REPL's history with given file. | |||
* | |||
* Synchronizing means loading existing history from given file, | |||
* merging it with current history sorted by timestamps, | |||
* saving merged version to given file, | |||
* keeping merged version as current REPL's history. | |||
* | |||
* This call is an equivalent of calling: | |||
* replxx_history_save( rx, "some-file" ); | |||
* replxx_history_load( rx, "some-file" ); | |||
* | |||
* \param filename - a path to the file with which REPL's current history should be synchronized. | |||
* \return 0 iff history file was successfully created, -1 otherwise. | |||
*/ | |||
REPLXX_IMPEXP int replxx_history_sync( Replxx*, const char* filename ); | |||
/*! \brief Save REPL's history into given file. | |||
* | |||
* Saving means loading existing history from given file, | |||
* merging it with current history sorted by timestamps, | |||
* saving merged version to given file, | |||
* keeping original (NOT merged) version as current REPL's history. | |||
* | |||
* \param filename - a path to the file where REPL's history should be saved. | |||
* \return 0 iff history file was successfully created, -1 otherwise. | |||
*/ | |||
REPLXX_IMPEXP int replxx_history_save( Replxx*, const char* filename ); | |||
/*! \brief Load REPL's history from given file. | |||
* | |||
* \param filename - a path to the file which contains REPL's history that should be loaded. | |||
* \return 0 iff history file was successfully opened, -1 otherwise. | |||
*/ | |||
REPLXX_IMPEXP int replxx_history_load( Replxx*, const char* filename ); | |||
/*! \brief Clear REPL's in-memory history. | |||
*/ | |||
REPLXX_IMPEXP void replxx_history_clear( Replxx* ); | |||
REPLXX_IMPEXP void replxx_clear_screen( Replxx* ); | |||
#ifdef __REPLXX_DEBUG__ | |||
void replxx_debug_dump_print_codes(void); | |||
#endif | |||
/* the following is extension to the original linenoise API */ | |||
REPLXX_IMPEXP int replxx_install_window_change_handler( Replxx* ); | |||
REPLXX_IMPEXP void replxx_enable_bracketed_paste( Replxx* ); | |||
REPLXX_IMPEXP void replxx_disable_bracketed_paste( Replxx* ); | |||
#ifdef __cplusplus | |||
} |
@@ -131,6 +131,8 @@ public: | |||
static char32_t const F23 = F22 + 1; | |||
static char32_t const F24 = F23 + 1; | |||
static char32_t const MOUSE = F24 + 1; | |||
static char32_t const PASTE_START = MOUSE + 1; | |||
static char32_t const PASTE_FINISH = PASTE_START + 1; | |||
static constexpr char32_t shift( char32_t key_ ) { | |||
return ( key_ | BASE_SHIFT ); | |||
} | |||
@@ -148,19 +150,25 @@ public: | |||
*/ | |||
enum class ACTION { | |||
INSERT_CHARACTER, | |||
NEW_LINE, | |||
DELETE_CHARACTER_UNDER_CURSOR, | |||
DELETE_CHARACTER_LEFT_OF_CURSOR, | |||
KILL_TO_END_OF_LINE, | |||
KILL_TO_BEGINING_OF_LINE, | |||
KILL_TO_END_OF_WORD, | |||
KILL_TO_BEGINING_OF_WORD, | |||
KILL_TO_END_OF_SUBWORD, | |||
KILL_TO_BEGINING_OF_SUBWORD, | |||
KILL_TO_WHITESPACE_ON_LEFT, | |||
YANK, | |||
YANK_CYCLE, | |||
YANK_LAST_ARG, | |||
MOVE_CURSOR_TO_BEGINING_OF_LINE, | |||
MOVE_CURSOR_TO_END_OF_LINE, | |||
MOVE_CURSOR_ONE_WORD_LEFT, | |||
MOVE_CURSOR_ONE_WORD_RIGHT, | |||
MOVE_CURSOR_ONE_SUBWORD_LEFT, | |||
MOVE_CURSOR_ONE_SUBWORD_RIGHT, | |||
MOVE_CURSOR_LEFT, | |||
MOVE_CURSOR_RIGHT, | |||
HISTORY_NEXT, | |||
@@ -174,12 +182,16 @@ public: | |||
CAPITALIZE_WORD, | |||
LOWERCASE_WORD, | |||
UPPERCASE_WORD, | |||
CAPITALIZE_SUBWORD, | |||
LOWERCASE_SUBWORD, | |||
UPPERCASE_SUBWORD, | |||
TRANSPOSE_CHARACTERS, | |||
TOGGLE_OVERWRITE_MODE, | |||
#ifndef _WIN32 | |||
VERBATIM_INSERT, | |||
SUSPEND, | |||
#endif | |||
BRACKETED_PASTE, | |||
CLEAR_SCREEN, | |||
CLEAR_SELF, | |||
REPAINT, | |||
@@ -222,8 +234,60 @@ public: | |||
} | |||
}; | |||
typedef std::vector<Completion> completions_t; | |||
class HistoryEntry { | |||
std::string _timestamp; | |||
std::string _text; | |||
public: | |||
HistoryEntry( std::string const& timestamp_, std::string const& text_ ) | |||
: _timestamp( timestamp_ ) | |||
, _text( text_ ) { | |||
} | |||
std::string const& timestamp( void ) const { | |||
return ( _timestamp ); | |||
} | |||
std::string const& text( void ) const { | |||
return ( _text ); | |||
} | |||
}; | |||
class HistoryScanImpl; | |||
class HistoryScan { | |||
public: | |||
typedef std::unique_ptr<HistoryScanImpl, void (*)( HistoryScanImpl* )> impl_t; | |||
private: | |||
#ifdef _MSC_VER | |||
#pragma warning(push) | |||
#pragma warning(disable:4251) | |||
#endif | |||
impl_t _impl; | |||
#ifdef _MSC_VER | |||
#pragma warning(pop) | |||
#endif | |||
public: | |||
HistoryScan( impl_t ); | |||
HistoryScan( HistoryScan&& ) = default; | |||
HistoryScan& operator = ( HistoryScan&& ) = default; | |||
bool next( void ); | |||
HistoryEntry const& get( void ) const; | |||
private: | |||
HistoryScan( HistoryScan const& ) = delete; | |||
HistoryScan& operator = ( HistoryScan const& ) = delete; | |||
}; | |||
typedef std::vector<std::string> hints_t; | |||
/*! \brief Line modification callback type definition. | |||
* | |||
* User can observe and modify line contents (and cursor position) | |||
* in response to changes to both introduced by the user through | |||
* normal interactions. | |||
* | |||
* When callback returns Replxx updates current line content | |||
* and current cursor position to the ones updated by the callback. | |||
* | |||
* \param line[in,out] - a R/W reference to an UTF-8 encoded input entered by the user so far. | |||
* \param cursorPosition[in,out] - a R/W reference to current cursor position. | |||
*/ | |||
typedef std::function<void ( std::string& line, int& cursorPosition )> modify_callback_t; | |||
/*! \brief Completions callback type definition. | |||
* | |||
* \e contextLen is counted in Unicode code points (not in bytes!). | |||
@@ -234,8 +298,8 @@ public: | |||
* input == "if ( obj.me" | |||
* contextLen == 2 (depending on \e set_word_break_characters()) | |||
* | |||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||
* number not greated than the number of code points in input) if it makes better sense | |||
* Client application is free to update \e contextLen to be 6 (or any other non-negative | |||
* number not greater than the number of code points in input) if it makes better sense | |||
* for given client application semantics. | |||
* | |||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||
@@ -252,7 +316,7 @@ public: | |||
* displayed user input. | |||
* | |||
* Size of \e colors buffer is equal to number of code points in user \e input | |||
* which will be different from simple `input.lenght()`! | |||
* which will be different from simple `input.length()`! | |||
* | |||
* \param input - an UTF-8 encoded input entered by the user so far. | |||
* \param colors - output buffer for color information. | |||
@@ -269,8 +333,8 @@ public: | |||
* input == "if ( obj.me" | |||
* contextLen == 2 (depending on \e set_word_break_characters()) | |||
* | |||
* Client application is free to update \e contextLen to be 6 (or any orther non-negative | |||
* number not greated than the number of code points in input) if it makes better sense | |||
* Client application is free to update \e contextLen to be 6 (or any other non-negative | |||
* number not greater than the number of code points in input) if it makes better sense | |||
* for given client application semantics. | |||
* | |||
* \param input - UTF-8 encoded input entered by the user until current cursor position. | |||
@@ -283,7 +347,7 @@ public: | |||
/*! \brief Key press handler type definition. | |||
* | |||
* \param code - the key code replxx got from terminal. | |||
* \return Decition on how should input() behave after this key handler returns. | |||
* \return Decision on how should input() behave after this key handler returns. | |||
*/ | |||
typedef std::function<ACTION_RESULT ( char32_t code )> key_press_handler_t; | |||
@@ -307,12 +371,12 @@ public: | |||
class ReplxxImpl; | |||
private: | |||
typedef std::unique_ptr<ReplxxImpl, void (*)( ReplxxImpl* )> impl_t; | |||
#ifdef _WIN32 | |||
#ifdef _MSC_VER | |||
#pragma warning(push) | |||
#pragma warning(disable:4251) | |||
#endif | |||
impl_t _impl; | |||
#ifdef _WIN32 | |||
#ifdef _MSC_VER | |||
#pragma warning(pop) | |||
#endif | |||
@@ -321,6 +385,12 @@ public: | |||
Replxx( Replxx&& ) = default; | |||
Replxx& operator = ( Replxx&& ) = default; | |||
/*! \brief Register modify callback. | |||
* | |||
* \param fn - user defined callback function. | |||
*/ | |||
void set_modify_callback( modify_callback_t const& fn ); | |||
/*! \brief Register completion callback. | |||
* | |||
* \param fn - user defined callback function. | |||
@@ -340,6 +410,8 @@ public: | |||
void set_hint_callback( hint_callback_t const& fn ); | |||
/*! \brief Read line of user input. | |||
* | |||
* Returned pointer is managed by the library and is not to be freed in the client. | |||
* | |||
* \param prompt - prompt to be displayed before getting user input. | |||
* \return An UTF-8 encoded input given by the user (or nullptr on EOF). | |||
@@ -370,11 +442,17 @@ public: | |||
* | |||
* \param fmt - printf style format. | |||
*/ | |||
#ifdef __GNUC__ | |||
__attribute__((format(printf, 2, 3))) | |||
#endif | |||
void print( char const* fmt, ... ); | |||
/*! \brief Prints a char array with the given length to standard output. | |||
* | |||
* \copydetails print | |||
* | |||
* \param str - The char array to print. | |||
* \param length - The length of the array. | |||
*/ | |||
void write( char const* str, int length ); | |||
/*! \brief Schedule an emulated key press event. | |||
* | |||
* \param code - key press code to be emulated. | |||
@@ -398,11 +476,60 @@ public: | |||
*/ | |||
void bind_key( char32_t code, key_press_handler_t handler ); | |||
/*! \brief Bind internal `replxx` action (by name) to handle given key-press event. | |||
* | |||
* Action names are the same as names of Replxx::ACTION enumerations | |||
* but in lower case, e.g.: an action for recalling previous history line | |||
* is \e Replxx::ACTION::HISTORY_PREVIOUS so action name to be used in this | |||
* interface for the same effect is "history_previous". | |||
* | |||
* \param code - handle this key-press event with following handler. | |||
* \param actionName - name of internal action to be invoked on key press. | |||
*/ | |||
void bind_key_internal( char32_t code, char const* actionName ); | |||
void history_add( std::string const& line ); | |||
int history_save( std::string const& filename ); | |||
int history_load( std::string const& filename ); | |||
/*! \brief Synchronize REPL's history with given file. | |||
* | |||
* Synchronizing means loading existing history from given file, | |||
* merging it with current history sorted by timestamps, | |||
* saving merged version to given file, | |||
* keeping merged version as current REPL's history. | |||
* | |||
* This call is an equivalent of calling: | |||
* history_save( "some-file" ); | |||
* history_load( "some-file" ); | |||
* | |||
* \param filename - a path to the file with which REPL's current history should be synchronized. | |||
* \return True iff history file was successfully created. | |||
*/ | |||
bool history_sync( std::string const& filename ); | |||
/*! \brief Save REPL's history into given file. | |||
* | |||
* Saving means loading existing history from given file, | |||
* merging it with current history sorted by timestamps, | |||
* saving merged version to given file, | |||
* keeping original (NOT merged) version as current REPL's history. | |||
* | |||
* \param filename - a path to the file where REPL's history should be saved. | |||
* \return True iff history file was successfully created. | |||
*/ | |||
bool history_save( std::string const& filename ); | |||
/*! \brief Load REPL's history from given file. | |||
* | |||
* \param filename - a path to the file which contains REPL's history that should be loaded. | |||
* \return True iff history file was successfully opened. | |||
*/ | |||
bool history_load( std::string const& filename ); | |||
/*! \brief Clear REPL's in-memory history. | |||
*/ | |||
void history_clear( void ); | |||
int history_size( void ) const; | |||
std::string history_line( int index ); | |||
HistoryScan history_scan( void ) const; | |||
void set_preload_buffer( std::string const& preloadText ); | |||
@@ -446,6 +573,23 @@ public: | |||
*/ | |||
void set_beep_on_ambiguous_completion( bool val ); | |||
/*! \brief Set complete next/complete previous behavior. | |||
* | |||
* COMPLETE_NEXT/COMPLETE_PREVIOUS actions have two modes of operations, | |||
* in case when a partial completion is possible complete only partial part (`false` setting) | |||
* or complete first proposed completion fully (`true` setting). | |||
* The default is to complete fully (a `true` setting - complete immediately). | |||
* | |||
* \param val - complete immediately. | |||
*/ | |||
void set_immediate_completion( bool val ); | |||
/*! \brief Set history duplicate entries behaviour. | |||
* | |||
* \param val - should history contain only unique entries? | |||
*/ | |||
void set_unique_history( bool val ); | |||
/*! \brief Disable output coloring. | |||
* | |||
* \param val - if set to non-zero disable output colors. | |||
@@ -457,6 +601,8 @@ public: | |||
void set_max_history_size( int len ); | |||
void clear_screen( void ); | |||
int install_window_change_handler( void ); | |||
void enable_bracketed_paste( void ); | |||
void disable_bracketed_paste( void ); | |||
private: | |||
Replxx( Replxx const& ) = delete; |
@@ -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 ); | |||
} | |||
} |
@@ -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; |
@@ -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(); |
@@ -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(); | |||
} | |||
} |
@@ -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; | |||
}; | |||
} |
@@ -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); | |||
} | |||
@@ -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(); | |||
} | |||
} |
@@ -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 { |
@@ -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_ ) { |
@@ -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; |
@@ -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 ); | |||
} |
@@ -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 ) { |
@@ -1,5 +1,7 @@ | |||
#include <chrono> | |||
#include <cstdlib> | |||
#include <cstring> | |||
#include <ctime> | |||
#include <wctype.h> | |||
#include "util.hxx" | |||
@@ -8,18 +10,6 @@ namespace replxx { | |||
int mk_wcwidth( char32_t ); | |||
/** | |||
* Recompute widths of all characters in a char32_t buffer | |||
* @param text - input buffer of Unicode characters | |||
* @param widths - output buffer of character widths | |||
* @param charCount - number of characters in buffer | |||
*/ | |||
void recompute_character_widths( char32_t const* text, char* widths, int charCount ) { | |||
for (int i = 0; i < charCount; ++i) { | |||
widths[i] = mk_wcwidth(text[i]); | |||
} | |||
} | |||
/** | |||
* Calculate a new screen position given a starting position, screen width and | |||
* character count | |||
@@ -148,5 +138,21 @@ char const* ansi_color( Replxx::Color color_ ) { | |||
return ( code ); | |||
} | |||
std::string now_ms_str( void ) { | |||
std::chrono::milliseconds ms( std::chrono::duration_cast<std::chrono::milliseconds>( std::chrono::system_clock::now().time_since_epoch() ) ); | |||
time_t t( ms.count() / 1000 ); | |||
tm broken; | |||
#ifdef _WIN32 | |||
#define localtime_r( t, b ) localtime_s( ( b ), ( t ) ) | |||
#endif | |||
localtime_r( &t, &broken ); | |||
#undef localtime_r | |||
static int const BUFF_SIZE( 32 ); | |||
char str[BUFF_SIZE]; | |||
strftime( str, BUFF_SIZE, "%Y-%m-%d %H:%M:%S.", &broken ); | |||
snprintf( str + sizeof ( "YYYY-mm-dd HH:MM:SS" ), 5, "%03d", static_cast<int>( ms.count() % 1000 ) ); | |||
return ( str ); | |||
} | |||
} | |||
@@ -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 ); | |||
} | |||
@@ -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, ¤tMode ) ) { | |||
UINT inputCodePage( GetConsoleCP() ); | |||
UINT outputCodePage( GetConsoleOutputCP() ); | |||
SetConsoleCP( 65001 ); | |||
SetConsoleOutputCP( 65001 ); | |||
if ( tty::out ) { | |||
DWORD nWritten( 0 ); | |||
if ( SetConsoleMode( consoleOut, currentMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING ) ) { | |||
WriteConsoleA( consoleOut, str_, size_, &nWritten, nullptr ); | |||
if ( autoEscape_ ) { | |||
WriteConsoleA( out_, str_, size_, &nWritten, nullptr ); | |||
count = nWritten; | |||
SetConsoleMode( consoleOut, currentMode ); | |||
} else { | |||
char const* s( str_ ); | |||
char const* e( str_ + size_ ); | |||
while ( str_ < e ) { | |||
if ( *str_ == 27 ) { | |||
if ( s < str_ ) { | |||
int toWrite( str_ - s ); | |||
WriteConsoleA( consoleOut, s, static_cast<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_ ); | |||
} |
@@ -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; | |||