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