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

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