2 * Copyright (C) 2013 Intel Corporation
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
29 * Character sequences recognized by code in this file
30 * Leading ESC 0x1B is not included
32 #define SEQ_INSERT "[2~"
33 #define SEQ_DELETE "[3~"
36 #define SEQ_PGUP "[5~"
37 #define SEQ_PGDOWN "[6~"
39 #define SEQ_RIGHT "[C"
45 #define SEQ_CLEFT "[1;5D"
46 #define SEQ_CRIGHT "[1;5C"
47 #define SEQ_CUP "[1;5A"
48 #define SEQ_CDOWN "[1;5B"
49 #define SEQ_SLEFT "[1;2D"
50 #define SEQ_SRIGHT "[1;2C"
51 #define SEQ_SUP "[1;2A"
52 #define SEQ_SDOWN "[1;2B"
53 #define SEQ_MLEFT "[1;3D"
54 #define SEQ_MRIGHT "[1;3C"
55 #define SEQ_MUP "[1;3A"
56 #define SEQ_MDOWN "[1;3B"
58 #define KEY_SEQUENCE(k) { KEY_##k, SEQ_##k }
59 struct ansii_sequence {
64 /* Table connects single int key codes with character sequences */
65 static const struct ansii_sequence ansii_sequnces[] = {
94 #define KEY_SEQUNCE_NOT_FINISHED -1
99 #define isseqence(c) ((c) == 0x1B)
102 * Number of characters that consist of ANSI sequence
103 * Should not be less then longest string in ansi_sequences
105 #define MAX_ASCII_SEQUENCE 10
107 static char current_sequence[MAX_ASCII_SEQUENCE];
108 static int current_sequence_len = -1;
110 /* single line typed by user goes here */
111 static char line_buf[LINE_BUF_MAX];
112 /* index of cursor in input line */
113 static int line_buf_ix = 0;
114 /* current length of input line */
115 static int line_len = 0;
117 /* line index used for fetching lines from history */
118 static int line_index = 0;
120 static char prompt_buf[10] = "> ";
121 static const char *const noprompt = "";
122 static const char *current_prompt = prompt_buf;
123 static const char *prompt = prompt_buf;
125 * Moves cursor to right or left
127 * n - positive - moves cursor right
128 * n - negative - moves cursor left
130 static void terminal_move_cursor(int n)
136 printf("%*s", n, line_buf + line_buf_ix);
140 /* Draw command line */
141 void terminal_draw_command_line(void)
144 * this needs to be checked here since line_buf is not cleared
145 * before parsing event though line_len and line_buf_ix are
148 printf("%s%s", prompt, line_buf);
150 printf("%s", prompt);
152 /* move cursor to it's place */
153 terminal_move_cursor(line_buf_ix - line_len);
156 /* inserts string into command line at cursor position */
157 void terminal_insert_into_command_line(const char *p)
161 if (line_len == line_buf_ix) {
164 line_len = line_len + len;
165 line_buf_ix = line_len;
167 memmove(line_buf + line_buf_ix + len,
168 line_buf + line_buf_ix, line_len - line_buf_ix + 1);
169 memmove(line_buf + line_buf_ix, p, len);
170 printf("%s", line_buf + line_buf_ix);
173 terminal_move_cursor(line_buf_ix - line_len);
177 /* Prints string and redraws command line */
178 int terminal_print(const char *format, ...)
183 va_start(args, format);
185 ret = terminal_vprint(format, args);
191 /* Prints string and redraws command line */
192 int terminal_vprint(const char *format, va_list args)
196 printf("\r%*s\r", (int) line_len + 1, " ");
198 ret = vprintf(format, args);
200 terminal_draw_command_line();
208 * Call this when text in line_buf was changed
209 * and line needs to be redrawn
211 static void terminal_line_replaced(void)
213 int len = strlen(line_buf);
215 /* line is shorter that previous */
216 if (len < line_len) {
217 /* if new line is shorter move cursor to end of new end */
218 while (line_buf_ix > len) {
223 /* If cursor was not at the end, move it to the end */
224 if (line_buf_ix < line_len)
225 printf("%.*s", line_len - line_buf_ix,
226 line_buf + line_buf_ix);
227 /* over write end of previous line */
228 while (line_len >= len++)
233 printf("\r%s%s", prompt, line_buf);
234 /* set up indexes to new line */
235 line_len = strlen(line_buf);
236 line_buf_ix = line_len;
240 static void terminal_clear_line(void)
243 terminal_line_replaced();
246 static void terminal_clear_screen(void)
252 printf("\x1b[2J\x1b[1;1H%s", prompt);
255 static void terminal_delete_char(void)
257 /* delete character under cursor if not at the very end */
258 if (line_buf_ix >= line_len)
261 * Prepare buffer with one character missing
262 * trailing 0 is moved
265 memmove(line_buf + line_buf_ix, line_buf + line_buf_ix + 1,
266 line_len - line_buf_ix + 1);
267 /* print rest of line from current cursor position */
268 printf("%s \b", line_buf + line_buf_ix);
269 /* move back cursor */
270 terminal_move_cursor(line_buf_ix - line_len);
274 * Function tries to replace current line with specified line in history
275 * new_line_index - new line to show, -1 to show oldest
277 static void terminal_get_line_from_history(int new_line_index)
279 new_line_index = history_get_line(new_line_index,
280 line_buf, LINE_BUF_MAX);
282 if (new_line_index >= 0) {
283 terminal_line_replaced();
284 line_index = new_line_index;
289 * Function searches history back or forward for command line that starts
290 * with characters up to cursor position
292 * back - true - searches backward
293 * back - false - searches forward (more recent commands)
295 static void terminal_match_hitory(bool back)
297 char buf[line_buf_ix + 1];
299 int matching_line = -1;
300 int dir = back ? 1 : -1;
302 line = line_index + dir;
303 while (matching_line == -1 && line >= 0) {
306 new_line_index = history_get_line(line, buf, line_buf_ix + 1);
307 if (new_line_index < 0)
310 if (0 == strncmp(line_buf, buf, line_buf_ix))
311 matching_line = line;
315 if (matching_line >= 0) {
316 int pos = line_buf_ix;
317 terminal_get_line_from_history(matching_line);
318 /* move back to cursor position to original place */
320 terminal_move_cursor(pos - line_len);
325 * Converts terminal character sequences to single value representing
328 static int terminal_convert_sequence(int c)
332 /* Not in sequence yet? */
333 if (current_sequence_len == -1) {
334 /* Is ansi sequence detected by 0x1B ? */
336 current_sequence_len++;
337 return KEY_SEQUNCE_NOT_FINISHED;
343 /* Inside sequence */
344 current_sequence[current_sequence_len++] = c;
345 current_sequence[current_sequence_len] = '\0';
346 for (i = 0; ansii_sequnces[i].code; ++i) {
347 /* Matches so far? */
348 if (0 != strncmp(current_sequence, ansii_sequnces[i].sequence,
349 current_sequence_len))
352 /* Matches as a whole? */
353 if (ansii_sequnces[i].sequence[current_sequence_len] == 0) {
354 current_sequence_len = -1;
355 return ansii_sequnces[i].code;
358 /* partial match (not whole sequence yet) */
359 return KEY_SEQUNCE_NOT_FINISHED;
362 terminal_print("ansi char 0x%X %c\n", c);
364 * Sequence does not match
365 * mark that no in sequence any more, return char
367 current_sequence_len = -1;
371 typedef void (*terminal_action)(int c, line_callback process_line);
373 #define TERMINAL_ACTION(n) \
374 static void n(int c, void (*process_line)(char *line))
376 TERMINAL_ACTION(terminal_action_null)
380 /* Mapping between keys and function */
383 terminal_action func;
386 int action_keys[] = {
387 KEY_SEQUNCE_NOT_FINISHED,
422 #define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0])))
425 * current_actions holds all recognizable kes and actions for them
426 * additional element (index 0) is used for default action
428 static KeyAction current_actions[NELEM(action_keys) + 1];
430 /* KeyAction comparator by key, for qsort and bsearch */
431 static int KeyActionKeyCompare(const void *a, const void *b)
433 return ((const KeyAction *) a)->key - ((const KeyAction *) b)->key;
436 /* Find action by key, NULL if no action for this key */
437 static KeyAction *terminal_get_action(int key)
439 KeyAction a = { .key = key };
441 return bsearch(&a, current_actions + 1, NELEM(action_keys), sizeof(a),
442 KeyActionKeyCompare);
445 /* Sets new set of actions to use */
446 static void terminal_set_actions(const KeyAction *actions)
450 /* Make map with empty function for every key */
451 for (i = 0; i < NELEM(action_keys); ++i) {
453 * + 1 due to 0 index reserved for default action that is
454 * called for non mapped key
456 current_actions[i + 1].key = action_keys[i];
457 current_actions[i + 1].func = terminal_action_null;
460 /* Sort action from 1 (index 0 - default action) */
461 qsort(current_actions + 1, NELEM(action_keys), sizeof(KeyAction),
462 KeyActionKeyCompare);
463 /* Set default action (first in array) */
464 current_actions[0] = *actions++;
466 /* Copy rest of actions into their places */
467 for (; actions->key; ++actions) {
468 KeyAction *place = terminal_get_action(actions->key);
471 place->func = actions->func;
475 TERMINAL_ACTION(terminal_action_left)
477 /* if not at the beginning move to previous character */
478 if (line_buf_ix <= 0)
481 terminal_move_cursor(-1);
484 TERMINAL_ACTION(terminal_action_right)
487 * If not at the end, just print current character
488 * and modify position
490 if (line_buf_ix < line_len)
491 putchar(line_buf[line_buf_ix++]);
494 TERMINAL_ACTION(terminal_action_home)
496 /* move to beginning of line and update position */
497 printf("\r%s", prompt);
501 TERMINAL_ACTION(terminal_action_end)
503 /* if not at the end of line */
504 if (line_buf_ix < line_len) {
505 /* print everything from cursor */
506 printf("%s", line_buf + line_buf_ix);
507 /* just modify current position */
508 line_buf_ix = line_len;
512 TERMINAL_ACTION(terminal_action_del)
514 terminal_delete_char();
517 TERMINAL_ACTION(terminal_action_word_left)
523 * Are we at the beginning of line?
525 if (line_buf_ix <= 0)
528 old_pos = line_buf_ix;
530 /* skip spaces left */
531 while (line_buf_ix && isspace(line_buf[line_buf_ix]))
534 /* skip all non spaces to the left */
535 while (line_buf_ix > 0 &&
536 !isspace(line_buf[line_buf_ix - 1]))
539 /* move cursor to new position */
540 terminal_move_cursor(line_buf_ix - old_pos);
543 TERMINAL_ACTION(terminal_action_word_right)
549 * are we at the end of line?
551 if (line_buf_ix >= line_len)
554 old_pos = line_buf_ix;
555 /* skip all spaces */
556 while (line_buf_ix < line_len && isspace(line_buf[line_buf_ix]))
559 /* skip all non spaces */
560 while (line_buf_ix < line_len && !isspace(line_buf[line_buf_ix]))
563 * Move cursor to right by printing text
564 * between old cursor and new
566 if (line_buf_ix > old_pos)
567 printf("%.*s", (int) (line_buf_ix - old_pos),
571 TERMINAL_ACTION(terminal_action_history_begin)
573 terminal_get_line_from_history(-1);
576 TERMINAL_ACTION(terminal_action_history_end)
579 terminal_get_line_from_history(0);
582 TERMINAL_ACTION(terminal_action_history_up)
584 terminal_get_line_from_history(line_index + 1);
587 TERMINAL_ACTION(terminal_action_history_down)
590 terminal_get_line_from_history(line_index - 1);
593 TERMINAL_ACTION(terminal_action_tab)
596 process_tab(line_buf, line_buf_ix);
600 TERMINAL_ACTION(terminal_action_backspace)
602 if (line_buf_ix <= 0)
605 if (line_buf_ix == line_len) {
607 line_len = --line_buf_ix;
608 line_buf[line_len] = 0;
613 memmove(line_buf + line_buf_ix,
614 line_buf + line_buf_ix + 1,
615 line_len - line_buf_ix + 1);
616 printf("%s \b", line_buf + line_buf_ix);
617 terminal_move_cursor(line_buf_ix - line_len);
621 TERMINAL_ACTION(terminal_action_find_history_forward)
623 /* Search history forward */
624 terminal_match_hitory(false);
627 TERMINAL_ACTION(terminal_action_find_history_backward)
629 /* Search history forward */
630 terminal_match_hitory(true);
633 TERMINAL_ACTION(terminal_action_ctrl_c)
635 terminal_clear_line();
638 TERMINAL_ACTION(terminal_action_ctrl_d)
641 terminal_delete_char();
648 TERMINAL_ACTION(terminal_action_clear_screen)
650 terminal_clear_screen();
653 TERMINAL_ACTION(terminal_action_enter)
656 * On new line add line to history
657 * forget history position
659 history_add_line(line_buf);
666 process_line(line_buf);
667 /* clear current line */
669 prompt = current_prompt;
670 printf("%s", prompt);
673 TERMINAL_ACTION(terminal_action_default)
675 char str[2] = { c, 0 };
679 * TODO: remove this print once all meaningful sequences
682 printf("char-0x%02x\n", c);
683 else if (line_buf_ix < LINE_BUF_MAX - 1)
684 terminal_insert_into_command_line(str);
687 /* Callback to call when user hit enter during prompt for */
688 static line_callback prompt_callback;
690 static KeyAction normal_actions[] = {
691 { 0, terminal_action_default },
692 { KEY_LEFT, terminal_action_left },
693 { KEY_RIGHT, terminal_action_right },
694 { KEY_HOME, terminal_action_home },
695 { KEY_END, terminal_action_end },
696 { KEY_DELETE, terminal_action_del },
697 { KEY_CLEFT, terminal_action_word_left },
698 { KEY_CRIGHT, terminal_action_word_right },
699 { KEY_SUP, terminal_action_history_begin },
700 { KEY_SDOWN, terminal_action_history_end },
701 { KEY_UP, terminal_action_history_up },
702 { KEY_DOWN, terminal_action_history_down },
703 { '\t', terminal_action_tab },
704 { KEY_BACKSPACE, terminal_action_backspace },
705 { KEY_M_n, terminal_action_find_history_forward },
706 { KEY_M_p, terminal_action_find_history_backward },
707 { KEY_C_C, terminal_action_ctrl_c },
708 { KEY_C_D, terminal_action_ctrl_d },
709 { KEY_C_L, terminal_action_clear_screen },
710 { '\r', terminal_action_enter },
711 { '\n', terminal_action_enter },
715 TERMINAL_ACTION(terminal_action_answer)
719 terminal_set_actions(normal_actions);
720 /* Restore default prompt */
721 current_prompt = prompt_buf;
723 /* No prompt for prints */
727 /* Call user function with what was typed */
728 prompt_callback(line_buf);
731 /* promot_callback could change current_prompt */
732 prompt = current_prompt;
734 printf("%s", prompt);
737 TERMINAL_ACTION(terminal_action_prompt_ctrl_c)
744 current_prompt = prompt_buf;
745 prompt = current_prompt;
746 terminal_set_actions(normal_actions);
748 printf("%s", prompt);
751 static KeyAction prompt_actions[] = {
752 { 0, terminal_action_default },
753 { KEY_LEFT, terminal_action_left },
754 { KEY_RIGHT, terminal_action_right },
755 { KEY_HOME, terminal_action_home },
756 { KEY_END, terminal_action_end },
757 { KEY_DELETE, terminal_action_del },
758 { KEY_CLEFT, terminal_action_word_left },
759 { KEY_CRIGHT, terminal_action_word_right },
760 { KEY_BACKSPACE, terminal_action_backspace },
761 { KEY_C_C, terminal_action_prompt_ctrl_c },
762 { KEY_C_D, terminal_action_ctrl_d },
763 { '\r', terminal_action_answer },
764 { '\n', terminal_action_answer },
768 void terminal_process_char(int c, line_callback process_line)
772 c = terminal_convert_sequence(c);
774 /* Get action for this key */
775 a = terminal_get_action(c);
777 /* No action found, get default one */
779 a = ¤t_actions[0];
781 a->func(c, process_line);
785 void terminal_prompt_for(const char *s, line_callback process_line)
788 if (prompt != noprompt) {
790 terminal_clear_line();
792 prompt_callback = process_line;
793 terminal_set_actions(prompt_actions);
796 static struct termios origianl_tios;
798 static void terminal_cleanup(void)
800 tcsetattr(0, TCSANOW, &origianl_tios);
803 void terminal_setup(void)
807 terminal_set_actions(normal_actions);
809 tcgetattr(0, &origianl_tios);
810 tios = origianl_tios;
813 * Turn off echo since all editing is done by hand,
814 * Ctrl-c handled internally
816 tios.c_lflag &= ~(ICANON | ECHO | BRKINT | IGNBRK);
817 tcsetattr(0, TCSANOW, &tios);
819 /* Restore terminal at exit */
820 atexit(terminal_cleanup);
822 printf("%s", prompt);