123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680 |
- /**
- Author: Samoylov Eugene aka Helius (ghelius@gmail.com)
- BUGS and TODO:
- -- add echo_off feature
- -- rewrite history for use more than 256 byte buffer
- */
- #include <string.h>
- #include <ctype.h>
- #include <stdlib.h>
- #include "microrl.h"
- #ifdef _USE_LIBC_STDIO
- #include <stdio.h>
- #endif
- //#define DBG(...) fprintf(stderr, "\033[33m");fprintf(stderr,__VA_ARGS__);fprintf(stderr,"\033[0m");
- char * prompt_default = _PROMPT_DEFAULT;
- #ifdef _USE_HISTORY
- #ifdef _HISTORY_DEBUG
- //*****************************************************************************
- // print buffer content on screen
- static void print_hist (ring_history_t * pThis)
- {
- printf ("\n");
- for (int i = 0; i < _RING_HISTORY_LEN; i++) {
- if (i == pThis->begin)
- printf ("b");
- else
- printf (" ");
- }
- printf ("\n");
- for (int i = 0; i < _RING_HISTORY_LEN; i++) {
- if (isalpha(pThis->ring_buf[i]))
- printf ("%c", pThis->ring_buf[i]);
- else
- printf ("%d", pThis->ring_buf[i]);
- }
- printf ("\n");
- for (int i = 0; i < _RING_HISTORY_LEN; i++) {
- if (i == pThis->end)
- printf ("e");
- else
- printf (" ");
- }
- printf ("\n");
- }
- #endif
- //*****************************************************************************
- // remove older message from ring buffer
- static void hist_erase_older (ring_history_t * pThis)
- {
- int new_pos = pThis->begin + pThis->ring_buf [pThis->begin] + 1;
- if (new_pos >= _RING_HISTORY_LEN)
- new_pos = new_pos - _RING_HISTORY_LEN;
-
- pThis->begin = new_pos;
- }
- //*****************************************************************************
- // check space for new line, remove older while not space
- static int hist_is_space_for_new (ring_history_t * pThis, int len)
- {
- if (pThis->ring_buf [pThis->begin] == 0)
- return true;
- if (pThis->end >= pThis->begin) {
- if (_RING_HISTORY_LEN - pThis->end + pThis->begin - 1 > len)
- return true;
- } else {
- if (pThis->begin - pThis->end - 1> len)
- return true;
- }
- return false;
- }
- //*****************************************************************************
- // put line to ring buffer
- static void hist_save_line (ring_history_t * pThis, char * line, int len)
- {
- if (len > _RING_HISTORY_LEN - 2)
- return;
- while (!hist_is_space_for_new (pThis, len)) {
- hist_erase_older (pThis);
- }
- // if it's first line
- if (pThis->ring_buf [pThis->begin] == 0)
- pThis->ring_buf [pThis->begin] = len;
-
- // store line
- if (len < _RING_HISTORY_LEN-pThis->end-1)
- memcpy (pThis->ring_buf + pThis->end + 1, line, len);
- else {
- int part_len = _RING_HISTORY_LEN-pThis->end-1;
- memcpy (pThis->ring_buf + pThis->end + 1, line, part_len);
- memcpy (pThis->ring_buf, line + part_len, len - part_len);
- }
- pThis->ring_buf [pThis->end] = len;
- pThis->end = pThis->end + len + 1;
- if (pThis->end >= _RING_HISTORY_LEN)
- pThis->end -= _RING_HISTORY_LEN;
- pThis->ring_buf [pThis->end] = 0;
- pThis->cur = 0;
- #ifdef _HISTORY_DEBUG
- print_hist (pThis);
- #endif
- }
- //*****************************************************************************
- // copy saved line to 'line' and return size of line
- static int hist_restore_line (ring_history_t * pThis, char * line, int dir)
- {
- int cnt = 0;
- // count history record
- int header = pThis->begin;
- while (pThis->ring_buf [header] != 0) {
- header += pThis->ring_buf [header] + 1;
- if (header >= _RING_HISTORY_LEN)
- header -= _RING_HISTORY_LEN;
- cnt++;
- }
- if (dir == _HIST_UP) {
- if (cnt >= pThis->cur) {
- int header = pThis->begin;
- int j = 0;
- // found record for 'pThis->cur' index
- while ((pThis->ring_buf [header] != 0) && (cnt - j -1 != pThis->cur)) {
- header += pThis->ring_buf [header] + 1;
- if (header >= _RING_HISTORY_LEN)
- header -= _RING_HISTORY_LEN;
- j++;
- }
- if (pThis->ring_buf[header]) {
- pThis->cur++;
- // obtain saved line
- if (pThis->ring_buf [header] + header < _RING_HISTORY_LEN) {
- memset (line, 0, _COMMAND_LINE_LEN);
- memcpy (line, pThis->ring_buf + header + 1, pThis->ring_buf[header]);
- } else {
- int part0 = _RING_HISTORY_LEN - header - 1;
- memset (line, 0, _COMMAND_LINE_LEN);
- memcpy (line, pThis->ring_buf + header + 1, part0);
- memcpy (line + part0, pThis->ring_buf, pThis->ring_buf[header] - part0);
- }
- return pThis->ring_buf[header];
- }
- }
- } else {
- if (pThis->cur > 0) {
- pThis->cur--;
- int header = pThis->begin;
- int j = 0;
- while ((pThis->ring_buf [header] != 0) && (cnt - j != pThis->cur)) {
- header += pThis->ring_buf [header] + 1;
- if (header >= _RING_HISTORY_LEN)
- header -= _RING_HISTORY_LEN;
- j++;
- }
- if (pThis->ring_buf [header] + header < _RING_HISTORY_LEN) {
- memcpy (line, pThis->ring_buf + header + 1, pThis->ring_buf[header]);
- } else {
- int part0 = _RING_HISTORY_LEN - header - 1;
- memcpy (line, pThis->ring_buf + header + 1, part0);
- memcpy (line + part0, pThis->ring_buf, pThis->ring_buf[header] - part0);
- }
- return pThis->ring_buf[header];
- } else {
- /* empty line */
- return 0;
- }
- }
- return -1;
- }
- #endif
- //*****************************************************************************
- // split cmdline to tkn array and return nmb of token
- static int split (microrl_t * pThis, int limit, char const ** tkn_arr)
- {
- int i = 0;
- int ind = 0;
- while (1) {
- // go to the first whitespace (zerro for us)
- while ((pThis->cmdline [ind] == '\0') && (ind < limit)) {
- ind++;
- }
- if (!(ind < limit)) return i;
- tkn_arr[i++] = pThis->cmdline + ind;
- if (i >= _COMMAND_TOKEN_NMB) {
- return -1;
- }
- // go to the first NOT whitespace (not zerro for us)
- while ((pThis->cmdline [ind] != '\0') && (ind < limit)) {
- ind++;
- }
- if (!(ind < limit)) return i;
- }
- return i;
- }
- //*****************************************************************************
- inline static void print_prompt (microrl_t * pThis)
- {
- pThis->print (pThis->prompt_str);
- }
- //*****************************************************************************
- inline static void terminal_backspace (microrl_t * pThis)
- {
- pThis->print ("\033[D \033[D");
- }
- //*****************************************************************************
- inline static void terminal_newline (microrl_t * pThis)
- {
- pThis->print (ENDL);
- }
- #ifndef _USE_LIBC_STDIO
- //*****************************************************************************
- // convert 16 bit value to string
- // 0 value not supported!!! just make empty string
- // Returns pointer to a buffer tail
- static char *u16bit_to_str (unsigned int nmb, char * buf)
- {
- char tmp_str [6] = {0,};
- int i = 0, j;
- if (nmb <= 0xFFFF) {
- while (nmb > 0) {
- tmp_str[i++] = (nmb % 10) + '0';
- nmb /=10;
- }
- for (j = 0; j < i; ++j)
- *(buf++) = tmp_str [i-j-1];
- }
- *buf = '\0';
- return buf;
- }
- #endif
- //*****************************************************************************
- // set cursor at position from begin cmdline (after prompt) + offset
- static void terminal_move_cursor (microrl_t * pThis, int offset)
- {
- char str[16] = {0,};
- #ifdef _USE_LIBC_STDIO
- if (offset > 0) {
- snprintf (str, 16, "\033[%dC", offset);
- } else if (offset < 0) {
- snprintf (str, 16, "\033[%dD", -(offset));
- }
- #else
- char *endstr;
- strcpy (str, "\033[");
- if (offset > 0) {
- endstr = u16bit_to_str (offset, str+2);
- strcpy (endstr, "C");
- } else if (offset < 0) {
- endstr = u16bit_to_str (-(offset), str+2);
- strcpy (endstr, "D");
- } else
- return;
- #endif
- pThis->print (str);
- }
- //*****************************************************************************
- static void terminal_reset_cursor (microrl_t * pThis)
- {
- char str[16];
- #ifdef _USE_LIBC_STDIO
- snprintf (str, 16, "\033[%dD\033[%dC", \
- _COMMAND_LINE_LEN + _PROMPT_LEN + 2, _PROMPT_LEN);
- #else
- char *endstr;
- strcpy (str, "\033[");
- endstr = u16bit_to_str ( _COMMAND_LINE_LEN + _PROMPT_LEN + 2,str+2);
- strcpy (endstr, "D\033["); endstr += 3;
- endstr = u16bit_to_str (_PROMPT_LEN, endstr);
- strcpy (endstr, "C");
- #endif
- pThis->print (str);
- }
- //*****************************************************************************
- // print cmdline to screen, replace '\0' to wihitespace
- static void terminal_print_line (microrl_t * pThis, int pos, int cursor)
- {
- pThis->print ("\033[K"); // delete all from cursor to end
- char nch [] = {0,0};
- int i;
- for (i = pos; i < pThis->cmdlen; i++) {
- nch [0] = pThis->cmdline [i];
- if (nch[0] == '\0')
- nch[0] = ' ';
- pThis->print (nch);
- }
-
- terminal_reset_cursor (pThis);
- terminal_move_cursor (pThis, cursor);
- }
- //*****************************************************************************
- void microrl_init (microrl_t * pThis, void (*print) (const char *))
- {
- memset(pThis->cmdline, 0, _COMMAND_LINE_LEN);
- #ifdef _USE_HISTORY
- memset(pThis->ring_hist.ring_buf, 0, _RING_HISTORY_LEN);
- pThis->ring_hist.begin = 0;
- pThis->ring_hist.end = 0;
- pThis->ring_hist.cur = 0;
- #endif
- pThis->cmdlen =0;
- pThis->cursor = 0;
- pThis->execute = NULL;
- pThis->get_completion = NULL;
- #ifdef _USE_CTLR_C
- pThis->sigint = NULL;
- #endif
- pThis->prompt_str = prompt_default;
- pThis->print = print;
- #ifdef _ENABLE_INIT_PROMPT
- print_prompt (pThis);
- #endif
- }
- //*****************************************************************************
- void microrl_set_complete_callback (microrl_t * pThis, char ** (*get_completion)(int, const char* const*))
- {
- pThis->get_completion = get_completion;
- }
- //*****************************************************************************
- void microrl_set_execute_callback (microrl_t * pThis, int (*execute)(int, const char* const*))
- {
- pThis->execute = execute;
- }
- #ifdef _USE_CTLR_C
- //*****************************************************************************
- void microrl_set_sigint_callback (microrl_t * pThis, void (*sigintf)(void))
- {
- pThis->sigint = sigintf;
- }
- #endif
- #ifdef _USE_ESC_SEQ
- static void hist_search (microrl_t * pThis, int dir)
- {
- int len = hist_restore_line (&pThis->ring_hist, pThis->cmdline, dir);
- if (len >= 0) {
- pThis->cursor = pThis->cmdlen = len;
- terminal_reset_cursor (pThis);
- terminal_print_line (pThis, 0, pThis->cursor);
- }
- }
- //*****************************************************************************
- // handling escape sequences
- static int escape_process (microrl_t * pThis, char ch)
- {
- if (ch == '[') {
- pThis->escape_seq = _ESC_BRACKET;
- return 0;
- } else if (pThis->escape_seq == _ESC_BRACKET) {
- if (ch == 'A') {
- #ifdef _USE_HISTORY
- hist_search (pThis, _HIST_UP);
- #endif
- return 1;
- } else if (ch == 'B') {
- #ifdef _USE_HISTORY
- hist_search (pThis, _HIST_DOWN);
- #endif
- return 1;
- } else if (ch == 'C') {
- if (pThis->cursor < pThis->cmdlen) {
- terminal_move_cursor (pThis, 1);
- pThis->cursor++;
- }
- return 1;
- } else if (ch == 'D') {
- if (pThis->cursor > 0) {
- terminal_move_cursor (pThis, -1);
- pThis->cursor--;
- }
- return 1;
- } else if (ch == '7') {
- pThis->escape_seq = _ESC_HOME;
- return 0;
- } else if (ch == '8') {
- pThis->escape_seq = _ESC_END;
- return 0;
- }
- } else if (ch == '~') {
- if (pThis->escape_seq == _ESC_HOME) {
- terminal_reset_cursor (pThis);
- pThis->cursor = 0;
- return 1;
- } else if (pThis->escape_seq == _ESC_END) {
- terminal_move_cursor (pThis, pThis->cmdlen-pThis->cursor);
- pThis->cursor = pThis->cmdlen;
- return 1;
- }
- }
- /* unknown escape sequence, stop */
- return 1;
- }
- #endif
- //*****************************************************************************
- // insert len char of text at cursor position
- static int microrl_insert_text (microrl_t * pThis, char * text, int len)
- {
- int i;
- if (pThis->cmdlen + len < _COMMAND_LINE_LEN) {
- memmove (pThis->cmdline + pThis->cursor + len,
- pThis->cmdline + pThis->cursor,
- pThis->cmdlen - pThis->cursor);
- for (i = 0; i < len; i++) {
- pThis->cmdline [pThis->cursor + i] = text [i];
- if (pThis->cmdline [pThis->cursor + i] == ' ') {
- pThis->cmdline [pThis->cursor + i] = 0;
- }
- }
- pThis->cursor += len;
- pThis->cmdlen += len;
- pThis->cmdline [pThis->cmdlen] = '\0';
- return true;
- }
- return false;
- }
- //*****************************************************************************
- // remove one char at cursor
- static void microrl_backspace (microrl_t * pThis)
- {
- if (pThis->cursor > 0) {
- terminal_backspace (pThis);
- memmove (pThis->cmdline + pThis->cursor-1,
- pThis->cmdline + pThis->cursor,
- pThis->cmdlen-pThis->cursor+1);
- pThis->cursor--;
- pThis->cmdline [pThis->cmdlen] = '\0';
- pThis->cmdlen--;
- }
- }
- #ifdef _USE_COMPLETE
- //*****************************************************************************
- static int common_len (char ** arr)
- {
- int len = 0;
- int i = 1;
- while (1) {
- while (arr[i]!=NULL) {
- if ((arr[i][len] != arr[i-1][len]) ||
- (arr[i][len] == '\0') ||
- (arr[i-1][len]=='\0'))
- return len;
- len++;
- }
- i++;
- }
- return 0;
- }
- //*****************************************************************************
- static void microrl_get_complite (microrl_t * pThis)
- {
- char const * tkn_arr[_COMMAND_TOKEN_NMB];
- char ** compl_token;
-
- if (pThis->get_completion == NULL) // callback was not set
- return;
-
- int status = split (pThis, pThis->cursor, tkn_arr);
- if (pThis->cmdline[pThis->cursor-1] == '\0')
- tkn_arr[status++] = "";
- compl_token = pThis->get_completion (status, tkn_arr);
- if (compl_token[0] != NULL) {
- int i = 0;
- int len;
- if (compl_token[1] == NULL) {
- len = strlen (compl_token[0]);
- } else {
- len = common_len (compl_token);
- terminal_newline (pThis);
- while (compl_token [i] != NULL) {
- pThis->print (compl_token[i]);
- pThis->print (" ");
- i++;
- }
- terminal_newline (pThis);
- print_prompt (pThis);
- }
-
- if (len) {
- microrl_insert_text (pThis, compl_token[0] + strlen(tkn_arr[status-1]),
- len - strlen(tkn_arr[status-1]));
- if (compl_token[1] == NULL)
- microrl_insert_text (pThis, " ", 1);
- }
- terminal_reset_cursor (pThis);
- terminal_print_line (pThis, 0, pThis->cursor);
- }
- }
- #endif
- //*****************************************************************************
- void new_line_handler(microrl_t * pThis){
- char const * tkn_arr [_COMMAND_TOKEN_NMB];
- int status;
- terminal_newline (pThis);
- #ifdef _USE_HISTORY
- if (pThis->cmdlen > 0)
- hist_save_line (&pThis->ring_hist, pThis->cmdline, pThis->cmdlen);
- #endif
- status = split (pThis, pThis->cmdlen, tkn_arr);
- if (status == -1){
- // pThis->print ("ERROR: Max token amount exseed\n");
- pThis->print ("ERROR:too many tokens");
- pThis->print (ENDL);
- }
- if ((status > 0) && (pThis->execute != NULL))
- pThis->execute (status, tkn_arr);
- print_prompt (pThis);
- pThis->cmdlen = 0;
- pThis->cursor = 0;
- memset(pThis->cmdline, 0, _COMMAND_LINE_LEN);
- #ifdef _USE_HISTORY
- pThis->ring_hist.cur = 0;
- #endif
- }
- //*****************************************************************************
- void microrl_insert_char (microrl_t * pThis, int ch)
- {
- #ifdef _USE_ESC_SEQ
- if (pThis->escape) {
- if (escape_process(pThis, ch))
- pThis->escape = 0;
- } else {
- #endif
- switch (ch) {
- //-----------------------------------------------------
- #ifdef _ENDL_CR
- case KEY_CR:
- new_line_handler(pThis);
- break;
- case KEY_LF:
- break;
- #elif defined(_ENDL_CRLF)
- case KEY_CR:
- pThis->tmpch = KEY_CR;
- break;
- case KEY_LF:
- if (pThis->tmpch == KEY_CR)
- new_line_handler(pThis);
- break;
- #elif defined(_ENDL_LFCR)
- case KEY_LF:
- pThis->tmpch = KEY_LF;
- break;
- case KEY_CR:
- if (pThis->tmpch == KEY_LF)
- new_line_handler(pThis);
- break;
- #else
- case KEY_CR:
- break;
- case KEY_LF:
- new_line_handler(pThis);
- break;
- #endif
- //-----------------------------------------------------
- #ifdef _USE_COMPLETE
- case KEY_HT:
- microrl_get_complite (pThis);
- break;
- #endif
- //-----------------------------------------------------
- case KEY_ESC:
- #ifdef _USE_ESC_SEQ
- pThis->escape = 1;
- #endif
- break;
- //-----------------------------------------------------
- case KEY_NAK: // ^U
- while (pThis->cursor > 0) {
- microrl_backspace (pThis);
- }
- terminal_print_line (pThis, 0, pThis->cursor);
- break;
- //-----------------------------------------------------
- case KEY_VT: // ^K
- pThis->print ("\033[K");
- pThis->cmdlen = pThis->cursor;
- break;
- //-----------------------------------------------------
- case KEY_ENQ: // ^E
- terminal_move_cursor (pThis, pThis->cmdlen-pThis->cursor);
- pThis->cursor = pThis->cmdlen;
- break;
- //-----------------------------------------------------
- case KEY_SOH: // ^A
- terminal_reset_cursor (pThis);
- pThis->cursor = 0;
- break;
- //-----------------------------------------------------
- case KEY_ACK: // ^F
- if (pThis->cursor < pThis->cmdlen) {
- terminal_move_cursor (pThis, 1);
- pThis->cursor++;
- }
- break;
- //-----------------------------------------------------
- case KEY_STX: // ^B
- if (pThis->cursor) {
- terminal_move_cursor (pThis, -1);
- pThis->cursor--;
- }
- break;
- //-----------------------------------------------------
- case KEY_DLE: //^P
- #ifdef _USE_HISTORY
- hist_search (pThis, _HIST_UP);
- #endif
- break;
- //-----------------------------------------------------
- case KEY_SO: //^N
- #ifdef _USE_HISTORY
- hist_search (pThis, _HIST_DOWN);
- #endif
- break;
- //-----------------------------------------------------
- case KEY_DEL: // Backspace
- case KEY_BS: // ^U
- microrl_backspace (pThis);
- terminal_print_line (pThis, pThis->cursor, pThis->cursor);
- break;
- #ifdef _USE_CTLR_C
- case KEY_ETX:
- if (pThis->sigint != NULL)
- pThis->sigint();
- break;
- #endif
- //-----------------------------------------------------
- default:
- if (((ch == ' ') && (pThis->cmdlen == 0)) || IS_CONTROL_CHAR(ch))
- break;
- if (microrl_insert_text (pThis, (char*)&ch, 1))
- terminal_print_line (pThis, pThis->cursor-1, pThis->cursor);
-
- break;
- }
- #ifdef _USE_ESC_SEQ
- }
- #endif
- }
|