src.hhvn.uk > rc > file > edit-readline.c

Byron Rakitzis' rc(1) port, with a few modifications
Log | Files | Refs | README | LICENSE

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 }