i237/lib/helius_microrl/microrl.c

685 lines
18 KiB
C
Raw Normal View History

/*
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)
{
unsigned int i;
unsigned int j;
char *shortest = arr[0];
unsigned int shortlen = strlen(shortest);
for (i = 0; arr[i] != NULL; ++i)
if (strlen(arr[i]) < shortlen) {
shortest = arr[i];
shortlen = strlen(shortest);
}
for (i = 0; i < shortlen; ++i)
for (j = 0; arr[j] != 0; ++j)
if (shortest[i] != arr[j][i])
return i;
return i;
}
//*****************************************************************************
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
}