blob: c5b17acb2b27deaeb68df02fd06ee1f231726ee0 [file] [log] [blame]
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -07001/*
2 * Command line editing and history
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -08003 * Copyright (c) 2010-2011, Jouni Malinen <j@w1.fi>
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -07004 *
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;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -080027static char currbuf[CMD_BUF_LEN];
28static int currbuf_valid = 0;
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -070029
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)) {
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800236 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 }
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -0700243 }
244
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -0700245 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);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800251 history_use();
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -0700252}
253
254
255static void history_next(void)
256{
257 if (history_curr == NULL ||
258 history_curr ==
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800259 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 }
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -0700267 return;
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800268 }
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -0700269
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);
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -0800326 if (currbuf_valid)
327 printf("{%s}\n", currbuf);
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -0700328 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{
Dmitry Shmidt1f69aa52012-01-24 16:10:04 -08001123 currbuf[0] = '\0';
Dmitry Shmidt8d520ff2011-05-09 14:06:53 -07001124 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}