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 |
|
18 | namespace py_readline {
|
19 |
|
20 | static Readline* gReadline = nullptr;
|
21 |
|
22 | // Assuming readline 4.0+
|
23 | #if HAVE_READLINE
|
24 |
|
25 | static 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 |
|
42 | static 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 |
|
51 | static 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 |
|
68 | Readline::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 |
|
93 | static 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
|
107 | BigStr* 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 |
|
161 | BigStr* 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 |
|
175 | void 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 |
|
185 | void 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 |
|
194 | void 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 |
|
209 | void 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 |
|
224 | void Readline::set_completer(completion::ReadlineCallback* completer) {
|
225 | #if HAVE_READLINE
|
226 | completer_ = completer;
|
227 | #else
|
228 | assert(0); // not implemented
|
229 | #endif
|
230 | }
|
231 |
|
232 | void 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 |
|
241 | void 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 |
|
250 | BigStr* 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 |
|
258 | int Readline::get_begidx() {
|
259 | #if HAVE_READLINE
|
260 | return begidx_;
|
261 | #else
|
262 | assert(0); // not implemented
|
263 | #endif
|
264 | }
|
265 |
|
266 | int Readline::get_endidx() {
|
267 | #if HAVE_READLINE
|
268 | return endidx_;
|
269 | #else
|
270 | assert(0); // not implemented
|
271 | #endif
|
272 | }
|
273 |
|
274 | void Readline::clear_history() {
|
275 | #if HAVE_READLINE
|
276 | rl_clear_history();
|
277 | #else
|
278 | assert(0); // not implemented
|
279 | #endif
|
280 | }
|
281 |
|
282 | void 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 |
|
295 | BigStr* 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 |
|
307 | int 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 |
|
318 | void Readline::resize_terminal() {
|
319 | #if HAVE_READLINE
|
320 | rl_resize_terminal();
|
321 | #else
|
322 | assert(0); // not implemented
|
323 | #endif
|
324 | }
|
325 |
|
326 | Readline* 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
|