OILS / cpp / frontend_pyreadline.cc View on Github | oils.pub

427 lines, 281 significant
1#include "frontend_pyreadline.h"
2
3#include <assert.h>
4#include <errno.h> // errno, EINTR
5#include <signal.h> // SIGINT
6#include <stdio.h> // required for readline/readline.h (man readline)
7#include <sys/select.h> // select(), FD_ISSET, FD_SET, FD_ZERO
8
9#include "_build/detected-cpp-config.h"
10
11#if HAVE_READLINE
12 #include <readline/history.h>
13 #include <readline/readline.h>
14#endif
15
16#include "cpp/core.h"
17#include "mycpp/gc_mylib.h"
18
19namespace py_readline {
20
21static Readline* gReadline = nullptr;
22
23#if HAVE_READLINE
24
25static char* do_complete(const char* text, int state) {
26 if (gReadline->completer_ == nullptr) {
27 return nullptr;
28 }
29 rl_attempted_completion_over = 1;
30 BigStr* gc_text = StrFromC(text);
31 BigStr* result = completion::ExecuteReadlineCallback(gReadline->completer_,
32 gc_text, state);
33 if (result == nullptr) {
34 return nullptr;
35 }
36
37 // According to https://web.mit.edu/gnu/doc/html/rlman_2.html#SEC37, readline
38 // will free any memory we return to it.
39 return strdup(result->data());
40}
41
42static char** completion_handler(const char* text, int start, int end) {
43 rl_completion_append_character = '\0';
44 #if HAVE_READLINE_COMPLETION_SUPPRESS_APPEND
45 rl_completion_suppress_append = 0;
46 #endif
47 gReadline->begidx_ = start;
48 gReadline->endidx_ = end;
49 return rl_completion_matches(text,
50 static_cast<rl_compentry_func_t*>(do_complete));
51}
52
53static void display_matches_hook(char** matches, int num_matches,
54 int max_length) {
55 if (gReadline->display_ == nullptr) {
56 return;
57 }
58 auto* gc_matches = Alloc<List<BigStr*>>();
59 // It isn't clear from the readline documentation, but matches[0] is the
60 // completion text and the matches returned by any callbacks start at index 1.
61 for (int i = 1; i <= num_matches; i++) {
62 gc_matches->append(StrFromC(matches[i]));
63 }
64 comp_ui::ExecutePrintCandidates(gReadline->display_, nullptr, gc_matches,
65 max_length);
66}
67
68static void free_history_entry_portable(HIST_ENTRY* entry) {
69 // See also: https://github.com/python/cpython/issues/53695
70 #if HAVE_READLINE_FREE_HISTORY_ENTRY
71 // GNU Readline 5.0+
72 histdata_t data = free_history_entry(entry);
73 free(data);
74 #else
75 // libedit or older Readline
76 if (entry->line) {
77 free((void*)entry->line);
78 }
79 if (entry->data) {
80 free(entry->data);
81 }
82 free(entry);
83 #endif
84}
85
86#endif
87
88Readline::Readline()
89 : begidx_(),
90 endidx_(),
91 completer_delims_(StrFromC(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?")),
92 completer_(),
93 display_(),
94 latest_line_() {
95#if HAVE_READLINE
96 using_history();
97 rl_readline_name = "oils";
98 /* Force rebind of TAB to insert-tab */
99 rl_bind_key('\t', rl_insert);
100 /* Bind both ESC-TAB and ESC-ESC to the completion function */
101 rl_bind_key_in_map('\t', rl_complete, emacs_meta_keymap);
102 rl_bind_key_in_map('\033', rl_complete, emacs_meta_keymap);
103 rl_attempted_completion_function = completion_handler;
104 #if HAVE_READLINE_COMPLETION_DISPLAY_MATCHES_HOOK
105 rl_completion_display_matches_hook = display_matches_hook;
106 #endif
107 #if HAVE_READLINE_CATCH
108 rl_catch_signals = 0;
109 rl_catch_sigwinch = 0;
110 #endif
111 rl_initialize();
112#else
113 assert(0); // not implemented
114#endif
115}
116
117static void readline_cb(char* line) {
118#if HAVE_READLINE
119 if (line == nullptr) {
120 gReadline->latest_line_ = nullptr;
121 } else {
122 gReadline->latest_line_ = line;
123 }
124 gReadline->ready_ = true;
125 rl_callback_handler_remove();
126#endif
127}
128
129// See the following for some loose documentation on the approach here:
130// https://tiswww.case.edu/php/chet/readline/readline.html#Alternate-Interface-Example
131BigStr* readline(BigStr* prompt) {
132#if HAVE_READLINE
133 fd_set fds;
134 FD_ZERO(&fds);
135 rl_callback_handler_install(prompt->data(), readline_cb);
136
137 gReadline->latest_line_ = nullptr;
138 gReadline->ready_ = false;
139 while (!gReadline->ready_) {
140 // Wait until stdin is ready or we are interrupted.
141 FD_SET(fileno(rl_instream), &fds);
142 int ec = select(FD_SETSIZE, &fds, NULL, NULL, NULL);
143 if (ec == -1) {
144 if (errno == EINTR && iolib::gSignalSafe->PollSigInt()) {
145 // User is trying to cancel. Abort and cleanup readline state.
146 rl_free_line_state();
147 #if HAVE_READLINE_CALLBACK_SIGCLEANUP
148 rl_callback_sigcleanup();
149 #endif
150 rl_cleanup_after_signal();
151 rl_callback_handler_remove();
152 throw Alloc<KeyboardInterrupt>();
153 }
154
155 // To be consistent with CPython, retry on all other errors and signals.
156 continue;
157 }
158
159 // Remove this check if we start calling select() with a timeout above.
160 DCHECK(ec > 0);
161 if (FD_ISSET(fileno(rl_instream), &fds)) {
162 // Feed readline.
163 rl_callback_read_char();
164 }
165 }
166
167 if (gReadline->latest_line_ == nullptr) {
168 return kEmptyString;
169 }
170
171 // Like StrFromC(), but add trailing newline to conform to Python's
172 // f.readline() interface.
173 int len = strlen(gReadline->latest_line_);
174 BigStr* s = NewStr(len + 1);
175 memcpy(s->data_, gReadline->latest_line_, len);
176 s->data_[len] = '\n'; // like Python
177
178 free(gReadline->latest_line_);
179 gReadline->latest_line_ = nullptr;
180 return s;
181#else
182 // TODO: This whole file could be omitted from both Ninja and shell builds?
183 FAIL("Shouldn't be called");
184#endif
185}
186
187BigStr* Readline::prompt_input(BigStr* prompt) {
188#if HAVE_READLINE
189 BigStr* ret = readline(prompt);
190 DCHECK(ret != nullptr);
191 if (len(ret) == 0) {
192 throw Alloc<EOFError>();
193 }
194 // log("LINE %d [%s]", len(ret), ret->data_);
195 return ret;
196#else
197 FAIL("Shouldn't be called");
198#endif
199}
200
201void Readline::parse_and_bind(BigStr* s) {
202#if HAVE_READLINE
203 // Make a copy -- rl_parse_and_bind() modifies its argument
204 BigStr* copy = StrFromC(s->data(), len(s));
205 rl_parse_and_bind(copy->data());
206#else
207 assert(0); // not implemented
208#endif
209}
210
211void Readline::add_history(BigStr* line) {
212#if HAVE_READLINE
213 assert(line != nullptr);
214 ::add_history(line->data());
215#else
216 assert(0); // not implemented
217#endif
218}
219
220void Readline::read_history_file(BigStr* path) {
221#if HAVE_READLINE
222 char* p = nullptr;
223 if (path != nullptr) {
224 p = path->data();
225 }
226 int err_num = read_history(p);
227 if (err_num) {
228 throw Alloc<IOError>(err_num);
229 }
230#else
231 assert(0); // not implemented
232#endif
233}
234
235void Readline::write_history_file(BigStr* path) {
236#if HAVE_READLINE
237 char* p = nullptr;
238 if (path != nullptr) {
239 p = path->data();
240 }
241 int err_num = write_history(p);
242 if (err_num) {
243 throw Alloc<IOError>(err_num);
244 }
245#else
246 assert(0); // not implemented
247#endif
248}
249
250void Readline::set_completer(completion::ReadlineCallback* completer) {
251#if HAVE_READLINE
252 completer_ = completer;
253#else
254 assert(0); // not implemented
255#endif
256}
257
258void Readline::set_completer_delims(BigStr* delims) {
259#if HAVE_READLINE
260 completer_delims_ = StrFromC(delims->data(), len(delims));
261 rl_completer_word_break_characters = completer_delims_->data();
262
263 // for compatibility with libedit, see
264 // https://github.com/python/cpython/issues/112105
265 rl_basic_word_break_characters = completer_delims_->data();
266#else
267 assert(0); // not implemented
268#endif
269}
270
271void Readline::set_completion_display_matches_hook(
272 comp_ui::_IDisplay* display) {
273#if HAVE_READLINE
274 display_ = display;
275#else
276 assert(0); // not implemented
277#endif
278}
279
280BigStr* Readline::get_line_buffer() {
281#if HAVE_READLINE
282 return StrFromC(rl_line_buffer);
283#else
284 assert(0); // not implemented
285#endif
286}
287
288int Readline::get_begidx() {
289#if HAVE_READLINE
290 return begidx_;
291#else
292 assert(0); // not implemented
293#endif
294}
295
296int Readline::get_endidx() {
297#if HAVE_READLINE
298 return endidx_;
299#else
300 assert(0); // not implemented
301#endif
302}
303
304void Readline::clear_history() {
305#if HAVE_READLINE
306 ::clear_history();
307#else
308 assert(0); // not implemented
309#endif
310}
311
312void Readline::remove_history_item(int pos) {
313#if HAVE_READLINE
314 HIST_ENTRY* entry = remove_history(pos);
315 if (!entry) {
316 throw Alloc<ValueError>(StrFormat("No history item at position %d", pos));
317 }
318 free_history_entry_portable(entry);
319#else
320 assert(0); // not implemented
321#endif
322}
323
324BigStr* Readline::get_history_item(int pos) {
325#if HAVE_READLINE
326 HIST_ENTRY* hist_ent = history_get(pos);
327 if (hist_ent != nullptr) {
328 return StrFromC(hist_ent->line);
329 }
330 return nullptr;
331#else
332 assert(0); // not implemented
333#endif
334}
335
336int Readline::get_current_history_length() {
337#if HAVE_READLINE
338 HISTORY_STATE* hist_st = history_get_history_state();
339 int length = hist_st->length;
340 free(hist_st);
341 return length;
342#else
343 assert(0); // not implemented
344#endif
345}
346
347void Readline::resize_terminal() {
348#if HAVE_READLINE && HAVE_READLINE_RESIZE_TERMINAL
349 rl_resize_terminal();
350#else
351 assert(0); // not implemented
352#endif
353}
354
355// bind fns
356void Readline::list_funmap_names() {
357#if HAVE_READLINE && HAVE_READLINE_LIST_FUNMAP_NAMES
358 rl_list_funmap_names();
359#else
360 assert(0); // not implemented
361#endif
362}
363
364void Readline::read_init_file(BigStr* s) {
365 // assert(0); // not implemented
366}
367
368void Readline::function_dumper(bool print_readably) {
369 // assert(0); // not implemented
370}
371
372void Readline::macro_dumper(bool print_readably) {
373 // assert(0); // not implemented
374}
375
376void Readline::variable_dumper(bool print_readably) {
377 // assert(0); // not implemented
378}
379
380void Readline::query_bindings(BigStr* fn_name) {
381 // assert(0); // not implemented
382}
383
384void Readline::unbind_rl_function(BigStr* fn_name) {
385 // assert(0); // not implemented
386}
387
388void Readline::use_temp_keymap(BigStr* fn_name) {
389 // assert(0); // not implemented
390}
391
392void Readline::restore_orig_keymap() {
393 // assert(0); // not implemented
394}
395
396void Readline::print_shell_cmd_map() {
397 // assert(0); // not implemented
398}
399
400void Readline::unbind_keyseq(BigStr* keyseq) {
401 // assert(0); // not implemented
402}
403
404void Readline::bind_shell_command(BigStr* keyseq, BigStr* cmd) {
405 // assert(0); // not implemented
406}
407
408void Readline::set_bind_shell_command_hook(
409 readline_osh::BindXCallback* bindx_cb) {
410#if HAVE_READLINE
411 bindx_cb_ = bindx_cb;
412#else
413 assert(0); // not implemented
414#endif
415}
416
417Readline* MaybeGetReadline() {
418#if HAVE_READLINE
419 gReadline = Alloc<Readline>();
420 gHeap.RootGlobalVar(gReadline);
421 return gReadline;
422#else
423 return nullptr;
424#endif
425}
426
427} // namespace py_readline