1 #include <algorithm>
2 #include <memory>
3 #include <cerrno>
4 #include <iostream>
5 #include <chrono>
6 
7 #ifdef _WIN32
8 
9 #include <windows.h>
10 #include <io.h>
11 #if _MSC_VER < 1900
12 #define snprintf _snprintf // Microsoft headers use underscores in some names
13 #endif
14 #define strcasecmp _stricmp
15 #define write _write
16 #define STDIN_FILENO 0
17 
18 #else /* _WIN32 */
19 
20 #include <unistd.h>
21 #include <signal.h>
22 
23 #endif /* _WIN32 */
24 
25 #ifdef _WIN32
26 #include "windows.hxx"
27 #endif
28 
29 #include "replxx_impl.hxx"
30 #include "utf8string.hxx"
31 #include "prompt.hxx"
32 #include "util.hxx"
33 #include "terminal.hxx"
34 #include "history.hxx"
35 #include "replxx.hxx"
36 
37 using namespace std;
38 
39 namespace replxx {
40 
41 namespace {
42 
43 namespace action_names {
44 
45 char const INSERT_CHARACTER[]                = "insert_character";
46 char const NEW_LINE[]                        = "new_line";
47 char const MOVE_CURSOR_TO_BEGINING_OF_LINE[] = "move_cursor_to_begining_of_line";
48 char const MOVE_CURSOR_TO_END_OF_LINE[]      = "move_cursor_to_end_of_line";
49 char const MOVE_CURSOR_LEFT[]                = "move_cursor_left";
50 char const MOVE_CURSOR_RIGHT[]               = "move_cursor_right";
51 char const MOVE_CURSOR_ONE_WORD_LEFT[]       = "move_cursor_one_word_left";
52 char const MOVE_CURSOR_ONE_WORD_RIGHT[]      = "move_cursor_one_word_right";
53 char const MOVE_CURSOR_ONE_SUBWORD_LEFT[]    = "move_cursor_one_subword_left";
54 char const MOVE_CURSOR_ONE_SUBWORD_RIGHT[]   = "move_cursor_one_subword_right";
55 char const KILL_TO_WHITESPACE_ON_LEFT[]      = "kill_to_whitespace_on_left";
56 char const KILL_TO_END_OF_WORD[]             = "kill_to_end_of_word";
57 char const KILL_TO_END_OF_SUBWORD[]          = "kill_to_end_of_subword";
58 char const KILL_TO_BEGINING_OF_WORD[]        = "kill_to_begining_of_word";
59 char const KILL_TO_BEGINING_OF_SUBWORD[]     = "kill_to_begining_of_subword";
60 char const KILL_TO_BEGINING_OF_LINE[]        = "kill_to_begining_of_line";
61 char const KILL_TO_END_OF_LINE[]             = "kill_to_end_of_line";
62 char const YANK[]                            = "yank";
63 char const YANK_CYCLE[]                      = "yank_cycle";
64 char const YANK_LAST_ARG[]                   = "yank_last_arg";
65 char const CAPITALIZE_WORD[]                 = "capitalize_word";
66 char const LOWERCASE_WORD[]                  = "lowercase_word";
67 char const UPPERCASE_WORD[]                  = "uppercase_word";
68 char const CAPITALIZE_SUBWORD[]              = "capitalize_subword";
69 char const LOWERCASE_SUBWORD[]               = "lowercase_subword";
70 char const UPPERCASE_SUBWORD[]               = "uppercase_subword";
71 char const TRANSPOSE_CHARACTERS[]            = "transpose_characters";
72 char const ABORT_LINE[]                      = "abort_line";
73 char const SEND_EOF[]                        = "send_eof";
74 char const TOGGLE_OVERWRITE_MODE[]           = "toggle_overwrite_mode";
75 char const DELETE_CHARACTER_UNDER_CURSOR[]   = "delete_character_under_cursor";
76 char const DELETE_CHARACTER_LEFT_OF_CURSOR[] = "delete_character_left_of_cursor";
77 char const COMMIT_LINE[]                     = "commit_line";
78 char const CLEAR_SCREEN[]                    = "clear_screen";
79 char const COMPLETE_NEXT[]                   = "complete_next";
80 char const COMPLETE_PREVIOUS[]               = "complete_previous";
81 char const HISTORY_NEXT[]                    = "history_next";
82 char const HISTORY_PREVIOUS[]                = "history_previous";
83 char const HISTORY_LAST[]                    = "history_last";
84 char const HISTORY_FIRST[]                   = "history_first";
85 char const HINT_PREVIOUS[]                   = "hint_previous";
86 char const HINT_NEXT[]                       = "hint_next";
87 char const VERBATIM_INSERT[]                 = "verbatim_insert";
88 char const SUSPEND[]                         = "suspend";
89 char const COMPLETE_LINE[]                   = "complete_line";
90 char const HISTORY_INCREMENTAL_SEARCH[]      = "history_incremental_search";
91 char const HISTORY_COMMON_PREFIX_SEARCH[]    = "history_common_prefix_search";
92 }
93 
94 static int const REPLXX_MAX_HINT_ROWS( 4 );
95 /*
96  * All whitespaces and all non-alphanumerical characters from ASCII range
97  * with an exception of an underscore ('_').
98  */
99 char const defaultWordBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?";
100 /*
101  * All whitespaces and all non-alphanumerical characters from ASCII range
102  */
103 char const defaultSubwordBreakChars[] = " \t\v\f\a\b\r\n`~!@#$%^&*()-=+[{]}\\|;:'\",<.>/?_";
104 static const char* unsupported_term[] = {"dumb", "cons25", "emacs", NULL};
105 
isUnsupportedTerm(void)106 static bool isUnsupportedTerm(void) {
107 	char* term = getenv("TERM");
108 	if (term == NULL) {
109 		return false;
110 	}
111 	for (int j = 0; unsupported_term[j]; ++j) {
112 		if (!strcasecmp(term, unsupported_term[j])) {
113 			return true;
114 		}
115 	}
116 	return false;
117 }
118 
119 int long long RAPID_REFRESH_MS = 1;
120 int long long RAPID_REFRESH_US = RAPID_REFRESH_MS * 1000;
121 
now_us(void)122 inline int long long now_us( void ) {
123 	return ( std::chrono::duration_cast<std::chrono::microseconds>( std::chrono::high_resolution_clock::now().time_since_epoch() ).count() );
124 }
125 
126 class IOModeGuard {
127 	Terminal& _terminal;
128 public:
IOModeGuard(Terminal & terminal_)129 	IOModeGuard( Terminal& terminal_ )
130 		: _terminal( terminal_ ) {
131 		_terminal.disable_raw_mode();
132 	}
~IOModeGuard(void)133 	~IOModeGuard( void ) {
134 		try {
135 			_terminal.enable_raw_mode();
136 		} catch ( ... ) {
137 		}
138 	}
139 };
140 
141 }
142 
ReplxxImpl(FILE *,FILE *,FILE *)143 Replxx::ReplxxImpl::ReplxxImpl( FILE*, FILE*, FILE* )
144 	: _utf8Buffer()
145 	, _data()
146 	, _pos( 0 )
147 	, _display()
148 	, _displayInputLength( 0 )
149 	, _hint()
150 	, _prefix( 0 )
151 	, _hintSelection( -1 )
152 	, _history()
153 	, _killRing()
154 	, _lastRefreshTime( now_us() )
155 	, _refreshSkipped( false )
156 	, _lastYankSize( 0 )
157 	, _maxHintRows( REPLXX_MAX_HINT_ROWS )
158 	, _hintDelay( 0 )
159 	, _wordBreakChars( defaultWordBreakChars )
160 	, _subwordBreakChars( defaultSubwordBreakChars )
161 	, _completionCountCutoff( 100 )
162 	, _overwrite( false )
163 	, _doubleTabCompletion( false )
164 	, _completeOnEmpty( true )
165 	, _beepOnAmbiguousCompletion( false )
166 	, _immediateCompletion( true )
167 	, _bracketedPaste( false )
168 	, _noColor( false )
169 	, _namedActions()
170 	, _keyPressHandlers()
171 	, _terminal()
172 	, _currentThread()
173 	, _prompt( _terminal )
174 	, _completionCallback( nullptr )
175 	, _highlighterCallback( nullptr )
176 	, _hintCallback( nullptr )
177 	, _keyPresses()
178 	, _messages()
179 	, _completions()
180 	, _completionContextLength( 0 )
181 	, _completionSelection( -1 )
182 	, _preloadedBuffer()
183 	, _errorMessage()
184 	, _previousSearchText()
185 	, _modifiedState( false )
186 	, _hintColor( Replxx::Color::GRAY )
187 	, _hintsCache()
188 	, _hintContextLenght( -1 )
189 	, _hintSeed()
190 	, _mutex() {
191 	using namespace std::placeholders;
192 	_namedActions[action_names::INSERT_CHARACTER]                = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::INSERT_CHARACTER,                _1 );
193 	_namedActions[action_names::NEW_LINE]                        = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::NEW_LINE,                        _1 );
194 	_namedActions[action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE, _1 );
195 	_namedActions[action_names::MOVE_CURSOR_TO_END_OF_LINE]      = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE,      _1 );
196 	_namedActions[action_names::MOVE_CURSOR_LEFT]                = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_LEFT,                _1 );
197 	_namedActions[action_names::MOVE_CURSOR_RIGHT]               = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_RIGHT,               _1 );
198 	_namedActions[action_names::MOVE_CURSOR_ONE_WORD_LEFT]       = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT,       _1 );
199 	_namedActions[action_names::MOVE_CURSOR_ONE_WORD_RIGHT]      = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT,      _1 );
200 	_namedActions[action_names::MOVE_CURSOR_ONE_SUBWORD_LEFT]    = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT,    _1 );
201 	_namedActions[action_names::MOVE_CURSOR_ONE_SUBWORD_RIGHT]   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT,   _1 );
202 	_namedActions[action_names::KILL_TO_WHITESPACE_ON_LEFT]      = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_WHITESPACE_ON_LEFT,      _1 );
203 	_namedActions[action_names::KILL_TO_END_OF_WORD]             = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_WORD,             _1 );
204 	_namedActions[action_names::KILL_TO_BEGINING_OF_WORD]        = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_WORD,        _1 );
205 	_namedActions[action_names::KILL_TO_END_OF_SUBWORD]          = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_SUBWORD,          _1 );
206 	_namedActions[action_names::KILL_TO_BEGINING_OF_SUBWORD]     = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_SUBWORD,     _1 );
207 	_namedActions[action_names::KILL_TO_BEGINING_OF_LINE]        = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_BEGINING_OF_LINE,        _1 );
208 	_namedActions[action_names::KILL_TO_END_OF_LINE]             = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::KILL_TO_END_OF_LINE,             _1 );
209 	_namedActions[action_names::YANK]                            = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK,                            _1 );
210 	_namedActions[action_names::YANK_CYCLE]                      = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_CYCLE,                      _1 );
211 	_namedActions[action_names::YANK_LAST_ARG]                   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::YANK_LAST_ARG,                   _1 );
212 	_namedActions[action_names::CAPITALIZE_WORD]                 = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_WORD,                 _1 );
213 	_namedActions[action_names::LOWERCASE_WORD]                  = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_WORD,                  _1 );
214 	_namedActions[action_names::UPPERCASE_WORD]                  = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_WORD,                  _1 );
215 	_namedActions[action_names::CAPITALIZE_SUBWORD]              = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CAPITALIZE_SUBWORD,              _1 );
216 	_namedActions[action_names::LOWERCASE_SUBWORD]               = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::LOWERCASE_SUBWORD,               _1 );
217 	_namedActions[action_names::UPPERCASE_SUBWORD]               = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::UPPERCASE_SUBWORD,               _1 );
218 	_namedActions[action_names::TRANSPOSE_CHARACTERS]            = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TRANSPOSE_CHARACTERS,            _1 );
219 	_namedActions[action_names::ABORT_LINE]                      = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::ABORT_LINE,                      _1 );
220 	_namedActions[action_names::SEND_EOF]                        = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SEND_EOF,                        _1 );
221 	_namedActions[action_names::TOGGLE_OVERWRITE_MODE]           = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::TOGGLE_OVERWRITE_MODE,           _1 );
222 	_namedActions[action_names::DELETE_CHARACTER_UNDER_CURSOR]   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR,   _1 );
223 	_namedActions[action_names::DELETE_CHARACTER_LEFT_OF_CURSOR] = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR, _1 );
224 	_namedActions[action_names::COMMIT_LINE]                     = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMMIT_LINE,                     _1 );
225 	_namedActions[action_names::CLEAR_SCREEN]                    = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::CLEAR_SCREEN,                    _1 );
226 	_namedActions[action_names::COMPLETE_NEXT]                   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_NEXT,                   _1 );
227 	_namedActions[action_names::COMPLETE_PREVIOUS]               = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_PREVIOUS,               _1 );
228 	_namedActions[action_names::HISTORY_NEXT]                    = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_NEXT,                    _1 );
229 	_namedActions[action_names::HISTORY_PREVIOUS]                = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_PREVIOUS,                _1 );
230 	_namedActions[action_names::HISTORY_LAST]                    = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_LAST,                    _1 );
231 	_namedActions[action_names::HISTORY_FIRST]                   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_FIRST,                   _1 );
232 	_namedActions[action_names::HINT_PREVIOUS]                   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_PREVIOUS,                   _1 );
233 	_namedActions[action_names::HINT_NEXT]                       = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HINT_NEXT,                       _1 );
234 #ifndef _WIN32
235 	_namedActions[action_names::VERBATIM_INSERT]                 = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::VERBATIM_INSERT,                 _1 );
236 	_namedActions[action_names::SUSPEND]                         = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::SUSPEND,                         _1 );
237 #else
238 	_namedActions[action_names::VERBATIM_INSERT] = _namedActions[action_names::SUSPEND] = Replxx::key_press_handler_t();
239 #endif
240 	_namedActions[action_names::COMPLETE_LINE]                   = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::COMPLETE_LINE,                   _1 );
241 	_namedActions[action_names::HISTORY_INCREMENTAL_SEARCH]      = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH,      _1 );
242 	_namedActions[action_names::HISTORY_COMMON_PREFIX_SEARCH]    = std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH,    _1 );
243 
244 	bind_key( Replxx::KEY::control( 'A' ),                 _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) );
245 	bind_key( Replxx::KEY::HOME + 0,                       _namedActions.at( action_names::MOVE_CURSOR_TO_BEGINING_OF_LINE ) );
246 	bind_key( Replxx::KEY::control( 'E' ),                 _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) );
247 	bind_key( Replxx::KEY::END + 0,                        _namedActions.at( action_names::MOVE_CURSOR_TO_END_OF_LINE ) );
248 	bind_key( Replxx::KEY::control( 'B' ),                 _namedActions.at( action_names::MOVE_CURSOR_LEFT ) );
249 	bind_key( Replxx::KEY::LEFT + 0,                       _namedActions.at( action_names::MOVE_CURSOR_LEFT ) );
250 	bind_key( Replxx::KEY::control( 'F' ),                 _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) );
251 	bind_key( Replxx::KEY::RIGHT + 0,                      _namedActions.at( action_names::MOVE_CURSOR_RIGHT ) );
252 	bind_key( Replxx::KEY::meta( 'b' ),                    _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) );
253 	bind_key( Replxx::KEY::meta( 'B' ),                    _namedActions.at( action_names::MOVE_CURSOR_ONE_SUBWORD_LEFT ) );
254 	bind_key( Replxx::KEY::control( Replxx::KEY::LEFT ),   _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) );
255 	bind_key( Replxx::KEY::meta( Replxx::KEY::LEFT ),      _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_LEFT ) ); // Emacs allows Meta, readline don't
256 	bind_key( Replxx::KEY::meta( 'f' ),                    _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) );
257 	bind_key( Replxx::KEY::meta( 'F' ),                    _namedActions.at( action_names::MOVE_CURSOR_ONE_SUBWORD_RIGHT ) );
258 	bind_key( Replxx::KEY::control( Replxx::KEY::RIGHT ),  _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) );
259 	bind_key( Replxx::KEY::meta( Replxx::KEY::RIGHT ),     _namedActions.at( action_names::MOVE_CURSOR_ONE_WORD_RIGHT ) ); // Emacs allows Meta, readline don't
260 	bind_key( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), _namedActions.at( action_names::KILL_TO_WHITESPACE_ON_LEFT ) );
261 	bind_key( Replxx::KEY::meta( 'd' ),                    _namedActions.at( action_names::KILL_TO_END_OF_WORD ) );
262 	bind_key( Replxx::KEY::meta( 'D' ),                    _namedActions.at( action_names::KILL_TO_END_OF_SUBWORD ) );
263 	bind_key( Replxx::KEY::control( 'W' ),                 _namedActions.at( action_names::KILL_TO_BEGINING_OF_WORD ) );
264 	bind_key( Replxx::KEY::meta( 'W' ),                    _namedActions.at( action_names::KILL_TO_BEGINING_OF_SUBWORD ) );
265 	bind_key( Replxx::KEY::control( 'U' ),                 _namedActions.at( action_names::KILL_TO_BEGINING_OF_LINE ) );
266 	bind_key( Replxx::KEY::control( 'K' ),                 _namedActions.at( action_names::KILL_TO_END_OF_LINE ) );
267 	bind_key( Replxx::KEY::control( 'Y' ),                 _namedActions.at( action_names::YANK ) );
268 	bind_key( Replxx::KEY::meta( 'y' ),                    _namedActions.at( action_names::YANK_CYCLE ) );
269 	bind_key( Replxx::KEY::meta( 'Y' ),                    _namedActions.at( action_names::YANK_CYCLE ) );
270 	bind_key( Replxx::KEY::meta( '.' ),                    _namedActions.at( action_names::YANK_LAST_ARG ) );
271 	bind_key( Replxx::KEY::meta( 'c' ),                    _namedActions.at( action_names::CAPITALIZE_WORD ) );
272 	bind_key( Replxx::KEY::meta( 'C' ),                    _namedActions.at( action_names::CAPITALIZE_SUBWORD ) );
273 	bind_key( Replxx::KEY::meta( 'l' ),                    _namedActions.at( action_names::LOWERCASE_WORD ) );
274 	bind_key( Replxx::KEY::meta( 'L' ),                    _namedActions.at( action_names::LOWERCASE_SUBWORD ) );
275 	bind_key( Replxx::KEY::meta( 'u' ),                    _namedActions.at( action_names::UPPERCASE_WORD ) );
276 	bind_key( Replxx::KEY::meta( 'U' ),                    _namedActions.at( action_names::UPPERCASE_SUBWORD ) );
277 	bind_key( Replxx::KEY::control( 'T' ),                 _namedActions.at( action_names::TRANSPOSE_CHARACTERS ) );
278 	bind_key( Replxx::KEY::control( 'C' ),                 _namedActions.at( action_names::ABORT_LINE ) );
279 	bind_key( Replxx::KEY::control( 'D' ),                 _namedActions.at( action_names::SEND_EOF ) );
280 	bind_key( Replxx::KEY::INSERT + 0,                     _namedActions.at( action_names::TOGGLE_OVERWRITE_MODE ) );
281 	bind_key( 127,                                         _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) );
282 	bind_key( Replxx::KEY::DELETE + 0,                     _namedActions.at( action_names::DELETE_CHARACTER_UNDER_CURSOR ) );
283 	bind_key( Replxx::KEY::BACKSPACE + 0,                  _namedActions.at( action_names::DELETE_CHARACTER_LEFT_OF_CURSOR ) );
284 	bind_key( Replxx::KEY::control( 'J' ),                 _namedActions.at( action_names::NEW_LINE ) );
285 	bind_key( Replxx::KEY::ENTER + 0,                      _namedActions.at( action_names::COMMIT_LINE ) );
286 	bind_key( Replxx::KEY::control( 'L' ),                 _namedActions.at( action_names::CLEAR_SCREEN ) );
287 	bind_key( Replxx::KEY::control( 'N' ),                 _namedActions.at( action_names::COMPLETE_NEXT ) );
288 	bind_key( Replxx::KEY::control( 'P' ),                 _namedActions.at( action_names::COMPLETE_PREVIOUS ) );
289 	bind_key( Replxx::KEY::DOWN + 0,                       _namedActions.at( action_names::HISTORY_NEXT ) );
290 	bind_key( Replxx::KEY::UP + 0,                         _namedActions.at( action_names::HISTORY_PREVIOUS ) );
291 	bind_key( Replxx::KEY::meta( '<' ),                    _namedActions.at( action_names::HISTORY_FIRST ) );
292 	bind_key( Replxx::KEY::PAGE_UP + 0,                    _namedActions.at( action_names::HISTORY_FIRST ) );
293 	bind_key( Replxx::KEY::meta( '>' ),                    _namedActions.at( action_names::HISTORY_LAST ) );
294 	bind_key( Replxx::KEY::PAGE_DOWN + 0,                  _namedActions.at( action_names::HISTORY_LAST ) );
295 	bind_key( Replxx::KEY::control( Replxx::KEY::UP ),     _namedActions.at( action_names::HINT_PREVIOUS ) );
296 	bind_key( Replxx::KEY::control( Replxx::KEY::DOWN ),   _namedActions.at( action_names::HINT_NEXT ) );
297 #ifndef _WIN32
298 	bind_key( Replxx::KEY::control( 'V' ),                 _namedActions.at( action_names::VERBATIM_INSERT ) );
299 	bind_key( Replxx::KEY::control( 'Z' ),                 _namedActions.at( action_names::SUSPEND ) );
300 #endif
301 	bind_key( Replxx::KEY::TAB + 0,                        _namedActions.at( action_names::COMPLETE_LINE ) );
302 	bind_key( Replxx::KEY::control( 'R' ),                 _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) );
303 	bind_key( Replxx::KEY::control( 'S' ),                 _namedActions.at( action_names::HISTORY_INCREMENTAL_SEARCH ) );
304 	bind_key( Replxx::KEY::meta( 'p' ),                    _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
305 	bind_key( Replxx::KEY::meta( 'P' ),                    _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
306 	bind_key( Replxx::KEY::meta( 'n' ),                    _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
307 	bind_key( Replxx::KEY::meta( 'N' ),                    _namedActions.at( action_names::HISTORY_COMMON_PREFIX_SEARCH ) );
308 	bind_key( Replxx::KEY::PASTE_START,                    std::bind( &ReplxxImpl::invoke, this, Replxx::ACTION::BRACKETED_PASTE, _1 ) );
309 }
310 
~ReplxxImpl(void)311 Replxx::ReplxxImpl::~ReplxxImpl( void ) {
312 	disable_bracketed_paste();
313 }
314 
invoke(Replxx::ACTION action_,char32_t code)315 Replxx::ACTION_RESULT Replxx::ReplxxImpl::invoke( Replxx::ACTION action_, char32_t code ) {
316 	switch ( action_ ) {
317 		case ( Replxx::ACTION::INSERT_CHARACTER ):                return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, code ) );
318 		case ( Replxx::ACTION::NEW_LINE ):                        return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::new_line, code ) );
319 		case ( Replxx::ACTION::DELETE_CHARACTER_UNDER_CURSOR ):   return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::delete_character, code ) );
320 		case ( Replxx::ACTION::DELETE_CHARACTER_LEFT_OF_CURSOR ): return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::backspace_character, code ) );
321 		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 ) );
322 		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 ) );
323 		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 ) );
324 		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 ) );
325 		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 ) );
326 		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 ) );
327 		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 ) );
328 		case ( Replxx::ACTION::YANK ):                            return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank, code ) );
329 		case ( Replxx::ACTION::YANK_CYCLE ):                      return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::yank_cycle, code ) );
330 		case ( Replxx::ACTION::YANK_LAST_ARG ):                   return ( action( HISTORY_RECALL_MOST_RECENT | DONT_RESET_HIST_YANK_INDEX, &Replxx::ReplxxImpl::yank_last_arg, code ) );
331 		case ( Replxx::ACTION::MOVE_CURSOR_TO_BEGINING_OF_LINE ): return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_begining_of_line, code ) );
332 		case ( Replxx::ACTION::MOVE_CURSOR_TO_END_OF_LINE ):      return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::go_to_end_of_line, code ) );
333 		case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_LEFT ):       return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left<false>, code ) );
334 		case ( Replxx::ACTION::MOVE_CURSOR_ONE_WORD_RIGHT ):      return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right<false>, code ) );
335 		case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_LEFT ):    return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_left<true>, code ) );
336 		case ( Replxx::ACTION::MOVE_CURSOR_ONE_SUBWORD_RIGHT ):   return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_word_right<true>, code ) );
337 		case ( Replxx::ACTION::MOVE_CURSOR_LEFT ):                return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_left, code ) );
338 		case ( Replxx::ACTION::MOVE_CURSOR_RIGHT ):               return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::move_one_char_right, code ) );
339 		case ( Replxx::ACTION::HISTORY_NEXT ):                    return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_next, code ) );
340 		case ( Replxx::ACTION::HISTORY_PREVIOUS ):                return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_previous, code ) );
341 		case ( Replxx::ACTION::HISTORY_FIRST ):                   return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_first, code ) );
342 		case ( Replxx::ACTION::HISTORY_LAST ):                    return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::history_last, code ) );
343 		case ( Replxx::ACTION::HISTORY_INCREMENTAL_SEARCH ):      return ( action( NOOP, &Replxx::ReplxxImpl::incremental_history_search, code ) );
344 		case ( Replxx::ACTION::HISTORY_COMMON_PREFIX_SEARCH ):    return ( action( RESET_KILL_ACTION | DONT_RESET_PREFIX, &Replxx::ReplxxImpl::common_prefix_search, code ) );
345 		case ( Replxx::ACTION::HINT_NEXT ):                       return ( action( NOOP, &Replxx::ReplxxImpl::hint_next, code ) );
346 		case ( Replxx::ACTION::HINT_PREVIOUS ):                   return ( action( NOOP, &Replxx::ReplxxImpl::hint_previous, code ) );
347 		case ( Replxx::ACTION::CAPITALIZE_WORD ):                 return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word<false>, code ) );
348 		case ( Replxx::ACTION::LOWERCASE_WORD ):                  return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word<false>, code ) );
349 		case ( Replxx::ACTION::UPPERCASE_WORD ):                  return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word<false>, code ) );
350 		case ( Replxx::ACTION::CAPITALIZE_SUBWORD ):              return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::capitalize_word<true>, code ) );
351 		case ( Replxx::ACTION::LOWERCASE_SUBWORD ):               return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::lowercase_word<true>, code ) );
352 		case ( Replxx::ACTION::UPPERCASE_SUBWORD ):               return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::uppercase_word<true>, code ) );
353 		case ( Replxx::ACTION::TRANSPOSE_CHARACTERS ):            return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::transpose_characters, code ) );
354 		case ( Replxx::ACTION::TOGGLE_OVERWRITE_MODE ):           return ( action( NOOP, &Replxx::ReplxxImpl::toggle_overwrite_mode, code ) );
355 #ifndef _WIN32
356 		case ( Replxx::ACTION::VERBATIM_INSERT ):                 return ( action( WANT_REFRESH | RESET_KILL_ACTION, &Replxx::ReplxxImpl::verbatim_insert, code ) );
357 		case ( Replxx::ACTION::SUSPEND ):                         return ( action( WANT_REFRESH, &Replxx::ReplxxImpl::suspend, code ) );
358 #endif
359 		case ( Replxx::ACTION::CLEAR_SCREEN ):                    return ( action( NOOP, &Replxx::ReplxxImpl::clear_screen, code ) );
360 		case ( Replxx::ACTION::CLEAR_SELF ): clear_self_to_end_of_screen(); return ( Replxx::ACTION_RESULT::CONTINUE );
361 		case ( Replxx::ACTION::REPAINT ):    repaint();           return ( Replxx::ACTION_RESULT::CONTINUE );
362 		case ( Replxx::ACTION::COMPLETE_LINE ):                   return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_line, code ) );
363 		case ( Replxx::ACTION::COMPLETE_NEXT ):                   return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_next, code ) );
364 		case ( Replxx::ACTION::COMPLETE_PREVIOUS ):               return ( action( RESET_KILL_ACTION | DONT_RESET_COMPLETIONS | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::complete_previous, code ) );
365 		case ( Replxx::ACTION::COMMIT_LINE ):                     return ( action( RESET_KILL_ACTION, &Replxx::ReplxxImpl::commit_line, code ) );
366 		case ( Replxx::ACTION::ABORT_LINE ):                      return ( action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::abort_line, code ) );
367 		case ( Replxx::ACTION::SEND_EOF ):                        return ( action( HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::send_eof, code ) );
368 		case ( Replxx::ACTION::BRACKETED_PASTE ):                 return ( action( WANT_REFRESH | RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::bracketed_paste, code ) );
369 	}
370 	return ( Replxx::ACTION_RESULT::BAIL );
371 }
372 
bind_key(char32_t code_,Replxx::key_press_handler_t handler_)373 void Replxx::ReplxxImpl::bind_key( char32_t code_, Replxx::key_press_handler_t handler_ ) {
374 	_keyPressHandlers[code_] = handler_;
375 }
376 
bind_key_internal(char32_t code_,char const * actionName_)377 void Replxx::ReplxxImpl::bind_key_internal( char32_t code_, char const* actionName_ ) {
378 	named_actions_t::const_iterator it( _namedActions.find( actionName_ ) );
379 	if ( it == _namedActions.end() ) {
380 		throw std::runtime_error( std::string( "replxx: Unknown action name: " ).append( actionName_ ) );
381 	}
382 	if ( !! it->second ) {
383 		bind_key( code_, it->second );
384 	}
385 }
386 
get_state(void) const387 Replxx::State Replxx::ReplxxImpl::get_state( void ) const {
388 	_utf8Buffer.assign( _data );
389 	return ( Replxx::State( _utf8Buffer.get(), _pos ) );
390 }
391 
set_state(Replxx::State const & state_)392 void Replxx::ReplxxImpl::set_state( Replxx::State const& state_ ) {
393 	_data.assign( state_.text() );
394 	if ( state_.cursor_position() >= 0 ) {
395 		_pos = min( state_.cursor_position(), _data.length() );
396 	}
397 	_modifiedState = true;
398 }
399 
read_char(HINT_ACTION hintAction_)400 char32_t Replxx::ReplxxImpl::read_char( HINT_ACTION hintAction_ ) {
401 	/* try scheduled key presses */ {
402 		std::lock_guard<std::mutex> l( _mutex );
403 		if ( !_keyPresses.empty() ) {
404 			char32_t keyPress( _keyPresses.front() );
405 			_keyPresses.pop_front();
406 			return ( keyPress );
407 		}
408 	}
409 	int hintDelay(
410 		_refreshSkipped
411 			? static_cast<int>( RAPID_REFRESH_MS * 2 )
412 			: ( hintAction_ != HINT_ACTION::SKIP ? _hintDelay : 0 )
413 	);
414 	while ( true ) {
415 		Terminal::EVENT_TYPE eventType( _terminal.wait_for_input( hintDelay ) );
416 		if ( eventType == Terminal::EVENT_TYPE::TIMEOUT ) {
417 			refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::REPAINT );
418 			hintDelay = 0;
419 			_refreshSkipped = false;
420 			continue;
421 		}
422 		if ( eventType == Terminal::EVENT_TYPE::KEY_PRESS ) {
423 			break;
424 		}
425 		if ( eventType == Terminal::EVENT_TYPE::RESIZE ) {
426 			// caught a window resize event
427 			// now redraw the prompt and line
428 			_prompt.update_screen_columns();
429 			// redraw the original prompt with current input
430 			refresh_line( HINT_ACTION::REPAINT );
431 			continue;
432 		}
433 		std::lock_guard<std::mutex> l( _mutex );
434 		clear_self_to_end_of_screen();
435 		while ( ! _messages.empty() ) {
436 			string const& message( _messages.front() );
437 			_terminal.write8( message.data(), static_cast<int>( message.length() ) );
438 			_messages.pop_front();
439 		}
440 		repaint();
441 	}
442 	/* try scheduled key presses */ {
443 		std::lock_guard<std::mutex> l( _mutex );
444 		if ( !_keyPresses.empty() ) {
445 			char32_t keyPress( _keyPresses.front() );
446 			_keyPresses.pop_front();
447 			return ( keyPress );
448 		}
449 	}
450 	return ( _terminal.read_char() );
451 }
452 
clear(void)453 void Replxx::ReplxxImpl::clear( void ) {
454 	_pos = 0;
455 	_prefix = 0;
456 	_completions.clear();
457 	_completionContextLength = 0;
458 	_completionSelection = -1;
459 	_data.clear();
460 	_hintSelection = -1;
461 	_hint = UnicodeString();
462 	_display.clear();
463 	_displayInputLength = 0;
464 }
465 
call_modify_callback(void)466 void Replxx::ReplxxImpl::call_modify_callback( void ) {
467 	if ( ! _modifyCallback ) {
468 		return;
469 	}
470 	_utf8Buffer.assign( _data );
471 	std::string origLine( _utf8Buffer.get() );
472 	int pos( _pos );
473 	std::string line( origLine );
474 	/* IOModeGuard scope */ {
475 		IOModeGuard ioModeGuard( _terminal );
476 		_modifyCallback( line, pos );
477 	}
478 	if ( ( pos != _pos ) || ( line != origLine ) ) {
479 		_data.assign( line.c_str() );
480 		_pos = min( pos, _data.length() );
481 		_modifiedState = true;
482 	}
483 }
484 
call_completer(std::string const & input,int & contextLen_) const485 Replxx::ReplxxImpl::completions_t Replxx::ReplxxImpl::call_completer( std::string const& input, int& contextLen_ ) const {
486 	Replxx::completions_t completionsIntermediary(
487 		!! _completionCallback
488 			? _completionCallback( input, contextLen_ )
489 			: Replxx::completions_t()
490 	);
491 	completions_t completions;
492 	completions.reserve( completionsIntermediary.size() );
493 	for ( Replxx::Completion const& c : completionsIntermediary ) {
494 		completions.emplace_back( c );
495 	}
496 	return ( completions );
497 }
498 
call_hinter(std::string const & input,int & contextLen,Replxx::Color & color) const499 Replxx::ReplxxImpl::hints_t Replxx::ReplxxImpl::call_hinter( std::string const& input, int& contextLen, Replxx::Color& color ) const {
500 	Replxx::hints_t hintsIntermediary(
501 		!! _hintCallback
502 			? _hintCallback( input, contextLen, color )
503 			: Replxx::hints_t()
504 	);
505 	hints_t hints;
506 	hints.reserve( hintsIntermediary.size() );
507 	for ( std::string const& h : hintsIntermediary ) {
508 		hints.emplace_back( h.c_str() );
509 	}
510 	return ( hints );
511 }
512 
set_preload_buffer(std::string const & preloadText)513 void Replxx::ReplxxImpl::set_preload_buffer( std::string const& preloadText ) {
514 	_preloadedBuffer = preloadText;
515 	// remove characters that won't display correctly
516 	bool controlsStripped = false;
517 	int whitespaceSeen( 0 );
518 	for ( std::string::iterator it( _preloadedBuffer.begin() ); it != _preloadedBuffer.end(); ) {
519 		unsigned char c = *it;
520 		if ( '\r' == c ) { // silently skip CR
521 			_preloadedBuffer.erase( it, it + 1 );
522 			continue;
523 		}
524 		if ( ( '\n' == c ) || ( '\t' == c ) ) { // note newline or tab
525 			++ whitespaceSeen;
526 			++ it;
527 			continue;
528 		}
529 		if ( whitespaceSeen > 0 ) {
530 			it -= whitespaceSeen;
531 			*it = ' ';
532 			_preloadedBuffer.erase( it + 1, it + whitespaceSeen - 1 );
533 		}
534 		if ( is_control_code( c ) ) { // remove other control characters, flag for message
535 			controlsStripped = true;
536 			if ( whitespaceSeen > 0 ) {
537 				_preloadedBuffer.erase( it, it + 1 );
538 				-- it;
539 			} else {
540 				*it = ' ';
541 			}
542 		}
543 		whitespaceSeen = 0;
544 		++ it;
545 	}
546 	if ( whitespaceSeen > 0 ) {
547 		std::string::iterator it = _preloadedBuffer.end() - whitespaceSeen;
548 		*it = ' ';
549 		if ( whitespaceSeen > 1 ) {
550 			_preloadedBuffer.erase( it + 1, _preloadedBuffer.end() );
551 		}
552 	}
553 	_errorMessage.clear();
554 	if ( controlsStripped ) {
555 		_errorMessage.assign( " [Edited line: control characters were converted to spaces]\n" );
556 	}
557 }
558 
read_from_stdin(void)559 char const* Replxx::ReplxxImpl::read_from_stdin( void ) {
560 	if ( _preloadedBuffer.empty() ) {
561 		getline( cin, _preloadedBuffer );
562 		if ( ! cin.good() ) {
563 			return nullptr;
564 		}
565 	}
566 	while ( ! _preloadedBuffer.empty() && ( ( _preloadedBuffer.back() == '\r' ) || ( _preloadedBuffer.back() == '\n' ) ) ) {
567 		_preloadedBuffer.pop_back();
568 	}
569 	_utf8Buffer.assign( _preloadedBuffer );
570 	_preloadedBuffer.clear();
571 	return _utf8Buffer.get();
572 }
573 
emulate_key_press(char32_t keyCode_)574 void Replxx::ReplxxImpl::emulate_key_press( char32_t keyCode_ ) {
575 	std::lock_guard<std::mutex> l( _mutex );
576 	_keyPresses.push_back( keyCode_ );
577 	if ( ( _currentThread != std::thread::id() ) && ( _currentThread != std::this_thread::get_id() ) ) {
578 		_terminal.notify_event( Terminal::EVENT_TYPE::KEY_PRESS );
579 	}
580 }
581 
input(std::string const & prompt)582 char const* Replxx::ReplxxImpl::input( std::string const& prompt ) {
583 	try {
584 		errno = 0;
585 		if ( ! tty::in ) { // input not from a terminal, we should work with piped input, i.e. redirected stdin
586 			return ( read_from_stdin() );
587 		}
588 		if (!_errorMessage.empty()) {
589 			printf("%s", _errorMessage.c_str());
590 			fflush(stdout);
591 			_errorMessage.clear();
592 		}
593 		if ( isUnsupportedTerm() ) {
594 			cout << prompt << flush;
595 			fflush(stdout);
596 			return ( read_from_stdin() );
597 		}
598 		if (_terminal.enable_raw_mode() == -1) {
599 			return nullptr;
600 		}
601 		_prompt.set_text( UnicodeString( prompt ) );
602 		_currentThread = std::this_thread::get_id();
603 		clear();
604 		if (!_preloadedBuffer.empty()) {
605 			preload_puffer(_preloadedBuffer.c_str());
606 			_preloadedBuffer.clear();
607 		}
608 		if ( get_input_line() == -1 ) {
609 			return ( finalize_input( nullptr ) );
610 		}
611 		_terminal.write8( "\n", 1 );
612 		_utf8Buffer.assign( _data );
613 		return ( finalize_input( _utf8Buffer.get() ) );
614 	} catch ( std::exception const& ) {
615 		return ( finalize_input( nullptr ) );
616 	}
617 }
618 
finalize_input(char const * retVal_)619 char const* Replxx::ReplxxImpl::finalize_input( char const* retVal_ ) {
620 	_currentThread = std::thread::id();
621 	_terminal.disable_raw_mode();
622 	return ( retVal_ );
623 }
624 
install_window_change_handler(void)625 int Replxx::ReplxxImpl::install_window_change_handler( void ) {
626 #ifndef _WIN32
627 	return ( _terminal.install_window_change_handler() );
628 #else
629 	return 0;
630 #endif
631 }
632 
enable_bracketed_paste(void)633 void Replxx::ReplxxImpl::enable_bracketed_paste( void ) {
634 	if ( _bracketedPaste ) {
635 		return;
636 	}
637 	_terminal.enable_bracketed_paste();
638 	_bracketedPaste = true;
639 }
640 
disable_bracketed_paste(void)641 void Replxx::ReplxxImpl::disable_bracketed_paste( void ) {
642 	if ( ! _bracketedPaste ) {
643 		return;
644 	}
645 	_terminal.disable_bracketed_paste();
646 	_bracketedPaste = false;
647 }
648 
print(char const * str_,int size_)649 void Replxx::ReplxxImpl::print( char const* str_, int size_ ) {
650 	if ( ( _currentThread == std::thread::id() ) || ( _currentThread == std::this_thread::get_id() ) ) {
651 		_terminal.write8( str_, size_ );
652 	} else {
653 		std::lock_guard<std::mutex> l( _mutex );
654 		_messages.emplace_back( str_, size_ );
655 		_terminal.notify_event( Terminal::EVENT_TYPE::MESSAGE );
656 	}
657 	return;
658 }
659 
preload_puffer(const char * preloadText)660 void Replxx::ReplxxImpl::preload_puffer(const char* preloadText) {
661 	_data.assign( preloadText );
662 	_prefix = _pos = _data.length();
663 }
664 
set_color(Replxx::Color color_)665 void Replxx::ReplxxImpl::set_color( Replxx::Color color_ ) {
666 	char const* code( ansi_color( color_ ) );
667 	while ( *code ) {
668 		_display.push_back( *code );
669 		++ code;
670 	}
671 }
672 
render(char32_t ch)673 void Replxx::ReplxxImpl::render( char32_t ch ) {
674 	if ( ch == Replxx::KEY::ESCAPE ) {
675 		_display.push_back( '^' );
676 		_display.push_back( '[' );
677 	} else if ( is_control_code( ch ) && ( ch != '\n' ) ) {
678 		_display.push_back( '^' );
679 		_display.push_back( control_to_human( ch ) );
680 	} else {
681 		_display.push_back( ch );
682 	}
683 	return;
684 }
685 
render(HINT_ACTION hintAction_)686 void Replxx::ReplxxImpl::render( HINT_ACTION hintAction_ ) {
687 	if ( hintAction_ == HINT_ACTION::TRIM ) {
688 		_display.erase( _display.begin() + _displayInputLength, _display.end() );
689 		_modifiedState = false;
690 		return;
691 	}
692 	if ( hintAction_ == HINT_ACTION::SKIP ) {
693 		return;
694 	}
695 	_display.clear();
696 	if ( _noColor ) {
697 		for ( char32_t ch : _data ) {
698 			render( ch );
699 		}
700 		_displayInputLength = static_cast<int>( _display.size() );
701 		_modifiedState = false;
702 		return;
703 	}
704 	Replxx::colors_t colors( _data.length(), Replxx::Color::DEFAULT );
705 	_utf8Buffer.assign( _data );
706 	if ( !! _highlighterCallback ) {
707 		IOModeGuard ioModeGuard( _terminal );
708 		_highlighterCallback( _utf8Buffer.get(), colors );
709 	}
710 	paren_info_t pi( matching_paren() );
711 	if ( pi.index != -1 ) {
712 		colors[pi.index] = pi.error ? Replxx::Color::ERROR : Replxx::Color::BRIGHTRED;
713 	}
714 	Replxx::Color c( Replxx::Color::DEFAULT );
715 	for ( int i( 0 ); i < _data.length(); ++ i ) {
716 		if ( colors[i] != c ) {
717 			c = colors[i];
718 			set_color( c );
719 		}
720 		render( _data[i] );
721 	}
722 	set_color( Replxx::Color::DEFAULT );
723 	_displayInputLength = static_cast<int>( _display.size() );
724 	_modifiedState = false;
725 	return;
726 }
727 
handle_hints(HINT_ACTION hintAction_)728 int Replxx::ReplxxImpl::handle_hints( HINT_ACTION hintAction_ ) {
729 	if ( _noColor ) {
730 		return ( 0 );
731 	}
732 	if ( ! _hintCallback ) {
733 		return ( 0 );
734 	}
735 	if ( ( _hintDelay > 0 ) && ( hintAction_ != HINT_ACTION::REPAINT ) ) {
736 		_hintSelection = -1;
737 		return ( 0 );
738 	}
739 	if ( ( hintAction_ == HINT_ACTION::SKIP ) || ( hintAction_ == HINT_ACTION::TRIM ) ) {
740 		return ( 0 );
741 	}
742 	if ( _pos != _data.length() ) {
743 		return ( 0 );
744 	}
745 	_hint = UnicodeString();
746 	int len( 0 );
747 	if ( hintAction_ == HINT_ACTION::REGENERATE ) {
748 		_hintSelection = -1;
749 	}
750 	_utf8Buffer.assign( _data, _pos );
751 	if ( ( _utf8Buffer != _hintSeed ) || ( _hintContextLenght < 0 ) ) {
752 		_hintSeed.assign( _utf8Buffer );
753 		_hintContextLenght = context_length();
754 		_hintColor = Replxx::Color::GRAY;
755 		IOModeGuard ioModeGuard( _terminal );
756 		_hintsCache = call_hinter( _utf8Buffer.get(), _hintContextLenght, _hintColor );
757 	}
758 	int hintCount( static_cast<int>( _hintsCache.size() ) );
759 	if ( hintCount == 1 ) {
760 		_hint = _hintsCache.front();
761 		len = _hint.length() - _hintContextLenght;
762 		if ( len > 0 ) {
763 			set_color( _hintColor );
764 			for ( int i( 0 ); i < len; ++ i ) {
765 				_display.push_back( _hint[i + _hintContextLenght] );
766 			}
767 			set_color( Replxx::Color::DEFAULT );
768 		}
769 	} else if ( ( _maxHintRows > 0 ) && ( hintCount > 0 ) ) {
770 		int startCol( _prompt.indentation() + _pos );
771 		int maxCol( _prompt.screen_columns() );
772 #ifdef _WIN32
773 		-- maxCol;
774 #endif
775 		if ( _hintSelection < -1 ) {
776 			_hintSelection = hintCount - 1;
777 		} else if ( _hintSelection >= hintCount ) {
778 			_hintSelection = -1;
779 		}
780 		if ( _hintSelection != -1 ) {
781 			_hint = _hintsCache[_hintSelection];
782 			len = min<int>( _hint.length(), maxCol - startCol );
783 			if ( _hintContextLenght < len ) {
784 				set_color( _hintColor );
785 				for ( int i( _hintContextLenght ); i < len; ++ i ) {
786 					_display.push_back( _hint[i] );
787 				}
788 				set_color( Replxx::Color::DEFAULT );
789 			}
790 		}
791 		startCol -= _hintContextLenght;
792 		for ( int hintRow( 0 ); hintRow < min( hintCount, _maxHintRows ); ++ hintRow ) {
793 #ifdef _WIN32
794 			_display.push_back( '\r' );
795 #endif
796 			_display.push_back( '\n' );
797 			int col( 0 );
798 			for ( int i( 0 ); ( i < startCol ) && ( col < maxCol ); ++ i, ++ col ) {
799 				_display.push_back( ' ' );
800 			}
801 			set_color( _hintColor );
802 			for ( int i( _pos - _hintContextLenght ); ( i < _pos ) && ( col < maxCol ); ++ i, ++ col ) {
803 				_display.push_back( _data[i] );
804 			}
805 			int hintNo( hintRow + _hintSelection + 1 );
806 			if ( hintNo == hintCount ) {
807 				continue;
808 			} else if ( hintNo > hintCount ) {
809 				-- hintNo;
810 			}
811 			UnicodeString const& h( _hintsCache[hintNo % hintCount] );
812 			for ( int i( _hintContextLenght ); ( i < h.length() ) && ( col < maxCol ); ++ i, ++ col ) {
813 				_display.push_back( h[i] );
814 			}
815 			set_color( Replxx::Color::DEFAULT );
816 		}
817 	}
818 	return ( len );
819 }
820 
matching_paren(void)821 Replxx::ReplxxImpl::paren_info_t Replxx::ReplxxImpl::matching_paren( void ) {
822 	if (_pos >= _data.length()) {
823 		return ( paren_info_t{ -1, false } );
824 	}
825 	/* this scans for a brace matching _data[_pos] to highlight */
826 	unsigned char part1, part2;
827 	int scanDirection = 0;
828 	if ( strchr("}])", _data[_pos]) ) {
829 		scanDirection = -1; /* backwards */
830 		if (_data[_pos] == '}') {
831 			part1 = '}'; part2 = '{';
832 		} else if (_data[_pos] == ']') {
833 			part1 = ']'; part2 = '[';
834 		} else {
835 			part1 = ')'; part2 = '(';
836 		}
837 	} else if ( strchr("{[(", _data[_pos]) ) {
838 		scanDirection = 1; /* forwards */
839 		if (_data[_pos] == '{') {
840 			//part1 = '{'; part2 = '}';
841 			part1 = '}'; part2 = '{';
842 		} else if (_data[_pos] == '[') {
843 			//part1 = '['; part2 = ']';
844 			part1 = ']'; part2 = '[';
845 		} else {
846 			//part1 = '('; part2 = ')';
847 			part1 = ')'; part2 = '(';
848 		}
849 	} else {
850 		return ( paren_info_t{ -1, false } );
851 	}
852 	int highlightIdx = -1;
853 	bool indicateError = false;
854 	int unmatched = scanDirection;
855 	int unmatchedOther = 0;
856 	for (int i = _pos + scanDirection; i >= 0 && i < _data.length(); i += scanDirection) {
857 		/* TODO: the right thing when inside a string */
858 		if (strchr("}])", _data[i])) {
859 			if (_data[i] == part1) {
860 				--unmatched;
861 			} else {
862 				--unmatchedOther;
863 			}
864 		} else if (strchr("{[(", _data[i])) {
865 			if (_data[i] == part2) {
866 				++unmatched;
867 			} else {
868 				++unmatchedOther;
869 			}
870 		}
871 
872 		if (unmatched == 0) {
873 			highlightIdx = i;
874 			indicateError = (unmatchedOther != 0);
875 			break;
876 		}
877 	}
878 	return ( paren_info_t{ highlightIdx, indicateError } );
879 }
880 
881 /**
882  * Refresh the user's input line: the prompt is already onscreen and is not
883  * redrawn here screen position
884  */
refresh_line(HINT_ACTION hintAction_)885 void Replxx::ReplxxImpl::refresh_line( HINT_ACTION hintAction_ ) {
886 	int long long now( now_us() );
887 	int long long duration( now - _lastRefreshTime );
888 	if ( duration < RAPID_REFRESH_US ) {
889 		_lastRefreshTime = now;
890 		_refreshSkipped = true;
891 		return;
892 	}
893 	_refreshSkipped = false;
894 	// check for a matching brace/bracket/paren, remember its position if found
895 	render( hintAction_ );
896 	int hintLen( handle_hints( hintAction_ ) );
897 	// calculate the position of the end of the input line
898 	int xEndOfInput( 0 ), yEndOfInput( 0 );
899 	calculate_screen_position(
900 		_prompt.indentation(), 0, _prompt.screen_columns(),
901 		calculate_displayed_length( _data.get(), _data.length() ) + hintLen,
902 		xEndOfInput, yEndOfInput
903 	);
904 	yEndOfInput += static_cast<int>( count( _display.begin(), _display.end(), '\n' ) );
905 
906 	// calculate the desired position of the cursor
907 	int xCursorPos( 0 ), yCursorPos( 0 );
908 	calculate_screen_position(
909 		_prompt.indentation(), 0, _prompt.screen_columns(),
910 		calculate_displayed_length( _data.get(), _pos ),
911 		xCursorPos, yCursorPos
912 	);
913 
914 	// position at the end of the prompt, clear to end of previous input
915 	_terminal.set_cursor_visible( false );
916 	_terminal.jump_cursor(
917 		_prompt.indentation(), // 0-based on Win32
918 		-( _prompt._cursorRowOffset - _prompt._extraLines )
919 	);
920 	// display the input line
921 	_terminal.write32( _display.data(), _displayInputLength );
922 	_terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
923 	_terminal.write32( _display.data() + _displayInputLength, static_cast<int>( _display.size() ) - _displayInputLength );
924 #ifndef _WIN32
925 	// we have to generate our own newline on line wrap
926 	if ( ( xEndOfInput == 0 ) && ( yEndOfInput > 0 ) ) {
927 		_terminal.write8( "\n", 1 );
928 	}
929 #endif
930 	// position the cursor
931 	_terminal.jump_cursor( xCursorPos, -( yEndOfInput - yCursorPos ) );
932 	_terminal.set_cursor_visible( true );
933 	_prompt._cursorRowOffset = _prompt._extraLines + yCursorPos; // remember row for next pass
934 	_lastRefreshTime = now_us();
935 }
936 
context_length()937 int Replxx::ReplxxImpl::context_length() {
938 	int prefixLength = _pos;
939 	while ( prefixLength > 0 ) {
940 		if ( is_word_break_character<false>( _data[prefixLength - 1] ) ) {
941 			break;
942 		}
943 		-- prefixLength;
944 	}
945 	return ( _pos - prefixLength );
946 }
947 
repaint(void)948 void Replxx::ReplxxImpl::repaint( void ) {
949 	_prompt.write();
950 	for ( int i( _prompt._extraLines ); i < _prompt._cursorRowOffset; ++ i ) {
951 		_terminal.write8( "\n", 1 );
952 	}
953 	refresh_line( HINT_ACTION::SKIP );
954 }
955 
clear_self_to_end_of_screen(Prompt const * prompt_)956 void Replxx::ReplxxImpl::clear_self_to_end_of_screen( Prompt const* prompt_ ) {
957 	// position at the start of the prompt, clear to end of previous input
958 	_terminal.jump_cursor( 0, prompt_ ? -prompt_->_cursorRowOffset : -_prompt._cursorRowOffset );
959 	_terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
960 	return;
961 }
962 
963 namespace {
longest_common_prefix(Replxx::ReplxxImpl::completions_t const & completions)964 int longest_common_prefix( Replxx::ReplxxImpl::completions_t const& completions ) {
965 	int completionsCount( static_cast<int>( completions.size() ) );
966 	if ( completionsCount < 1 ) {
967 		return ( 0 );
968 	}
969 	int longestCommonPrefix( 0 );
970 	UnicodeString const& sample( completions.front().text() );
971 	while ( true ) {
972 		if ( longestCommonPrefix >= sample.length() ) {
973 			return ( longestCommonPrefix );
974 		}
975 		char32_t sc( sample[longestCommonPrefix] );
976 		for ( int i( 1 ); i < completionsCount; ++ i ) {
977 			UnicodeString const& candidate( completions[i].text() );
978 			if ( longestCommonPrefix >= candidate.length() ) {
979 				return ( longestCommonPrefix );
980 			}
981 			char32_t cc( candidate[longestCommonPrefix] );
982 			if ( cc != sc ) {
983 				return ( longestCommonPrefix );
984 			}
985 		}
986 		++ longestCommonPrefix;
987 	}
988 }
989 }
990 
991 /**
992  * Handle command completion, using a completionCallback() routine to provide
993  * possible substitutions
994  * This routine handles the mechanics of updating the user's input buffer with
995  * possible replacement of text as the user selects a proposed completion string,
996  * or cancels the completion attempt.
997  * @param pi - Prompt struct holding information about the prompt and our
998  * screen position
999  */
do_complete_line(bool showCompletions_)1000 char32_t Replxx::ReplxxImpl::do_complete_line( bool showCompletions_ ) {
1001 	char32_t c = 0;
1002 
1003 	// completionCallback() expects a parsable entity, so find the previous break
1004 	// character and
1005 	// extract a copy to parse.	we also handle the case where tab is hit while
1006 	// not at end-of-line.
1007 
1008 	_utf8Buffer.assign( _data, _pos );
1009 	// get a list of completions
1010 	_completionSelection = -1;
1011 	_completionContextLength = context_length();
1012 	/* IOModeGuard scope */ {
1013 		IOModeGuard ioModeGuard( _terminal );
1014 		_completions = call_completer( _utf8Buffer.get(), _completionContextLength );
1015 	}
1016 
1017 	// if no completions, we are done
1018 	if ( _completions.empty() ) {
1019 		beep();
1020 		return 0;
1021 	}
1022 
1023 	// at least one completion
1024 	int longestCommonPrefix = 0;
1025 	int completionsCount( static_cast<int>( _completions.size() ) );
1026 	int selectedCompletion( 0 );
1027 	if ( _hintSelection != -1 ) {
1028 		selectedCompletion = _hintSelection;
1029 		completionsCount = 1;
1030 	}
1031 	if ( completionsCount == 1 ) {
1032 		longestCommonPrefix = static_cast<int>( _completions[selectedCompletion].text().length() );
1033 	} else {
1034 		longestCommonPrefix = longest_common_prefix( _completions );
1035 	}
1036 	if ( _beepOnAmbiguousCompletion && ( completionsCount != 1 ) ) { // beep if ambiguous
1037 		beep();
1038 	}
1039 
1040 	// if we can extend the item, extend it and return to main loop
1041 	if ( ( longestCommonPrefix > _completionContextLength ) || ( completionsCount == 1 ) ) {
1042 		_pos -= _completionContextLength;
1043 		_data.erase( _pos, _completionContextLength );
1044 		_data.insert( _pos, _completions[selectedCompletion].text(), 0, longestCommonPrefix );
1045 		_pos = _pos + longestCommonPrefix;
1046 		_completionContextLength = longestCommonPrefix;
1047 		refresh_line();
1048 		return 0;
1049 	}
1050 
1051 	if ( ! showCompletions_ ) {
1052 		return ( 0 );
1053 	}
1054 
1055 	if ( _doubleTabCompletion ) {
1056 		// we can't complete any further, wait for second tab
1057 		do {
1058 			c = read_char();
1059 		} while ( c == static_cast<char32_t>( -1 ) );
1060 
1061 		// if any character other than tab, pass it to the main loop
1062 		if ( c != Replxx::KEY::TAB ) {
1063 			return c;
1064 		}
1065 	}
1066 
1067 	// we got a second tab, maybe show list of possible completions
1068 	bool showCompletions = true;
1069 	bool onNewLine = false;
1070 	if ( static_cast<int>( _completions.size() ) > _completionCountCutoff ) {
1071 		int savePos = _pos; // move cursor to EOL to avoid overwriting the command line
1072 		_pos = _data.length();
1073 		refresh_line();
1074 		_pos = savePos;
1075 		printf( "\nDisplay all %u possibilities? (y or n)", static_cast<unsigned int>( _completions.size() ) );
1076 		fflush(stdout);
1077 		onNewLine = true;
1078 		while (c != 'y' && c != 'Y' && c != 'n' && c != 'N' && c != Replxx::KEY::control('C')) {
1079 			do {
1080 				c = read_char();
1081 			} while (c == static_cast<char32_t>(-1));
1082 		}
1083 		switch (c) {
1084 			case 'n':
1085 			case 'N':
1086 				showCompletions = false;
1087 				break;
1088 			case Replxx::KEY::control('C'):
1089 				showCompletions = false;
1090 				// Display the ^C we got
1091 				_terminal.write8( "^C", 2 );
1092 				c = 0;
1093 				break;
1094 		}
1095 	}
1096 
1097 	// if showing the list, do it the way readline does it
1098 	bool stopList( false );
1099 	if ( showCompletions ) {
1100 		int longestCompletion( 0 );
1101 		for ( size_t j( 0 ); j < _completions.size(); ++ j ) {
1102 			int itemLength( static_cast<int>( _completions[j].text().length() ) );
1103 			if ( itemLength > longestCompletion ) {
1104 				longestCompletion = itemLength;
1105 			}
1106 		}
1107 		longestCompletion += 2;
1108 		int columnCount = _prompt.screen_columns() / longestCompletion;
1109 		if ( columnCount < 1 ) {
1110 			columnCount = 1;
1111 		}
1112 		if ( ! onNewLine ) {  // skip this if we showed "Display all %d possibilities?"
1113 			int savePos = _pos; // move cursor to EOL to avoid overwriting the command line
1114 			_pos = _data.length();
1115 			refresh_line( HINT_ACTION::TRIM );
1116 			_pos = savePos;
1117 		} else {
1118 			_terminal.clear_screen( Terminal::CLEAR_SCREEN::TO_END );
1119 		}
1120 		size_t pauseRow = _terminal.get_screen_rows() - 1;
1121 		size_t rowCount = (_completions.size() + columnCount - 1) / columnCount;
1122 		for (size_t row = 0; row < rowCount; ++row) {
1123 			if (row == pauseRow) {
1124 				printf("\n--More--");
1125 				fflush(stdout);
1126 				c = 0;
1127 				bool doBeep = false;
1128 				while (c != ' ' && c != Replxx::KEY::ENTER && c != 'y' && c != 'Y' &&
1129 							 c != 'n' && c != 'N' && c != 'q' && c != 'Q' &&
1130 							 c != Replxx::KEY::control('C')) {
1131 					if (doBeep) {
1132 						beep();
1133 					}
1134 					doBeep = true;
1135 					do {
1136 						c = read_char();
1137 					} while (c == static_cast<char32_t>(-1));
1138 				}
1139 				switch (c) {
1140 					case ' ':
1141 					case 'y':
1142 					case 'Y':
1143 						printf("\r				\r");
1144 						pauseRow += _terminal.get_screen_rows() - 1;
1145 						break;
1146 					case Replxx::KEY::ENTER:
1147 						printf("\r				\r");
1148 						++pauseRow;
1149 						break;
1150 					case 'n':
1151 					case 'N':
1152 					case 'q':
1153 					case 'Q':
1154 						printf("\r				\r");
1155 						stopList = true;
1156 						break;
1157 					case Replxx::KEY::control('C'):
1158 						// Display the ^C we got
1159 						_terminal.write8( "^C", 2 );
1160 						stopList = true;
1161 						break;
1162 				}
1163 			} else {
1164 				_terminal.write8( "\n", 1 );
1165 			}
1166 			if (stopList) {
1167 				break;
1168 			}
1169 			static UnicodeString const res( ansi_color( Replxx::Color::DEFAULT ) );
1170 			for (int column = 0; column < columnCount; ++column) {
1171 				size_t index = (column * rowCount) + row;
1172 				if ( index < _completions.size() ) {
1173 					Completion const& c( _completions[index] );
1174 					int itemLength = static_cast<int>(c.text().length());
1175 					fflush(stdout);
1176 
1177 					if ( longestCommonPrefix > 0 ) {
1178 						static UnicodeString const col( ansi_color( Replxx::Color::BRIGHTMAGENTA ) );
1179 						if (!_noColor) {
1180 							_terminal.write32(col.get(), col.length());
1181 						}
1182 						_terminal.write32(&_data[_pos - _completionContextLength], longestCommonPrefix);
1183 						if (!_noColor) {
1184 							_terminal.write32(res.get(), res.length());
1185 						}
1186 					}
1187 
1188 					if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) {
1189 						UnicodeString ac( ansi_color( c.color() ) );
1190 						_terminal.write32( ac.get(), ac.length() );
1191 					}
1192 					_terminal.write32( c.text().get() + longestCommonPrefix, itemLength - longestCommonPrefix );
1193 					if ( !_noColor && ( c.color() != Replxx::Color::DEFAULT ) ) {
1194 						_terminal.write32( res.get(), res.length() );
1195 					}
1196 
1197 					if ( ((column + 1) * rowCount) + row < _completions.size() ) {
1198 						for ( int k( itemLength ); k < longestCompletion; ++k ) {
1199 							printf( " " );
1200 						}
1201 					}
1202 				}
1203 			}
1204 		}
1205 		fflush(stdout);
1206 	}
1207 
1208 	// display the prompt on a new line, then redisplay the input buffer
1209 	if (!stopList || c == Replxx::KEY::control('C')) {
1210 		_terminal.write8( "\n", 1 );
1211 	}
1212 	_prompt.write();
1213 	_prompt._cursorRowOffset = _prompt._extraLines;
1214 	refresh_line();
1215 	return 0;
1216 }
1217 
get_input_line(void)1218 int Replxx::ReplxxImpl::get_input_line( void ) {
1219 	// The latest history entry is always our current buffer
1220 	if ( _data.length() > 0 ) {
1221 		_history.add( _data );
1222 	} else {
1223 		_history.add( UnicodeString() );
1224 	}
1225 	_history.jump( false, false );
1226 
1227 	// display the prompt
1228 	_prompt.write();
1229 
1230 	// the cursor starts out at the end of the prompt
1231 	_prompt._cursorRowOffset = _prompt._extraLines;
1232 
1233 	// kill and yank start in "other" mode
1234 	_killRing.lastAction = KillRing::actionOther;
1235 
1236 	// if there is already text in the buffer, display it first
1237 	if (_data.length() > 0) {
1238 		refresh_line();
1239 	}
1240 
1241 	// loop collecting characters, respond to line editing characters
1242 	Replxx::ACTION_RESULT next( Replxx::ACTION_RESULT::CONTINUE );
1243 	while ( next == Replxx::ACTION_RESULT::CONTINUE ) {
1244 		int c( read_char( HINT_ACTION::REPAINT ) ); // get a new keystroke
1245 
1246 		if (c == 0) {
1247 			return _data.length();
1248 		}
1249 
1250 		if (c == -1) {
1251 			refresh_line();
1252 			continue;
1253 		}
1254 
1255 		if (c == -2) {
1256 			_prompt.write();
1257 			refresh_line();
1258 			continue;
1259 		}
1260 
1261 		key_press_handlers_t::iterator it( _keyPressHandlers.find( c ) );
1262 		if ( it != _keyPressHandlers.end() ) {
1263 			next = it->second( c );
1264 			if ( _modifiedState ) {
1265 				refresh_line();
1266 			}
1267 		} else {
1268 			next = action( RESET_KILL_ACTION | HISTORY_RECALL_MOST_RECENT, &Replxx::ReplxxImpl::insert_character, c );
1269 		}
1270 	}
1271 	return ( next == Replxx::ACTION_RESULT::RETURN ? _data.length() : -1 );
1272 }
1273 
action(action_trait_t actionTrait_,key_press_handler_raw_t const & handler_,char32_t code_)1274 Replxx::ACTION_RESULT Replxx::ReplxxImpl::action( action_trait_t actionTrait_, key_press_handler_raw_t const& handler_, char32_t code_ ) {
1275 	Replxx::ACTION_RESULT res( ( this->*handler_ )( code_ ) );
1276 	call_modify_callback();
1277 	if ( actionTrait_ & HISTORY_RECALL_MOST_RECENT ) {
1278 		_history.reset_recall_most_recent();
1279 	}
1280 	if ( actionTrait_ & RESET_KILL_ACTION ) {
1281 		_killRing.lastAction = KillRing::actionOther;
1282 	}
1283 	if ( actionTrait_ & SET_KILL_ACTION ) {
1284 		_killRing.lastAction = KillRing::actionKill;
1285 	}
1286 	if ( ! ( actionTrait_ & DONT_RESET_PREFIX ) ) {
1287 		_prefix = _pos;
1288 	}
1289 	if ( ! ( actionTrait_ & DONT_RESET_COMPLETIONS ) ) {
1290 		_completions.clear();
1291 		_completionSelection = -1;
1292 		_completionContextLength = 0;
1293 	}
1294 	if ( ! ( actionTrait_ & DONT_RESET_HIST_YANK_INDEX ) ) {
1295 		_history.reset_yank_iterator();
1296 	}
1297 	if ( actionTrait_ & WANT_REFRESH ) {
1298 		_modifiedState = true;
1299 	}
1300 	return ( res );
1301 }
1302 
insert_character(char32_t c)1303 Replxx::ACTION_RESULT Replxx::ReplxxImpl::insert_character( char32_t c ) {
1304 	/*
1305 	 * beep on unknown Ctrl and/or Meta keys
1306 	 * don't insert control characters
1307 	 */
1308 	if ( ( c >= static_cast<int>( Replxx::KEY::BASE ) ) || ( is_control_code( c ) && ( c != '\n' ) ) ) {
1309 		beep();
1310 		return ( Replxx::ACTION_RESULT::CONTINUE );
1311 	}
1312 	if ( ! _overwrite || ( _pos >= _data.length() ) ) {
1313 		_data.insert( _pos, c );
1314 	} else {
1315 		_data[_pos] = c;
1316 	}
1317 	++ _pos;
1318 	call_modify_callback();
1319 	int long long now( now_us() );
1320 	int long long duration( now - _lastRefreshTime );
1321 	if ( duration < RAPID_REFRESH_US ) {
1322 		_lastRefreshTime = now;
1323 		_refreshSkipped = true;
1324 		return ( Replxx::ACTION_RESULT::CONTINUE );
1325 	}
1326 	int inputLen = calculate_displayed_length( _data.get(), _data.length() );
1327 	if (
1328 		( _pos == _data.length() )
1329 		&& ! _modifiedState
1330 		&& ( _noColor || ! ( !! _highlighterCallback || !! _hintCallback ) )
1331 		&& ( _prompt.indentation() + inputLen < _prompt.screen_columns() )
1332 	) {
1333 		/* Avoid a full assign of the line in the
1334 		 * trivial case. */
1335 		render( c );
1336 		_displayInputLength = static_cast<int>( _display.size() );
1337 		_terminal.write32( reinterpret_cast<char32_t*>( &c ), 1 );
1338 	} else {
1339 		refresh_line();
1340 	}
1341 	_lastRefreshTime = now_us();
1342 	return ( Replxx::ACTION_RESULT::CONTINUE );
1343 }
1344 
1345 // ctrl-J/linefeed/newline
new_line(char32_t)1346 Replxx::ACTION_RESULT Replxx::ReplxxImpl::new_line( char32_t ) {
1347 	return ( insert_character( '\n' ) );
1348 }
1349 
1350 // ctrl-A, HOME: move cursor to start of line
go_to_begining_of_line(char32_t)1351 Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_begining_of_line( char32_t ) {
1352 	_pos = 0;
1353 	return ( Replxx::ACTION_RESULT::CONTINUE );
1354 }
1355 
go_to_end_of_line(char32_t)1356 Replxx::ACTION_RESULT Replxx::ReplxxImpl::go_to_end_of_line( char32_t ) {
1357 	_pos = _data.length();
1358 	return ( Replxx::ACTION_RESULT::CONTINUE );
1359 }
1360 
1361 // ctrl-B, move cursor left by one character
move_one_char_left(char32_t)1362 Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_left( char32_t ) {
1363 	if (_pos > 0) {
1364 		--_pos;
1365 		refresh_line();
1366 	}
1367 	return ( Replxx::ACTION_RESULT::CONTINUE );
1368 }
1369 
1370 // ctrl-F, move cursor right by one character
move_one_char_right(char32_t)1371 Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_char_right( char32_t ) {
1372 	if ( _pos < _data.length() ) {
1373 		++_pos;
1374 		refresh_line();
1375 	}
1376 	return ( Replxx::ACTION_RESULT::CONTINUE );
1377 }
1378 
1379 // meta-B, move cursor left by one word
1380 template <bool subword>
move_one_word_left(char32_t)1381 Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_left( char32_t ) {
1382 	if (_pos > 0) {
1383 		while (_pos > 0 && is_word_break_character<subword>( _data[_pos - 1] ) ) {
1384 			--_pos;
1385 		}
1386 		while (_pos > 0 && !is_word_break_character<subword>( _data[_pos - 1] ) ) {
1387 			--_pos;
1388 		}
1389 		refresh_line();
1390 	}
1391 	return ( Replxx::ACTION_RESULT::CONTINUE );
1392 }
1393 
1394 // meta-f, move cursor right by one word
1395 template <bool subword>
move_one_word_right(char32_t)1396 Replxx::ACTION_RESULT Replxx::ReplxxImpl::move_one_word_right( char32_t ) {
1397 	if ( _pos < _data.length() ) {
1398 		while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
1399 			++_pos;
1400 		}
1401 		while ( _pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
1402 			++_pos;
1403 		}
1404 		refresh_line();
1405 	}
1406 	return ( Replxx::ACTION_RESULT::CONTINUE );
1407 }
1408 
1409 // meta-Backspace, kill word to left of cursor
1410 template <bool subword>
kill_word_to_left(char32_t)1411 Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_left( char32_t ) {
1412 	if ( _pos > 0 ) {
1413 		int startingPos = _pos;
1414 		while ( _pos > 0 && is_word_break_character<subword>( _data[_pos - 1] ) ) {
1415 			-- _pos;
1416 		}
1417 		while ( _pos > 0 && !is_word_break_character<subword>( _data[_pos - 1] ) ) {
1418 			-- _pos;
1419 		}
1420 		_killRing.kill( _data.get() + _pos, startingPos - _pos, false);
1421 		_data.erase( _pos, startingPos - _pos );
1422 		refresh_line();
1423 	}
1424 	return ( Replxx::ACTION_RESULT::CONTINUE );
1425 }
1426 
1427 // meta-D, kill word to right of cursor
1428 template <bool subword>
kill_word_to_right(char32_t)1429 Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_word_to_right( char32_t ) {
1430 	if ( _pos < _data.length() ) {
1431 		int endingPos = _pos;
1432 		while ( endingPos < _data.length() && is_word_break_character<subword>( _data[endingPos] ) ) {
1433 			++ endingPos;
1434 		}
1435 		while ( endingPos < _data.length() && !is_word_break_character<subword>( _data[endingPos] ) ) {
1436 			++ endingPos;
1437 		}
1438 		_killRing.kill( _data.get() + _pos, endingPos - _pos, true );
1439 		_data.erase( _pos, endingPos - _pos );
1440 		refresh_line();
1441 	}
1442 	return ( Replxx::ACTION_RESULT::CONTINUE );
1443 }
1444 
1445 // ctrl-W, kill to whitespace (not word) to left of cursor
kill_to_whitespace_to_left(char32_t)1446 Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_whitespace_to_left( char32_t ) {
1447 	if ( _pos > 0 ) {
1448 		int startingPos = _pos;
1449 		while ( ( _pos > 0 ) && isspace( _data[_pos - 1] ) ) {
1450 			--_pos;
1451 		}
1452 		while ( ( _pos > 0 ) && ! isspace( _data[_pos - 1] ) ) {
1453 			-- _pos;
1454 		}
1455 		_killRing.kill( _data.get() + _pos, startingPos - _pos, false );
1456 		_data.erase( _pos, startingPos - _pos );
1457 		refresh_line();
1458 	}
1459 	return ( Replxx::ACTION_RESULT::CONTINUE );
1460 }
1461 
1462 // ctrl-K, kill from cursor to end of line
kill_to_end_of_line(char32_t)1463 Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_end_of_line( char32_t ) {
1464 	_killRing.kill( _data.get() + _pos, _data.length() - _pos, true );
1465 	_data.erase( _pos, _data.length() - _pos );
1466 	return ( Replxx::ACTION_RESULT::CONTINUE );
1467 }
1468 
1469 // ctrl-U, kill all characters to the left of the cursor
kill_to_begining_of_line(char32_t)1470 Replxx::ACTION_RESULT Replxx::ReplxxImpl::kill_to_begining_of_line( char32_t ) {
1471 	if (_pos > 0) {
1472 		_killRing.kill( _data.get(), _pos, false );
1473 		_data.erase( 0, _pos );
1474 		_pos = 0;
1475 		refresh_line();
1476 	}
1477 	return ( Replxx::ACTION_RESULT::CONTINUE );
1478 }
1479 
1480 // ctrl-Y, yank killed text
yank(char32_t)1481 Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank( char32_t ) {
1482 	UnicodeString* restoredText( _killRing.yank() );
1483 	if ( restoredText ) {
1484 		_data.insert( _pos, *restoredText, 0, restoredText->length() );
1485 		_pos += restoredText->length();
1486 		refresh_line();
1487 		_killRing.lastAction = KillRing::actionYank;
1488 		_lastYankSize = restoredText->length();
1489 	} else {
1490 		beep();
1491 	}
1492 	return ( Replxx::ACTION_RESULT::CONTINUE );
1493 }
1494 
1495 // meta-Y, "yank-pop", rotate popped text
yank_cycle(char32_t)1496 Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_cycle( char32_t ) {
1497 	if ( _killRing.lastAction != KillRing::actionYank ) {
1498 		beep();
1499 		return ( Replxx::ACTION_RESULT::CONTINUE );
1500 	}
1501 	UnicodeString* restoredText = _killRing.yankPop();
1502 	if ( !restoredText ) {
1503 		beep();
1504 		return ( Replxx::ACTION_RESULT::CONTINUE );
1505 	}
1506 	_pos -= _lastYankSize;
1507 	_data.erase( _pos, _lastYankSize );
1508 	_data.insert( _pos, *restoredText, 0, restoredText->length() );
1509 	_pos += restoredText->length();
1510 	_lastYankSize = restoredText->length();
1511 	refresh_line();
1512 	return ( Replxx::ACTION_RESULT::CONTINUE );
1513 }
1514 
1515 // meta-., "yank-last-arg", on consecutive uses move back in history for popped text
yank_last_arg(char32_t)1516 Replxx::ACTION_RESULT Replxx::ReplxxImpl::yank_last_arg( char32_t ) {
1517 	if ( _history.size() < 2 ) {
1518 		return ( Replxx::ACTION_RESULT::CONTINUE );
1519 	}
1520 	if ( _history.next_yank_position() ) {
1521 		_lastYankSize = 0;
1522 	}
1523 	UnicodeString const& histLine( _history.yank_line() );
1524 	int endPos( histLine.length() );
1525 	while ( ( endPos > 0 ) && isspace( histLine[endPos - 1] ) ) {
1526 		-- endPos;
1527 	}
1528 	int startPos( endPos );
1529 	while ( ( startPos > 0 ) && ! isspace( histLine[startPos - 1] ) ) {
1530 		-- startPos;
1531 	}
1532 	_pos -= _lastYankSize;
1533 	_data.erase( _pos, _lastYankSize );
1534 	_lastYankSize = endPos - startPos;
1535 	_data.insert( _pos, histLine, startPos, _lastYankSize );
1536 	_pos += _lastYankSize;
1537 	refresh_line();
1538 	return ( Replxx::ACTION_RESULT::CONTINUE );
1539 }
1540 
1541 // meta-C, give word initial Cap
1542 template <bool subword>
capitalize_word(char32_t)1543 Replxx::ACTION_RESULT Replxx::ReplxxImpl::capitalize_word( char32_t ) {
1544 	if (_pos < _data.length()) {
1545 		while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
1546 			++_pos;
1547 		}
1548 		if (_pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
1549 			if ( _data[_pos] >= 'a' && _data[_pos] <= 'z' ) {
1550 				_data[_pos] += 'A' - 'a';
1551 			}
1552 			++_pos;
1553 		}
1554 		while (_pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
1555 			if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) {
1556 				_data[_pos] += 'a' - 'A';
1557 			}
1558 			++_pos;
1559 		}
1560 		refresh_line();
1561 	}
1562 	return ( Replxx::ACTION_RESULT::CONTINUE );
1563 }
1564 
1565 // meta-L, lowercase word
1566 template <bool subword>
lowercase_word(char32_t)1567 Replxx::ACTION_RESULT Replxx::ReplxxImpl::lowercase_word( char32_t ) {
1568 	if (_pos < _data.length()) {
1569 		while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
1570 			++ _pos;
1571 		}
1572 		while (_pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
1573 			if ( _data[_pos] >= 'A' && _data[_pos] <= 'Z' ) {
1574 				_data[_pos] += 'a' - 'A';
1575 			}
1576 			++ _pos;
1577 		}
1578 		refresh_line();
1579 	}
1580 	return ( Replxx::ACTION_RESULT::CONTINUE );
1581 }
1582 
1583 // meta-U, uppercase word
1584 template <bool subword>
uppercase_word(char32_t)1585 Replxx::ACTION_RESULT Replxx::ReplxxImpl::uppercase_word( char32_t ) {
1586 	if (_pos < _data.length()) {
1587 		while ( _pos < _data.length() && is_word_break_character<subword>( _data[_pos] ) ) {
1588 			++ _pos;
1589 		}
1590 		while ( _pos < _data.length() && !is_word_break_character<subword>( _data[_pos] ) ) {
1591 			if ( _data[_pos] >= 'a' && _data[_pos] <= 'z') {
1592 				_data[_pos] += 'A' - 'a';
1593 			}
1594 			++ _pos;
1595 		}
1596 		refresh_line();
1597 	}
1598 	return ( Replxx::ACTION_RESULT::CONTINUE );
1599 }
1600 
1601 // ctrl-T, transpose characters
transpose_characters(char32_t)1602 Replxx::ACTION_RESULT Replxx::ReplxxImpl::transpose_characters( char32_t ) {
1603 	if ( _pos > 0 && _data.length() > 1 ) {
1604 		size_t leftCharPos = ( _pos == _data.length() ) ? _pos - 2 : _pos - 1;
1605 		char32_t aux = _data[leftCharPos];
1606 		_data[leftCharPos] = _data[leftCharPos + 1];
1607 		_data[leftCharPos + 1] = aux;
1608 		if ( _pos != _data.length() ) {
1609 			++_pos;
1610 		}
1611 		refresh_line();
1612 	}
1613 	return ( Replxx::ACTION_RESULT::CONTINUE );
1614 }
1615 
1616 // ctrl-C, abort this line
abort_line(char32_t)1617 Replxx::ACTION_RESULT Replxx::ReplxxImpl::abort_line( char32_t ) {
1618 	errno = EAGAIN;
1619 	_history.drop_last();
1620 	// we need one last refresh with the cursor at the end of the line
1621 	// so we don't display the next prompt over the previous input line
1622 	_pos = _data.length(); // pass _data.length() as _pos for EOL
1623 	_lastRefreshTime = 0;
1624 	refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM );
1625 	_terminal.write8( "^C\r\n", 4 );
1626 	return ( Replxx::ACTION_RESULT::BAIL );
1627 }
1628 
1629 // DEL, delete the character under the cursor
delete_character(char32_t)1630 Replxx::ACTION_RESULT Replxx::ReplxxImpl::delete_character( char32_t ) {
1631 	if ( ( _data.length() > 0 ) && ( _pos < _data.length() ) ) {
1632 		_data.erase( _pos );
1633 		refresh_line();
1634 	}
1635 	return ( Replxx::ACTION_RESULT::CONTINUE );
1636 }
1637 
1638 // ctrl-D, delete the character under the cursor
1639 // on an empty line, exit the shell
send_eof(char32_t key_)1640 Replxx::ACTION_RESULT Replxx::ReplxxImpl::send_eof( char32_t key_ ) {
1641 	if ( _data.length() == 0 ) {
1642 		_history.drop_last();
1643 		return ( Replxx::ACTION_RESULT::BAIL );
1644 	}
1645 	return ( delete_character( key_ ) );
1646 }
1647 
1648 // backspace/ctrl-H, delete char to left of cursor
backspace_character(char32_t)1649 Replxx::ACTION_RESULT Replxx::ReplxxImpl::backspace_character( char32_t ) {
1650 	if ( _pos > 0 ) {
1651 		-- _pos;
1652 		_data.erase( _pos );
1653 		refresh_line();
1654 	}
1655 	return ( Replxx::ACTION_RESULT::CONTINUE );
1656 }
1657 
1658 // ctrl-M/return/enter, accept line
commit_line(char32_t)1659 Replxx::ACTION_RESULT Replxx::ReplxxImpl::commit_line( char32_t ) {
1660 	// we need one last refresh with the cursor at the end of the line
1661 	// so we don't display the next prompt over the previous input line
1662 	_pos = _data.length(); // pass _data.length() as _pos for EOL
1663 	_lastRefreshTime = 0;
1664 	refresh_line( _refreshSkipped ? HINT_ACTION::REGENERATE : HINT_ACTION::TRIM );
1665 	_history.commit_index();
1666 	_history.drop_last();
1667 	return ( Replxx::ACTION_RESULT::RETURN );
1668 }
1669 
1670 // Down, recall next line in history
history_next(char32_t)1671 Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_next( char32_t ) {
1672 	return ( history_move( false ) );
1673 }
1674 
1675 // Up, recall previous line in history
history_previous(char32_t)1676 Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_previous( char32_t ) {
1677 	return ( history_move( true ) );
1678 }
1679 
history_move(bool previous_)1680 Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_move( bool previous_ ) {
1681 	// if not already recalling, add the current line to the history list so
1682 	// we don't
1683 	// have to special case it
1684 	if ( _history.is_last() ) {
1685 		_history.update_last( _data );
1686 	}
1687 	if ( _history.is_empty() ) {
1688 		return ( Replxx::ACTION_RESULT::CONTINUE );
1689 	}
1690 	if ( ! _history.move( previous_ ) ) {
1691 		return ( Replxx::ACTION_RESULT::CONTINUE );
1692 	}
1693 	_data.assign( _history.current() );
1694 	_pos = _data.length();
1695 	refresh_line();
1696 	return ( Replxx::ACTION_RESULT::CONTINUE );
1697 }
1698 
1699 // meta-<, beginning of history
1700 // Page Up, beginning of history
history_first(char32_t)1701 Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_first( char32_t ) {
1702 	return ( history_jump( true ) );
1703 }
1704 
1705 // meta->, end of history
1706 // Page Down, end of history
history_last(char32_t)1707 Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_last( char32_t ) {
1708 	return ( history_jump( false ) );
1709 }
1710 
history_jump(bool back_)1711 Replxx::ACTION_RESULT Replxx::ReplxxImpl::history_jump( bool back_ ) {
1712 	// if not already recalling, add the current line to the history list so
1713 	// we don't
1714 	// have to special case it
1715 	if ( _history.is_last() ) {
1716 		_history.update_last( _data );
1717 	}
1718 	if ( ! _history.is_empty() ) {
1719 		_history.jump( back_ );
1720 		_data.assign( _history.current() );
1721 		_pos = _data.length();
1722 		refresh_line();
1723 	}
1724 	return ( Replxx::ACTION_RESULT::CONTINUE );
1725 }
1726 
hint_next(char32_t)1727 Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_next( char32_t ) {
1728 	return ( hint_move( false ) );
1729 }
1730 
hint_previous(char32_t)1731 Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_previous( char32_t ) {
1732 	return ( hint_move( true ) );
1733 }
1734 
hint_move(bool previous_)1735 Replxx::ACTION_RESULT Replxx::ReplxxImpl::hint_move( bool previous_ ) {
1736 	if ( ! _noColor ) {
1737 		_killRing.lastAction = KillRing::actionOther;
1738 		if ( previous_ ) {
1739 			-- _hintSelection;
1740 		} else {
1741 			++ _hintSelection;
1742 		}
1743 		refresh_line( HINT_ACTION::REPAINT );
1744 	}
1745 	return ( Replxx::ACTION_RESULT::CONTINUE );
1746 }
1747 
toggle_overwrite_mode(char32_t)1748 Replxx::ACTION_RESULT Replxx::ReplxxImpl::toggle_overwrite_mode( char32_t ) {
1749 	_overwrite = ! _overwrite;
1750 	return ( Replxx::ACTION_RESULT::CONTINUE );
1751 }
1752 
1753 #ifndef _WIN32
verbatim_insert(char32_t)1754 Replxx::ACTION_RESULT Replxx::ReplxxImpl::verbatim_insert( char32_t ) {
1755 	static int const MAX_ESC_SEQ( 32 );
1756 	char32_t buf[MAX_ESC_SEQ];
1757 	int len( _terminal.read_verbatim( buf, MAX_ESC_SEQ ) );
1758 	_data.insert( _pos, UnicodeString( buf, len ), 0, len );
1759 	_pos += len;
1760 	return ( Replxx::ACTION_RESULT::CONTINUE );
1761 }
1762 
1763 // ctrl-Z, job control
suspend(char32_t)1764 Replxx::ACTION_RESULT Replxx::ReplxxImpl::suspend( char32_t ) {
1765 	/* IOModeGuard scope */ {
1766 		IOModeGuard ioModeGuard( _terminal );
1767 		raise( SIGSTOP );   // Break out in mid-line
1768 	}
1769 	// Redraw prompt
1770 	_prompt.write();
1771 	return ( Replxx::ACTION_RESULT::CONTINUE );
1772 }
1773 #endif
1774 
complete_line(char32_t c)1775 Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_line( char32_t c ) {
1776 	if ( !! _completionCallback && ( _completeOnEmpty || ( _pos > 0 ) ) ) {
1777 		// complete_line does the actual completion and replacement
1778 		c = do_complete_line( c != 0 );
1779 
1780 		if ( static_cast<int>( c ) < 0 ) {
1781 			return ( Replxx::ACTION_RESULT::BAIL );
1782 		}
1783 		if ( c != 0 ) {
1784 			emulate_key_press( c );
1785 		}
1786 	} else {
1787 		insert_character( c );
1788 	}
1789 	return ( Replxx::ACTION_RESULT::CONTINUE );
1790 }
1791 
complete(bool previous_)1792 Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete( bool previous_ ) {
1793 	if ( _completions.empty() ) {
1794 		bool first( _completions.empty() );
1795 		int dataLen( _data.length() );
1796 		complete_line( 0 );
1797 		if ( ! _immediateCompletion && first && ( _data.length() > dataLen ) ) {
1798 			return ( Replxx::ACTION_RESULT::CONTINUE );
1799 		}
1800 	}
1801 	int newSelection( _completionSelection + ( previous_ ? -1 : 1 ) );
1802 	if ( newSelection >= static_cast<int>( _completions.size() ) ) {
1803 		newSelection = -1;
1804 	} else if ( newSelection == -2 ) {
1805 		newSelection = static_cast<int>( _completions.size() ) - 1;
1806 	}
1807 	if ( _completionSelection != -1 ) {
1808 		int oldCompletionLength( max( _completions[_completionSelection].text().length() - _completionContextLength, 0 ) );
1809 		_pos -= oldCompletionLength;
1810 		_data.erase( _pos, oldCompletionLength );
1811 	}
1812 	if ( newSelection != -1 ) {
1813 		int newCompletionLength( max( _completions[newSelection].text().length() - _completionContextLength, 0 ) );
1814 		_data.insert( _pos, _completions[newSelection].text(), _completionContextLength, newCompletionLength );
1815 		_pos += newCompletionLength;
1816 	}
1817 	_completionSelection = newSelection;
1818 	refresh_line();  // Refresh the line
1819 	return ( Replxx::ACTION_RESULT::CONTINUE );
1820 }
1821 
complete_next(char32_t)1822 Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_next( char32_t ) {
1823 	return ( complete( false ) );
1824 }
1825 
complete_previous(char32_t)1826 Replxx::ACTION_RESULT Replxx::ReplxxImpl::complete_previous( char32_t ) {
1827 	return ( complete( true ) );
1828 }
1829 
1830 // Alt-P, reverse history search for prefix
1831 // Alt-P, reverse history search for prefix
1832 // Alt-N, forward history search for prefix
1833 // Alt-N, forward history search for prefix
common_prefix_search(char32_t startChar)1834 Replxx::ACTION_RESULT Replxx::ReplxxImpl::common_prefix_search( char32_t startChar ) {
1835 	int prefixSize( calculate_displayed_length( _data.get(), _prefix ) );
1836 	if (
1837 		_history.common_prefix_search(
1838 			_data, prefixSize, ( startChar == ( Replxx::KEY::meta( 'p' ) ) ) || ( startChar == ( Replxx::KEY::meta( 'P' ) ) )
1839 		)
1840 	) {
1841 		_data.assign( _history.current() );
1842 		_pos = _data.length();
1843 		refresh_line();
1844 	}
1845 	return ( Replxx::ACTION_RESULT::CONTINUE );
1846 }
1847 
1848 // ctrl-R, reverse history search
1849 // ctrl-S, forward history search
1850 /**
1851  * Incremental history search -- take over the prompt and keyboard as the user
1852  * types a search string, deletes characters from it, changes _direction,
1853  * and either accepts the found line (for execution orediting) or cancels.
1854  * @param startChar - the character that began the search, used to set the initial
1855  * _direction
1856  */
incremental_history_search(char32_t startChar)1857 Replxx::ACTION_RESULT Replxx::ReplxxImpl::incremental_history_search( char32_t startChar ) {
1858 	// if not already recalling, add the current line to the history list so we
1859 	// don't have to special case it
1860 	if ( _history.is_last() ) {
1861 		_history.update_last( _data );
1862 	}
1863 	_history.save_pos();
1864 	int historyLinePosition( _pos );
1865 	clear_self_to_end_of_screen();
1866 
1867 	DynamicPrompt dp( _terminal, (startChar == Replxx::KEY::control('R')) ? -1 : 1 );
1868 
1869 	// draw user's text with our prompt
1870 	dynamicRefresh(_prompt, dp, _data.get(), _data.length(), historyLinePosition);
1871 
1872 	// loop until we get an exit character
1873 	char32_t c( 0 );
1874 	bool keepLooping = true;
1875 	bool useSearchedLine = true;
1876 	bool searchAgain = false;
1877 	UnicodeString activeHistoryLine;
1878 	while ( keepLooping ) {
1879 		c = read_char();
1880 
1881 		switch (c) {
1882 			// these characters keep the selected text but do not execute it
1883 			case Replxx::KEY::control('A'): // ctrl-A, move cursor to start of line
1884 			case Replxx::KEY::HOME:
1885 			case Replxx::KEY::control('B'): // ctrl-B, move cursor left by one character
1886 			case Replxx::KEY::LEFT:
1887 			case Replxx::KEY::meta( 'b' ): // meta-B, move cursor left by one word
1888 			case Replxx::KEY::meta( 'B' ):
1889 			case Replxx::KEY::control( Replxx::KEY::LEFT ):
1890 			case Replxx::KEY::meta( Replxx::KEY::LEFT ): // Emacs allows Meta, bash & readline don't
1891 			case Replxx::KEY::control('D'):
1892 			case Replxx::KEY::meta( 'd' ): // meta-D, kill word to right of cursor
1893 			case Replxx::KEY::meta( 'D' ):
1894 			case Replxx::KEY::control('E'): // ctrl-E, move cursor to end of line
1895 			case Replxx::KEY::END:
1896 			case Replxx::KEY::control('F'): // ctrl-F, move cursor right by one character
1897 			case Replxx::KEY::RIGHT:
1898 			case Replxx::KEY::meta( 'f' ): // meta-F, move cursor right by one word
1899 			case Replxx::KEY::meta( 'F' ):
1900 			case Replxx::KEY::control( Replxx::KEY::RIGHT ):
1901 			case Replxx::KEY::meta( Replxx::KEY::RIGHT ): // Emacs allows Meta, bash & readline don't
1902 			case Replxx::KEY::meta( Replxx::KEY::BACKSPACE ):
1903 			case Replxx::KEY::control('J'):
1904 			case Replxx::KEY::control('K'): // ctrl-K, kill from cursor to end of line
1905 			case Replxx::KEY::ENTER:
1906 			case Replxx::KEY::control('N'): // ctrl-N, recall next line in history
1907 			case Replxx::KEY::control('P'): // ctrl-P, recall previous line in history
1908 			case Replxx::KEY::DOWN:
1909 			case Replxx::KEY::UP:
1910 			case Replxx::KEY::control('T'): // ctrl-T, transpose characters
1911 			case Replxx::KEY::control('U'): // ctrl-U, kill all characters to the left of the cursor
1912 			case Replxx::KEY::control('W'):
1913 			case Replxx::KEY::meta( 'y' ): // meta-Y, "yank-pop", rotate popped text
1914 			case Replxx::KEY::meta( 'Y' ):
1915 			case 127:
1916 			case Replxx::KEY::DELETE:
1917 			case Replxx::KEY::meta( '<' ): // start of history
1918 			case Replxx::KEY::PAGE_UP:
1919 			case Replxx::KEY::meta( '>' ): // end of history
1920 			case Replxx::KEY::PAGE_DOWN: {
1921 				keepLooping = false;
1922 			} break;
1923 
1924 			// these characters revert the input line to its previous state
1925 			case Replxx::KEY::control('C'): // ctrl-C, abort this line
1926 			case Replxx::KEY::control('G'):
1927 			case Replxx::KEY::control('L'): { // ctrl-L, clear screen and redisplay line
1928 				keepLooping = false;
1929 				useSearchedLine = false;
1930 				if (c != Replxx::KEY::control('L')) {
1931 					c = -1; // ctrl-C and ctrl-G just abort the search and do nothing else
1932 				}
1933 			} break;
1934 
1935 			// these characters stay in search mode and assign the display
1936 			case Replxx::KEY::control('S'):
1937 			case Replxx::KEY::control('R'): {
1938 				if ( dp._searchText.length() == 0 ) { // if no current search text, recall previous text
1939 					if ( _previousSearchText.length() > 0 ) {
1940 						dp._searchText = _previousSearchText;
1941 					}
1942 				}
1943 				if (
1944 					( ( dp._direction == 1 ) && ( c == Replxx::KEY::control( 'R' ) ) )
1945 					|| ( ( dp._direction == -1 ) && ( c == Replxx::KEY::control( 'S' ) ) )
1946 				) {
1947 					dp._direction = 0 - dp._direction; // reverse direction
1948 					dp.updateSearchPrompt();           // change the prompt
1949 				} else {
1950 					searchAgain = true; // same direction, search again
1951 				}
1952 			} break;
1953 
1954 // job control is its own thing
1955 #ifndef _WIN32
1956 			case Replxx::KEY::control('Z'): { // ctrl-Z, job control
1957 				/* IOModeGuard scope */ {
1958 					IOModeGuard ioModeGuard( _terminal );
1959 					// Returning to Linux (whatever) shell, leave raw mode
1960 					// Break out in mid-line
1961 					// Back from Linux shell, re-enter raw mode
1962 					raise( SIGSTOP );
1963 				}
1964 				dynamicRefresh( dp, dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition );
1965 				continue;
1966 			} break;
1967 #endif
1968 
1969 			// these characters assign the search string, and hence the selected input line
1970 			case Replxx::KEY::BACKSPACE: { // backspace/ctrl-H, delete char to left of cursor
1971 				if ( dp._searchText.length() > 0 ) {
1972 					dp._searchText.erase( dp._searchText.length() - 1 );
1973 					dp.updateSearchPrompt();
1974 					_history.restore_pos();
1975 					historyLinePosition = _pos;
1976 				} else {
1977 					beep();
1978 				}
1979 			} break;
1980 
1981 			case Replxx::KEY::control('Y'): { // ctrl-Y, yank killed text
1982 			} break;
1983 
1984 			default: {
1985 				if ( ! is_control_code( c ) && ( c < static_cast<int>( Replxx::KEY::BASE ) ) ) { // not an action character
1986 					dp._searchText.insert( dp._searchText.length(), c );
1987 					dp.updateSearchPrompt();
1988 				} else {
1989 					beep();
1990 				}
1991 			}
1992 		} // switch
1993 
1994 		// if we are staying in search mode, search now
1995 		if ( ! keepLooping ) {
1996 			break;
1997 		}
1998 		activeHistoryLine.assign( _history.current() );
1999 		if ( dp._searchText.length() > 0 ) {
2000 			bool found = false;
2001 			int lineSearchPos = historyLinePosition;
2002 			if ( searchAgain ) {
2003 				lineSearchPos += dp._direction;
2004 			}
2005 			searchAgain = false;
2006 			while ( true ) {
2007 				while (
2008 					dp._direction < 0
2009 						? ( lineSearchPos >= 0 )
2010 						: ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() )
2011 				) {
2012 					if (
2013 						( lineSearchPos >= 0 )
2014 						&& ( ( lineSearchPos + dp._searchText.length() ) <= activeHistoryLine.length() )
2015 						&& std::equal( dp._searchText.begin(), dp._searchText.end(), activeHistoryLine.begin() + lineSearchPos )
2016 					) {
2017 						found = true;
2018 						break;
2019 					}
2020 					lineSearchPos += dp._direction;
2021 				}
2022 				if ( found ) {
2023 					historyLinePosition = lineSearchPos;
2024 					break;
2025 				} else if ( _history.move( dp._direction < 0 ) ) {
2026 					activeHistoryLine.assign( _history.current() );
2027 					lineSearchPos = ( dp._direction > 0 ) ? 0 : ( activeHistoryLine.length() - dp._searchText.length() );
2028 				} else {
2029 					historyLinePosition = _pos;
2030 					beep();
2031 					break;
2032 				}
2033 			} // while
2034 			if ( ! found ) {
2035 				_history.restore_pos();
2036 			}
2037 		} else {
2038 			_history.restore_pos();
2039 			historyLinePosition = _pos;
2040 		}
2041 		activeHistoryLine.assign( _history.current() );
2042 		dynamicRefresh( dp, dp, activeHistoryLine.get(), activeHistoryLine.length(), historyLinePosition ); // draw user's text with our prompt
2043 	} // while
2044 
2045 	// leaving history search, restore previous prompt, maybe make searched line
2046 	// current
2047 	Prompt pb( _terminal );
2048 	UnicodeString tempUnicode( &_prompt._text[_prompt._lastLinePosition], _prompt._text.length() - _prompt._lastLinePosition );
2049 	pb.set_text( tempUnicode );
2050 	pb.update_screen_columns();
2051 	if ( useSearchedLine && ( activeHistoryLine.length() > 0 ) ) {
2052 		_history.commit_index();
2053 		_data.assign( activeHistoryLine );
2054 		_pos = historyLinePosition;
2055 		_modifiedState = true;
2056 	} else if ( ! useSearchedLine ) {
2057 		_history.restore_pos();
2058 	}
2059 	dynamicRefresh(pb, _prompt, _data.get(), _data.length(), _pos); // redraw the original prompt with current input
2060 	_previousSearchText = dp._searchText; // save search text for possible reuse on ctrl-R ctrl-R
2061 	emulate_key_press( c ); // pass a character or -1 back to main loop
2062 	return ( Replxx::ACTION_RESULT::CONTINUE );
2063 }
2064 
2065 // ctrl-L, clear screen and redisplay line
clear_screen(char32_t c)2066 Replxx::ACTION_RESULT Replxx::ReplxxImpl::clear_screen( char32_t c ) {
2067 	_terminal.clear_screen( Terminal::CLEAR_SCREEN::WHOLE );
2068 	if ( c ) {
2069 		_prompt.write();
2070 		_prompt._cursorRowOffset = _prompt._extraLines;
2071 		refresh_line();
2072 	}
2073 	return ( Replxx::ACTION_RESULT::CONTINUE );
2074 }
2075 
bracketed_paste(char32_t)2076 Replxx::ACTION_RESULT Replxx::ReplxxImpl::bracketed_paste( char32_t ) {
2077 	UnicodeString buf;
2078 	while ( char32_t c = _terminal.read_char() ) {
2079 		if ( c == KEY::PASTE_FINISH ) {
2080 			break;
2081 		}
2082 		if ( ( c == '\r' ) || ( c == KEY::control( 'M' ) ) ) {
2083 			c = '\n';
2084 		}
2085 		buf.push_back( c );
2086 	}
2087 	_data.insert( _pos, buf, 0, buf.length() );
2088 	_pos += buf.length();
2089 	return ( Replxx::ACTION_RESULT::CONTINUE );
2090 }
2091 
2092 template <bool subword>
is_word_break_character(char32_t char_) const2093 bool Replxx::ReplxxImpl::is_word_break_character( char32_t char_ ) const {
2094 	bool wbc( false );
2095 	if ( char_ < 128 ) {
2096 		wbc = strchr( subword ? _subwordBreakChars.c_str() : _wordBreakChars.c_str(), static_cast<char>( char_ ) ) != nullptr;
2097 	}
2098 	return ( wbc );
2099 }
2100 
history_add(std::string const & line)2101 void Replxx::ReplxxImpl::history_add( std::string const& line ) {
2102 	_history.add( UnicodeString( line ) );
2103 }
2104 
history_save(std::string const & filename)2105 bool Replxx::ReplxxImpl::history_save( std::string const& filename ) {
2106 	return ( _history.save( filename, false ) );
2107 }
2108 
history_sync(std::string const & filename)2109 bool Replxx::ReplxxImpl::history_sync( std::string const& filename ) {
2110 	return ( _history.save( filename, true ) );
2111 }
2112 
history_load(std::string const & filename)2113 bool Replxx::ReplxxImpl::history_load( std::string const& filename ) {
2114 	return ( _history.load( filename ) );
2115 }
2116 
history_clear(void)2117 void Replxx::ReplxxImpl::history_clear( void ) {
2118 	_history.clear();
2119 }
2120 
history_size(void) const2121 int Replxx::ReplxxImpl::history_size( void ) const {
2122 	return ( _history.size() );
2123 }
2124 
history_scan(void) const2125 Replxx::HistoryScan::impl_t Replxx::ReplxxImpl::history_scan( void ) const {
2126 	return ( _history.scan() );
2127 }
2128 
set_modify_callback(Replxx::modify_callback_t const & fn)2129 void Replxx::ReplxxImpl::set_modify_callback( Replxx::modify_callback_t const& fn ) {
2130 	_modifyCallback = fn;
2131 }
2132 
set_completion_callback(Replxx::completion_callback_t const & fn)2133 void Replxx::ReplxxImpl::set_completion_callback( Replxx::completion_callback_t const& fn ) {
2134 	_completionCallback = fn;
2135 }
2136 
set_highlighter_callback(Replxx::highlighter_callback_t const & fn)2137 void Replxx::ReplxxImpl::set_highlighter_callback( Replxx::highlighter_callback_t const& fn ) {
2138 	_highlighterCallback = fn;
2139 }
2140 
set_hint_callback(Replxx::hint_callback_t const & fn)2141 void Replxx::ReplxxImpl::set_hint_callback( Replxx::hint_callback_t const& fn ) {
2142 	_hintCallback = fn;
2143 }
2144 
set_max_history_size(int len)2145 void Replxx::ReplxxImpl::set_max_history_size( int len ) {
2146 	_history.set_max_size( len );
2147 }
2148 
set_completion_count_cutoff(int count)2149 void Replxx::ReplxxImpl::set_completion_count_cutoff( int count ) {
2150 	_completionCountCutoff = count;
2151 }
2152 
set_max_hint_rows(int count)2153 void Replxx::ReplxxImpl::set_max_hint_rows( int count ) {
2154 	_maxHintRows = count;
2155 }
2156 
set_hint_delay(int hintDelay_)2157 void Replxx::ReplxxImpl::set_hint_delay( int hintDelay_ ) {
2158 	_hintDelay = hintDelay_;
2159 }
2160 
set_word_break_characters(char const * wordBreakers)2161 void Replxx::ReplxxImpl::set_word_break_characters( char const* wordBreakers ) {
2162 	_wordBreakChars = wordBreakers;
2163 }
2164 
set_subword_break_characters(char const * subwordBreakers)2165 void Replxx::ReplxxImpl::set_subword_break_characters( char const* subwordBreakers ) {
2166 	_subwordBreakChars = subwordBreakers;
2167 }
2168 
set_double_tab_completion(bool val)2169 void Replxx::ReplxxImpl::set_double_tab_completion( bool val ) {
2170 	_doubleTabCompletion = val;
2171 }
2172 
set_complete_on_empty(bool val)2173 void Replxx::ReplxxImpl::set_complete_on_empty( bool val ) {
2174 	_completeOnEmpty = val;
2175 }
2176 
set_beep_on_ambiguous_completion(bool val)2177 void Replxx::ReplxxImpl::set_beep_on_ambiguous_completion( bool val ) {
2178 	_beepOnAmbiguousCompletion = val;
2179 }
2180 
set_immediate_completion(bool val)2181 void Replxx::ReplxxImpl::set_immediate_completion( bool val ) {
2182 	_immediateCompletion = val;
2183 }
2184 
set_unique_history(bool val)2185 void Replxx::ReplxxImpl::set_unique_history( bool val ) {
2186 	_history.set_unique( val );
2187 }
2188 
set_no_color(bool val)2189 void Replxx::ReplxxImpl::set_no_color( bool val ) {
2190 	_noColor = val;
2191 }
2192 
2193 /**
2194  * Display the dynamic incremental search prompt and the current user input
2195  * line.
2196  * @param pi    Prompt struct holding information about the prompt and our
2197  * screen position
2198  * @param buf32 input buffer to be displayed
2199  * @param len   count of characters in the buffer
2200  * @param pos   current cursor position within the buffer (0 <= pos <= len)
2201  */
dynamicRefresh(Prompt & oldPrompt,Prompt & newPrompt,char32_t * buf32,int len,int pos)2202 void Replxx::ReplxxImpl::dynamicRefresh(Prompt& oldPrompt, Prompt& newPrompt, char32_t* buf32, int len, int pos) {
2203 	clear_self_to_end_of_screen( &oldPrompt );
2204 	// calculate the position of the end of the prompt
2205 	int xEndOfPrompt, yEndOfPrompt;
2206 	calculate_screen_position(
2207 		0, 0, newPrompt.screen_columns(), newPrompt._characterCount,
2208 		xEndOfPrompt, yEndOfPrompt
2209 	);
2210 
2211 	// calculate the position of the end of the input line
2212 	int xEndOfInput, yEndOfInput;
2213 	calculate_screen_position(
2214 		xEndOfPrompt, yEndOfPrompt, newPrompt.screen_columns(),
2215 		calculate_displayed_length(buf32, len), xEndOfInput,
2216 		yEndOfInput
2217 	);
2218 
2219 	// calculate the desired position of the cursor
2220 	int xCursorPos, yCursorPos;
2221 	calculate_screen_position(
2222 		xEndOfPrompt, yEndOfPrompt, newPrompt.screen_columns(),
2223 		calculate_displayed_length(buf32, pos), xCursorPos,
2224 		yCursorPos
2225 	);
2226 
2227 	// display the prompt
2228 	newPrompt.write();
2229 
2230 	// display the input line
2231 	_terminal.write32( buf32, len );
2232 
2233 #ifndef _WIN32
2234 	// we have to generate our own newline on line wrap
2235 	if (xEndOfInput == 0 && yEndOfInput > 0) {
2236 		_terminal.write8( "\n", 1 );
2237 	}
2238 #endif
2239 	// position the cursor
2240 	_terminal.jump_cursor(
2241 		xCursorPos, // 0-based on Win32
2242 		-( yEndOfInput - yCursorPos )
2243 	);
2244 	newPrompt._cursorRowOffset = newPrompt._extraLines + yCursorPos; // remember row for next pass
2245 }
2246 
2247 }
2248 
2249