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

428 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// 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
265 // https://github.com/python/cpython/issues/112105
266 rl_basic_word_break_characters = completer_delims_->data();
267#else
268 assert(0); // not implemented
269#endif
270}
271
272void Readline::set_completion_display_matches_hook(
273 comp_ui::_IDisplay* display) {
274#if HAVE_READLINE
275 display_ = display;
276#else
277 assert(0); // not implemented
278#endif
279}
280
281BigStr* Readline::get_line_buffer() {
282#if HAVE_READLINE
283 return StrFromC(rl_line_buffer);
284#else
285 assert(0); // not implemented
286#endif
287}
288
289int Readline::get_begidx() {
290#if HAVE_READLINE
291 return begidx_;
292#else
293 assert(0); // not implemented
294#endif
295}
296
297int Readline::get_endidx() {
298#if HAVE_READLINE
299 return endidx_;
300#else
301 assert(0); // not implemented
302#endif
303}
304
305void Readline::clear_history() {
306#if HAVE_READLINE
307 ::clear_history();
308#else
309 assert(0); // not implemented
310#endif
311}
312
313void Readline::remove_history_item(int pos) {
314#if HAVE_READLINE
315 HIST_ENTRY* entry = remove_history(pos);
316 if (!entry) {
317 throw Alloc<ValueError>(StrFormat("No history item at position %d", pos));
318 }
319 free_history_entry_portable(entry);
320#else
321 assert(0); // not implemented
322#endif
323}
324
325BigStr* Readline::get_history_item(int pos) {
326#if HAVE_READLINE
327 HIST_ENTRY* hist_ent = history_get(pos);
328 if (hist_ent != nullptr) {
329 return StrFromC(hist_ent->line);
330 }
331 return nullptr;
332#else
333 assert(0); // not implemented
334#endif
335}
336
337int Readline::get_current_history_length() {
338#if HAVE_READLINE
339 HISTORY_STATE* hist_st = history_get_history_state();
340 int length = hist_st->length;
341 free(hist_st);
342 return length;
343#else
344 assert(0); // not implemented
345#endif
346}
347
348void Readline::resize_terminal() {
349#if HAVE_READLINE_RESIZE_TERMINAL
350 rl_resize_terminal();
351#else
352 assert(0); // not implemented
353#endif
354}
355
356// bind fns
357void Readline::list_funmap_names() {
358#if HAVE_READLINE_LIST_FUNMAP_NAMES
359 rl_list_funmap_names();
360#else
361 assert(0); // not implemented
362#endif
363}
364
365void Readline::read_init_file(BigStr* s) {
366 // assert(0); // not implemented
367}
368
369void Readline::function_dumper(bool print_readably) {
370 // assert(0); // not implemented
371}
372
373void Readline::macro_dumper(bool print_readably) {
374 // assert(0); // not implemented
375}
376
377void Readline::variable_dumper(bool print_readably) {
378 // assert(0); // not implemented
379}
380
381void Readline::query_bindings(BigStr* fn_name) {
382 // assert(0); // not implemented
383}
384
385void Readline::unbind_rl_function(BigStr* fn_name) {
386 // assert(0); // not implemented
387}
388
389void Readline::use_temp_keymap(BigStr* fn_name) {
390 // assert(0); // not implemented
391}
392
393void Readline::restore_orig_keymap() {
394 // assert(0); // not implemented
395}
396
397void Readline::print_shell_cmd_map() {
398 // assert(0); // not implemented
399}
400
401void Readline::unbind_keyseq(BigStr* keyseq) {
402 // assert(0); // not implemented
403}
404
405void Readline::bind_shell_command(BigStr* keyseq, BigStr* cmd) {
406 // assert(0); // not implemented
407}
408
409void Readline::set_bind_shell_command_hook(
410 readline_osh::BindXCallback* bindx_cb) {
411#if HAVE_READLINE
412 bindx_cb_ = bindx_cb;
413#else
414 assert(0); // not implemented
415#endif
416}
417
418Readline* MaybeGetReadline() {
419#if HAVE_READLINE
420 gReadline = Alloc<Readline>();
421 gHeap.RootGlobalVar(gReadline);
422 return gReadline;
423#else
424 return nullptr;
425#endif
426}
427
428} // namespace py_readline