| 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
 |