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

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