rc

[fork] interactive rc shell
git clone https://hhvn.uk/rc
git clone git://hhvn.uk/rc
Log | Files | Refs | README | LICENSE

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 }