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