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