1 #include <regex>
2 #include <string>
3 #include <vector>
4 #include <cerrno>
5 #include <cctype>
6 #include <cstdlib>
7 #include <utility>
8 #include <iomanip>
9 #include <iostream>
10 #include <thread>
11 #include <chrono>
12
13 #include "replxx.hxx"
14 #include "util.h"
15
16 using Replxx = replxx::Replxx;
17
18 class Tick {
19 typedef std::vector<char32_t> keys_t;
20 std::thread _thread;
21 int _tick;
22 bool _alive;
23 keys_t _keys;
24 Replxx& _replxx;
25 public:
Tick(Replxx & replxx_,std::string const & keys_={} )26 Tick( Replxx& replxx_, std::string const& keys_ = {} )
27 : _thread()
28 , _tick( 0 )
29 , _alive( false )
30 , _keys( keys_.begin(), keys_.end() )
31 , _replxx( replxx_ ) {
32 }
start()33 void start() {
34 _alive = true;
35 _thread = std::thread( &Tick::run, this );
36 }
stop()37 void stop() {
38 _alive = false;
39 _thread.join();
40 }
run()41 void run() {
42 std::string s;
43 while ( _alive ) {
44 if ( _keys.empty() ) {
45 _replxx.print( "%d\n", _tick );
46 } else if ( _tick < static_cast<int>( _keys.size() ) ) {
47 _replxx.emulate_key_press( _keys[_tick] );
48 } else {
49 break;
50 }
51 ++ _tick;
52 std::this_thread::sleep_for( std::chrono::seconds( 1 ) );
53 }
54 }
55 };
56
57 // prototypes
58 Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector<std::string> const& user_data);
59 Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector<std::string> const& user_data);
60 void hook_color(std::string const& str, Replxx::colors_t& colors, std::vector<std::pair<std::string, Replxx::Color>> const& user_data);
61
hook_completion(std::string const & context,int & contextLen,std::vector<std::string> const & examples)62 Replxx::completions_t hook_completion(std::string const& context, int& contextLen, std::vector<std::string> const& examples) {
63 Replxx::completions_t completions;
64 int utf8ContextLen( context_len( context.c_str() ) );
65 int prefixLen( context.length() - utf8ContextLen );
66 if ( ( prefixLen > 0 ) && ( context[prefixLen - 1] == '\\' ) ) {
67 -- prefixLen;
68 ++ utf8ContextLen;
69 }
70 contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen );
71
72 std::string prefix { context.substr(prefixLen) };
73 if ( prefix == "\\pi" ) {
74 completions.push_back( "π" );
75 } else {
76 for (auto const& e : examples) {
77 if (e.compare(0, prefix.size(), prefix) == 0) {
78 Replxx::Color c( Replxx::Color::DEFAULT );
79 if ( e.find( "brightred" ) != std::string::npos ) {
80 c = Replxx::Color::BRIGHTRED;
81 } else if ( e.find( "red" ) != std::string::npos ) {
82 c = Replxx::Color::RED;
83 }
84 completions.emplace_back(e.c_str(), c);
85 }
86 }
87 }
88
89 return completions;
90 }
91
hook_hint(std::string const & context,int & contextLen,Replxx::Color & color,std::vector<std::string> const & examples)92 Replxx::hints_t hook_hint(std::string const& context, int& contextLen, Replxx::Color& color, std::vector<std::string> const& examples) {
93 Replxx::hints_t hints;
94
95 // only show hint if prefix is at least 'n' chars long
96 // or if prefix begins with a specific character
97
98 int utf8ContextLen( context_len( context.c_str() ) );
99 int prefixLen( context.length() - utf8ContextLen );
100 contextLen = utf8str_codepoint_len( context.c_str() + prefixLen, utf8ContextLen );
101 std::string prefix { context.substr(prefixLen) };
102
103 if (prefix.size() >= 2 || (! prefix.empty() && prefix.at(0) == '.')) {
104 for (auto const& e : examples) {
105 if (e.compare(0, prefix.size(), prefix) == 0) {
106 hints.emplace_back(e.c_str());
107 }
108 }
109 }
110
111 // set hint color to green if single match found
112 if (hints.size() == 1) {
113 color = Replxx::Color::GREEN;
114 }
115
116 return hints;
117 }
118
hook_color(std::string const & context,Replxx::colors_t & colors,std::vector<std::pair<std::string,Replxx::Color>> const & regex_color)119 void hook_color(std::string const& context, Replxx::colors_t& colors, std::vector<std::pair<std::string, Replxx::Color>> const& regex_color) {
120 // highlight matching regex sequences
121 for (auto const& e : regex_color) {
122 size_t pos {0};
123 std::string str = context;
124 std::smatch match;
125
126 while(std::regex_search(str, match, std::regex(e.first))) {
127 std::string c{ match[0] };
128 std::string prefix( match.prefix().str() );
129 pos += utf8str_codepoint_len( prefix.c_str(), static_cast<int>( prefix.length() ) );
130 int len( utf8str_codepoint_len( c.c_str(), static_cast<int>( c.length() ) ) );
131
132 for (int i = 0; i < len; ++i) {
133 colors.at(pos + i) = e.second;
134 }
135
136 pos += len;
137 str = match.suffix();
138 }
139 }
140 }
141
message(Replxx & replxx,std::string s,char32_t)142 Replxx::ACTION_RESULT message( Replxx& replxx, std::string s, char32_t ) {
143 replxx.invoke( Replxx::ACTION::CLEAR_SELF, 0 );
144 replxx.print( "%s\n", s.c_str() );
145 replxx.invoke( Replxx::ACTION::REPAINT, 0 );
146 return ( Replxx::ACTION_RESULT::CONTINUE );
147 }
148
main(int argc_,char ** argv_)149 int main( int argc_, char** argv_ ) {
150 // words to be completed
151 std::vector<std::string> examples {
152 ".help", ".history", ".quit", ".exit", ".clear", ".prompt ",
153 "hello", "world", "db", "data", "drive", "print", "put",
154 "color_black", "color_red", "color_green", "color_brown", "color_blue",
155 "color_magenta", "color_cyan", "color_lightgray", "color_gray",
156 "color_brightred", "color_brightgreen", "color_yellow", "color_brightblue",
157 "color_brightmagenta", "color_brightcyan", "color_white", "color_normal",
158 };
159
160 // highlight specific words
161 // a regex string, and a color
162 // the order matters, the last match will take precedence
163 using cl = Replxx::Color;
164 std::vector<std::pair<std::string, cl>> regex_color {
165 // single chars
166 {"\\`", cl::BRIGHTCYAN},
167 {"\\'", cl::BRIGHTBLUE},
168 {"\\\"", cl::BRIGHTBLUE},
169 {"\\-", cl::BRIGHTBLUE},
170 {"\\+", cl::BRIGHTBLUE},
171 {"\\=", cl::BRIGHTBLUE},
172 {"\\/", cl::BRIGHTBLUE},
173 {"\\*", cl::BRIGHTBLUE},
174 {"\\^", cl::BRIGHTBLUE},
175 {"\\.", cl::BRIGHTMAGENTA},
176 {"\\(", cl::BRIGHTMAGENTA},
177 {"\\)", cl::BRIGHTMAGENTA},
178 {"\\[", cl::BRIGHTMAGENTA},
179 {"\\]", cl::BRIGHTMAGENTA},
180 {"\\{", cl::BRIGHTMAGENTA},
181 {"\\}", cl::BRIGHTMAGENTA},
182
183 // color keywords
184 {"color_black", cl::BLACK},
185 {"color_red", cl::RED},
186 {"color_green", cl::GREEN},
187 {"color_brown", cl::BROWN},
188 {"color_blue", cl::BLUE},
189 {"color_magenta", cl::MAGENTA},
190 {"color_cyan", cl::CYAN},
191 {"color_lightgray", cl::LIGHTGRAY},
192 {"color_gray", cl::GRAY},
193 {"color_brightred", cl::BRIGHTRED},
194 {"color_brightgreen", cl::BRIGHTGREEN},
195 {"color_yellow", cl::YELLOW},
196 {"color_brightblue", cl::BRIGHTBLUE},
197 {"color_brightmagenta", cl::BRIGHTMAGENTA},
198 {"color_brightcyan", cl::BRIGHTCYAN},
199 {"color_white", cl::WHITE},
200 {"color_normal", cl::NORMAL},
201
202 // commands
203 {"\\.help", cl::BRIGHTMAGENTA},
204 {"\\.history", cl::BRIGHTMAGENTA},
205 {"\\.quit", cl::BRIGHTMAGENTA},
206 {"\\.exit", cl::BRIGHTMAGENTA},
207 {"\\.clear", cl::BRIGHTMAGENTA},
208 {"\\.prompt", cl::BRIGHTMAGENTA},
209
210 // numbers
211 {"[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // integers
212 {"[\\-|+]{0,1}[0-9]*\\.[0-9]+", cl::YELLOW}, // decimals
213 {"[\\-|+]{0,1}[0-9]+e[\\-|+]{0,1}[0-9]+", cl::YELLOW}, // scientific notation
214
215 // strings
216 {"\".*?\"", cl::BRIGHTGREEN}, // double quotes
217 {"\'.*?\'", cl::BRIGHTGREEN}, // single quotes
218 };
219
220 // init the repl
221 Replxx rx;
222 Tick tick( rx, argc_ > 1 ? argv_[1] : "" );
223 rx.install_window_change_handler();
224
225 // the path to the history file
226 std::string history_file {"./replxx_history.txt"};
227
228 // load the history file if it exists
229 rx.history_load(history_file);
230
231 // set the max history size
232 rx.set_max_history_size(128);
233
234 // set the max number of hint rows to show
235 rx.set_max_hint_rows(3);
236
237 // set the callbacks
238 using namespace std::placeholders;
239 rx.set_completion_callback( std::bind( &hook_completion, _1, _2, cref( examples ) ) );
240 rx.set_highlighter_callback( std::bind( &hook_color, _1, _2, cref( regex_color ) ) );
241 rx.set_hint_callback( std::bind( &hook_hint, _1, _2, _3, cref( examples ) ) );
242
243 // other api calls
244 rx.set_word_break_characters( " \t.,-%!;:=*~^'\"/?<>|[](){}" );
245 rx.set_completion_count_cutoff( 128 );
246 rx.set_double_tab_completion( false );
247 rx.set_complete_on_empty( true );
248 rx.set_beep_on_ambiguous_completion( false );
249 rx.set_no_color( false );
250
251 // showcase key bindings
252 rx.bind_key_internal( Replxx::KEY::BACKSPACE, "delete_character_left_of_cursor" );
253 rx.bind_key_internal( Replxx::KEY::DELETE, "delete_character_under_cursor" );
254 rx.bind_key_internal( Replxx::KEY::LEFT, "move_cursor_left" );
255 rx.bind_key_internal( Replxx::KEY::RIGHT, "move_cursor_right" );
256 rx.bind_key_internal( Replxx::KEY::UP, "history_previous" );
257 rx.bind_key_internal( Replxx::KEY::DOWN, "history_next" );
258 rx.bind_key_internal( Replxx::KEY::PAGE_UP, "history_first" );
259 rx.bind_key_internal( Replxx::KEY::PAGE_DOWN, "history_last" );
260 rx.bind_key_internal( Replxx::KEY::HOME, "move_cursor_to_begining_of_line" );
261 rx.bind_key_internal( Replxx::KEY::END, "move_cursor_to_end_of_line" );
262 rx.bind_key_internal( Replxx::KEY::TAB, "complete_line" );
263 rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::LEFT ), "move_cursor_one_word_left" );
264 rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::RIGHT ), "move_cursor_one_word_right" );
265 rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::UP ), "hint_previous" );
266 rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::DOWN ), "hint_next" );
267 rx.bind_key_internal( Replxx::KEY::control( Replxx::KEY::ENTER ), "commit_line" );
268 rx.bind_key_internal( Replxx::KEY::control( 'R' ), "history_incremental_search" );
269 rx.bind_key_internal( Replxx::KEY::control( 'W' ), "kill_to_begining_of_word" );
270 rx.bind_key_internal( Replxx::KEY::control( 'U' ), "kill_to_begining_of_line" );
271 rx.bind_key_internal( Replxx::KEY::control( 'K' ), "kill_to_end_of_line" );
272 rx.bind_key_internal( Replxx::KEY::control( 'Y' ), "yank" );
273 rx.bind_key_internal( Replxx::KEY::control( 'L' ), "clear_screen" );
274 rx.bind_key_internal( Replxx::KEY::control( 'D' ), "send_eof" );
275 rx.bind_key_internal( Replxx::KEY::control( 'C' ), "abort_line" );
276 rx.bind_key_internal( Replxx::KEY::control( 'T' ), "transpose_characters" );
277 #ifndef _WIN32
278 rx.bind_key_internal( Replxx::KEY::control( 'V' ), "verbatim_insert" );
279 rx.bind_key_internal( Replxx::KEY::control( 'Z' ), "suspend" );
280 #endif
281 rx.bind_key_internal( Replxx::KEY::meta( Replxx::KEY::BACKSPACE ), "kill_to_whitespace_on_left" );
282 rx.bind_key_internal( Replxx::KEY::meta( 'p' ), "history_common_prefix_search" );
283 rx.bind_key_internal( Replxx::KEY::meta( 'n' ), "history_common_prefix_search" );
284 rx.bind_key_internal( Replxx::KEY::meta( 'd' ), "kill_to_end_of_word" );
285 rx.bind_key_internal( Replxx::KEY::meta( 'y' ), "yank_cycle" );
286 rx.bind_key_internal( Replxx::KEY::meta( 'u' ), "uppercase_word" );
287 rx.bind_key_internal( Replxx::KEY::meta( 'l' ), "lowercase_word" );
288 rx.bind_key_internal( Replxx::KEY::meta( 'c' ), "capitalize_word" );
289 rx.bind_key_internal( 'a', "insert_character" );
290 rx.bind_key_internal( Replxx::KEY::INSERT, "toggle_overwrite_mode" );
291 rx.bind_key( Replxx::KEY::F1, std::bind( &message, std::ref( rx ), "<F1>", _1 ) );
292 rx.bind_key( Replxx::KEY::F2, std::bind( &message, std::ref( rx ), "<F2>", _1 ) );
293 rx.bind_key( Replxx::KEY::F3, std::bind( &message, std::ref( rx ), "<F3>", _1 ) );
294 rx.bind_key( Replxx::KEY::F4, std::bind( &message, std::ref( rx ), "<F4>", _1 ) );
295 rx.bind_key( Replxx::KEY::F5, std::bind( &message, std::ref( rx ), "<F5>", _1 ) );
296 rx.bind_key( Replxx::KEY::F6, std::bind( &message, std::ref( rx ), "<F6>", _1 ) );
297 rx.bind_key( Replxx::KEY::F7, std::bind( &message, std::ref( rx ), "<F7>", _1 ) );
298 rx.bind_key( Replxx::KEY::F8, std::bind( &message, std::ref( rx ), "<F8>", _1 ) );
299 rx.bind_key( Replxx::KEY::F9, std::bind( &message, std::ref( rx ), "<F9>", _1 ) );
300 rx.bind_key( Replxx::KEY::F10, std::bind( &message, std::ref( rx ), "<F10>", _1 ) );
301 rx.bind_key( Replxx::KEY::F11, std::bind( &message, std::ref( rx ), "<F11>", _1 ) );
302 rx.bind_key( Replxx::KEY::F12, std::bind( &message, std::ref( rx ), "<F12>", _1 ) );
303 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "<S-F1>", _1 ) );
304 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "<S-F2>", _1 ) );
305 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "<S-F3>", _1 ) );
306 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "<S-F4>", _1 ) );
307 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "<S-F5>", _1 ) );
308 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "<S-F6>", _1 ) );
309 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "<S-F7>", _1 ) );
310 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "<S-F8>", _1 ) );
311 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "<S-F9>", _1 ) );
312 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "<S-F10>", _1 ) );
313 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "<S-F11>", _1 ) );
314 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "<S-F12>", _1 ) );
315 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F1 ), std::bind( &message, std::ref( rx ), "<C-F1>", _1 ) );
316 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F2 ), std::bind( &message, std::ref( rx ), "<C-F2>", _1 ) );
317 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F3 ), std::bind( &message, std::ref( rx ), "<C-F3>", _1 ) );
318 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F4 ), std::bind( &message, std::ref( rx ), "<C-F4>", _1 ) );
319 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F5 ), std::bind( &message, std::ref( rx ), "<C-F5>", _1 ) );
320 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F6 ), std::bind( &message, std::ref( rx ), "<C-F6>", _1 ) );
321 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F7 ), std::bind( &message, std::ref( rx ), "<C-F7>", _1 ) );
322 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F8 ), std::bind( &message, std::ref( rx ), "<C-F8>", _1 ) );
323 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F9 ), std::bind( &message, std::ref( rx ), "<C-F9>", _1 ) );
324 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F10 ), std::bind( &message, std::ref( rx ), "<C-F10>", _1 ) );
325 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F11 ), std::bind( &message, std::ref( rx ), "<C-F11>", _1 ) );
326 rx.bind_key( Replxx::KEY::control( Replxx::KEY::F12 ), std::bind( &message, std::ref( rx ), "<C-F12>", _1 ) );
327 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::TAB ), std::bind( &message, std::ref( rx ), "<S-Tab>", _1 ) );
328 rx.bind_key( Replxx::KEY::control( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "<C-Home>", _1 ) );
329 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::HOME ), std::bind( &message, std::ref( rx ), "<S-Home>", _1 ) );
330 rx.bind_key( Replxx::KEY::control( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "<C-End>", _1 ) );
331 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::END ), std::bind( &message, std::ref( rx ), "<S-End>", _1 ) );
332 rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_UP ), std::bind( &message, std::ref( rx ), "<C-PgUp>", _1 ) );
333 rx.bind_key( Replxx::KEY::control( Replxx::KEY::PAGE_DOWN ), std::bind( &message, std::ref( rx ), "<C-PgDn>", _1 ) );
334 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::LEFT ), std::bind( &message, std::ref( rx ), "<S-Left>", _1 ) );
335 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::RIGHT ), std::bind( &message, std::ref( rx ), "<S-Right>", _1 ) );
336 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::UP ), std::bind( &message, std::ref( rx ), "<S-Up>", _1 ) );
337 rx.bind_key( Replxx::KEY::shift( Replxx::KEY::DOWN ), std::bind( &message, std::ref( rx ), "<S-Down>", _1 ) );
338
339 // display initial welcome message
340 std::cout
341 << "Welcome to Replxx\n"
342 << "Press 'tab' to view autocompletions\n"
343 << "Type '.help' for help\n"
344 << "Type '.quit' or '.exit' to exit\n\n";
345
346 // set the repl prompt
347 std::string prompt {"\x1b[1;32mreplxx\x1b[0m> "};
348
349 // main repl loop
350 if ( argc_ > 1 ) {
351 tick.start();
352 }
353 for (;;) {
354 // display the prompt and retrieve input from the user
355 char const* cinput{ nullptr };
356
357 do {
358 cinput = rx.input(prompt);
359 } while ( ( cinput == nullptr ) && ( errno == EAGAIN ) );
360
361 if (cinput == nullptr) {
362 break;
363 }
364
365 // change cinput into a std::string
366 // easier to manipulate
367 std::string input {cinput};
368
369 if (input.empty()) {
370 // user hit enter on an empty line
371
372 continue;
373
374 } else if (input.compare(0, 5, ".quit") == 0 || input.compare(0, 5, ".exit") == 0) {
375 // exit the repl
376
377 rx.history_add(input);
378 break;
379
380 } else if (input.compare(0, 5, ".help") == 0) {
381 // display the help output
382 std::cout
383 << ".help\n\tdisplays the help output\n"
384 << ".quit\n\texit the repl\n"
385 << ".exit\n\texit the repl\n"
386 << ".clear\n\tclears the screen\n"
387 << ".history\n\tdisplays the history output\n"
388 << ".prompt <str>\n\tset the repl prompt to <str>\n";
389
390 rx.history_add(input);
391 continue;
392
393 } else if (input.compare(0, 7, ".prompt") == 0) {
394 // set the repl prompt text
395 auto pos = input.find(" ");
396 if (pos == std::string::npos) {
397 std::cout << "Error: '.prompt' missing argument\n";
398 } else {
399 prompt = input.substr(pos + 1) + " ";
400 }
401
402 rx.history_add(input);
403 continue;
404
405 } else if (input.compare(0, 8, ".history") == 0) {
406 // display the current history
407 Replxx::HistoryScan hs( rx.history_scan() );
408 for ( int i( 0 ); hs.next(); ++ i ) {
409 std::cout << std::setw(4) << i << ": " << hs.get().text() << "\n";
410 }
411
412 rx.history_add(input);
413 continue;
414
415 } else if (input.compare(0, 6, ".merge") == 0) {
416 history_file = "replxx_history_alt.txt";
417
418 rx.history_add(input);
419 continue;
420
421 } else if (input.compare(0, 6, ".clear") == 0) {
422 // clear the screen
423 rx.clear_screen();
424
425 rx.history_add(input);
426 continue;
427
428 } else {
429 // default action
430 // echo the input
431
432 rx.print( "%s\n", input.c_str() );
433
434 rx.history_add( input );
435 continue;
436 }
437 }
438 if ( argc_ > 1 ) {
439 tick.stop();
440 }
441
442 // save the history
443 rx.history_save(history_file);
444
445 std::cout << "\nExiting Replxx\n";
446
447 return 0;
448 }
449