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