OILS / cpp / frontend_pyreadline.cc View on Github | oilshell.org

336 lines, 237 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
18namespace py_readline {
19
20static Readline* gReadline = nullptr;
21
22// Assuming readline 4.0+
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 rl_completion_suppress_append = 0;
45 gReadline->begidx_ = start;
46 gReadline->endidx_ = end;
47 return rl_completion_matches(text,
48 static_cast<rl_compentry_func_t*>(do_complete));
49}
50
51static void display_matches_hook(char** matches, int num_matches,
52 int max_length) {
53 if (gReadline->display_ == nullptr) {
54 return;
55 }
56 auto* gc_matches = Alloc<List<BigStr*>>();
57 // It isn't clear from the readline documentation, but matches[0] is the
58 // completion text and the matches returned by any callbacks start at index 1.
59 for (int i = 1; i <= num_matches; i++) {
60 gc_matches->append(StrFromC(matches[i]));
61 }
62 comp_ui::ExecutePrintCandidates(gReadline->display_, nullptr, gc_matches,
63 max_length);
64}
65
66#endif
67
68Readline::Readline()
69 : begidx_(),
70 endidx_(),
71 completer_delims_(StrFromC(" \t\n`~!@#$%^&*()-=+[{]}\\|;:'\",<>/?")),
72 completer_(),
73 display_(),
74 latest_line_() {
75#if HAVE_READLINE
76 using_history();
77 rl_readline_name = "oils";
78 /* Force rebind of TAB to insert-tab */
79 rl_bind_key('\t', rl_insert);
80 /* Bind both ESC-TAB and ESC-ESC to the completion function */
81 rl_bind_key_in_map('\t', rl_complete, emacs_meta_keymap);
82 rl_bind_key_in_map('\033', rl_complete, emacs_meta_keymap);
83 rl_attempted_completion_function = completion_handler;
84 rl_completion_display_matches_hook = display_matches_hook;
85 rl_catch_signals = 0;
86 rl_catch_sigwinch = 0;
87 rl_initialize();
88#else
89 assert(0); // not implemented
90#endif
91}
92
93static void readline_cb(char* line) {
94#if HAVE_READLINE
95 if (line == nullptr) {
96 gReadline->latest_line_ = nullptr;
97 } else {
98 gReadline->latest_line_ = line;
99 }
100 gReadline->ready_ = true;
101 rl_callback_handler_remove();
102#endif
103}
104
105// See the following for some loose documentation on the approach here:
106// https://tiswww.case.edu/php/chet/readline/readline.html#Alternate-Interface-Example
107BigStr* readline(BigStr* prompt) {
108#if HAVE_READLINE
109 fd_set fds;
110 FD_ZERO(&fds);
111 rl_callback_handler_install(prompt->data(), readline_cb);
112
113 gReadline->latest_line_ = nullptr;
114 gReadline->ready_ = false;
115 while (!gReadline->ready_) {
116 // Wait until stdin is ready or we are interrupted.
117 FD_SET(fileno(rl_instream), &fds);
118 int ec = select(FD_SETSIZE, &fds, NULL, NULL, NULL);
119 if (ec == -1) {
120 if (errno == EINTR && pyos::gSignalSafe->PollSigInt()) {
121 // User is trying to cancel. Abort and cleanup readline state.
122 rl_free_line_state();
123 rl_callback_sigcleanup();
124 rl_cleanup_after_signal();
125 rl_callback_handler_remove();
126 throw Alloc<KeyboardInterrupt>();
127 }
128
129 // To be consistent with CPython, retry on all other errors and signals.
130 continue;
131 }
132
133 // Remove this check if we start calling select() with a timeout above.
134 DCHECK(ec > 0);
135 if (FD_ISSET(fileno(rl_instream), &fds)) {
136 // Feed readline.
137 rl_callback_read_char();
138 }
139 }
140
141 if (gReadline->latest_line_ == nullptr) {
142 return kEmptyString;
143 }
144
145 // Like StrFromC(), but add trailing newline to conform to Python's
146 // f.readline() interface.
147 int len = strlen(gReadline->latest_line_);
148 BigStr* s = NewStr(len + 1);
149 memcpy(s->data_, gReadline->latest_line_, len);
150 s->data_[len] = '\n'; // like Python
151
152 free(gReadline->latest_line_);
153 gReadline->latest_line_ = nullptr;
154 return s;
155#else
156 // TODO: This whole file could be omitted from both Ninja and shell builds?
157 FAIL("Shouldn't be called");
158#endif
159}
160
161BigStr* Readline::prompt_input(BigStr* prompt) {
162#if HAVE_READLINE
163 BigStr* ret = readline(prompt);
164 DCHECK(ret != nullptr);
165 if (len(ret) == 0) {
166 throw Alloc<EOFError>();
167 }
168 // log("LINE %d [%s]", len(ret), ret->data_);
169 return ret;
170#else
171 FAIL("Shouldn't be called");
172#endif
173}
174
175void Readline::parse_and_bind(BigStr* s) {
176#if HAVE_READLINE
177 // Make a copy -- rl_parse_and_bind() modifies its argument
178 BigStr* copy = StrFromC(s->data(), len(s));
179 rl_parse_and_bind(copy->data());
180#else
181 assert(0); // not implemented
182#endif
183}
184
185void Readline::add_history(BigStr* line) {
186#if HAVE_READLINE
187 assert(line != nullptr);
188 ::add_history(line->data());
189#else
190 assert(0); // not implemented
191#endif
192}
193
194void Readline::read_history_file(BigStr* path) {
195#if HAVE_READLINE
196 char* p = nullptr;
197 if (path != nullptr) {
198 p = path->data();
199 }
200 int err_num = read_history(p);
201 if (err_num) {
202 throw Alloc<IOError>(err_num);
203 }
204#else
205 assert(0); // not implemented
206#endif
207}
208
209void Readline::write_history_file(BigStr* path) {
210#if HAVE_READLINE
211 char* p = nullptr;
212 if (path != nullptr) {
213 p = path->data();
214 }
215 int err_num = write_history(p);
216 if (err_num) {
217 throw Alloc<IOError>(err_num);
218 }
219#else
220 assert(0); // not implemented
221#endif
222}
223
224void Readline::set_completer(completion::ReadlineCallback* completer) {
225#if HAVE_READLINE
226 completer_ = completer;
227#else
228 assert(0); // not implemented
229#endif
230}
231
232void Readline::set_completer_delims(BigStr* delims) {
233#if HAVE_READLINE
234 completer_delims_ = StrFromC(delims->data(), len(delims));
235 rl_completer_word_break_characters = completer_delims_->data();
236#else
237 assert(0); // not implemented
238#endif
239}
240
241void Readline::set_completion_display_matches_hook(
242 comp_ui::_IDisplay* display) {
243#if HAVE_READLINE
244 display_ = display;
245#else
246 assert(0); // not implemented
247#endif
248}
249
250BigStr* Readline::get_line_buffer() {
251#if HAVE_READLINE
252 return StrFromC(rl_line_buffer);
253#else
254 assert(0); // not implemented
255#endif
256}
257
258int Readline::get_begidx() {
259#if HAVE_READLINE
260 return begidx_;
261#else
262 assert(0); // not implemented
263#endif
264}
265
266int Readline::get_endidx() {
267#if HAVE_READLINE
268 return endidx_;
269#else
270 assert(0); // not implemented
271#endif
272}
273
274void Readline::clear_history() {
275#if HAVE_READLINE
276 rl_clear_history();
277#else
278 assert(0); // not implemented
279#endif
280}
281
282void Readline::remove_history_item(int pos) {
283#if HAVE_READLINE
284 HIST_ENTRY* entry = remove_history(pos);
285 if (!entry) {
286 throw Alloc<ValueError>(StrFormat("No history item at position %d", pos));
287 }
288 histdata_t data = free_history_entry(entry);
289 free(data);
290#else
291 assert(0); // not implemented
292#endif
293}
294
295BigStr* Readline::get_history_item(int pos) {
296#if HAVE_READLINE
297 HIST_ENTRY* hist_ent = history_get(pos);
298 if (hist_ent != nullptr) {
299 return StrFromC(hist_ent->line);
300 }
301 return nullptr;
302#else
303 assert(0); // not implemented
304#endif
305}
306
307int Readline::get_current_history_length() {
308#if HAVE_READLINE
309 HISTORY_STATE* hist_st = history_get_history_state();
310 int length = hist_st->length;
311 free(hist_st);
312 return length;
313#else
314 assert(0); // not implemented
315#endif
316}
317
318void Readline::resize_terminal() {
319#if HAVE_READLINE
320 rl_resize_terminal();
321#else
322 assert(0); // not implemented
323#endif
324}
325
326Readline* MaybeGetReadline() {
327#if HAVE_READLINE
328 gReadline = Alloc<Readline>();
329 gHeap.RootGlobalVar(gReadline);
330 return gReadline;
331#else
332 return nullptr;
333#endif
334}
335
336} // namespace py_readline