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