Add cli_ prefix to readline functions
[oweals/u-boot.git] / common / cli_readline.c
1 /*
2  * (C) Copyright 2000
3  * Wolfgang Denk, DENX Software Engineering, wd@denx.de.
4  *
5  * Add to readline cmdline-editing by
6  * (C) Copyright 2005
7  * JinHua Luo, GuangDong Linux Center, <luo.jinhua@gd-linux.com>
8  *
9  * SPDX-License-Identifier:     GPL-2.0+
10  */
11
12 #include <common.h>
13 #include <cli.h>
14 #include <watchdog.h>
15
16 DECLARE_GLOBAL_DATA_PTR;
17
18 static const char erase_seq[] = "\b \b";        /* erase sequence */
19 static const char   tab_seq[] = "        ";     /* used to expand TABs */
20
21 #ifdef CONFIG_BOOT_RETRY_TIME
22 static uint64_t endtime;      /* must be set, default is instant timeout */
23 static int      retry_time = -1; /* -1 so can call readline before main_loop */
24 #endif
25
26 char console_buffer[CONFIG_SYS_CBSIZE + 1];     /* console I/O buffer   */
27
28 #ifndef CONFIG_BOOT_RETRY_MIN
29 #define CONFIG_BOOT_RETRY_MIN CONFIG_BOOT_RETRY_TIME
30 #endif
31
32 static char *delete_char (char *buffer, char *p, int *colp, int *np, int plen)
33 {
34         char *s;
35
36         if (*np == 0)
37                 return p;
38
39         if (*(--p) == '\t') {           /* will retype the whole line */
40                 while (*colp > plen) {
41                         puts(erase_seq);
42                         (*colp)--;
43                 }
44                 for (s = buffer; s < p; ++s) {
45                         if (*s == '\t') {
46                                 puts(tab_seq + ((*colp) & 07));
47                                 *colp += 8 - ((*colp) & 07);
48                         } else {
49                                 ++(*colp);
50                                 putc(*s);
51                         }
52                 }
53         } else {
54                 puts(erase_seq);
55                 (*colp)--;
56         }
57         (*np)--;
58
59         return p;
60 }
61
62 #ifdef CONFIG_CMDLINE_EDITING
63
64 /*
65  * cmdline-editing related codes from vivi.
66  * Author: Janghoon Lyu <nandy@mizi.com>
67  */
68
69 #define putnstr(str, n) printf("%.*s", (int)n, str)
70
71 #define CTL_CH(c)               ((c) - 'a' + 1)
72 #define CTL_BACKSPACE           ('\b')
73 #define DEL                     ((char)255)
74 #define DEL7                    ((char)127)
75 #define CREAD_HIST_CHAR         ('!')
76
77 #define getcmd_putch(ch)        putc(ch)
78 #define getcmd_getch()          getc()
79 #define getcmd_cbeep()          getcmd_putch('\a')
80
81 #define HIST_MAX                20
82 #define HIST_SIZE               CONFIG_SYS_CBSIZE
83
84 static int hist_max;
85 static int hist_add_idx;
86 static int hist_cur = -1;
87 static unsigned hist_num;
88
89 static char *hist_list[HIST_MAX];
90 static char hist_lines[HIST_MAX][HIST_SIZE + 1];        /* Save room for NULL */
91
92 #define add_idx_minus_one() ((hist_add_idx == 0) ? hist_max : hist_add_idx-1)
93
94 static void hist_init(void)
95 {
96         int i;
97
98         hist_max = 0;
99         hist_add_idx = 0;
100         hist_cur = -1;
101         hist_num = 0;
102
103         for (i = 0; i < HIST_MAX; i++) {
104                 hist_list[i] = hist_lines[i];
105                 hist_list[i][0] = '\0';
106         }
107 }
108
109 static void cread_add_to_hist(char *line)
110 {
111         strcpy(hist_list[hist_add_idx], line);
112
113         if (++hist_add_idx >= HIST_MAX)
114                 hist_add_idx = 0;
115
116         if (hist_add_idx > hist_max)
117                 hist_max = hist_add_idx;
118
119         hist_num++;
120 }
121
122 static char *hist_prev(void)
123 {
124         char *ret;
125         int old_cur;
126
127         if (hist_cur < 0)
128                 return NULL;
129
130         old_cur = hist_cur;
131         if (--hist_cur < 0)
132                 hist_cur = hist_max;
133
134         if (hist_cur == hist_add_idx) {
135                 hist_cur = old_cur;
136                 ret = NULL;
137         } else {
138                 ret = hist_list[hist_cur];
139         }
140
141         return ret;
142 }
143
144 static char *hist_next(void)
145 {
146         char *ret;
147
148         if (hist_cur < 0)
149                 return NULL;
150
151         if (hist_cur == hist_add_idx)
152                 return NULL;
153
154         if (++hist_cur > hist_max)
155                 hist_cur = 0;
156
157         if (hist_cur == hist_add_idx)
158                 ret = "";
159         else
160                 ret = hist_list[hist_cur];
161
162         return ret;
163 }
164
165 #ifndef CONFIG_CMDLINE_EDITING
166 static void cread_print_hist_list(void)
167 {
168         int i;
169         unsigned long n;
170
171         n = hist_num - hist_max;
172
173         i = hist_add_idx + 1;
174         while (1) {
175                 if (i > hist_max)
176                         i = 0;
177                 if (i == hist_add_idx)
178                         break;
179                 printf("%s\n", hist_list[i]);
180                 n++;
181                 i++;
182         }
183 }
184 #endif /* CONFIG_CMDLINE_EDITING */
185
186 #define BEGINNING_OF_LINE() {                   \
187         while (num) {                           \
188                 getcmd_putch(CTL_BACKSPACE);    \
189                 num--;                          \
190         }                                       \
191 }
192
193 #define ERASE_TO_EOL() {                                \
194         if (num < eol_num) {                            \
195                 printf("%*s", (int)(eol_num - num), ""); \
196                 do {                                    \
197                         getcmd_putch(CTL_BACKSPACE);    \
198                 } while (--eol_num > num);              \
199         }                                               \
200 }
201
202 #define REFRESH_TO_EOL() {                      \
203         if (num < eol_num) {                    \
204                 wlen = eol_num - num;           \
205                 putnstr(buf + num, wlen);       \
206                 num = eol_num;                  \
207         }                                       \
208 }
209
210 static void cread_add_char(char ichar, int insert, unsigned long *num,
211                unsigned long *eol_num, char *buf, unsigned long len)
212 {
213         unsigned long wlen;
214
215         /* room ??? */
216         if (insert || *num == *eol_num) {
217                 if (*eol_num > len - 1) {
218                         getcmd_cbeep();
219                         return;
220                 }
221                 (*eol_num)++;
222         }
223
224         if (insert) {
225                 wlen = *eol_num - *num;
226                 if (wlen > 1)
227                         memmove(&buf[*num+1], &buf[*num], wlen-1);
228
229                 buf[*num] = ichar;
230                 putnstr(buf + *num, wlen);
231                 (*num)++;
232                 while (--wlen)
233                         getcmd_putch(CTL_BACKSPACE);
234         } else {
235                 /* echo the character */
236                 wlen = 1;
237                 buf[*num] = ichar;
238                 putnstr(buf + *num, wlen);
239                 (*num)++;
240         }
241 }
242
243 static void cread_add_str(char *str, int strsize, int insert,
244                           unsigned long *num, unsigned long *eol_num,
245                           char *buf, unsigned long len)
246 {
247         while (strsize--) {
248                 cread_add_char(*str, insert, num, eol_num, buf, len);
249                 str++;
250         }
251 }
252
253 static int cread_line(const char *const prompt, char *buf, unsigned int *len,
254                 int timeout)
255 {
256         unsigned long num = 0;
257         unsigned long eol_num = 0;
258         unsigned long wlen;
259         char ichar;
260         int insert = 1;
261         int esc_len = 0;
262         char esc_save[8];
263         int init_len = strlen(buf);
264         int first = 1;
265
266         if (init_len)
267                 cread_add_str(buf, init_len, 1, &num, &eol_num, buf, *len);
268
269         while (1) {
270 #ifdef CONFIG_BOOT_RETRY_TIME
271                 while (!tstc()) {       /* while no incoming data */
272                         if (retry_time >= 0 && get_ticks() > endtime)
273                                 return -2;      /* timed out */
274                         WATCHDOG_RESET();
275                 }
276 #endif
277                 if (first && timeout) {
278                         uint64_t etime = endtick(timeout);
279
280                         while (!tstc()) {       /* while no incoming data */
281                                 if (get_ticks() >= etime)
282                                         return -2;      /* timed out */
283                                 WATCHDOG_RESET();
284                         }
285                         first = 0;
286                 }
287
288                 ichar = getcmd_getch();
289
290                 if ((ichar == '\n') || (ichar == '\r')) {
291                         putc('\n');
292                         break;
293                 }
294
295                 /*
296                  * handle standard linux xterm esc sequences for arrow key, etc.
297                  */
298                 if (esc_len != 0) {
299                         if (esc_len == 1) {
300                                 if (ichar == '[') {
301                                         esc_save[esc_len] = ichar;
302                                         esc_len = 2;
303                                 } else {
304                                         cread_add_str(esc_save, esc_len,
305                                                       insert, &num, &eol_num,
306                                                       buf, *len);
307                                         esc_len = 0;
308                                 }
309                                 continue;
310                         }
311
312                         switch (ichar) {
313                         case 'D':       /* <- key */
314                                 ichar = CTL_CH('b');
315                                 esc_len = 0;
316                                 break;
317                         case 'C':       /* -> key */
318                                 ichar = CTL_CH('f');
319                                 esc_len = 0;
320                                 break;  /* pass off to ^F handler */
321                         case 'H':       /* Home key */
322                                 ichar = CTL_CH('a');
323                                 esc_len = 0;
324                                 break;  /* pass off to ^A handler */
325                         case 'A':       /* up arrow */
326                                 ichar = CTL_CH('p');
327                                 esc_len = 0;
328                                 break;  /* pass off to ^P handler */
329                         case 'B':       /* down arrow */
330                                 ichar = CTL_CH('n');
331                                 esc_len = 0;
332                                 break;  /* pass off to ^N handler */
333                         default:
334                                 esc_save[esc_len++] = ichar;
335                                 cread_add_str(esc_save, esc_len, insert,
336                                               &num, &eol_num, buf, *len);
337                                 esc_len = 0;
338                                 continue;
339                         }
340                 }
341
342                 switch (ichar) {
343                 case 0x1b:
344                         if (esc_len == 0) {
345                                 esc_save[esc_len] = ichar;
346                                 esc_len = 1;
347                         } else {
348                                 puts("impossible condition #876\n");
349                                 esc_len = 0;
350                         }
351                         break;
352
353                 case CTL_CH('a'):
354                         BEGINNING_OF_LINE();
355                         break;
356                 case CTL_CH('c'):       /* ^C - break */
357                         *buf = '\0';    /* discard input */
358                         return -1;
359                 case CTL_CH('f'):
360                         if (num < eol_num) {
361                                 getcmd_putch(buf[num]);
362                                 num++;
363                         }
364                         break;
365                 case CTL_CH('b'):
366                         if (num) {
367                                 getcmd_putch(CTL_BACKSPACE);
368                                 num--;
369                         }
370                         break;
371                 case CTL_CH('d'):
372                         if (num < eol_num) {
373                                 wlen = eol_num - num - 1;
374                                 if (wlen) {
375                                         memmove(&buf[num], &buf[num+1], wlen);
376                                         putnstr(buf + num, wlen);
377                                 }
378
379                                 getcmd_putch(' ');
380                                 do {
381                                         getcmd_putch(CTL_BACKSPACE);
382                                 } while (wlen--);
383                                 eol_num--;
384                         }
385                         break;
386                 case CTL_CH('k'):
387                         ERASE_TO_EOL();
388                         break;
389                 case CTL_CH('e'):
390                         REFRESH_TO_EOL();
391                         break;
392                 case CTL_CH('o'):
393                         insert = !insert;
394                         break;
395                 case CTL_CH('x'):
396                 case CTL_CH('u'):
397                         BEGINNING_OF_LINE();
398                         ERASE_TO_EOL();
399                         break;
400                 case DEL:
401                 case DEL7:
402                 case 8:
403                         if (num) {
404                                 wlen = eol_num - num;
405                                 num--;
406                                 memmove(&buf[num], &buf[num+1], wlen);
407                                 getcmd_putch(CTL_BACKSPACE);
408                                 putnstr(buf + num, wlen);
409                                 getcmd_putch(' ');
410                                 do {
411                                         getcmd_putch(CTL_BACKSPACE);
412                                 } while (wlen--);
413                                 eol_num--;
414                         }
415                         break;
416                 case CTL_CH('p'):
417                 case CTL_CH('n'):
418                 {
419                         char *hline;
420
421                         esc_len = 0;
422
423                         if (ichar == CTL_CH('p'))
424                                 hline = hist_prev();
425                         else
426                                 hline = hist_next();
427
428                         if (!hline) {
429                                 getcmd_cbeep();
430                                 continue;
431                         }
432
433                         /* nuke the current line */
434                         /* first, go home */
435                         BEGINNING_OF_LINE();
436
437                         /* erase to end of line */
438                         ERASE_TO_EOL();
439
440                         /* copy new line into place and display */
441                         strcpy(buf, hline);
442                         eol_num = strlen(buf);
443                         REFRESH_TO_EOL();
444                         continue;
445                 }
446 #ifdef CONFIG_AUTO_COMPLETE
447                 case '\t': {
448                         int num2, col;
449
450                         /* do not autocomplete when in the middle */
451                         if (num < eol_num) {
452                                 getcmd_cbeep();
453                                 break;
454                         }
455
456                         buf[num] = '\0';
457                         col = strlen(prompt) + eol_num;
458                         num2 = num;
459                         if (cmd_auto_complete(prompt, buf, &num2, &col)) {
460                                 col = num2 - num;
461                                 num += col;
462                                 eol_num += col;
463                         }
464                         break;
465                 }
466 #endif
467                 default:
468                         cread_add_char(ichar, insert, &num, &eol_num, buf,
469                                        *len);
470                         break;
471                 }
472         }
473         *len = eol_num;
474         buf[eol_num] = '\0';    /* lose the newline */
475
476         if (buf[0] && buf[0] != CREAD_HIST_CHAR)
477                 cread_add_to_hist(buf);
478         hist_cur = hist_add_idx;
479
480         return 0;
481 }
482
483 #endif /* CONFIG_CMDLINE_EDITING */
484
485 /****************************************************************************/
486
487 int cli_readline(const char *const prompt)
488 {
489         /*
490          * If console_buffer isn't 0-length the user will be prompted to modify
491          * it instead of entering it from scratch as desired.
492          */
493         console_buffer[0] = '\0';
494
495         return cli_readline_into_buffer(prompt, console_buffer, 0);
496 }
497
498
499 int cli_readline_into_buffer(const char *const prompt, char *buffer,
500                              int timeout)
501 {
502         char *p = buffer;
503 #ifdef CONFIG_CMDLINE_EDITING
504         unsigned int len = CONFIG_SYS_CBSIZE;
505         int rc;
506         static int initted;
507
508         /*
509          * History uses a global array which is not
510          * writable until after relocation to RAM.
511          * Revert to non-history version if still
512          * running from flash.
513          */
514         if (gd->flags & GD_FLG_RELOC) {
515                 if (!initted) {
516                         hist_init();
517                         initted = 1;
518                 }
519
520                 if (prompt)
521                         puts(prompt);
522
523                 rc = cread_line(prompt, p, &len, timeout);
524                 return rc < 0 ? rc : len;
525
526         } else {
527 #endif  /* CONFIG_CMDLINE_EDITING */
528         char *p_buf = p;
529         int     n = 0;                          /* buffer index         */
530         int     plen = 0;                       /* prompt length        */
531         int     col;                            /* output column cnt    */
532         char    c;
533
534         /* print prompt */
535         if (prompt) {
536                 plen = strlen(prompt);
537                 puts(prompt);
538         }
539         col = plen;
540
541         for (;;) {
542 #ifdef CONFIG_BOOT_RETRY_TIME
543                 while (!tstc()) {       /* while no incoming data */
544                         if (retry_time >= 0 && get_ticks() > endtime)
545                                 return -2;      /* timed out */
546                         WATCHDOG_RESET();
547                 }
548 #endif
549                 WATCHDOG_RESET();       /* Trigger watchdog, if needed */
550
551 #ifdef CONFIG_SHOW_ACTIVITY
552                 while (!tstc()) {
553                         show_activity(0);
554                         WATCHDOG_RESET();
555                 }
556 #endif
557                 c = getc();
558
559                 /*
560                  * Special character handling
561                  */
562                 switch (c) {
563                 case '\r':                      /* Enter                */
564                 case '\n':
565                         *p = '\0';
566                         puts("\r\n");
567                         return p - p_buf;
568
569                 case '\0':                      /* nul                  */
570                         continue;
571
572                 case 0x03:                      /* ^C - break           */
573                         p_buf[0] = '\0';        /* discard input */
574                         return -1;
575
576                 case 0x15:                      /* ^U - erase line      */
577                         while (col > plen) {
578                                 puts(erase_seq);
579                                 --col;
580                         }
581                         p = p_buf;
582                         n = 0;
583                         continue;
584
585                 case 0x17:                      /* ^W - erase word      */
586                         p = delete_char(p_buf, p, &col, &n, plen);
587                         while ((n > 0) && (*p != ' '))
588                                 p = delete_char(p_buf, p, &col, &n, plen);
589                         continue;
590
591                 case 0x08:                      /* ^H  - backspace      */
592                 case 0x7F:                      /* DEL - backspace      */
593                         p = delete_char(p_buf, p, &col, &n, plen);
594                         continue;
595
596                 default:
597                         /*
598                          * Must be a normal character then
599                          */
600                         if (n < CONFIG_SYS_CBSIZE-2) {
601                                 if (c == '\t') {        /* expand TABs */
602 #ifdef CONFIG_AUTO_COMPLETE
603                                         /*
604                                          * if auto completion triggered just
605                                          * continue
606                                          */
607                                         *p = '\0';
608                                         if (cmd_auto_complete(prompt,
609                                                               console_buffer,
610                                                               &n, &col)) {
611                                                 p = p_buf + n;  /* reset */
612                                                 continue;
613                                         }
614 #endif
615                                         puts(tab_seq + (col & 07));
616                                         col += 8 - (col & 07);
617                                 } else {
618                                         char buf[2];
619
620                                         /*
621                                          * Echo input using puts() to force an
622                                          * LCD flush if we are using an LCD
623                                          */
624                                         ++col;
625                                         buf[0] = c;
626                                         buf[1] = '\0';
627                                         puts(buf);
628                                 }
629                                 *p++ = c;
630                                 ++n;
631                         } else {                        /* Buffer full */
632                                 putc('\a');
633                         }
634                 }
635         }
636 #ifdef CONFIG_CMDLINE_EDITING
637         }
638 #endif
639 }
640
641 #ifdef CONFIG_BOOT_RETRY_TIME
642 /***************************************************************************
643  * initialize command line timeout
644  */
645 void init_cmd_timeout(void)
646 {
647         char *s = getenv("bootretry");
648
649         if (s != NULL)
650                 retry_time = (int)simple_strtol(s, NULL, 10);
651         else
652                 retry_time =  CONFIG_BOOT_RETRY_TIME;
653
654         if (retry_time >= 0 && retry_time < CONFIG_BOOT_RETRY_MIN)
655                 retry_time = CONFIG_BOOT_RETRY_MIN;
656 }
657
658 /***************************************************************************
659  * reset command line timeout to retry_time seconds
660  */
661 void reset_cmd_timeout(void)
662 {
663         endtime = endtick(retry_time);
664 }
665
666 void bootretry_dont_retry(void)
667 {
668         retry_time = -1;
669 }
670
671 #endif