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 |
|
19 | namespace py_readline {
|
20 |
|
21 | static Readline* gReadline = nullptr;
|
22 |
|
23 | // Assuming readline 4.0+
|
24 | #if HAVE_READLINE
|
25 |
|
26 | static 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 |
|
43 | static 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 |
|
52 | static 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 |
|
69 | Readline::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 |
|
94 | static 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
|
108 | BigStr* 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 |
|
162 | BigStr* 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 |
|
176 | void 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 |
|
186 | void 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 |
|
195 | void 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 |
|
210 | void 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 |
|
225 | void Readline::set_completer(completion::ReadlineCallback* completer) {
|
226 | #if HAVE_READLINE
|
227 | completer_ = completer;
|
228 | #else
|
229 | assert(0); // not implemented
|
230 | #endif
|
231 | }
|
232 |
|
233 | void 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 |
|
242 | void 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 |
|
251 | BigStr* 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 |
|
259 | int Readline::get_begidx() {
|
260 | #if HAVE_READLINE
|
261 | return begidx_;
|
262 | #else
|
263 | assert(0); // not implemented
|
264 | #endif
|
265 | }
|
266 |
|
267 | int Readline::get_endidx() {
|
268 | #if HAVE_READLINE
|
269 | return endidx_;
|
270 | #else
|
271 | assert(0); // not implemented
|
272 | #endif
|
273 | }
|
274 |
|
275 | void Readline::clear_history() {
|
276 | #if HAVE_READLINE
|
277 | rl_clear_history();
|
278 | #else
|
279 | assert(0); // not implemented
|
280 | #endif
|
281 | }
|
282 |
|
283 | void 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 |
|
296 | BigStr* 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 |
|
308 | int 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 |
|
319 | void Readline::resize_terminal() {
|
320 | #if HAVE_READLINE
|
321 | rl_resize_terminal();
|
322 | #else
|
323 | assert(0); // not implemented
|
324 | #endif
|
325 | }
|
326 |
|
327 | Readline* 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
|