| 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 screenstatic 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 bufferstatic 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 spacestatic 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 bufferstatic 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 linestatic 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 tokenstatic 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 tailstatic 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) + offsetstatic 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_SEQstatic 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 sequencesstatic 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 positionstatic 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 cursorstatic 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}
 |