1 /*
2  *  edit.c  --  line editing functions for powwow
3  *
4  *  Copyright (C) 1998 by Massimiliano Ghilardi
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  */
12 
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <unistd.h>
16 #include <string.h>
17 #include <ctype.h>
18 #include <time.h>
19 #include <sys/types.h>
20 #include <sys/time.h>
21 
22 #include "defines.h"
23 #include "main.h"
24 #include "utils.h"
25 #include "cmd.h"
26 #include "edit.h"
27 #include "tcp.h"
28 #include "tty.h"
29 #include "eval.h"
30 #include "log.h"
31 
32 static void insert_string	__P ((char *arg));
33 
34 /* history buffer */
35 char *hist[MAX_HIST];	/* saved history lines */
36 int curline = 0;	/* current history line */
37 int pickline = 0;	/* line to pick history from */
38 
39 /* word completion list */
40 wordnode words[MAX_WORDS];
41 int wordindex = 0;
42 
43 edit_function internal_functions[] = {
44     {(char *)0, (function_str)0, },
45     {"&enter-line", enter_line, },
46     {"&complete-word", complete_word, },
47     {"&complete-line", complete_line, },
48     {"&del-char-left", del_char_left, },
49     {"&del-char-right",  del_char_right, },
50     {"&prev-char", prev_char, },
51     {"&prev-line", prev_line, },
52     {"&next-char", next_char, },
53     {"&next-line", next_line, },
54     {"&to-history", to_history, },
55     {"&clear-line", clear_line, },
56     {"&redraw-line", redraw_line, },
57     {"&redraw-line-noprompt", redraw_line_noprompt, },
58     {"&begin-of-line", begin_of_line, },
59     {"&end-of-line", end_of_line, },
60     {"&kill-to-eol", kill_to_eol, },
61     {"&transpose", transpose_chars, },
62     {"&transpose-words", transpose_words, },
63     {"&suspend", (function_str)suspend_powwow, }, /* yep, it's an hack */
64     {"&del-word-left", del_word_left, },
65     {"&del-word-right", del_word_right, },
66     {"&prev-word", prev_word, },
67     {"&upcase-word", upcase_word, },
68     {"&downcase-word", downcase_word, },
69     {"&next-word", next_word, },
70     {"&insert-string", insert_string, },
71     {(char *)0, (function_str)0 }
72 };
73 
__P2(char *,name,char **,arg)74 int lookup_edit_name __P2 (char *,name, char **,arg)
75 {
76     int i, len, flen;
77     char *fname, *extra = NULL;
78 
79     if ((fname = strchr(name, ' ')))
80 	len = fname - name;
81     else
82 	len = strlen(name);
83 
84     for (i=1; (fname = internal_functions[i].name); i++) {
85 	flen = strlen(fname);
86 	if (flen == len && !strncmp(name, fname, flen)) {
87 	    extra = name + flen;
88 	    if (*extra == ' ') extra++;
89 	    if (!*extra) extra = NULL;
90 	    *arg = extra;
91 	    return i;
92 	}
93     }
94     *arg = extra;
95     return 0;
96 }
97 
__P1(function_str,funct)98 int lookup_edit_function __P1 (function_str,funct)
99 {
100     int i;
101     function_str ffunct;
102 
103     for (i = 1; (ffunct = internal_functions[i].funct); i++)
104 	if (funct == ffunct)
105 	    return i;
106 
107     return 0;
108 }
109 
110 /* return pointer to any unterminated escape code at the end of s */
__P1(char *,s)111 static char *find_partial_esc __P1 (char *,s)
112 {
113     size_t len = strlen(s);
114     char *end = s + len;
115     while (end > s) {
116         char c = *--end;
117         if (c == '\033')
118             return end;
119         if (isalpha(c))
120             return NULL;
121     }
122     return NULL;
123 }
124 
125 /*
126  * redisplay the prompt
127  * assume cursor is at beginning of line
128  */
__P0(void)129 void draw_prompt __P0 (void)
130 {
131     if (promptlen && prompt_status == 1) {
132         char *esc, *pstr;
133 	int e = error;
134 	error = 0;
135 	marked_prompt = ptraddmarks(marked_prompt, prompt->str);
136 	if (MEM_ERROR) { promptzero(); errmsg("malloc(prompt)"); return; }
137 
138         /* if prompt ends in unterminated escape code, do not print
139          * that part */
140         pstr = ptrdata(marked_prompt);
141         esc = find_partial_esc(pstr);
142         if (esc)
143             *esc = 0;
144         tty_puts(pstr);
145 	col0 = printstrlen(pstr);
146         if (esc)
147             *esc = '\033';
148 
149 	error = e;
150     }
151     prompt_status = 0;
152 }
153 
154 /*
155  * clear current input line (deleteprompt == 1 if to clear also prompt)
156  * cursor is left right after the prompt.
157  *
158  * since we do not expect data from the user at this point,
159  * do not print edattrbeg now.
160  */
__P1(int,deleteprompt)161 void clear_input_line __P1 (int,deleteprompt)
162 {
163     /*
164      * be careful: if prompt and/or input line have been erased from screen,
165      * pos will be different from the actual cursor position
166      */
167     if ((edlen && line_status == 0) || (promptlen && prompt_status == 0 && deleteprompt)) {
168 	int newcol = deleteprompt ? 0 : col0;
169 	int realpos = line_status == 0 ? pos : (prompt_status == 0 ? 0 : -col0);
170 
171 	tty_gotoxy_opt(CURCOL(realpos), CURLINE(realpos), newcol, line0);
172 	tty_puts(edattrend);
173 	if (line0 < lines - 1)
174 	    tty_puts(tty_clreoscr);
175         else
176 	    tty_puts(tty_clreoln);
177 	col0 = newcol;
178     } else {
179 	tty_puts(edattrend);
180     }
181     if (deleteprompt)
182 	status(1);
183     else
184 	line_status = 1;
185 }
186 
187 /*
188  * clear input line, but do nothing else
189  */
__P1(char *,dummy)190 void clear_line __P1 (char *,dummy)
191 {
192     if (!edlen)
193 	return;
194     clear_input_line(0);
195     pickline = curline;
196     *edbuf = '\0';
197     pos = edlen = 0;
198 }
199 
200 /*
201  * Redraw the input line and put the cursor at the current position.
202  * The cursor is assumed to be directly after the prompt.
203  */
__P0(void)204 void draw_input_line __P0 (void)
205 {
206     int i, oldline0;
207 
208     if (line_status == 0 || linemode & LM_NOECHO)
209 	return;
210 
211     tty_puts(edattrbeg);
212 
213     if (edlen) {
214 	oldline0 = line0;
215 	if (edlen < cols_1 - col0) {
216 	    tty_puts(edbuf);
217 	} else {
218 	    tty_printf("%.*s", cols_1 - col0, edbuf);
219 	    for (i = cols_1 - col0; i <= edlen; i += cols_1) {
220 #ifdef BUG_ANSI
221 		if (edattrbg)
222 		    tty_printf("%s\n%s%.*s", edattrend, edattrbeg, cols_1, edbuf + i);
223 		else
224 #endif
225 		    tty_printf("\n%.*s", cols_1, edbuf + i);
226 	    }
227 	}
228 	line0 = lines - (edlen + col0) / cols_1 - 1;
229 	if (line0 > oldline0)
230 	    line0 = oldline0;
231 	if ((i = CURLINE(pos)) < 0)
232 	    line0 -= i;
233 	else if (i > lines - 1)
234 	    line0 -= i - lines + 1;
235 	tty_gotoxy_opt(CURCOL(edlen), CURLINE(edlen), CURCOL(pos), CURLINE(pos));
236     }
237     line_status = 0;
238 }
239 
240 /*
241  * redraw the input line
242  */
__P1(char *,dummy)243 void redraw_line __P1 (char *,dummy)
244 {
245     clear_input_line(1);
246 }
247 
248 /*
249  * redraw the input line, clearing the prompt
250  */
__P1(char *,dummy)251 void redraw_line_noprompt __P1 (char *,dummy)
252 {
253     clear_input_line(0);
254     tty_putc('\n');
255     if (line0 < lines - 1)
256 	line0++;
257     status(-1);
258 }
259 
260 /*
261  * GH: transpose two words to the left
262  */
__P1(char *,dummy)263 void transpose_words __P1 (char *,dummy)
264 {
265     /* other refers to the word to the left, this is the one we are at */
266 
267     int this_so, other_so, this_eo, other_eo;
268     char buf[BUFSIZE];
269     int n;
270 
271     if (pos > 2) {
272 
273 	this_eo = this_so = pos;
274 	/* optionally traceback to find a word */
275 	while (this_so && strchr(DELIM, edbuf[this_so]))
276 	    this_so--;
277 
278 	/* now find where the current word ends */
279 	while (this_eo < edlen && !strchr(DELIM, edbuf[this_eo]))
280 	    this_eo++;
281 
282 	/* found a word; now find its start */
283 	while (this_so > 0 && !strchr(DELIM, edbuf[this_so - 1]))
284 	    this_so--;
285 
286 	if (this_so < 2)
287 	    return;		/* impossible that there's another word */
288 
289 	other_so = this_so - 1;
290 	while (other_so >= 0 && strchr(DELIM, edbuf[other_so]))
291 	    other_so--;
292 	if (other_so < 0)
293 	    return;
294 	other_eo = other_so + 1;
295 	while (other_so > 0 && !strchr(DELIM, edbuf[other_so - 1]))
296 	    other_so--;
297 
298 	sprintf(buf, "%.*s%.*s%.*s",
299 		this_eo - this_so, edbuf + this_so,
300 		this_so - other_eo, edbuf + other_eo,
301 		other_eo - other_so, edbuf + other_so);
302 
303 	input_moveto(other_so);
304 	for (n = 0; buf[n]; input_overtype_follow(buf[n++]))
305 	    ;
306     }
307 }
308 
309 /*
310  * transpose two characters to the left
311  */
__P1(char *,dummy)312 void transpose_chars __P1 (char *,dummy)
313 {
314     int i, j;
315     char c;
316     if (pos > 1 || (pos > 0 && pos < edlen)) {
317         if (pos < edlen) {
318 	    j = pos;
319 	    i = pos - 1;
320 	} else {
321 	    j = pos - 1;
322 	    i = pos - 2;
323 	}
324 	c = edbuf[j]; edbuf[j] = edbuf[i]; edbuf[i] = c;
325 
326 	if (line_status == 0) {
327 	    tty_gotoxy_opt(CURCOL(pos), CURLINE(pos), CURCOL(i), CURLINE(i));
328 	    tty_putc(edbuf[i]);
329 	    tty_gotoxy_opt(CURCOL(i+1), CURLINE(i+1), CURCOL(j), CURLINE(j));
330 	    tty_putc(edbuf[j]);
331 	    if (pos < edlen) {
332 		pos++;
333 		tty_gotoxy_opt(CURCOL(j+1), CURLINE(j+1), CURCOL(pos), CURLINE(pos));
334 	    }
335 	} else
336 	    pos++;
337     }
338 }
339 
340 /*
341  * erase everything to the end of line
342  */
__P1(char *,dummy)343 void kill_to_eol __P1 (char *,dummy)
344 {
345     if (line_status == 0) {
346 	if (edattrbg)
347 	    tty_printf("%s%s", edattrend, tty_clreoln);
348 	else
349 	    tty_puts(tty_clreoln);
350 	if (CURLINE(edlen) > CURLINE(pos)) {
351 	    tty_printf("\n%s", tty_clreoscr);
352 	    tty_gotoxy_opt(0, CURLINE(pos) + 1, CURCOL(pos), CURLINE(pos));
353 	}
354 	if (edattrbg)
355 	    tty_puts(edattrbeg);
356     }
357     edbuf[edlen = pos] = '\0';
358 }
359 
360 /*
361  * move cursor to end of line
362  */
__P1(char *,dummy)363 void end_of_line __P1 (char *,dummy)
364 {
365     input_moveto(edlen);
366 }
367 
368 /*
369  * move cursor to beginning of line
370  */
__P1(char *,dummy)371 void begin_of_line __P1 (char *,dummy)
372 {
373     input_moveto(0);
374 }
375 
376 /*
377  * delete a character to the right
378  */
__P1(char *,dummy)379 void del_char_right __P1 (char *,dummy)
380 {
381     input_delete_nofollow_chars(1);
382 }
383 
384 /*
385  * delete a character to the left
386  */
__P1(char *,dummy)387 void del_char_left __P1 (char *,dummy)
388 {
389     if (pos) {
390 	input_moveto(pos-1);
391 	input_delete_nofollow_chars(1);
392     }
393 }
394 
395 /*
396  * move a line into history, but don't do anything else
397  */
__P1(char *,dummy)398 void to_history __P1 (char *,dummy)
399 {
400     if (!edlen)
401 	return;
402     clear_input_line(0);
403     put_history(edbuf);
404     pickline = curline;
405     *edbuf = '\0';
406     pos = edlen = 0;
407 }
408 
409 /*
410  * put string in history at current position
411  * (string is assumed to be trashable)
412  */
__P1(char *,str)413 void put_history __P1 (char *,str)
414 {
415     char *p;
416     if (hist[curline]) free(hist[curline]);
417     if (!(hist[curline] = my_strdup(str))) {
418 	errmsg("malloc");
419 	return;
420     }
421 
422     if (++curline == MAX_HIST)
423 	curline = 0;
424 
425     /* split into words and put into completion list */
426     for (p = strtok(str, DELIM); p;
427 	 p = strtok(NULL, DELIM)) {
428         if (strlen(p) >= MIN_WORDLEN &&
429 	    p[0] != '#') /* no commands/short words */
430 	    put_word(p);
431     }
432 }
433 
434 /*
435  * move a node before wordindex, i.e. make it the last word
436  */
__P1(int,i)437 static void demote_word __P1 (int,i)
438 {
439     words[words[i].prev].next = words[i].next;
440     words[words[i].next].prev = words[i].prev;
441     words[i].prev = words[words[i].next = wordindex].prev;
442     words[wordindex].prev = words[words[wordindex].prev].next = i;
443 }
444 
445 static struct {
446     int size, used;
447     char **words;
448 } static_words;
449 
__P1(int,i)450 static int compl_next_word __P1 (int,i)
451 {
452     if (i < 0) {
453     go_static:
454         --i;
455         if (-i - 1 >= static_words.used)
456             i = wordindex;
457     } else {
458         i = words[i].next;
459         if (i == wordindex || words[i].word == NULL) {
460             i = 0;
461             goto go_static;
462         }
463     }
464     return i;
465 }
466 
__P1(int,i)467 static char *compl_get_word __P1 (int,i)
468 {
469     return i < 0 ? static_words.words[-i - 1] : words[i].word;
470 }
471 
472 /*
473  * match and complete a word referring to the word list
474  */
__P1(char *,dummy)475 void complete_word __P1 (char *,dummy)
476 {
477     /*
478      * GH: rewritten to allow circulating through history with
479      * repetitive command
480      *     code stolen from cancan 2.6.3a
481      *        curr_word:   index into words[]
482      *        comp_len     length of current completition
483      *        root_len     length of the root word (before the completition)
484      *        root         start of the root word
485      */
486 
487     static int curr_word, comp_len = 0, root_len = 0;
488     char *root, *p;
489     int k, n;
490 
491     /* find word start */
492     if (last_edit_cmd == (function_any)complete_word && comp_len) {
493 	k = comp_len;
494 	input_moveto(pos - k);
495 	n = pos - root_len;
496     } else {
497 	for (n = pos; n > 0 && !IS_DELIM(edbuf[n - 1]); n--)
498 	    ;
499 	k = 0;
500 	curr_word = wordindex;
501 	root_len = pos - n;
502     }
503     root = edbuf + n; comp_len = 0;
504 
505     /* k = chars to delete,  n = position of starting word */
506 
507     /* scan word list for next match */
508     while ((p = compl_get_word(curr_word = compl_next_word(curr_word)))) {
509 	if (!strncasecmp(p, root, root_len) &&
510 	    *(p += root_len) &&
511 	    (n = strlen(p)) + edlen < BUFSIZE) {
512 	    comp_len = n;
513 	    for (; k && n; k--, n--)
514 		input_overtype_follow(*p++);
515 	    if (n > 0)
516 		input_insert_follow_chars(p, n);
517 	    break;
518 	}
519     }
520     if (k > 0)
521 	input_delete_nofollow_chars(k);
522 
523     /* delete duplicate instances of the word */
524     if (p && curr_word >= 0
525         && !(words[k = curr_word].flags & WORD_UNIQUE)) {
526 	words[k].flags |= WORD_UNIQUE;
527 	p = words[k].word;
528 	n = words[k].next;
529 	while (words[k = n].word) {
530 	    n = words[k].next;
531 	    if (!strcmp(p, words[k].word)) {
532 		demote_word(k);
533 		free(words[k].word);
534 		words[k].word = 0;
535 		words[curr_word].flags |= words[k].flags;	/* move retain flag */
536 		if ((words[k].flags &= WORD_UNIQUE))
537 		    break;
538 	    }
539 	}
540     }
541 }
542 
543 /*
544  * match and complete entire lines backwards in history
545  * GH: made repeated complete_line cycle through history
546  */
__P1(char *,dummy)547 void complete_line __P1 (char *,dummy)
548 {
549     static int curr_line = MAX_HIST-1, root_len = 0, first_line = 0;
550     int i;
551 
552     if (last_edit_cmd != (function_any)complete_line) {
553 	root_len = edlen;
554 	first_line = curr_line = curline;
555     }
556 
557     for (i = curr_line - 1; i != curr_line; i--) {
558 	if (i < 0) i = MAX_HIST - 1;
559 	if (i == first_line)
560 	    break;
561 	if (hist[i] && !strncmp(edbuf, hist[i], root_len))
562 	    break;
563     }
564     if (i != curr_line) {
565 	clear_input_line(0);
566 	if (i == first_line) {
567 	    edbuf[root_len] = 0;
568 	    edlen = root_len;
569 	} else {
570 	    strcpy(edbuf, hist[i]);
571 	    edlen = strlen(edbuf);
572 	}
573 	pos = edlen;
574 	curr_line = i;
575     }
576 }
577 
578 /*
579  * GH: word history handling stolen from cancan 2.6.3a
580  */
581 
__P0(void)582 static void default_completions __P0 (void)
583 {
584     char buf[BUFSIZE];
585     cmdstruct *p;
586     int i;
587     /* TODO: add some way to handle new commands going in the default
588      * completions list */
589     for (i = 0, buf[0] = '#', p = commands; p != NULL; p = p -> next)
590 	if (p->funct) {
591 	    strcpy(buf + 1, p->name);
592             put_static_word(buf);
593 	}
594     /* init 'words' double-linked list */
595     for (i = MAX_WORDS; i--; words[i].prev = i - 1, words[i].next = i + 1)
596 	;
597     words[0].prev = MAX_WORDS - 1;
598     words[MAX_WORDS - 1].next = 0;
599 }
600 
__P1(char *,s)601 void put_static_word __P1 (char *,s)
602 {
603     if (static_words.used >= static_words.size) {
604         do {
605             static_words.size = static_words.size ? static_words.size * 2 : 16;
606         } while (static_words.used >= static_words.size);
607         static_words.words = realloc(static_words.words,
608                                      sizeof static_words.words[0]
609                                      * static_words.size);
610     }
611 
612     if ((s = my_strdup(s)) == NULL) {
613         errmsg("malloc");
614         return;
615     }
616     static_words.words[static_words.used++] = s;
617 }
618 
619 /*
620  * put word in word completion ring
621  */
__P1(char *,s)622 void put_word __P1 (char *,s)
623 {
624     int r = wordindex;
625     if (!(words[r].word = my_strdup(s))) {
626 	errmsg("malloc");
627 	return;
628     }
629     words[r].flags = 0;
630     r = words[r].prev;
631     demote_word(r);
632     wordindex = r;
633     if (words[r].word) {
634 	free(words[r].word);
635 	words[r].word = 0;
636     }
637 }
638 
639 /*
640  * GH: set delimeters[DELIM_CUSTOM]
641  */
__P1(char *,s)642 void set_custom_delimeters __P1 (char *,s)
643 {
644     char *old = delim_list[DELIM_CUSTOM];
645     if (!(delim_list[DELIM_CUSTOM] = my_strdup(s)))
646 	errmsg("malloc");
647     else {
648 	if (old)
649 	    free(old);
650 	delim_len[DELIM_CUSTOM] = strlen(s);
651 	delim_mode = DELIM_CUSTOM;
652     }
653 }
654 
655 /*
656  * enter a line
657  */
__P1(char *,dummy)658 void enter_line __P1 (char *,dummy)
659 {
660     char *p;
661 
662     if (line_status == 0)
663 	input_moveto(edlen);
664     else {
665 	if (prompt_status != 0)
666 	    col0 = 0;
667 	draw_input_line();
668     }
669     PRINTF("%s\n", edattrend);
670 
671     line0 = CURLINE(edlen);
672     if (line0 < lines - 1) line0++;
673 
674     if (recordfile)
675 	fprintf(recordfile, "%s\n", edbuf);
676 
677     col0 = error = pos = line_status = 0;
678 
679     if (!*edbuf || (verbatim && *edbuf != '#'))
680 	tcp_write(tcp_fd, edbuf);
681     else
682 	parse_user_input(edbuf, 1);
683     history_done = 0;
684 
685     /* don't put identical lines in history, nor empty ones */
686     p = hist[curline ? curline - 1 : MAX_HIST - 1];
687     if (!p || (edlen > 0 && strcmp(edbuf, p)))
688 	put_history(edbuf);
689     pickline = curline;
690     if (*inserted_next) {
691 	strcpy(edbuf, inserted_next);
692 	inserted_next[0] = '\0';
693 	line_status = 1;
694     } else if (*prefixstr) {
695 	strcpy(edbuf, prefixstr);
696 	line_status = 1;
697     } else
698 	edbuf[0] = '\0';
699     pos = edlen = strlen(edbuf);
700 }
701 
702 /*
703  * move one word forward
704  */
__P1(char *,dummy)705 void next_word __P1 (char *,dummy)
706 {
707     int i;
708     for (i = pos; edbuf[i] && !isalnum(edbuf[i]); i++)
709 	;
710     while (isalnum(edbuf[i]))
711 	i++;
712     input_moveto(i);
713 }
714 
715 /*
716  * move one word backward
717  */
__P1(char *,dummy)718 void prev_word __P1 (char *,dummy)
719 {
720     int i;
721     for (i = pos; i && !isalnum(edbuf[i - 1]); i--)
722 	;
723     while (i && isalnum(edbuf[i - 1]))
724 	i--;
725     input_moveto(i);
726 }
727 
728 /*
729  * delete word to the right
730  */
__P1(char *,dummy)731 void del_word_right __P1 (char *,dummy)
732 {
733     int i;
734     for (i = pos; edbuf[i] && !isalnum(edbuf[i]); i++)
735 	;
736     while (isalnum(edbuf[i]))
737 	i++;
738     input_delete_nofollow_chars(i - pos);
739 }
740 
741 /*
742  * delete word to the left
743  */
__P1(char *,dummy)744 void del_word_left __P1 (char *,dummy)
745 {
746     int i;
747     for (i = pos; i && !isalnum(edbuf[i - 1]); i--)
748 	;
749     while (i && isalnum(edbuf[i - 1]))
750 	i--;
751     i = pos - i;
752     input_moveto(pos - i);
753     input_delete_nofollow_chars(i);
754 }
755 
756 /*
757  * GH: make word upcase
758  */
__P1(char *,dummy)759 void upcase_word __P1 (char *,dummy)
760 {
761     int opos = pos;
762     int npos = pos;
763 
764     if (last_edit_cmd == (function_any)upcase_word)
765 	npos = 0;
766     else {
767 	while (npos > 0 && IS_DELIM(edbuf[npos])) npos--;
768 	while (npos > 0 && !IS_DELIM(edbuf[npos - 1])) npos--;
769     }
770     input_moveto(npos);
771     while (!IS_DELIM(edbuf[npos]) ||
772 	   (last_edit_cmd == (function_any)upcase_word && edbuf[npos]))
773 	input_overtype_follow(toupper(edbuf[npos++]));
774     input_moveto(opos);
775 }
776 
777 /*
778  * GH: make word downcase
779  */
__P1(char *,dummy)780 void downcase_word __P1 (char *,dummy)
781 {
782     int opos = pos;
783     int npos = pos;
784 
785     if (last_edit_cmd == (function_any)downcase_word)
786 	npos = 0;
787     else {
788 	while (npos > 0 && IS_DELIM(edbuf[npos])) npos--;
789 	while (npos > 0 && !IS_DELIM(edbuf[npos - 1])) npos--;
790     }
791     input_moveto(npos);
792     while (!IS_DELIM(edbuf[npos]) ||
793 	   (last_edit_cmd == (function_any)downcase_word && edbuf[npos])) {
794 	input_overtype_follow(tolower(edbuf[npos++]));
795     }
796     input_moveto(opos);
797 }
798 
799 /*
800  * get previous line from history list
801  */
__P1(char *,dummy)802 void prev_line __P1 (char *,dummy)
803 {
804     int i = pickline - 1;
805     if (i < 0) i = MAX_HIST - 1;
806     if (hist[i]) {
807 	if (hist[pickline] && strcmp(hist[pickline], edbuf)) {
808 	    free(hist[pickline]);
809 	    hist[pickline] = NULL;
810 	}
811 	if (!hist[pickline]) {
812 	    if (!(hist[pickline] = my_strdup(edbuf))) {
813 		errmsg("malloc");
814 		return;
815 	    }
816 	}
817 	pickline = i;
818 	clear_input_line(0);
819 	strcpy(edbuf, hist[pickline]);
820 	pos = edlen = strlen(edbuf);
821     }
822 }
823 
824 /*
825  * get next line from history list
826  */
__P1(char *,dummy)827 void next_line __P1 (char *,dummy)
828 {
829     int i = pickline + 1;
830     if (i == MAX_HIST) i = 0;
831     if (hist[i]) {
832 	if (hist[pickline] && strcmp(hist[pickline], edbuf)) {
833 	    free(hist[pickline]);
834 	    hist[pickline] = NULL;
835 	}
836 	if (!hist[pickline]) {
837 	    if (!(hist[pickline] = my_strdup(edbuf))) {
838 		errmsg("malloc");
839 		return;
840 	    }
841 	}
842 	pickline = i;
843 	clear_input_line(0);
844 	strcpy(edbuf, hist[pickline]);
845 	edlen = pos = strlen(edbuf);
846     }
847 }
848 
849 /*
850  * move one char backward
851  */
__P1(char *,dummy)852 void prev_char __P1 (char *,dummy)
853 {
854     input_moveto(pos-1);
855 }
856 
857 /*
858  * move one char forward
859  */
__P1(char *,dummy)860 void next_char __P1 (char *,dummy)
861 {
862     input_moveto(pos+1);
863 }
864 
865 /*
866  * Flash cursor at parentheses that matches c inserted before current pos
867  */
__P1(char,c)868 static void flashparen __P1 (char,c)
869 {
870     int lev, i;
871     if (line_status != 0)
872 	return;
873     for (i = pos - 1, lev = 0; i >= 0; i--) {
874 	if (ISRPAREN(edbuf[i])) {
875 	    lev++;
876 	} else if (ISLPAREN(edbuf[i])) {
877 	    lev--;
878 	    if (!lev) {
879 		if (LPAREN(c) == edbuf[i])
880 		    break;
881 		else
882 		    i = -1;
883 	    }
884 	}
885     }
886     if (i >= 0) {
887 	tty_gotoxy_opt(CURCOL(pos), CURLINE(pos), CURCOL(i), CURLINE(i));
888 	flashback = 1;
889 	excursion = i;
890     }
891 }
892 
893 /*
894  * put cursor back where it belongs
895  */
__P0(void)896 void putbackcursor __P0 (void)
897 {
898     if (line_status == 0)
899 	tty_gotoxy_opt(CURCOL(excursion), CURLINE(excursion), CURCOL(pos), CURLINE(pos));
900     flashback = 0;
901 }
902 
903 /*
904  * insert a typed character on screen (if it is printable)
905  */
__P1(char,c)906 void insert_char __P1 (char,c)
907 {
908     if (((c & 0x80) || (c >= ' ' && c <= '~')) && edlen < BUFSIZE - 2) {
909 	if (flashback) putbackcursor();
910 	input_insert_follow_chars(&c, 1);
911 	if (ISRPAREN(c))
912 	    flashparen(c);
913     }
914 }
915 
__P1(char *,arg)916 static void insert_string __P1 (char *,arg)
917 {
918     char buf[BUFSIZE];
919     int len;
920 
921     if (!arg || !*arg)
922 	return;
923 
924     my_strncpy(buf, arg, BUFSIZE-1);
925     unescape(buf);
926     len = strlen(buf);
927 
928     if (len > 1) {
929 	if (flashback) putbackcursor();
930 	input_insert_follow_chars(buf, len);
931     } else if (len == 1)
932 	insert_char(buf[0]); /* also flash matching parentheses */
933 }
934 
935 /*
936  * execute string as if typed
937  */
__P1(char *,cmd)938 void key_run_command __P1 (char *,cmd)
939 {
940     clear_input_line(opt_compact && !opt_keyecho);
941     if (opt_keyecho) {
942 	tty_printf("%s%s%s\n", edattrbeg, cmd, edattrend);
943     } else if (!opt_compact)
944         tty_putc('\n');
945 
946     status(1);
947     error = 0;
948 
949     if (recordfile)
950 	fprintf(recordfile, "%s\n", edbuf);
951 
952     parse_instruction(cmd, 1, 0, 1);
953     history_done = 0;
954 }
955 
__P0(void)956 void edit_bootstrap __P0 (void)
957 {
958     default_completions();
959 }
960 
961