edit-readline.c (9323B)
1 #include "rc.h" 2 3 #include <errno.h> 4 #include <stdio.h> 5 #include <dirent.h> 6 #include <sys/stat.h> 7 #include <sys/types.h> 8 #include <readline/readline.h> 9 #include <readline/history.h> 10 #include <readline/rltypedefs.h> 11 12 #include "edit.h" 13 14 /* A number of readline functions used in this file are not 15 * present in the version of readline supplied on OpenBSD. 16 * These are replaced with no-ops. 17 * This does not seem to have any major effect other than 18 * ruining the completion of quoted strings. */ 19 #if RL_VERSION_MAJOR < 5 20 typedef char *rl_cpvfunc_t (void); 21 rl_cpvfunc_t *rl_completion_word_break_hook; 22 int rl_bind_keyseq (const char *s, rl_command_func_t *f) { 23 (void)s; 24 (void)f; 25 return 0; 26 } 27 histdata_t free_history_entry (HIST_ENTRY *e) { 28 (void)e; 29 return (histdata_t){}; 30 } 31 #endif 32 33 #if RL_VERSION_MAJOR < 5 || (RL_VERSION_MAJOR == 5 && RL_VERSION_MINOR < 1) 34 int rl_reset_screen_size(void) { 35 return 0; 36 } 37 #endif 38 39 #if RL_VERSION_MAJOR < 6 40 int rl_sort_completion_matches; 41 #endif 42 43 bool editing = 1; 44 45 static const char *quote_chars = "\t\n !#$&'()*;<=>?@[\\]^`{|}~"; 46 47 struct cookie { 48 char *buffer; 49 }; 50 51 /* Join two strings with a "/" between them, into a malloc string */ 52 static char *dir_join(const char *a, const char *b) { 53 size_t l; 54 if (!a) a = ""; 55 if (!b) b = ""; 56 l = strlen(a); 57 return mprint("%s%s%s", a, l && a[l-1] != '/' ? "/" : "", b); 58 } 59 60 char *quote(char *p, int open) { 61 if (strpbrk(p, quote_chars)) { 62 char *r = mprint("%#S", p); 63 if (open) 64 r[strlen(r)-1] = '\0'; 65 efree(p); 66 return r; 67 } 68 return p; 69 } 70 71 static char *unquote(const char *text) { 72 int quoted = 0; 73 char *p, *r; 74 p = r = ealloc(strlen(text) + 1); 75 while ((*p = *text++)) { 76 if (*p == '\'' && (!quoted || *text != '\'')) 77 quoted = !quoted; 78 else 79 p++; 80 } 81 return r; 82 } 83 84 /* Decide if this directory entry is a completion candidate, either executable 85 * or a directory. "dname" is the absolute path of the directory, "name" is the 86 * current entry. "subdirs" is the name being completed up to and including the 87 * last slash (or NULL if there is no slash), "prefix" is the remainder of the 88 * name being completed, "len" is the length of "prefix". 89 */ 90 static char *entry(char *dname, char *name, char *subdirs, 91 char *prefix, size_t len) { 92 char *full; 93 struct stat st; 94 95 if (strncmp(name, prefix, len) != 0) 96 return NULL; 97 if (streq(name, ".") || streq(name, "..")) 98 return NULL; 99 full = dir_join(dname, name); 100 int exe = rc_access(full, FALSE, &st); 101 efree(full); 102 if (S_ISDIR(st.st_mode)) 103 rl_completion_append_character = '/'; 104 if (exe || S_ISDIR(st.st_mode)) 105 return dir_join(subdirs, name); 106 return NULL; 107 } 108 109 /* Split a string "text" after the last "/" into "pre" and "post". If there is 110 * no "/", "pre" will be NULL. */ 111 void split_last_slash(const char *text, char **pre, char **post) { 112 char *last_slash = strrchr(text, '/'); 113 if (last_slash) { 114 size_t l = last_slash + 1 - text; 115 *pre = ealloc(l + 1); 116 memcpy(*pre, text, l); 117 (*pre)[l] = '\0'; 118 *post = last_slash + 1; 119 } else { 120 *pre = NULL; 121 *post = (char *)text; 122 } 123 } 124 125 static char *compl_extcmd(const char *text, int state) { 126 static char *dname, *prefix, *subdirs; 127 static DIR *d; 128 static List nil, *path; 129 static size_t len; 130 131 if (!state) { 132 split_last_slash(text, &subdirs, &prefix); 133 d = NULL; 134 if (subdirs && isabsolute(subdirs)) 135 path = &nil; 136 else 137 path = varlookup("path"); 138 len = strlen(prefix); 139 } 140 while (d || path) { 141 if (!d) { 142 dname = dir_join(path->w, subdirs); 143 d = opendir(dname); 144 path = path->n; 145 if (!d) efree(dname); 146 } else { 147 struct dirent *e; 148 while ((e = readdir(d))) { 149 char *x; 150 x = entry(dname, e->d_name, subdirs, 151 prefix, len); 152 if (x) return x; 153 } 154 closedir(d); 155 efree(dname); 156 d = NULL; 157 } 158 } 159 efree(subdirs); 160 return NULL; 161 } 162 163 static rl_compentry_func_t *const compl_cmd_funcs[] = { 164 compl_builtin, 165 compl_fn, 166 compl_extcmd 167 }; 168 169 static char *compl_command(const char *text, int state) { 170 static size_t i; 171 static int s; 172 char *name = NULL; 173 174 if (!state) { 175 i = 0; 176 s = 0; 177 } 178 while (name == NULL && i < arraysize(compl_cmd_funcs)) { 179 name = compl_cmd_funcs[i](text, s); 180 if (name != NULL) { 181 s = 1; 182 } else { 183 i++; 184 s = 0; 185 } 186 } 187 return name; 188 } 189 190 static char *compl_filename(const char *text, int state) { 191 char *name = rl_filename_completion_function(text, state); 192 struct stat st; 193 if (name != NULL && stat(name, &st) == 0 && S_ISDIR(st.st_mode)) 194 rl_completion_append_character = '/'; 195 return name; 196 } 197 198 static rl_compentry_func_t *compl_func(char prefix) { 199 switch (prefix) { 200 case '`': case '@': case '|': case '&': 201 case '(': case ')': case '{': case ';': 202 return compl_command; 203 case '$': 204 return compl_var; 205 } 206 return compl_filename; 207 } 208 209 static char compl_prefix(int index) { 210 while (index-- > 0) { 211 char c = rl_line_buffer[index]; 212 if (c != ' ' && c != '\t') 213 return c; 214 } 215 return ';'; 216 } 217 218 /* Find the start of the word to complete. This function is the only way to 219 * make readline's code fully support rc's quoting rules. It is called in 220 * *_rl_find_completion_word* as the *rl_completion_word_break_hook* and 221 * exploits the fact that readline stores the start of the word in *rl_point*. 222 * We put the correct postion there first and prevent readline from overwriting 223 * it by keeping *rl_completer_quote_characters* empty! 224 */ 225 static char *compl_start() { 226 int i, quoted = 0, start = 0; 227 for (i = 0; i < rl_point; i++) { 228 char c = rl_line_buffer[i]; 229 if (c == '\'') 230 quoted = !quoted; 231 if (!quoted && strchr(rl_basic_word_break_characters, c)) 232 start = i; 233 } 234 rl_point = start; 235 return NULL; 236 } 237 238 static int matchcmp(const void *a, const void *b) { 239 return strcoll(*(const char **)a, *(const char **)b); 240 } 241 242 static rl_compentry_func_t *compentry_func; 243 244 static char **rc_completion(const char *text, int start, int end) { 245 size_t i, n; 246 char *t = unquote(text); 247 char **matches = NULL; 248 rl_compentry_func_t *func; 249 250 rl_completion_append_character = '\0'; 251 252 if (compentry_func != NULL) { 253 func = compentry_func; 254 compentry_func = NULL; 255 } else 256 func = compl_func(compl_prefix(start)); 257 matches = rl_completion_matches(t, func); 258 if (matches) { 259 for (n = 1; matches[n]; n++); 260 qsort(&matches[1], n - 1, sizeof(matches[0]), matchcmp); 261 if (rl_completion_type != '?') 262 matches[0] = quote(matches[0], n > 1); 263 if (rl_completion_type == '*') 264 for (i = 1; i < n; i++) 265 matches[i] = quote(matches[i], 0); 266 } 267 efree(t); 268 rl_attempted_completion_over = 1; 269 rl_sort_completion_matches = 0; 270 return matches; 271 } 272 273 static int expl_complete(rl_compentry_func_t *func, int count, int key) { 274 if (rl_last_func == rl_complete) 275 rl_last_func = NULL; 276 compentry_func = func; 277 return rl_complete(count, key); 278 } 279 280 static int rc_complete_command(int count, int key) { 281 return expl_complete(compl_extcmd, count, key); 282 } 283 284 static int rc_complete_filename(int count, int key) { 285 return expl_complete(compl_filename, count, key); 286 } 287 288 static int rc_complete_variable(int count, int key) { 289 return expl_complete(compl_var, count, key); 290 } 291 292 void *edit_begin(int fd) { 293 List *hist; 294 struct cookie *c; 295 296 rl_attempted_completion_function = rc_completion; 297 rl_basic_quote_characters = ""; 298 rl_basic_word_break_characters = " \t\n`@$><=;|&{("; 299 rl_catch_signals = 0; 300 rl_completion_word_break_hook = compl_start; 301 rl_readline_name = "rc"; 302 303 rl_initialize(); 304 305 rl_add_funmap_entry("rc-complete-command", rc_complete_command); 306 rl_add_funmap_entry("rc-complete-filename", rc_complete_filename); 307 rl_add_funmap_entry("rc-complete-variable", rc_complete_variable); 308 rl_bind_keyseq("\e!", rc_complete_command); 309 rl_bind_keyseq("\e/", rc_complete_filename); 310 rl_bind_keyseq("\e$", rc_complete_variable); 311 312 hist = varlookup("history"); 313 if (hist != NULL) 314 if (read_history(hist->w) != 0 && 315 errno != ENOENT) /* ignore if missing */ 316 uerror(hist->w); 317 318 c = ealloc(sizeof *c); 319 c->buffer = NULL; 320 return c; 321 } 322 323 static void (*oldint)(int), (*oldquit)(int); 324 325 static void edit_catcher(int sig) { 326 sys_signal(SIGINT, oldint); 327 sys_signal(SIGQUIT, oldquit); 328 write(2, "\n", 1); 329 rc_raise(eError); 330 } 331 332 static char *prompt; 333 334 char *edit_alloc(void *cookie, size_t *count) { 335 struct cookie *c = cookie; 336 337 oldint = sys_signal(SIGINT, edit_catcher); 338 oldquit = sys_signal(SIGQUIT, edit_catcher); 339 340 rl_reset_screen_size(); 341 c->buffer = readline(prompt); 342 343 sys_signal(SIGINT, oldint); 344 sys_signal(SIGQUIT, oldquit); 345 346 if (c->buffer) { 347 *count = strlen(c->buffer); 348 if (*count) { 349 history_set_pos(history_length); 350 while (history_search_prefix(c->buffer, -1) == 0) { 351 HIST_ENTRY *e = current_history(); 352 if (e != NULL && e->line[*count] == '\0') { 353 if ((e = remove_history(where_history()))) 354 free_history_entry(e); 355 } 356 if (!previous_history()) 357 break; 358 } 359 add_history(c->buffer); 360 } 361 c->buffer[*count] = '\n'; 362 ++*count; /* include the \n */ 363 } 364 return c->buffer; 365 } 366 367 void edit_prompt(void *cookie, char *pr) { 368 prompt = pr; 369 } 370 371 void edit_free(void *cookie) { 372 struct cookie *c = cookie; 373 374 efree(c->buffer); 375 /* Set c->buffer to NULL, allowing us to "overfree" it. This is a bit 376 * of a kludge, but it's otherwise hard to deal with the case where a 377 * signal causes an early return from readline. */ 378 c->buffer = NULL; 379 } 380 381 void edit_end(void *cookie) { 382 struct cookie *c = cookie; 383 384 efree(c); 385 } 386 387 void edit_reset(void *cookie) { 388 rl_reset_terminal(NULL); 389 }