]> git.ipfire.org Git - thirdparty/hostap.git/blob - src/utils/edit.c
edit: Fix completion of arguments other than the first one
[thirdparty/hostap.git] / src / utils / edit.c
1 /*
2 * Command line editing and history
3 * Copyright (c) 2010, 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 "edit.h"
21
22 #define CMD_BUF_LEN 256
23 static char cmdbuf[CMD_BUF_LEN];
24 static int cmdbuf_pos = 0;
25 static int cmdbuf_len = 0;
26 #define CMD_HISTORY_LEN 20
27 static char history_buf[CMD_HISTORY_LEN][CMD_BUF_LEN];
28 static int history_pos = 0;
29 static int history_current = 0;
30
31 static void *edit_cb_ctx;
32 static void (*edit_cmd_cb)(void *ctx, char *cmd);
33 static void (*edit_eof_cb)(void *ctx);
34 static char ** (*edit_completion_cb)(void *ctx, const char *cmd, int pos) =
35 NULL;
36
37 static struct termios prevt, newt;
38
39
40 void edit_clear_line(void)
41 {
42 int i;
43 putchar('\r');
44 for (i = 0; i < cmdbuf_len + 2; i++)
45 putchar(' ');
46 }
47
48
49 static void move_start(void)
50 {
51 cmdbuf_pos = 0;
52 edit_redraw();
53 }
54
55
56 static void move_end(void)
57 {
58 cmdbuf_pos = cmdbuf_len;
59 edit_redraw();
60 }
61
62
63 static void move_left(void)
64 {
65 if (cmdbuf_pos > 0) {
66 cmdbuf_pos--;
67 edit_redraw();
68 }
69 }
70
71
72 static void move_right(void)
73 {
74 if (cmdbuf_pos < cmdbuf_len) {
75 cmdbuf_pos++;
76 edit_redraw();
77 }
78 }
79
80
81 static void move_word_left(void)
82 {
83 while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] == ' ')
84 cmdbuf_pos--;
85 while (cmdbuf_pos > 0 && cmdbuf[cmdbuf_pos - 1] != ' ')
86 cmdbuf_pos--;
87 edit_redraw();
88 }
89
90
91 static void move_word_right(void)
92 {
93 while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] == ' ')
94 cmdbuf_pos++;
95 while (cmdbuf_pos < cmdbuf_len && cmdbuf[cmdbuf_pos] != ' ')
96 cmdbuf_pos++;
97 edit_redraw();
98 }
99
100
101 static void delete_left(void)
102 {
103 if (cmdbuf_pos == 0)
104 return;
105
106 edit_clear_line();
107 os_memmove(cmdbuf + cmdbuf_pos - 1, cmdbuf + cmdbuf_pos,
108 cmdbuf_len - cmdbuf_pos);
109 cmdbuf_pos--;
110 cmdbuf_len--;
111 edit_redraw();
112 }
113
114
115 static void delete_current(void)
116 {
117 if (cmdbuf_pos == cmdbuf_len)
118 return;
119
120 edit_clear_line();
121 os_memmove(cmdbuf + cmdbuf_pos, cmdbuf + cmdbuf_pos + 1,
122 cmdbuf_len - cmdbuf_pos);
123 cmdbuf_len--;
124 edit_redraw();
125 }
126
127
128 static void delete_word(void)
129 {
130 edit_clear_line();
131 while (cmdbuf_len > 0 && cmdbuf[cmdbuf_len - 1] == ' ')
132 cmdbuf_len--;
133 while (cmdbuf_len > 0 && cmdbuf[cmdbuf_len - 1] != ' ')
134 cmdbuf_len--;
135 edit_redraw();
136 }
137
138
139 static void clear_left(void)
140 {
141 if (cmdbuf_pos == 0)
142 return;
143
144 edit_clear_line();
145 os_memmove(cmdbuf, cmdbuf + cmdbuf_pos, cmdbuf_len - cmdbuf_pos);
146 cmdbuf_len -= cmdbuf_pos;
147 cmdbuf_pos = 0;
148 edit_redraw();
149 }
150
151
152 static void clear_right(void)
153 {
154 if (cmdbuf_pos == cmdbuf_len)
155 return;
156
157 edit_clear_line();
158 cmdbuf_len = cmdbuf_pos;
159 edit_redraw();
160 }
161
162
163 static void history_add(const char *str)
164 {
165 int prev;
166
167 if (str[0] == '\0')
168 return;
169
170 if (history_pos == 0)
171 prev = CMD_HISTORY_LEN - 1;
172 else
173 prev = history_pos - 1;
174 if (os_strcmp(history_buf[prev], str) == 0)
175 return;
176
177 os_strlcpy(history_buf[history_pos], str, CMD_BUF_LEN);
178 history_pos++;
179 if (history_pos == CMD_HISTORY_LEN)
180 history_pos = 0;
181 history_current = history_pos;
182 }
183
184
185 static void history_prev(void)
186 {
187 int pos;
188
189 if (history_current == (history_pos + 1) % CMD_HISTORY_LEN)
190 return;
191
192 pos = history_current;
193
194 if (history_current == history_pos && cmdbuf_len) {
195 cmdbuf[cmdbuf_len] = '\0';
196 history_add(cmdbuf);
197 }
198
199 if (pos > 0)
200 pos--;
201 else
202 pos = CMD_HISTORY_LEN - 1;
203 if (history_buf[pos][0] == '\0')
204 return;
205 history_current = pos;
206
207 edit_clear_line();
208 cmdbuf_len = cmdbuf_pos = os_strlen(history_buf[history_current]);
209 os_memcpy(cmdbuf, history_buf[history_current], cmdbuf_len);
210 edit_redraw();
211 }
212
213
214 static void history_next(void)
215 {
216 if (history_current == history_pos)
217 return;
218
219 history_current++;
220 if (history_current == CMD_HISTORY_LEN)
221 history_current = 0;
222
223 edit_clear_line();
224 cmdbuf_len = cmdbuf_pos = os_strlen(history_buf[history_current]);
225 os_memcpy(cmdbuf, history_buf[history_current], cmdbuf_len);
226 edit_redraw();
227 }
228
229
230 static void history_debug_dump(void)
231 {
232 int p;
233 edit_clear_line();
234 printf("\r");
235 p = (history_pos + 1) % CMD_HISTORY_LEN;
236 for (;;) {
237 printf("[%d%s%s] %s\n",
238 p, p == history_current ? "C" : "",
239 p == history_pos ? "P" : "", history_buf[p]);
240 if (p == history_pos)
241 break;
242 p++;
243 if (p == CMD_HISTORY_LEN)
244 p = 0;
245 }
246 edit_redraw();
247 }
248
249
250 static void insert_char(int c)
251 {
252 if (c < 32 && c > 255) {
253 printf("[%d]\n", c);
254 edit_redraw();
255 return;
256 }
257
258 if (cmdbuf_len >= (int) sizeof(cmdbuf) - 1)
259 return;
260 if (cmdbuf_len == cmdbuf_pos) {
261 cmdbuf[cmdbuf_pos++] = c;
262 cmdbuf_len++;
263 putchar(c);
264 fflush(stdout);
265 } else {
266 os_memmove(cmdbuf + cmdbuf_pos + 1, cmdbuf + cmdbuf_pos,
267 cmdbuf_len - cmdbuf_pos);
268 cmdbuf[cmdbuf_pos++] = c;
269 cmdbuf_len++;
270 edit_redraw();
271 }
272 }
273
274
275 static void process_cmd(void)
276 {
277
278 if (cmdbuf_len == 0) {
279 printf("\n> ");
280 fflush(stdout);
281 return;
282 }
283 printf("\n");
284 cmdbuf[cmdbuf_len] = '\0';
285 history_add(cmdbuf);
286 cmdbuf_pos = 0;
287 cmdbuf_len = 0;
288 edit_cmd_cb(edit_cb_ctx, cmdbuf);
289 printf("> ");
290 fflush(stdout);
291 }
292
293
294 static void free_completions(char **c)
295 {
296 int i;
297 if (c == NULL)
298 return;
299 for (i = 0; c[i]; i++)
300 os_free(c[i]);
301 os_free(c);
302 }
303
304
305 static int filter_strings(char **c, char *str, size_t len)
306 {
307 int i, j;
308
309 for (i = 0, j = 0; c[j]; j++) {
310 if (os_strncasecmp(c[j], str, len) == 0) {
311 if (i != j) {
312 c[i] = c[j];
313 c[j] = NULL;
314 }
315 i++;
316 } else {
317 os_free(c[j]);
318 c[j] = NULL;
319 }
320 }
321 c[i] = NULL;
322 return i;
323 }
324
325
326 static int common_len(const char *a, const char *b)
327 {
328 int len = 0;
329 while (a[len] && a[len] == b[len])
330 len++;
331 return len;
332 }
333
334
335 static int max_common_length(char **c)
336 {
337 int len, i;
338
339 len = os_strlen(c[0]);
340 for (i = 1; c[i]; i++) {
341 int same = common_len(c[0], c[i]);
342 if (same < len)
343 len = same;
344 }
345
346 return len;
347 }
348
349
350 static void complete(int list)
351 {
352 char **c;
353 int i, len, count;
354 int start, end;
355 int room, plen, add_space;
356
357 if (edit_completion_cb == NULL)
358 return;
359
360 cmdbuf[cmdbuf_len] = '\0';
361 c = edit_completion_cb(edit_cb_ctx, cmdbuf, cmdbuf_pos);
362 if (c == NULL)
363 return;
364
365 end = cmdbuf_pos;
366 start = end;
367 while (start > 0 && cmdbuf[start - 1] != ' ')
368 start--;
369 plen = end - start;
370
371 count = filter_strings(c, &cmdbuf[start], plen);
372 if (count == 0) {
373 free_completions(c);
374 return;
375 }
376
377 len = max_common_length(c);
378 if (len <= plen) {
379 if (list) {
380 edit_clear_line();
381 printf("\r");
382 for (i = 0; c[i]; i++)
383 printf("%s%s", i > 0 ? " " : "", c[i]);
384 printf("\n");
385 edit_redraw();
386 }
387 free_completions(c);
388 return;
389 }
390 len -= plen;
391
392 room = sizeof(cmdbuf) - 1 - cmdbuf_len;
393 if (room < len)
394 len = room;
395 add_space = count == 1 && len < room;
396
397 os_memmove(cmdbuf + cmdbuf_pos + len, cmdbuf + cmdbuf_pos,
398 cmdbuf_len - cmdbuf_pos + add_space);
399 os_memcpy(&cmdbuf[cmdbuf_pos - plen], c[0], plen + len);
400 if (add_space)
401 cmdbuf[cmdbuf_pos + len] = ' ';
402
403 cmdbuf_pos += len + add_space;
404 cmdbuf_len += len + add_space;
405
406 edit_redraw();
407
408 free_completions(c);
409 }
410
411
412 static void edit_read_char(int sock, void *eloop_ctx, void *sock_ctx)
413 {
414 int c;
415 unsigned char buf[1];
416 int res;
417 static int esc = -1;
418 static char esc_buf[6];
419 static int last_tab = 0;
420
421 res = read(sock, buf, 1);
422 if (res < 0)
423 perror("read");
424 if (res <= 0) {
425 edit_eof_cb(edit_cb_ctx);
426 return;
427 }
428 c = buf[0];
429 if (c != 9)
430 last_tab = 0;
431
432 if (esc >= 0) {
433 if (esc == 5) {
434 printf("{ESC%s}[0]\n", esc_buf);
435 edit_redraw();
436 esc = -1;
437 } else {
438 esc_buf[esc++] = c;
439 esc_buf[esc] = '\0';
440 if (esc == 1)
441 return;
442 }
443 }
444
445 if (esc == 2 && esc_buf[0] == '[' && c >= 'A' && c <= 'Z') {
446 switch (c) {
447 case 'A': /* up */
448 history_prev();
449 break;
450 case 'B': /* down */
451 history_next();
452 break;
453 case 'C': /* right */
454 move_right();
455 break;
456 case 'D': /* left */
457 move_left();
458 break;
459 default:
460 printf("{ESC%s}[1]\n", esc_buf);
461 edit_redraw();
462 break;
463 }
464 esc = -1;
465 return;
466 }
467
468 if (esc > 1 && esc_buf[0] == '[') {
469 if ((c >= '0' && c <= '9') || c == ';')
470 return;
471
472 if (esc_buf[1] == '1' && esc_buf[2] == ';' &&
473 esc_buf[3] == '5') {
474 switch (esc_buf[4]) {
475 case 'A': /* Ctrl-Up */
476 case 'B': /* Ctrl-Down */
477 break;
478 case 'C': /* Ctrl-Right */
479 move_word_right();
480 break;
481 case 'D': /* Ctrl-Left */
482 move_word_left();
483 break;
484 default:
485 printf("{ESC%s}[2]\n", esc_buf);
486 edit_redraw();
487 break;
488 }
489 esc = -1;
490 return;
491 }
492
493 switch (c) {
494 case '~':
495 switch (atoi(&esc_buf[1])) {
496 case 2: /* Insert */
497 break;
498 case 3: /* Delete */
499 delete_current();
500 break;
501 case 5: /* Page Up */
502 case 6: /* Page Down */
503 case 15: /* F5 */
504 case 17: /* F6 */
505 case 18: /* F7 */
506 case 19: /* F8 */
507 case 20: /* F9 */
508 case 21: /* F10 */
509 case 23: /* F11 */
510 case 24: /* F12 */
511 break;
512 default:
513 printf("{ESC%s}[3]\n", esc_buf);
514 edit_redraw();
515 break;
516 }
517 break;
518 default:
519 printf("{ESC%s}[4]\n", esc_buf);
520 edit_redraw();
521 break;
522 }
523
524 esc = -1;
525 return;
526 }
527
528 if (esc > 1 && esc_buf[0] == 'O') {
529 switch (esc_buf[1]) {
530 case 'F': /* end */
531 move_end();
532 break;
533 case 'H': /* home */
534 move_start();
535 break;
536 case 'P': /* F1 */
537 history_debug_dump();
538 break;
539 case 'Q': /* F2 */
540 case 'R': /* F3 */
541 case 'S': /* F4 */
542 break;
543 default:
544 printf("{ESC%s}[5]\n", esc_buf);
545 edit_redraw();
546 break;
547 }
548 esc = -1;
549 return;
550 }
551
552 if (esc > 1) {
553 printf("{ESC%s}[6]\n", esc_buf);
554 edit_redraw();
555 esc = -1;
556 return;
557 }
558
559 switch (c) {
560 case 1: /* ^A */
561 move_start();
562 break;
563 case 4: /* ^D */
564 if (cmdbuf_len > 0) {
565 delete_current();
566 return;
567 }
568 printf("\n");
569 edit_eof_cb(edit_cb_ctx);
570 break;
571 case 5: /* ^E */
572 move_end();
573 break;
574 case 8: /* ^H = BS */
575 delete_left();
576 break;
577 case 9: /* ^I = TAB */
578 complete(last_tab);
579 last_tab = 1;
580 break;
581 case 10: /* NL */
582 case 13: /* CR */
583 process_cmd();
584 break;
585 case 11: /* ^K */
586 clear_right();
587 break;
588 case 12: /* ^L */
589 edit_clear_line();
590 edit_redraw();
591 break;
592 case 14: /* ^N */
593 history_next();
594 break;
595 case 16: /* ^P */
596 history_prev();
597 break;
598 case 18: /* ^R */
599 /* TODO: search history */
600 break;
601 case 21: /* ^U */
602 clear_left();
603 break;
604 case 23: /* ^W */
605 delete_word();
606 break;
607 case 27: /* ESC */
608 esc = 0;
609 break;
610 case 127: /* DEL */
611 delete_left();
612 break;
613 default:
614 insert_char(c);
615 break;
616 }
617 }
618
619
620 int edit_init(void (*cmd_cb)(void *ctx, char *cmd),
621 void (*eof_cb)(void *ctx),
622 void *ctx)
623 {
624 os_memset(history_buf, 0, sizeof(history_buf));
625
626 edit_cb_ctx = ctx;
627 edit_cmd_cb = cmd_cb;
628 edit_eof_cb = eof_cb;
629
630 tcgetattr(STDIN_FILENO, &prevt);
631 newt = prevt;
632 newt.c_lflag &= ~(ICANON | ECHO);
633 tcsetattr(STDIN_FILENO, TCSANOW, &newt);
634
635 eloop_register_read_sock(STDIN_FILENO, edit_read_char, NULL, NULL);
636
637 printf("> ");
638 fflush(stdout);
639
640 return 0;
641 }
642
643
644 void edit_deinit(void)
645 {
646 eloop_unregister_read_sock(STDIN_FILENO);
647 tcsetattr(STDIN_FILENO, TCSANOW, &prevt);
648 }
649
650
651 void edit_redraw(void)
652 {
653 char tmp;
654 cmdbuf[cmdbuf_len] = '\0';
655 printf("\r> %s", cmdbuf);
656 if (cmdbuf_pos != cmdbuf_len) {
657 tmp = cmdbuf[cmdbuf_pos];
658 cmdbuf[cmdbuf_pos] = '\0';
659 printf("\r> %s", cmdbuf);
660 cmdbuf[cmdbuf_pos] = tmp;
661 }
662 fflush(stdout);
663 }
664
665
666 void edit_set_filter_history_cb(int (*cb)(void *ctx, const char *cmd))
667 {
668 }
669
670
671 void edit_set_completion_cb(char ** (*cb)(void *ctx, const char *cmd, int pos))
672 {
673 edit_completion_cb = cb;
674 }