edit.c revision 51b6ea882f234c14cd1fe1332a3840cf61fafcca
1/*
2 * Command line editing and history
3 * Copyright (c) 2010-2011, Jouni Malinen <j@w1.fi>
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#include "includes.h"
10#include <termios.h>
11
12#include "common.h"
13#include "eloop.h"
14#include "list.h"
15#include "edit.h"
16
17#define CMD_BUF_LEN 256
18static char cmdbuf[CMD_BUF_LEN];
19static int cmdbuf_pos = 0;
20static int cmdbuf_len = 0;
21static char currbuf[CMD_BUF_LEN];
22static int currbuf_valid = 0;
23static const char *ps2 = NULL;
24
25#define HISTORY_MAX 100
26
27struct edit_history {
28	struct dl_list list;
29	char str[1];
30};
31
32static struct dl_list history_list;
33static struct edit_history *history_curr;
34
35static void *edit_cb_ctx;
36static void (*edit_cmd_cb)(void *ctx, char *cmd);
37static void (*edit_eof_cb)(void *ctx);
38static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) =
39	NULL;
40
41static struct termios prevt, newt;
42
43
44#define CLEAR_END_LINE "\e[K"
45
46
47void edit_clear_line(void)
48{
49	int i;
50	putchar('\r');
51	for (i = 0; i < cmdbuf_len + 2 + (ps2 ? (int) os_strlen(ps2) : 0); i++)
52		putchar(' ');
53}
54
55
56static void move_start(void)
57{
58	cmdbuf_pos = 0;
59	edit_redraw();
60}
61
62
63static void move_end(void)
64{
65	cmdbuf_pos = cmdbuf_len;
66	edit_redraw();
67}
68
69
70static void move_left(void)
71{
72	if (cmdbuf_pos > 0) {
73		cmdbuf_pos--;
74		edit_redraw();
75	}
76}
77
78
79static void move_right(void)
80{
81	if (cmdbuf_pos < cmdbuf_len) {
82		cmdbuf_pos++;
83		edit_redraw();
84	}
85}
86
87
88static void move_word_left(void)
89{
90	while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] == ' ')
91		cmdbuf_pos--;
92	while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] != ' ')
93		cmdbuf_pos--;
94	edit_redraw();
95}
96
97
98static void move_word_right(void)
99{
100	while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] == ' ')
101		cmdbuf_pos++;
102	while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] != ' ')
103		cmdbuf_pos++;
104	edit_redraw();
105}
106
107
108static void delete_left(void)
109{
110	if (cmdbuf_pos == 0)
111		return;
112
113	edit_clear_line();
114	os_memmove(cmdbuf + cmdbuf_pos - 1, cmdbuf + cmdbuf_pos,
115		   cmdbuf_len - cmdbuf_pos);
116	cmdbuf_pos--;
117	cmdbuf_len--;
118	edit_redraw();
119}
120
121
122static void delete_current(void)
123{
124	if (cmdbuf_pos == cmdbuf_len)
125		return;
126
127	edit_clear_line();
128	os_memmove(cmdbuf + cmdbuf_pos, cmdbuf + cmdbuf_pos + 1,
129		   cmdbuf_len - cmdbuf_pos);
130	cmdbuf_len--;
131	edit_redraw();
132}
133
134
135static void delete_word(void)
136{
137	int pos;
138
139	edit_clear_line();
140	pos = cmdbuf_pos;
141	while (pos > 0 && cmdbuf[pos - 1] == ' ')
142		pos--;
143	while (pos > 0 && cmdbuf[pos - 1] != ' ')
144		pos--;
145	os_memmove(cmdbuf + pos, cmdbuf + cmdbuf_pos, cmdbuf_len - cmdbuf_pos);
146	cmdbuf_len -= cmdbuf_pos - pos;
147	cmdbuf_pos = pos;
148	edit_redraw();
149}
150
151
152static void clear_left(void)
153{
154	if (cmdbuf_pos == 0)
155		return;
156
157	edit_clear_line();
158	os_memmove(cmdbuf, cmdbuf + cmdbuf_pos, cmdbuf_len - cmdbuf_pos);
159	cmdbuf_len -= cmdbuf_pos;
160	cmdbuf_pos = 0;
161	edit_redraw();
162}
163
164
165static void clear_right(void)
166{
167	if (cmdbuf_pos == cmdbuf_len)
168		return;
169
170	edit_clear_line();
171	cmdbuf_len = cmdbuf_pos;
172	edit_redraw();
173}
174
175
176static void history_add(const char *str)
177{
178	struct edit_history *h, *match = NULL, *last = NULL;
179	size_t len, count = 0;
180
181	if (str[0] == '\0')
182		return;
183
184	dl_list_for_each(h, &history_list, struct edit_history, list) {
185		if (os_strcmp(str, h->str) == 0) {
186			match = h;
187			break;
188		}
189		last = h;
190		count++;
191	}
192
193	if (match) {
194		dl_list_del(&h->list);
195		dl_list_add(&history_list, &h->list);
196		history_curr = h;
197		return;
198	}
199
200	if (count >= HISTORY_MAX && last) {
201		dl_list_del(&last->list);
202		os_free(last);
203	}
204
205	len = os_strlen(str);
206	h = os_zalloc(sizeof(*h) + len);
207	if (h == NULL)
208		return;
209	dl_list_add(&history_list, &h->list);
210	os_strlcpy(h->str, str, len + 1);
211	history_curr = h;
212}
213
214
215static void history_use(void)
216{
217	edit_clear_line();
218	cmdbuf_len = cmdbuf_pos = os_strlen(history_curr->str);
219	os_memcpy(cmdbuf, history_curr->str, cmdbuf_len);
220	edit_redraw();
221}
222
223
224static void history_prev(void)
225{
226	if (history_curr == NULL)
227		return;
228
229	if (history_curr ==
230	    dl_list_first(&history_list, struct edit_history, list)) {
231		if (!currbuf_valid) {
232			cmdbuf[cmdbuf_len] = '\0';
233			os_memcpy(currbuf, cmdbuf, cmdbuf_len + 1);
234			currbuf_valid = 1;
235			history_use();
236			return;
237		}
238	}
239
240	if (history_curr ==
241	    dl_list_last(&history_list, struct edit_history, list))
242		return;
243
244	history_curr = dl_list_entry(history_curr->list.next,
245				     struct edit_history, list);
246	history_use();
247}
248
249
250static void history_next(void)
251{
252	if (history_curr == NULL ||
253	    history_curr ==
254	    dl_list_first(&history_list, struct edit_history, list)) {
255		if (currbuf_valid) {
256			currbuf_valid = 0;
257			edit_clear_line();
258			cmdbuf_len = cmdbuf_pos = os_strlen(currbuf);
259			os_memcpy(cmdbuf, currbuf, cmdbuf_len);
260			edit_redraw();
261		}
262		return;
263	}
264
265	history_curr = dl_list_entry(history_curr->list.prev,
266				     struct edit_history, list);
267	history_use();
268}
269
270
271static void history_read(const char *fname)
272{
273	FILE *f;
274	char buf[CMD_BUF_LEN], *pos;
275
276	f = fopen(fname, "r");
277	if (f == NULL)
278		return;
279
280	while (fgets(buf, CMD_BUF_LEN, f)) {
281		for (pos = buf; *pos; pos++) {
282			if (*pos == '\r' || *pos == '\n') {
283				*pos = '\0';
284				break;
285			}
286		}
287		history_add(buf);
288	}
289
290	fclose(f);
291}
292
293
294static void history_write(const char *fname,
295			  int (*filter_cb)(void *ctx, const char *cmd))
296{
297	FILE *f;
298	struct edit_history *h;
299
300	f = fopen(fname, "w");
301	if (f == NULL)
302		return;
303
304	dl_list_for_each_reverse(h, &history_list, struct edit_history, list) {
305		if (filter_cb && filter_cb(edit_cb_ctx, h->str))
306			continue;
307		fprintf(f, "%s\n", h->str);
308	}
309
310	fclose(f);
311}
312
313
314static void history_debug_dump(void)
315{
316	struct edit_history *h;
317	edit_clear_line();
318	printf("\r");
319	dl_list_for_each_reverse(h, &history_list, struct edit_history, list)
320		printf("%s%s\n", h == history_curr ? "[C]" : "", h->str);
321	if (currbuf_valid)
322		printf("{%s}\n", currbuf);
323	edit_redraw();
324}
325
326
327static void insert_char(int c)
328{
329	if (cmdbuf_len >= (int) sizeof(cmdbuf) - 1)
330		return;
331	if (cmdbuf_len == cmdbuf_pos) {
332		cmdbuf[cmdbuf_pos++] = c;
333		cmdbuf_len++;
334		putchar(c);
335		fflush(stdout);
336	} else {
337		os_memmove(cmdbuf + cmdbuf_pos + 1, cmdbuf + cmdbuf_pos,
338			   cmdbuf_len - cmdbuf_pos);
339		cmdbuf[cmdbuf_pos++] = c;
340		cmdbuf_len++;
341		edit_redraw();
342	}
343}
344
345
346static void process_cmd(void)
347{
348	currbuf_valid = 0;
349	if (cmdbuf_len == 0) {
350		printf("\n%s> ", ps2 ? ps2 : "");
351		fflush(stdout);
352		return;
353	}
354	printf("\n");
355	cmdbuf[cmdbuf_len] = '\0';
356	history_add(cmdbuf);
357	cmdbuf_pos = 0;
358	cmdbuf_len = 0;
359	edit_cmd_cb(edit_cb_ctx, cmdbuf);
360	printf("%s> ", ps2 ? ps2 : "");
361	fflush(stdout);
362}
363
364
365static void free_completions(char **c)
366{
367	int i;
368	if (c == NULL)
369		return;
370	for (i = 0; c[i]; i++)
371		os_free(c[i]);
372	os_free(c);
373}
374
375
376static int filter_strings(char **c, char *str, size_t len)
377{
378	int i, j;
379
380	for (i = 0, j = 0; c[j]; j++) {
381		if (os_strncasecmp(c[j], str, len) == 0) {
382			if (i != j) {
383				c[i] = c[j];
384				c[j] = NULL;
385			}
386			i++;
387		} else {
388			os_free(c[j]);
389			c[j] = NULL;
390		}
391	}
392	c[i] = NULL;
393	return i;
394}
395
396
397static int common_len(const char *a, const char *b)
398{
399	int len = 0;
400	while (a[len] && a[len] == b[len])
401		len++;
402	return len;
403}
404
405
406static int max_common_length(char **c)
407{
408	int len, i;
409
410	len = os_strlen(c[0]);
411	for (i = 1; c[i]; i++) {
412		int same = common_len(c[0], c[i]);
413		if (same < len)
414			len = same;
415	}
416
417	return len;
418}
419
420
421static int cmp_str(const void *a, const void *b)
422{
423	return os_strcmp(* (const char **) a, * (const char **) b);
424}
425
426static void complete(int list)
427{
428	char **c;
429	int i, len, count;
430	int start, end;
431	int room, plen, add_space;
432
433	if (edit_completion_cb == NULL)
434		return;
435
436	cmdbuf[cmdbuf_len] = '\0';
437	c = edit_completion_cb(edit_cb_ctx, cmdbuf, cmdbuf_pos);
438	if (c == NULL)
439		return;
440
441	end = cmdbuf_pos;
442	start = end;
443	while (start > 0 && cmdbuf[start - 1] != ' ')
444		start--;
445	plen = end - start;
446
447	count = filter_strings(c, &cmdbuf[start], plen);
448	if (count == 0) {
449		free_completions(c);
450		return;
451	}
452
453	len = max_common_length(c);
454	if (len <= plen && count > 1) {
455		if (list) {
456			qsort(c, count, sizeof(char *), cmp_str);
457			edit_clear_line();
458			printf("\r");
459			for (i = 0; c[i]; i++)
460				printf("%s%s", i > 0 ? " " : "", c[i]);
461			printf("\n");
462			edit_redraw();
463		}
464		free_completions(c);
465		return;
466	}
467	len -= plen;
468
469	room = sizeof(cmdbuf) - 1 - cmdbuf_len;
470	if (room < len)
471		len = room;
472	add_space = count == 1 && len < room;
473
474	os_memmove(cmdbuf + cmdbuf_pos + len + add_space, cmdbuf + cmdbuf_pos,
475		   cmdbuf_len - cmdbuf_pos);
476	os_memcpy(&cmdbuf[cmdbuf_pos - plen], c[0], plen + len);
477	if (add_space)
478		cmdbuf[cmdbuf_pos + len] = ' ';
479
480	cmdbuf_pos += len + add_space;
481	cmdbuf_len += len + add_space;
482
483	edit_redraw();
484
485	free_completions(c);
486}
487
488
489enum edit_key_code {
490	EDIT_KEY_NONE = 256,
491	EDIT_KEY_TAB,
492	EDIT_KEY_UP,
493	EDIT_KEY_DOWN,
494	EDIT_KEY_RIGHT,
495	EDIT_KEY_LEFT,
496	EDIT_KEY_ENTER,
497	EDIT_KEY_BACKSPACE,
498	EDIT_KEY_INSERT,
499	EDIT_KEY_DELETE,
500	EDIT_KEY_HOME,
501	EDIT_KEY_END,
502	EDIT_KEY_PAGE_UP,
503	EDIT_KEY_PAGE_DOWN,
504	EDIT_KEY_F1,
505	EDIT_KEY_F2,
506	EDIT_KEY_F3,
507	EDIT_KEY_F4,
508	EDIT_KEY_F5,
509	EDIT_KEY_F6,
510	EDIT_KEY_F7,
511	EDIT_KEY_F8,
512	EDIT_KEY_F9,
513	EDIT_KEY_F10,
514	EDIT_KEY_F11,
515	EDIT_KEY_F12,
516	EDIT_KEY_CTRL_UP,
517	EDIT_KEY_CTRL_DOWN,
518	EDIT_KEY_CTRL_RIGHT,
519	EDIT_KEY_CTRL_LEFT,
520	EDIT_KEY_CTRL_A,
521	EDIT_KEY_CTRL_B,
522	EDIT_KEY_CTRL_D,
523	EDIT_KEY_CTRL_E,
524	EDIT_KEY_CTRL_F,
525	EDIT_KEY_CTRL_G,
526	EDIT_KEY_CTRL_H,
527	EDIT_KEY_CTRL_J,
528	EDIT_KEY_CTRL_K,
529	EDIT_KEY_CTRL_L,
530	EDIT_KEY_CTRL_N,
531	EDIT_KEY_CTRL_O,
532	EDIT_KEY_CTRL_P,
533	EDIT_KEY_CTRL_R,
534	EDIT_KEY_CTRL_T,
535	EDIT_KEY_CTRL_U,
536	EDIT_KEY_CTRL_V,
537	EDIT_KEY_CTRL_W,
538	EDIT_KEY_ALT_UP,
539	EDIT_KEY_ALT_DOWN,
540	EDIT_KEY_ALT_RIGHT,
541	EDIT_KEY_ALT_LEFT,
542	EDIT_KEY_SHIFT_UP,
543	EDIT_KEY_SHIFT_DOWN,
544	EDIT_KEY_SHIFT_RIGHT,
545	EDIT_KEY_SHIFT_LEFT,
546	EDIT_KEY_ALT_SHIFT_UP,
547	EDIT_KEY_ALT_SHIFT_DOWN,
548	EDIT_KEY_ALT_SHIFT_RIGHT,
549	EDIT_KEY_ALT_SHIFT_LEFT,
550	EDIT_KEY_EOF
551};
552
553static void show_esc_buf(const char *esc_buf, char c, int i)
554{
555	edit_clear_line();
556	printf("\rESC buffer '%s' c='%c' [%d]\n", esc_buf, c, i);
557	edit_redraw();
558}
559
560
561static enum edit_key_code esc_seq_to_key1_no(char last)
562{
563	switch (last) {
564	case 'A':
565		return EDIT_KEY_UP;
566	case 'B':
567		return EDIT_KEY_DOWN;
568	case 'C':
569		return EDIT_KEY_RIGHT;
570	case 'D':
571		return EDIT_KEY_LEFT;
572	default:
573		return EDIT_KEY_NONE;
574	}
575}
576
577
578static enum edit_key_code esc_seq_to_key1_shift(char last)
579{
580	switch (last) {
581	case 'A':
582		return EDIT_KEY_SHIFT_UP;
583	case 'B':
584		return EDIT_KEY_SHIFT_DOWN;
585	case 'C':
586		return EDIT_KEY_SHIFT_RIGHT;
587	case 'D':
588		return EDIT_KEY_SHIFT_LEFT;
589	default:
590		return EDIT_KEY_NONE;
591	}
592}
593
594
595static enum edit_key_code esc_seq_to_key1_alt(char last)
596{
597	switch (last) {
598	case 'A':
599		return EDIT_KEY_ALT_UP;
600	case 'B':
601		return EDIT_KEY_ALT_DOWN;
602	case 'C':
603		return EDIT_KEY_ALT_RIGHT;
604	case 'D':
605		return EDIT_KEY_ALT_LEFT;
606	default:
607		return EDIT_KEY_NONE;
608	}
609}
610
611
612static enum edit_key_code esc_seq_to_key1_alt_shift(char last)
613{
614	switch (last) {
615	case 'A':
616		return EDIT_KEY_ALT_SHIFT_UP;
617	case 'B':
618		return EDIT_KEY_ALT_SHIFT_DOWN;
619	case 'C':
620		return EDIT_KEY_ALT_SHIFT_RIGHT;
621	case 'D':
622		return EDIT_KEY_ALT_SHIFT_LEFT;
623	default:
624		return EDIT_KEY_NONE;
625	}
626}
627
628
629static enum edit_key_code esc_seq_to_key1_ctrl(char last)
630{
631	switch (last) {
632	case 'A':
633		return EDIT_KEY_CTRL_UP;
634	case 'B':
635		return EDIT_KEY_CTRL_DOWN;
636	case 'C':
637		return EDIT_KEY_CTRL_RIGHT;
638	case 'D':
639		return EDIT_KEY_CTRL_LEFT;
640	default:
641		return EDIT_KEY_NONE;
642	}
643}
644
645
646static enum edit_key_code esc_seq_to_key1(int param1, int param2, char last)
647{
648	/* ESC-[<param1>;<param2><last> */
649
650	if (param1 < 0 && param2 < 0)
651		return esc_seq_to_key1_no(last);
652
653	if (param1 == 1 && param2 == 2)
654		return esc_seq_to_key1_shift(last);
655
656	if (param1 == 1 && param2 == 3)
657		return esc_seq_to_key1_alt(last);
658
659	if (param1 == 1 && param2 == 4)
660		return esc_seq_to_key1_alt_shift(last);
661
662	if (param1 == 1 && param2 == 5)
663		return esc_seq_to_key1_ctrl(last);
664
665	if (param2 < 0) {
666		if (last != '~')
667			return EDIT_KEY_NONE;
668		switch (param1) {
669		case 2:
670			return EDIT_KEY_INSERT;
671		case 3:
672			return EDIT_KEY_DELETE;
673		case 5:
674			return EDIT_KEY_PAGE_UP;
675		case 6:
676			return EDIT_KEY_PAGE_DOWN;
677		case 15:
678			return EDIT_KEY_F5;
679		case 17:
680			return EDIT_KEY_F6;
681		case 18:
682			return EDIT_KEY_F7;
683		case 19:
684			return EDIT_KEY_F8;
685		case 20:
686			return EDIT_KEY_F9;
687		case 21:
688			return EDIT_KEY_F10;
689		case 23:
690			return EDIT_KEY_F11;
691		case 24:
692			return EDIT_KEY_F12;
693		}
694	}
695
696	return EDIT_KEY_NONE;
697}
698
699
700static enum edit_key_code esc_seq_to_key2(int param1, int param2, char last)
701{
702	/* ESC-O<param1>;<param2><last> */
703
704	if (param1 >= 0 || param2 >= 0)
705		return EDIT_KEY_NONE;
706
707	switch (last) {
708	case 'F':
709		return EDIT_KEY_END;
710	case 'H':
711		return EDIT_KEY_HOME;
712	case 'P':
713		return EDIT_KEY_F1;
714	case 'Q':
715		return EDIT_KEY_F2;
716	case 'R':
717		return EDIT_KEY_F3;
718	case 'S':
719		return EDIT_KEY_F4;
720	default:
721		return EDIT_KEY_NONE;
722	}
723}
724
725
726static enum edit_key_code esc_seq_to_key(char *seq)
727{
728	char last, *pos;
729	int param1 = -1, param2 = -1;
730	enum edit_key_code ret = EDIT_KEY_NONE;
731
732	last = '\0';
733	for (pos = seq; *pos; pos++)
734		last = *pos;
735
736	if (seq[1] >= '0' && seq[1] <= '9') {
737		param1 = atoi(&seq[1]);
738		pos = os_strchr(seq, ';');
739		if (pos)
740			param2 = atoi(pos + 1);
741	}
742
743	if (seq[0] == '[')
744		ret = esc_seq_to_key1(param1, param2, last);
745	else if (seq[0] == 'O')
746		ret = esc_seq_to_key2(param1, param2, last);
747
748	if (ret != EDIT_KEY_NONE)
749		return ret;
750
751	edit_clear_line();
752	printf("\rUnknown escape sequence '%s'\n", seq);
753	edit_redraw();
754	return EDIT_KEY_NONE;
755}
756
757
758static enum edit_key_code edit_read_key(int sock)
759{
760	int c;
761	unsigned char buf[1];
762	int res;
763	static int esc = -1;
764	static char esc_buf[7];
765
766	res = read(sock, buf, 1);
767	if (res < 0)
768		perror("read");
769	if (res <= 0)
770		return EDIT_KEY_EOF;
771
772	c = buf[0];
773
774	if (esc >= 0) {
775		if (c == 27 /* ESC */) {
776			esc = 0;
777			return EDIT_KEY_NONE;
778		}
779
780		if (esc == 6) {
781			show_esc_buf(esc_buf, c, 0);
782			esc = -1;
783		} else {
784			esc_buf[esc++] = c;
785			esc_buf[esc] = '\0';
786		}
787	}
788
789	if (esc == 1) {
790		if (esc_buf[0] != '[' && esc_buf[0] != 'O') {
791			show_esc_buf(esc_buf, c, 1);
792			esc = -1;
793			return EDIT_KEY_NONE;
794		} else
795			return EDIT_KEY_NONE; /* Escape sequence continues */
796	}
797
798	if (esc > 1) {
799		if ((c >= '0' && c <= '9') || c == ';')
800			return EDIT_KEY_NONE; /* Escape sequence continues */
801
802		if (c == '~' || (c >= 'A' && c <= 'Z')) {
803			esc = -1;
804			return esc_seq_to_key(esc_buf);
805		}
806
807		show_esc_buf(esc_buf, c, 2);
808		esc = -1;
809		return EDIT_KEY_NONE;
810	}
811
812	switch (c) {
813	case 1:
814		return EDIT_KEY_CTRL_A;
815	case 2:
816		return EDIT_KEY_CTRL_B;
817	case 4:
818		return EDIT_KEY_CTRL_D;
819	case 5:
820		return EDIT_KEY_CTRL_E;
821	case 6:
822		return EDIT_KEY_CTRL_F;
823	case 7:
824		return EDIT_KEY_CTRL_G;
825	case 8:
826		return EDIT_KEY_CTRL_H;
827	case 9:
828		return EDIT_KEY_TAB;
829	case 10:
830		return EDIT_KEY_CTRL_J;
831	case 13: /* CR */
832		return EDIT_KEY_ENTER;
833	case 11:
834		return EDIT_KEY_CTRL_K;
835	case 12:
836		return EDIT_KEY_CTRL_L;
837	case 14:
838		return EDIT_KEY_CTRL_N;
839	case 15:
840		return EDIT_KEY_CTRL_O;
841	case 16:
842		return EDIT_KEY_CTRL_P;
843	case 18:
844		return EDIT_KEY_CTRL_R;
845	case 20:
846		return EDIT_KEY_CTRL_T;
847	case 21:
848		return EDIT_KEY_CTRL_U;
849	case 22:
850		return EDIT_KEY_CTRL_V;
851	case 23:
852		return EDIT_KEY_CTRL_W;
853	case 27: /* ESC */
854		esc = 0;
855		return EDIT_KEY_NONE;
856	case 127:
857		return EDIT_KEY_BACKSPACE;
858	default:
859		return c;
860	}
861}
862
863
864static char search_buf[21];
865static int search_skip;
866
867static char * search_find(void)
868{
869	struct edit_history *h;
870	size_t len = os_strlen(search_buf);
871	int skip = search_skip;
872
873	if (len == 0)
874		return NULL;
875
876	dl_list_for_each(h, &history_list, struct edit_history, list) {
877		if (os_strstr(h->str, search_buf)) {
878			if (skip == 0)
879				return h->str;
880			skip--;
881		}
882	}
883
884	search_skip = 0;
885	return NULL;
886}
887
888
889static void search_redraw(void)
890{
891	char *match = search_find();
892	printf("\rsearch '%s': %s" CLEAR_END_LINE,
893	       search_buf, match ? match : "");
894	printf("\rsearch '%s", search_buf);
895	fflush(stdout);
896}
897
898
899static void search_start(void)
900{
901	edit_clear_line();
902	search_buf[0] = '\0';
903	search_skip = 0;
904	search_redraw();
905}
906
907
908static void search_clear(void)
909{
910	search_redraw();
911	printf("\r" CLEAR_END_LINE);
912}
913
914
915static void search_stop(void)
916{
917	char *match = search_find();
918	search_buf[0] = '\0';
919	search_clear();
920	if (match) {
921		os_strlcpy(cmdbuf, match, CMD_BUF_LEN);
922		cmdbuf_len = os_strlen(cmdbuf);
923		cmdbuf_pos = cmdbuf_len;
924	}
925	edit_redraw();
926}
927
928
929static void search_cancel(void)
930{
931	search_buf[0] = '\0';
932	search_clear();
933	edit_redraw();
934}
935
936
937static void search_backspace(void)
938{
939	size_t len;
940	len = os_strlen(search_buf);
941	if (len == 0)
942		return;
943	search_buf[len - 1] = '\0';
944	search_skip = 0;
945	search_redraw();
946}
947
948
949static void search_next(void)
950{
951	search_skip++;
952	search_find();
953	search_redraw();
954}
955
956
957static void search_char(char c)
958{
959	size_t len;
960	len = os_strlen(search_buf);
961	if (len == sizeof(search_buf) - 1)
962		return;
963	search_buf[len] = c;
964	search_buf[len + 1] = '\0';
965	search_skip = 0;
966	search_redraw();
967}
968
969
970static enum edit_key_code search_key(enum edit_key_code c)
971{
972	switch (c) {
973	case EDIT_KEY_ENTER:
974	case EDIT_KEY_CTRL_J:
975	case EDIT_KEY_LEFT:
976	case EDIT_KEY_RIGHT:
977	case EDIT_KEY_HOME:
978	case EDIT_KEY_END:
979	case EDIT_KEY_CTRL_A:
980	case EDIT_KEY_CTRL_E:
981		search_stop();
982		return c;
983	case EDIT_KEY_DOWN:
984	case EDIT_KEY_UP:
985		search_cancel();
986		return EDIT_KEY_EOF;
987	case EDIT_KEY_CTRL_H:
988	case EDIT_KEY_BACKSPACE:
989		search_backspace();
990		break;
991	case EDIT_KEY_CTRL_R:
992		search_next();
993		break;
994	default:
995		if (c >= 32 && c <= 255)
996			search_char(c);
997		break;
998	}
999
1000	return EDIT_KEY_NONE;
1001}
1002
1003
1004static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx)
1005{
1006	static int last_tab = 0;
1007	static int search = 0;
1008	enum edit_key_code c;
1009
1010	c = edit_read_key(sock);
1011
1012	if (search) {
1013		c = search_key(c);
1014		if (c == EDIT_KEY_NONE)
1015			return;
1016		search = 0;
1017		if (c == EDIT_KEY_EOF)
1018			return;
1019	}
1020
1021	if (c != EDIT_KEY_TAB && c != EDIT_KEY_NONE)
1022		last_tab = 0;
1023
1024	switch (c) {
1025	case EDIT_KEY_NONE:
1026		break;
1027	case EDIT_KEY_EOF:
1028		edit_eof_cb(edit_cb_ctx);
1029		break;
1030	case EDIT_KEY_TAB:
1031		complete(last_tab);
1032		last_tab = 1;
1033		break;
1034	case EDIT_KEY_UP:
1035	case EDIT_KEY_CTRL_P:
1036		history_prev();
1037		break;
1038	case EDIT_KEY_DOWN:
1039	case EDIT_KEY_CTRL_N:
1040		history_next();
1041		break;
1042	case EDIT_KEY_RIGHT:
1043	case EDIT_KEY_CTRL_F:
1044		move_right();
1045		break;
1046	case EDIT_KEY_LEFT:
1047	case EDIT_KEY_CTRL_B:
1048		move_left();
1049		break;
1050	case EDIT_KEY_CTRL_RIGHT:
1051		move_word_right();
1052		break;
1053	case EDIT_KEY_CTRL_LEFT:
1054		move_word_left();
1055		break;
1056	case EDIT_KEY_DELETE:
1057		delete_current();
1058		break;
1059	case EDIT_KEY_END:
1060		move_end();
1061		break;
1062	case EDIT_KEY_HOME:
1063	case EDIT_KEY_CTRL_A:
1064		move_start();
1065		break;
1066	case EDIT_KEY_F2:
1067		history_debug_dump();
1068		break;
1069	case EDIT_KEY_CTRL_D:
1070		if (cmdbuf_len > 0) {
1071			delete_current();
1072			return;
1073		}
1074		printf("\n");
1075		edit_eof_cb(edit_cb_ctx);
1076		break;
1077	case EDIT_KEY_CTRL_E:
1078		move_end();
1079		break;
1080	case EDIT_KEY_CTRL_H:
1081	case EDIT_KEY_BACKSPACE:
1082		delete_left();
1083		break;
1084	case EDIT_KEY_ENTER:
1085	case EDIT_KEY_CTRL_J:
1086		process_cmd();
1087		break;
1088	case EDIT_KEY_CTRL_K:
1089		clear_right();
1090		break;
1091	case EDIT_KEY_CTRL_L:
1092		edit_clear_line();
1093		edit_redraw();
1094		break;
1095	case EDIT_KEY_CTRL_R:
1096		search = 1;
1097		search_start();
1098		break;
1099	case EDIT_KEY_CTRL_U:
1100		clear_left();
1101		break;
1102	case EDIT_KEY_CTRL_W:
1103		delete_word();
1104		break;
1105	default:
1106		if (c >= 32 && c <= 255)
1107			insert_char(c);
1108		break;
1109	}
1110}
1111
1112
1113int edit_init(void (*cmd_cb)(void *ctx, char *cmd),
1114	      void (*eof_cb)(void *ctx),
1115	      char ** (*completion_cb)(void *ctx, const char *cmd, int pos),
1116	      void *ctx, const char *history_file, const char *ps)
1117{
1118	currbuf[0] = '\0';
1119	dl_list_init(&history_list);
1120	history_curr = NULL;
1121	if (history_file)
1122		history_read(history_file);
1123
1124	edit_cb_ctx = ctx;
1125	edit_cmd_cb = cmd_cb;
1126	edit_eof_cb = eof_cb;
1127	edit_completion_cb = completion_cb;
1128
1129	tcgetattr(STDIN_FILENO, &prevt);
1130	newt = prevt;
1131	newt.c_lflag &= ~(ICANON | ECHO);
1132	tcsetattr(STDIN_FILENO, TCSANOW, &newt);
1133
1134	eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL);
1135
1136	ps2 = ps;
1137	printf("%s> ", ps2 ? ps2 : "");
1138	fflush(stdout);
1139
1140	return 0;
1141}
1142
1143
1144void edit_deinit(const char *history_file,
1145		 int (*filter_cb)(void *ctx, const char *cmd))
1146{
1147	struct edit_history *h;
1148	if (history_file)
1149		history_write(history_file, filter_cb);
1150	while ((h = dl_list_first(&history_list, struct edit_history, list))) {
1151		dl_list_del(&h->list);
1152		os_free(h);
1153	}
1154	edit_clear_line();
1155	putchar('\r');
1156	fflush(stdout);
1157	eloop_unregister_read_sock(STDIN_FILENO);
1158	tcsetattr(STDIN_FILENO, TCSANOW, &prevt);
1159}
1160
1161
1162void edit_redraw(void)
1163{
1164	char tmp;
1165	cmdbuf[cmdbuf_len] = '\0';
1166	printf("\r%s> %s", ps2 ? ps2 : "", cmdbuf);
1167	if (cmdbuf_pos != cmdbuf_len) {
1168		tmp = cmdbuf[cmdbuf_pos];
1169		cmdbuf[cmdbuf_pos] = '\0';
1170		printf("\r%s> %s", ps2 ? ps2 : "", cmdbuf);
1171		cmdbuf[cmdbuf_pos] = tmp;
1172	}
1173	fflush(stdout);
1174}
1175