From 6b1d550e6b91b324cf8ee83e4fbc3e3d0a24f105 Mon Sep 17 00:00:00 2001 From: prozessorkern Date: Mon, 24 Jun 2019 23:31:26 +0200 Subject: [PATCH] moved all separatable functions in seperate modules. The main module shellmatta.c now only contains the interface function implementation. Work on #4 --- src/shellmatta.c | 860 +--------------------------------- src/shellmatta_autocomplete.c | 176 +++++++ src/shellmatta_autocomplete.h | 27 ++ src/shellmatta_escape.c | 159 +++++++ src/shellmatta_escape.h | 31 ++ src/shellmatta_history.c | 232 +++++++++ src/shellmatta_history.h | 33 ++ src/shellmatta_utils.c | 346 ++++++++++++++ src/shellmatta_utils.h | 68 +++ 9 files changed, 1088 insertions(+), 844 deletions(-) create mode 100644 src/shellmatta_autocomplete.c create mode 100644 src/shellmatta_autocomplete.h create mode 100644 src/shellmatta_escape.c create mode 100644 src/shellmatta_escape.h create mode 100644 src/shellmatta_history.c create mode 100644 src/shellmatta_history.h create mode 100644 src/shellmatta_utils.c create mode 100644 src/shellmatta_utils.h diff --git a/src/shellmatta.c b/src/shellmatta.c index 35b1fce..97d646e 100644 --- a/src/shellmatta.c +++ b/src/shellmatta.c @@ -13,846 +13,19 @@ */ /** - * @addtogroup shellmatta - * @{ * @addtogroup shellmatta_private * @{ */ #include "shellmatta.h" -#include +#include "shellmatta_autocomplete.h" +#include "shellmatta_history.h" +#include "shellmatta_utils.h" +#include "shellmatta_escape.h" #include #include #include - - -/* defines */ - -#ifndef SHELLMATTA_OUTPUT_BUFFER_SIZE -#define SHELLMATTA_OUTPUT_BUFFER_SIZE 128u -#endif - -#define SHELLMATTA_MIN(a,b) (((a) > (b)) ? (b) : (a)) -#define SHELLMATTA_MAX(a,b) (((a) < (b)) ? (b) : (a)) -#define SHELLMATTA_PRINT_BUFFER(buffer,cnt,fct) \ - while((cnt) > sizeof((buffer))) \ - { \ - (cnt) -= sizeof((buffer)); \ - (fct)((buffer), sizeof((buffer))); \ - } \ - if((cnt) != 0u) \ - { \ - (fct)((buffer), (cnt)); \ - } - -#define SHELLMATTA_MAGIC 0x5101E110u - -/* static functions */ - -/** - * @brief function to write an echo to the output depending on - * the echo enabled state of the instance - * @param[in] inst pointer to a shellmatta instance - * @param[in] data pointer to the data so send - * @param[in] length length of the data to send (in byte) - */ -static void writeEcho( shellmatta_instance_t *inst, - const char *data, - uint32_t length) -{ - if(true == inst->echoEnabled) - { - inst->write(data, length); - } -} - -/** - * @brief itoa like function to convert int to an ascii string - * @warning you have to provide a large enough buffer - * @param[in] value - * @param[in,out] buffer - * @param[in] base - * @return number of bytes in string - */ -static uint32_t shellItoa(int32_t value, char *buffer, uint32_t base) -{ - char tempBuffer[34u]; - uint32_t i; - uint32_t bufferIdx = 0u; - char digitValue; - - /** -# check the base for plausibility */ - if((2 <= base) && (16 >= base)) - { - /** -# check for sign */ - if(0 > value) - { - value = value * (-1); - buffer[0u] = '-'; - bufferIdx += 1u; - } - - /** -# loop through all digits in reverse order */ - i = 0u; - do - { - digitValue = (char) (value % base); - tempBuffer[i] = (digitValue < 10u) ? ('0' + digitValue) : (('A' - 10) + digitValue); - value /= base; - i ++; - }while(value > 0); - - /** -# store the string in the correct order onto the buffer */ - while(i > 0u) - { - buffer[bufferIdx] = tempBuffer[i - 1u]; - i --; - bufferIdx ++; - } - } - return bufferIdx; -} - -/** - * @brief appends a byte to the history ring stack buffer - * @param[in] inst pointer to a shellmatta instance - * @param[in] byte byte to append to the history buffer - */ -static void appendHistoryByte(shellmatta_instance_t *inst, char byte) -{ - /** -# calculate the new history buffer index */ - inst->historyEnd ++; - if(inst->historyEnd >= inst->historyBufferSize) - { - inst->historyEnd = 0u; - } - - /** -# append the byte */ - inst->historyBuffer[inst->historyEnd] = byte; - - /** -# check if the we overwrite an existing stored command */ - if(inst->historyEnd == inst->historyStart) - { - /** -# move the start pointer to the next termination (0) */ - do - { - inst->historyStart ++; - - if(inst->historyStart >= inst->historyBufferSize) - { - inst->historyStart = 0u; - } - - }while(0u != inst->historyBuffer[inst->historyStart]); - } -} - -/** - * @brief reads a byte from the history buffer and decreases the read index - * @param[in] inst pointer to a shellmatta instance - * @param[out] byte pointer to a char where the read out byte will be stored - * @return - */ -static bool getHistoryByte(shellmatta_instance_t *inst, char *byte) -{ - bool ret = false; - - /** -# check if we have reached the end of the buffer already */ - if(inst->historyRead != inst->historyStart) - { - /** -# read out one byte and decrease the read index */ - *byte = inst->historyBuffer[inst->historyRead]; - - if(0u == inst->historyRead) - { - inst->historyRead = inst->historyBufferSize; - } - - inst->historyRead --; - - ret = true; - } - - return ret; -} - -/** - * @brief stores the current command from the instances buffer into the - * history buffer - * @param[in] inst pointer to a shellmatta instance - */ -static void storeHistoryCmd(shellmatta_instance_t *inst) -{ - uint32_t i; - - /** -# check if we have enough room for the command in the history buffer - * and there is a new command to be stored */ - if( (inst->historyBufferSize > inst->inputCount) - && (0u != inst->inputCount) - && (true == inst->dirty)) - { - /** -# append the command termination */ - appendHistoryByte(inst, 0u); - - /** -# append the command byte wise in reverse direction */ - for(i = inst->inputCount; i > 0u; i --) - { - appendHistoryByte(inst, inst->buffer[i - 1u]); - } - } - - /** -# remove the dirty flag - everything is nice and saved */ - inst->dirty = false; -} - -/** - * @brief navigates in the history buffer by the given number of commands - * @param[in] inst pointer to a shellmatta instance - * @param[in] cnt direction and count to navigate - * @return - */ -static bool navigateHistoryCmd(shellmatta_instance_t *inst, int32_t cnt) -{ - bool ret = true; - uint32_t tempReadIdx = 0u; - - while((cnt > 0) && (true == ret)) - { - if(inst->historyRead != inst->historyEnd) - { - inst->historyRead ++; - } - while(inst->historyRead != inst->historyEnd) - { - inst->historyRead ++; - if(inst->historyRead >= inst->historyBufferSize) - { - inst->historyRead = 0u; - } - - if( (inst->historyRead != inst->historyEnd) - && (0u == inst->historyBuffer[inst->historyRead])) - { - if(0u == inst->historyRead) - { - inst->historyRead = inst->historyBufferSize; - } - inst->historyRead --; - cnt -= 1; - break; - } - } - - if(inst->historyRead == inst->historyEnd) - { - ret = false; - } - } - - while((cnt < 0) && (true == ret)) - { - tempReadIdx = inst->historyRead; - while(inst->historyRead != inst->historyStart) - { - if(0u == inst->historyRead) - { - inst->historyRead = inst->historyBufferSize; - } - inst->historyRead --; - - if( (inst->historyRead != inst->historyStart) - && (0u == inst->historyBuffer[inst->historyRead])) - { - if(0u == inst->historyRead) - { - inst->historyRead = inst->historyBufferSize; - } - inst->historyRead --; - cnt += 1; - break; - } - } - if(inst->historyRead == inst->historyStart) - { - inst->historyRead = tempReadIdx; - inst->historyReadUp = false; - ret = false; - } - } - - return ret; -} - -/** - * @brief restores the command from the history buffer where the read - * index points on - * @param[in] inst pointer to a shellmatta instance - */ -static void restoreHistoryCmd(shellmatta_instance_t *inst) -{ - char byte; - bool ret = true; - - ret = getHistoryByte(inst, &byte); - while((ret == true) && (byte != 0u)) - { - inst->buffer[inst->inputCount] = byte; - inst->inputCount ++; - inst->cursor ++; - ret = getHistoryByte(inst, &byte); - } - - writeEcho(inst, inst->buffer, inst->inputCount); - navigateHistoryCmd(inst, 1); - inst->dirty = false; -} - -/** - * @brief resets the history buffer pointers to show to the most recent - * command again - * @param[in] inst pointer to a shellmatta instance - */ -static void resetHistoryBuffer(shellmatta_instance_t *inst) -{ - inst->historyRead = inst->historyEnd; - inst->historyReadUp = true; -} - -/** - * @brief tells the terminal to save the current cursor position - * @param[in] inst pointer to shellmatta instance - */ -static void saveCursorPos(shellmatta_instance_t *inst) -{ - writeEcho(inst, "\e[s", 3u); -} - -/** - * @brief tells the terminal to restore the saved cursor position - * @param[in] inst pointer to shellmatta instance - */ -static void restoreCursorPos(shellmatta_instance_t *inst) -{ - writeEcho(inst, "\e[u", 3u); -} - -/** - * @brief tells the terminal to erase the line on the right side of the - * cursor - * @param[in] inst pointer to shellmatta instance - */ -static void eraseLine(shellmatta_instance_t *inst) -{ - writeEcho(inst, "\e[K", 3u); -} - -/** - * @brief moves the cursor back by the given amoung of characters - * @param[in] inst pointer to shellmatta instance - * @param[in] length number of characters to rewind - */ -static void rewindCursor(shellmatta_instance_t *inst, uint32_t length) -{ - char terminalCmd[16]; - size_t size; - - length = SHELLMATTA_MIN (length, inst->cursor); - if(length > 0u) - { - terminalCmd[0] = '\e'; - terminalCmd[1] = '['; - size = 2u + shellItoa(length, &terminalCmd[2], 10); - terminalCmd[size] = 'D'; - writeEcho(inst, terminalCmd, size + 1u); - inst->cursor -= length; - } -} - -/** - * @brief moves the cursor forward by the given amoung of characters - * @param[in] inst pointer to shellmatta instance - * @param[in] length number of characters to move forward - */ -static void forwardCursor(shellmatta_instance_t *inst, uint32_t length) -{ - char terminalCmd[16]; - size_t size; - - length = SHELLMATTA_MAX (length, (inst->inputCount - inst->cursor)); - if (length > 0u) - { - terminalCmd[0] = '\e'; - terminalCmd[1] = '['; - size = 2u + shellItoa(length, &terminalCmd[2], 10); - terminalCmd[size] = 'C'; - writeEcho(inst, terminalCmd, size + 1u); - inst->cursor += length; - } -} - -/** - * @brief inserts the given amount of characters at the cursor position - * @param[in] inst pointer to shellmatta instance - * @param[in] data pointer to the data to be inserted - * @param[in] length length of the data to be inserted - */ -static void insertChars(shellmatta_instance_t *inst, - char *data, - uint32_t length) -{ - if(0u != length) - { - /** -# check if we have to move chars in the buffer */ - if( (inst->cursor != inst->inputCount) - && (SHELLMATTA_MODE_INSERT == inst->mode)) - { - /** -# move the existing chars */ - for ( uint32_t i = inst->inputCount; - i > inst->cursor; - i --) - { - inst->buffer[i + length - 1] = inst->buffer[i - 1]; - } - - /** -# store and print the new chars */ - memcpy(&(inst->buffer[inst->cursor]), data, length); - writeEcho(inst, data, length); - - /** -# print the other chars and restore the cursor to this position */ - eraseLine(inst); - saveCursorPos(inst); - writeEcho( inst, - &(inst->buffer[inst->cursor + length]), - inst->inputCount - inst->cursor); - restoreCursorPos(inst); - } - /** -# just overwrite/append the chars */ - else - { - memcpy(&(inst->buffer[inst->cursor]), data, length); - writeEcho(inst, data, length); - } - - inst->inputCount += length; - inst->cursor += length; - } -} - -/** - * @brief removes the given amount of characters from the current cursor - * position - * @param[in] inst pointer to a shellmatta instance - * @param[in] length number of characters to remove - * @param[in] backspace remove characters left of the cursor - */ -static void removeChars(shellmatta_instance_t *inst, - uint32_t length, - bool backspace) -{ - if(0u != length) - { - /** -# rewind the cursor in case of backspace */ - if(true == backspace) - { - length = SHELLMATTA_MIN (length, inst->cursor); - rewindCursor(inst, length); - } - else - { - length = SHELLMATTA_MIN (length, inst->inputCount - inst->cursor); - } - /** -# delete the char at the cursor position */ - for ( uint32_t i = inst->cursor; - i < (inst->inputCount - length); - i++) - { - inst->buffer[i] = inst->buffer[i + length]; - } - - /** -# print the rest of the line again */ - eraseLine(inst); - saveCursorPos(inst); - writeEcho( inst, - &(inst->buffer[inst->cursor]), - (inst->inputCount - inst->cursor - length)); - restoreCursorPos(inst); - - inst->inputCount -= length; - } -} - -/** - * @brief clears the input buffer and removes the currend command from - * the terminal output - * @param[in] inst pointer to a shellmatta instance - */ -static void clearInput(shellmatta_instance_t *inst) -{ - rewindCursor(inst, inst->cursor); - eraseLine(inst); - inst->inputCount = 0u; - inst->dirty = false; -} - -/** - * @brief processes the excape character stream of the instance and chacks - * if an arrow key was pressed - * @param[in] inst pointer to a shellmatta instance - * @return #SHELLMATTA_OK if an arrow key was pressed - */ -static shellmatta_retCode_t processArrowKeys(shellmatta_instance_t *inst) -{ - shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT; - - if ('[' == inst->escapeChars[0]) - { - ret = SHELLMATTA_OK; - - switch (inst->escapeChars[1]) - { - case 'A': /* arrow up */ - storeHistoryCmd(inst); - if(false == inst->historyReadUp) - { - navigateHistoryCmd(inst, -1); - } - inst->historyReadUp = true; - clearInput(inst); - restoreHistoryCmd(inst); - navigateHistoryCmd(inst, -1); - - break; - case 'B': /* arrow down */ - - if((inst->historyRead != inst->historyEnd)) - { - storeHistoryCmd(inst); - if(true == inst->historyReadUp) - { - navigateHistoryCmd(inst, 1); - } - inst->historyReadUp = false; - navigateHistoryCmd(inst, 1); - clearInput(inst); - restoreHistoryCmd(inst); - } - break; - case 'C': /* arrow right */ - forwardCursor(inst, 1u); - break; - case 'D': /* arrow left */ - rewindCursor(inst, 1u); - break; - default: - /** ignore unknown escape */ - ret = SHELLMATTA_USE_FAULT; - break; - } - } - return ret; -} - -/** - * @brief handles a ANSI escape sequence to be able to react to some - * special keys - * @param[in] inst pointer to a shellmatta instance - * @param[in] data new received character of the escape sequence - */ -static void handleEscapeSequence(shellmatta_instance_t *inst, char data) -{ - switch (inst->escapeCounter) - { - case 1u: - inst->escapeChars[inst->escapeCounter - 1] = data; - inst->escapeCounter ++; - break; - case 2u: - inst->escapeChars[inst->escapeCounter - 1] = data; - /** -# check if an arrow key was pressed */ - if(SHELLMATTA_OK == processArrowKeys(inst)) - { - inst->escapeCounter = 0u; - } - /** -# check if end was pressed */ - else if ( (0x4f == inst->escapeChars[0]) - && (0x46 == inst->escapeChars[1])) - { - forwardCursor(inst, inst->inputCount - inst->cursor); - inst->escapeCounter = 0u; - } - /** -# if the highest bit is not set this is usually not the end of the - * sequence - so we go on */ - else if(0u == (0x40 & data)) - { - inst->escapeCounter ++; - } - else - { - inst->escapeCounter = 0u; - } - break; - case 3u: - /** -# check if delete was pressed */ - inst->escapeChars[inst->escapeCounter - 1] = data; - if( (0x5b == inst->escapeChars[0]) - && (0x33 == inst->escapeChars[1]) - && (0x7e == inst->escapeChars[2])) - { - removeChars(inst, 1u, false); - inst->escapeCounter = 0u; - } - /** -# check if pos1 was pressed */ - else if ( (0x5bu == inst->escapeChars[0]) - && (0x31u == inst->escapeChars[1]) - && (0x7eu == inst->escapeChars[2])) - { - rewindCursor(inst, inst->cursor); - inst->escapeCounter = 0u; - } - else if(0u == (0x40u & data)) - { - inst->escapeCounter ++; - } - else - { - inst->escapeCounter = 0u; - } - break; - default: - inst->escapeCounter = 0u; - break; - } -} - -/** - * @brief terminates an input and prints the prompt again - * @param[in] inst pointer to a shellmatta instance - */ -static void terminateInput(shellmatta_instance_t *inst) -{ - inst->inputCount = 0u; - inst->cursor = 0u; - inst->write("\r\n", 2u); - inst->write(inst->prompt, strlen(inst->prompt)); -} - -/** - * @brief searches the registered commands for matching ones and prints - * the common part of all matching commands on the output - * if called twice all matching commands are printed - * @param[in] inst pointer to a shellmatta instance - */ -static void doAutocomplete(shellmatta_instance_t *inst) -{ - shellmatta_cmd_t *cmd = inst->cmdList; - char *tempCmd = NULL; - uint32_t minLen = 0u; - uint32_t sizeDiff = 0u; - bool exactMatch = true; - uint32_t printedLen = 0u; - uint32_t tempCursor = 0u; - - /** -# increase the tab counter to print all matching commands next time */ - inst->tabCounter++; - - /** -# on douple tab show all matching commands */ - if (1u < inst->tabCounter) - { - inst->tabCounter = 0u; - - /** -# loop through all registered commands */ - while (NULL != cmd) - { - /** -# check if command matches the input */ - if( (strlen(cmd->cmd) >= inst->cursor) - && (0u == memcmp(cmd->cmd, inst->buffer, inst->cursor))) - { - /** -# add newline on first find */ - if(0u == printedLen) - { - inst->write("\r\n", 2u); - } - inst->write(cmd->cmd, strlen(cmd->cmd)); - printedLen += strlen(cmd->cmd); - inst->write(" ", 4u); - printedLen += 4u; - } - /** -# check if command alias matches the input */ - if( (strlen(cmd->cmdAlias) >= inst->cursor) - && (0u == memcmp(cmd->cmdAlias, inst->buffer, inst->cursor))) - { - /** -# add newline on first find */ - if(0u == printedLen) - { - inst->write("\r\n", 2u); - } - inst->write(cmd->cmdAlias, strlen(cmd->cmdAlias)); - printedLen += strlen(cmd->cmdAlias); - inst->write(" ", 4u); - printedLen += 4u; - } - cmd = cmd->next; - } - /** -# print input again if a commands was found */ - if(printedLen > 0u) - { - writeEcho(inst, "\r\n", 2u); - writeEcho(inst, inst->prompt, strlen(inst->prompt)); - writeEcho(inst, inst->buffer, inst->inputCount); - tempCursor = inst->cursor; - inst->cursor = inst->inputCount; - rewindCursor(inst, inst->inputCount - tempCursor); - } - } - /** -# on single tab autocomplete as far as possible */ - else if(0u != inst->cursor) - { - /** -# loop through all registered commands */ - while (NULL != cmd) - { - /** -# check if command matches the input */ - if( (strlen(cmd->cmd) >= inst->cursor) - && (0u == memcmp(cmd->cmd, inst->buffer, inst->cursor))) - { - /** -# store first match */ - if(NULL == tempCmd) - { - tempCmd = cmd->cmd; - minLen = strlen(cmd->cmd); - } - /** -# find common part of the matching commands */ - else - { - exactMatch = false; - minLen = SHELLMATTA_MIN(strlen(cmd->cmd), minLen); - for(uint32_t i = 0u; i < minLen; i++) - { - if(cmd->cmd[i] != tempCmd[i]) - { - minLen = i; - } - } - } - } - - /** -# check if command Alias matches the input */ - if( (strlen(cmd->cmdAlias) >= inst->cursor) - && (0u == memcmp(cmd->cmdAlias, inst->buffer, inst->cursor))) - { - /** -# store first match */ - if(NULL == tempCmd) - { - tempCmd = cmd->cmdAlias; - minLen = strlen(cmd->cmdAlias); - } - /** -# find common part of the matches */ - else - { - exactMatch = false; - minLen = SHELLMATTA_MIN(strlen(cmd->cmdAlias), minLen); - for(uint32_t i = 0u; i < minLen; i++) - { - if(cmd->cmdAlias[i] != tempCmd[i]) - { - minLen = i; - } - } - } - } - cmd = cmd->next; - } - - /** -# autocomplete found command */ - if(NULL != tempCmd) - { - /** -# calculate the size of the command to be inserted */ - sizeDiff = minLen - inst->cursor; - - /** -# copy the found part into the buffer and display it */ - insertChars(inst, &(tempCmd[inst->cursor]), sizeDiff); - - /** -# on exact match there is no need to double Tab to display all */ - if(true == exactMatch) - { - inst->tabCounter = 0u; - } - } - } - else - { - /* nothing to do here */ - } -} - -/** - * @brief prints all possible commands with description and usage - * @param[in] handle handle shellmatta instance handle - * @param[in] arguments not used here - * @param[in] length not used here - * @return #SHELLMATTA_OK - * #SHELLMATTA_ERROR (buffer overflow) - */ -static shellmatta_retCode_t helpCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length) -{ - shellmatta_retCode_t ret = SHELLMATTA_OK; - shellmatta_instance_t *inst = (shellmatta_instance_t*) handle; - shellmatta_cmd_t *cmd = inst->cmdList; - size_t maxCmdLen = 0u; - size_t maxCmdAliasLen = 0u; - size_t maxCmdHelpLen = 0u; - size_t cmdLen = 0u; - size_t cmdAliasLen = 0u; - size_t cmdHelpLen = 0u; - uint32_t tabCnt = 0u; - static const char tabBuffer[] = { ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' ', - ' ', ' ', ' ', ' '}; - - /** -# loop through all commands to determine the lengths of each cmd */ - while(NULL != cmd) - { - maxCmdLen = SHELLMATTA_MAX(maxCmdLen, strlen(cmd->cmd)); - maxCmdAliasLen = SHELLMATTA_MAX(maxCmdAliasLen, strlen(cmd->cmdAlias)); - maxCmdHelpLen = SHELLMATTA_MAX(maxCmdHelpLen, strlen(cmd->helpText)); - cmd = cmd->next; - } - - /** -# loop through all commands and print all possible information */ - cmd = inst->cmdList; - while(NULL != cmd) - { - /** -# determine the length of each field to add padding */ - cmdLen = strlen(cmd->cmd); - cmdAliasLen = strlen(cmd->cmdAlias); - cmdHelpLen = strlen(cmd->helpText); - - inst->write(cmd->cmd, strlen(cmd->cmd)); - tabCnt = (maxCmdLen - cmdLen) + 2u; - SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write); - - inst->write(cmd->cmdAlias, strlen(cmd->cmdAlias)); - tabCnt = (maxCmdAliasLen - cmdAliasLen) + 2u; - SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write); - - inst->write(cmd->helpText, strlen(cmd->helpText)); - tabCnt = (maxCmdHelpLen - cmdHelpLen) + 2u; - SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write); - - inst->write(cmd->usageText, strlen(cmd->usageText)); - inst->write("\r\n", 2u); - - cmd = cmd->next; - } - - (void)arguments; - (void)length; - - return ret; -} -shellmatta_cmd_t helpCmd = {"help", "h", "Print this help text", "help", helpCmdFct, NULL}; - -/* interface functions */ +#include /** * @} @@ -928,7 +101,7 @@ shellmatta_retCode_t shellmatta_doInit( *handle = (shellmatta_handle_t)inst; /** -# print the first prompt */ - terminateInput(inst); + utils_terminateInput(inst); } return SHELLMATTA_OK; @@ -1034,7 +207,7 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, /** -# handle escape sequences */ if(inst->escapeCounter != 0u) { - handleEscapeSequence(inst, *data); + escape_handleSequence(inst, *data); } /** -# handle return as start of processing the command */ else if ('\r' == *data) @@ -1044,8 +217,8 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, /** -# store the current command and reset the history buffer */ inst->dirty = true; - storeHistoryCmd(inst); - resetHistoryBuffer(inst); + history_storeCmd(inst); + history_reset(inst); /** -# search for a matching command */ while (NULL != cmd) @@ -1072,33 +245,33 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, inst->write(inst->buffer, inst->inputCount); inst->write(" not found", 10u); } - terminateInput(inst); + utils_terminateInput(inst); } /** -# check for tabulator key - auto complete */ else if('\t' == *data) { inst->dirty = true; - doAutocomplete(inst); + autocomplete_run(inst); } /** -# check for cancel - * terminate current input and print prompt again */ else if(3 == *data) { inst->dirty = false; - resetHistoryBuffer(inst); - terminateInput(inst); + history_reset(inst); + utils_terminateInput(inst); } /** -# check for backspace */ else if('\b' == *data) { inst->dirty = true; - removeChars(inst, 1u, true); + utils_removeChars(inst, 1u, true); } /** -# check for delete key */ else if(0x7eu == *data) { inst->dirty = true; - removeChars(inst, 1u, false); + utils_removeChars(inst, 1u, false); } /** -# check for start of escape sequence */ else if('\e' == *data) @@ -1108,7 +281,7 @@ shellmatta_retCode_t shellmatta_processData(shellmatta_handle_t handle, else { inst->dirty = true; - insertChars(inst, data, 1); + utils_insertChars(inst, data, 1); } /** -# reset tab counter on not a tab */ @@ -1199,5 +372,4 @@ shellmatta_retCode_t shellmatta_printf( shellmatta_handle_t handle, #endif /** @} */ -/** @} */ diff --git a/src/shellmatta_autocomplete.c b/src/shellmatta_autocomplete.c new file mode 100644 index 0000000..c5bf7bb --- /dev/null +++ b/src/shellmatta_autocomplete.c @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_autocomplete.c + * @brief autocomplete function of shellmatta + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_autocomplete + * @{ + */ + +#include "shellmatta.h" +#include "shellmatta_autocomplete.h" +#include "shellmatta_utils.h" +#include +#include + +/** + * @brief searches the registered commands for matching ones and prints + * the common part of all matching commands on the output + * if called twice all matching commands are printed + * @param[in] inst pointer to a shellmatta instance + */ +void autocomplete_run(shellmatta_instance_t *inst) +{ + shellmatta_cmd_t *cmd = inst->cmdList; + char *tempCmd = NULL; + uint32_t minLen = 0u; + uint32_t sizeDiff = 0u; + bool exactMatch = true; + uint32_t printedLen = 0u; + uint32_t tempCursor = 0u; + + /** -# increase the tab counter to print all matching commands next time */ + inst->tabCounter++; + + /** -# on douple tab show all matching commands */ + if (1u < inst->tabCounter) + { + inst->tabCounter = 0u; + + /** -# loop through all registered commands */ + while (NULL != cmd) + { + /** -# check if command matches the input */ + if( (strlen(cmd->cmd) >= inst->cursor) + && (0u == memcmp(cmd->cmd, inst->buffer, inst->cursor))) + { + /** -# add newline on first find */ + if(0u == printedLen) + { + inst->write("\r\n", 2u); + } + inst->write(cmd->cmd, strlen(cmd->cmd)); + printedLen += strlen(cmd->cmd); + inst->write(" ", 4u); + printedLen += 4u; + } + /** -# check if command alias matches the input */ + if( (strlen(cmd->cmdAlias) >= inst->cursor) + && (0u == memcmp(cmd->cmdAlias, inst->buffer, inst->cursor))) + { + /** -# add newline on first find */ + if(0u == printedLen) + { + inst->write("\r\n", 2u); + } + inst->write(cmd->cmdAlias, strlen(cmd->cmdAlias)); + printedLen += strlen(cmd->cmdAlias); + inst->write(" ", 4u); + printedLen += 4u; + } + cmd = cmd->next; + } + /** -# print input again if a commands was found */ + if(printedLen > 0u) + { + utils_writeEcho(inst, "\r\n", 2u); + utils_writeEcho(inst, inst->prompt, strlen(inst->prompt)); + utils_writeEcho(inst, inst->buffer, inst->inputCount); + tempCursor = inst->cursor; + inst->cursor = inst->inputCount; + utils_rewindCursor(inst, inst->inputCount - tempCursor); + } + } + /** -# on single tab autocomplete as far as possible */ + else if(0u != inst->cursor) + { + /** -# loop through all registered commands */ + while (NULL != cmd) + { + /** -# check if command matches the input */ + if( (strlen(cmd->cmd) >= inst->cursor) + && (0u == memcmp(cmd->cmd, inst->buffer, inst->cursor))) + { + /** -# store first match */ + if(NULL == tempCmd) + { + tempCmd = cmd->cmd; + minLen = strlen(cmd->cmd); + } + /** -# find common part of the matching commands */ + else + { + exactMatch = false; + minLen = SHELLMATTA_MIN(strlen(cmd->cmd), minLen); + for(uint32_t i = 0u; i < minLen; i++) + { + if(cmd->cmd[i] != tempCmd[i]) + { + minLen = i; + } + } + } + } + + /** -# check if command Alias matches the input */ + if( (strlen(cmd->cmdAlias) >= inst->cursor) + && (0u == memcmp(cmd->cmdAlias, inst->buffer, inst->cursor))) + { + /** -# store first match */ + if(NULL == tempCmd) + { + tempCmd = cmd->cmdAlias; + minLen = strlen(cmd->cmdAlias); + } + /** -# find common part of the matches */ + else + { + exactMatch = false; + minLen = SHELLMATTA_MIN(strlen(cmd->cmdAlias), minLen); + for(uint32_t i = 0u; i < minLen; i++) + { + if(cmd->cmdAlias[i] != tempCmd[i]) + { + minLen = i; + } + } + } + } + cmd = cmd->next; + } + + /** -# autocomplete found command */ + if(NULL != tempCmd) + { + /** -# calculate the size of the command to be inserted */ + sizeDiff = minLen - inst->cursor; + + /** -# copy the found part into the buffer and display it */ + utils_insertChars(inst, &(tempCmd[inst->cursor]), sizeDiff); + + /** -# on exact match there is no need to double Tab to display all */ + if(true == exactMatch) + { + inst->tabCounter = 0u; + } + } + } + else + { + /* nothing to do here */ + } +} + +/** + * @} + */ diff --git a/src/shellmatta_autocomplete.h b/src/shellmatta_autocomplete.h new file mode 100644 index 0000000..9b8349c --- /dev/null +++ b/src/shellmatta_autocomplete.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_autocomplete.h + * @brief autocomplete function of shellmatta + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_api + * @{ + */ +#ifndef _SHELLMATTA_AUTOCOMPLETE_H_ +#define _SHELLMATTA_AUTOCOMPLETE_H_ + +void autocomplete_run(shellmatta_instance_t *inst); + +#endif + +/** @} */ + diff --git a/src/shellmatta_escape.c b/src/shellmatta_escape.c new file mode 100644 index 0000000..92feb5f --- /dev/null +++ b/src/shellmatta_escape.c @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_escape.c + * @brief functions to parse and generate escape sequences + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_escape + * @{ + */ + +#include "shellmatta.h" +#include "shellmatta_escape.h" +#include "shellmatta_utils.h" +#include "shellmatta_history.h" +#include "shellmatta_autocomplete.h" +#include + +/** + * @brief processes the excape character stream of the instance and chacks + * if an arrow key was pressed + * @param[in] inst pointer to a shellmatta instance + * @return #SHELLMATTA_OK if an arrow key was pressed + */ +shellmatta_retCode_t escape_processArrowKeys(shellmatta_instance_t *inst) +{ + shellmatta_retCode_t ret = SHELLMATTA_USE_FAULT; + + if ('[' == inst->escapeChars[0]) + { + ret = SHELLMATTA_OK; + + switch (inst->escapeChars[1]) + { + case 'A': /* arrow up */ + history_storeCmd(inst); + if(false == inst->historyReadUp) + { + history_navigate(inst, -1); + } + inst->historyReadUp = true; + utils_clearInput(inst); + history_restoreCmd(inst); + history_navigate(inst, -1); + + break; + case 'B': /* arrow down */ + + if((inst->historyRead != inst->historyEnd)) + { + history_storeCmd(inst); + if(true == inst->historyReadUp) + { + history_navigate(inst, 1); + } + inst->historyReadUp = false; + history_navigate(inst, 1); + utils_clearInput(inst); + history_restoreCmd(inst); + } + break; + case 'C': /* arrow right */ + utils_forwardCursor(inst, 1u); + break; + case 'D': /* arrow left */ + utils_rewindCursor(inst, 1u); + break; + default: + /** ignore unknown escape */ + ret = SHELLMATTA_USE_FAULT; + break; + } + } + return ret; +} + +/** + * @brief handles a ANSI escape sequence to be able to react to some + * special keys + * @param[in] inst pointer to a shellmatta instance + * @param[in] data new received character of the escape sequence + */ +void escape_handleSequence(shellmatta_instance_t *inst, char data) +{ + switch (inst->escapeCounter) + { + case 1u: + inst->escapeChars[inst->escapeCounter - 1] = data; + inst->escapeCounter ++; + break; + case 2u: + inst->escapeChars[inst->escapeCounter - 1] = data; + /** -# check if an arrow key was pressed */ + if(SHELLMATTA_OK == escape_processArrowKeys(inst)) + { + inst->escapeCounter = 0u; + } + /** -# check if end was pressed */ + else if ( (0x4f == inst->escapeChars[0]) + && (0x46 == inst->escapeChars[1])) + { + utils_forwardCursor(inst, inst->inputCount - inst->cursor); + inst->escapeCounter = 0u; + } + /** -# if the highest bit is not set this is usually not the end of the + * sequence - so we go on */ + else if(0u == (0x40 & data)) + { + inst->escapeCounter ++; + } + else + { + inst->escapeCounter = 0u; + } + break; + case 3u: + /** -# check if delete was pressed */ + inst->escapeChars[inst->escapeCounter - 1] = data; + if( (0x5b == inst->escapeChars[0]) + && (0x33 == inst->escapeChars[1]) + && (0x7e == inst->escapeChars[2])) + { + utils_removeChars(inst, 1u, false); + inst->escapeCounter = 0u; + } + /** -# check if pos1 was pressed */ + else if ( (0x5bu == inst->escapeChars[0]) + && (0x31u == inst->escapeChars[1]) + && (0x7eu == inst->escapeChars[2])) + { + utils_rewindCursor(inst, inst->cursor); + inst->escapeCounter = 0u; + } + else if(0u == (0x40u & data)) + { + inst->escapeCounter ++; + } + else + { + inst->escapeCounter = 0u; + } + break; + default: + inst->escapeCounter = 0u; + break; + } +} + +/** + * @} + */ diff --git a/src/shellmatta_escape.h b/src/shellmatta_escape.h new file mode 100644 index 0000000..45d1a73 --- /dev/null +++ b/src/shellmatta_escape.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_escape.h + * @brief functions to parse and generate escape sequences + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_escape + * @{ + */ +#ifndef _SHELLMATTA_ESCAPE_H_ +#define _SHELLMATTA_ESCAPE_H_ + +#include "shellmatta.h" +#include + +shellmatta_retCode_t escape_processArrowKeys(shellmatta_instance_t *inst); +void escape_handleSequence(shellmatta_instance_t *inst, char data); + +#endif + +/** @} */ + diff --git a/src/shellmatta_history.c b/src/shellmatta_history.c new file mode 100644 index 0000000..bc8317a --- /dev/null +++ b/src/shellmatta_history.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_history.c + * @brief history buffer functions of shellmatta + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_history + * @{ + */ + +#include "shellmatta_history.h" +#include "shellmatta.h" +#include "shellmatta_utils.h" + +/** + * @brief appends a byte to the history ring stack buffer + * @param[in] inst pointer to a shellmatta instance + * @param[in] byte byte to append to the history buffer + */ +static void appendHistoryByte(shellmatta_instance_t *inst, char byte) +{ + /** -# calculate the new history buffer index */ + inst->historyEnd ++; + if(inst->historyEnd >= inst->historyBufferSize) + { + inst->historyEnd = 0u; + } + + /** -# append the byte */ + inst->historyBuffer[inst->historyEnd] = byte; + + /** -# check if the we overwrite an existing stored command */ + if(inst->historyEnd == inst->historyStart) + { + /** -# move the start pointer to the next termination (0) */ + do + { + inst->historyStart ++; + + if(inst->historyStart >= inst->historyBufferSize) + { + inst->historyStart = 0u; + } + + }while(0u != inst->historyBuffer[inst->historyStart]); + } +} + +/** + * @brief reads a byte from the history buffer and decreases the read index + * @param[in] inst pointer to a shellmatta instance + * @param[out] byte pointer to a char where the read out byte will be stored + * @return + */ +static bool getHistoryByte(shellmatta_instance_t *inst, char *byte) +{ + bool ret = false; + + /** -# check if we have reached the end of the buffer already */ + if(inst->historyRead != inst->historyStart) + { + /** -# read out one byte and decrease the read index */ + *byte = inst->historyBuffer[inst->historyRead]; + + if(0u == inst->historyRead) + { + inst->historyRead = inst->historyBufferSize; + } + + inst->historyRead --; + + ret = true; + } + + return ret; +} + +bool history_navigate(shellmatta_instance_t *inst, int32_t cnt) +{ + bool ret = true; + uint32_t tempReadIdx = 0u; + + while((cnt > 0) && (true == ret)) + { + if(inst->historyRead != inst->historyEnd) + { + inst->historyRead ++; + } + while(inst->historyRead != inst->historyEnd) + { + inst->historyRead ++; + if(inst->historyRead >= inst->historyBufferSize) + { + inst->historyRead = 0u; + } + + if( (inst->historyRead != inst->historyEnd) + && (0u == inst->historyBuffer[inst->historyRead])) + { + if(0u == inst->historyRead) + { + inst->historyRead = inst->historyBufferSize; + } + inst->historyRead --; + cnt -= 1; + break; + } + } + + if(inst->historyRead == inst->historyEnd) + { + ret = false; + } + } + + while((cnt < 0) && (true == ret)) + { + tempReadIdx = inst->historyRead; + while(inst->historyRead != inst->historyStart) + { + if(0u == inst->historyRead) + { + inst->historyRead = inst->historyBufferSize; + } + inst->historyRead --; + + if( (inst->historyRead != inst->historyStart) + && (0u == inst->historyBuffer[inst->historyRead])) + { + if(0u == inst->historyRead) + { + inst->historyRead = inst->historyBufferSize; + } + inst->historyRead --; + cnt += 1; + break; + } + } + if(inst->historyRead == inst->historyStart) + { + inst->historyRead = tempReadIdx; + inst->historyReadUp = false; + ret = false; + } + } + + return ret; +} + +/** + * @brief stores the current command from the instances buffer into the + * history buffer + * @param[in] inst pointer to a shellmatta instance + */ +void history_storeCmd(shellmatta_instance_t *inst) +{ + uint32_t i; + + /** -# check if we have enough room for the command in the history buffer + * and there is a new command to be stored */ + if( (inst->historyBufferSize > inst->inputCount) + && (0u != inst->inputCount) + && (true == inst->dirty)) + { + /** -# append the command termination */ + appendHistoryByte(inst, 0u); + + /** -# append the command byte wise in reverse direction */ + for(i = inst->inputCount; i > 0u; i --) + { + appendHistoryByte(inst, inst->buffer[i - 1u]); + } + } + + /** -# remove the dirty flag - everything is nice and saved */ + inst->dirty = false; +} + +/** + * @brief navigates in the history buffer by the given number of commands + * @param[in] inst pointer to a shellmatta instance + * @param[in] cnt direction and count to navigate + * @return + */ + +/** + * @brief restores the command from the history buffer where the read + * index points on + * @param[in] inst pointer to a shellmatta instance + */ +void history_restoreCmd(shellmatta_instance_t *inst) +{ + char byte; + bool ret = true; + + ret = getHistoryByte(inst, &byte); + while((ret == true) && (byte != 0u)) + { + inst->buffer[inst->inputCount] = byte; + inst->inputCount ++; + inst->cursor ++; + ret = getHistoryByte(inst, &byte); + } + + utils_writeEcho(inst, inst->buffer, inst->inputCount); + history_navigate(inst, 1); + inst->dirty = false; +} + +/** + * @brief resets the history buffer pointers to show to the most recent + * command again + * @param[in] inst pointer to a shellmatta instance + */ +void history_reset(shellmatta_instance_t *inst) +{ + inst->historyRead = inst->historyEnd; + inst->historyReadUp = true; +} + +/** + * @} + */ diff --git a/src/shellmatta_history.h b/src/shellmatta_history.h new file mode 100644 index 0000000..445b4de --- /dev/null +++ b/src/shellmatta_history.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_history.h + * @brief history buffer functions of shellmatta + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_api + * @{ + */ +#ifndef _SHELLMATTA_HISTORY_H_ +#define _SHELLMATTA_HISTORY_H_ + +#include "shellmatta.h" +#include + +bool history_navigate(shellmatta_instance_t *inst, int32_t cnt); +void history_storeCmd(shellmatta_instance_t *inst); +void history_restoreCmd(shellmatta_instance_t *inst); +void history_reset(shellmatta_instance_t *inst); + +#endif + +/** @} */ + diff --git a/src/shellmatta_utils.c b/src/shellmatta_utils.c new file mode 100644 index 0000000..7ee44e7 --- /dev/null +++ b/src/shellmatta_utils.c @@ -0,0 +1,346 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_utils.c + * @brief util/helper functions of shellmatta + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_utils + * @{ + */ + +#include "shellmatta_utils.h" +#include "shellmatta.h" +#include + +/** + * @brief function to write an echo to the output depending on + * the echo enabled state of the instance + * @param[in] inst pointer to a shellmatta instance + * @param[in] data pointer to the data so send + * @param[in] length length of the data to send (in byte) + */ +void utils_writeEcho( shellmatta_instance_t *inst, + const char *data, + uint32_t length) +{ + if(true == inst->echoEnabled) + { + inst->write(data, length); + } +} + +/** + * @brief itoa like function to convert int to an ascii string + * @warning you have to provide a large enough buffer + * @param[in] value + * @param[in,out] buffer + * @param[in] base + * @return number of bytes in string + */ +uint32_t utils_shellItoa(int32_t value, char *buffer, uint32_t base) +{ + char tempBuffer[34u]; + uint32_t i; + uint32_t bufferIdx = 0u; + char digitValue; + + /** -# check the base for plausibility */ + if((2 <= base) && (16 >= base)) + { + /** -# check for sign */ + if(0 > value) + { + value = value * (-1); + buffer[0u] = '-'; + bufferIdx += 1u; + } + + /** -# loop through all digits in reverse order */ + i = 0u; + do + { + digitValue = (char) (value % base); + tempBuffer[i] = (digitValue < 10u) ? ('0' + digitValue) : (('A' - 10) + digitValue); + value /= base; + i ++; + }while(value > 0); + + /** -# store the string in the correct order onto the buffer */ + while(i > 0u) + { + buffer[bufferIdx] = tempBuffer[i - 1u]; + i --; + bufferIdx ++; + } + } + return bufferIdx; +} + +/** + * @brief tells the terminal to save the current cursor position + * @param[in] inst pointer to shellmatta instance + */ +void utils_saveCursorPos(shellmatta_instance_t *inst) +{ + utils_writeEcho(inst, "\e[s", 3u); +} + +/** + * @brief tells the terminal to restore the saved cursor position + * @param[in] inst pointer to shellmatta instance + */ +void utils_restoreCursorPos(shellmatta_instance_t *inst) +{ + utils_writeEcho(inst, "\e[u", 3u); +} + +/** + * @brief tells the terminal to erase the line on the right side of the + * cursor + * @param[in] inst pointer to shellmatta instance + */ +void utils_eraseLine(shellmatta_instance_t *inst) +{ + utils_writeEcho(inst, "\e[K", 3u); +} + +/** + * @brief moves the cursor back by the given amoung of characters + * @param[in] inst pointer to shellmatta instance + * @param[in] length number of characters to rewind + */ +void utils_rewindCursor(shellmatta_instance_t *inst, uint32_t length) +{ + char terminalCmd[16]; + size_t size; + + length = SHELLMATTA_MIN (length, inst->cursor); + if(length > 0u) + { + terminalCmd[0] = '\e'; + terminalCmd[1] = '['; + size = 2u + utils_shellItoa(length, &terminalCmd[2], 10); + terminalCmd[size] = 'D'; + utils_writeEcho(inst, terminalCmd, size + 1u); + inst->cursor -= length; + } +} + +/** + * @brief moves the cursor forward by the given amoung of characters + * @param[in] inst pointer to shellmatta instance + * @param[in] length number of characters to move forward + */ +void utils_forwardCursor(shellmatta_instance_t *inst, uint32_t length) +{ + char terminalCmd[16]; + size_t size; + + length = SHELLMATTA_MAX (length, (inst->inputCount - inst->cursor)); + if (length > 0u) + { + terminalCmd[0] = '\e'; + terminalCmd[1] = '['; + size = 2u + utils_shellItoa(length, &terminalCmd[2], 10); + terminalCmd[size] = 'C'; + utils_writeEcho(inst, terminalCmd, size + 1u); + inst->cursor += length; + } +} + +/** + * @brief inserts the given amount of characters at the cursor position + * @param[in] inst pointer to shellmatta instance + * @param[in] data pointer to the data to be inserted + * @param[in] length length of the data to be inserted + */ +void utils_insertChars( shellmatta_instance_t *inst, + char *data, + uint32_t length) +{ + if(0u != length) + { + /** -# check if we have to move chars in the buffer */ + if( (inst->cursor != inst->inputCount) + && (SHELLMATTA_MODE_INSERT == inst->mode)) + { + /** -# move the existing chars */ + for ( uint32_t i = inst->inputCount; + i > inst->cursor; + i --) + { + inst->buffer[i + length - 1] = inst->buffer[i - 1]; + } + + /** -# store and print the new chars */ + memcpy(&(inst->buffer[inst->cursor]), data, length); + utils_writeEcho(inst, data, length); + + /** -# print the other chars and restore the cursor to this position */ + utils_eraseLine(inst); + utils_saveCursorPos(inst); + utils_writeEcho( inst, + &(inst->buffer[inst->cursor + length]), + inst->inputCount - inst->cursor); + utils_restoreCursorPos(inst); + } + /** -# just overwrite/append the chars */ + else + { + memcpy(&(inst->buffer[inst->cursor]), data, length); + utils_writeEcho(inst, data, length); + } + + inst->inputCount += length; + inst->cursor += length; + } +} + +/** + * @brief removes the given amount of characters from the current cursor + * position + * @param[in] inst pointer to a shellmatta instance + * @param[in] length number of characters to remove + * @param[in] backspace remove characters left of the cursor + */ +void utils_removeChars( shellmatta_instance_t *inst, + uint32_t length, + bool backspace) +{ + if(0u != length) + { + /** -# rewind the cursor in case of backspace */ + if(true == backspace) + { + length = SHELLMATTA_MIN (length, inst->cursor); + utils_rewindCursor(inst, length); + } + else + { + length = SHELLMATTA_MIN (length, inst->inputCount - inst->cursor); + } + /** -# delete the char at the cursor position */ + for ( uint32_t i = inst->cursor; + i < (inst->inputCount - length); + i++) + { + inst->buffer[i] = inst->buffer[i + length]; + } + + /** -# print the rest of the line again */ + utils_eraseLine(inst); + utils_saveCursorPos(inst); + utils_writeEcho( inst, + &(inst->buffer[inst->cursor]), + (inst->inputCount - inst->cursor - length)); + utils_restoreCursorPos(inst); + + inst->inputCount -= length; + } +} + +/** + * @brief clears the input buffer and removes the currend command from + * the terminal output + * @param[in] inst pointer to a shellmatta instance + */ +void utils_clearInput(shellmatta_instance_t *inst) +{ + utils_rewindCursor(inst, inst->cursor); + utils_eraseLine(inst); + inst->inputCount = 0u; + inst->dirty = false; +} + +/** + * @brief prints all possible commands with description and usage + * @param[in] handle handle shellmatta instance handle + * @param[in] arguments not used here + * @param[in] length not used here + * @return #SHELLMATTA_OK + * #SHELLMATTA_ERROR (buffer overflow) + */ +static shellmatta_retCode_t helpCmdFct(shellmatta_handle_t handle, const char *arguments, uint32_t length) +{ + shellmatta_retCode_t ret = SHELLMATTA_OK; + shellmatta_instance_t *inst = (shellmatta_instance_t*) handle; + shellmatta_cmd_t *cmd = inst->cmdList; + size_t maxCmdLen = 0u; + size_t maxCmdAliasLen = 0u; + size_t maxCmdHelpLen = 0u; + size_t cmdLen = 0u; + size_t cmdAliasLen = 0u; + size_t cmdHelpLen = 0u; + uint32_t tabCnt = 0u; + static const char tabBuffer[] = { ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' ', + ' ', ' ', ' ', ' '}; + + /** -# loop through all commands to determine the lengths of each cmd */ + while(NULL != cmd) + { + maxCmdLen = SHELLMATTA_MAX(maxCmdLen, strlen(cmd->cmd)); + maxCmdAliasLen = SHELLMATTA_MAX(maxCmdAliasLen, strlen(cmd->cmdAlias)); + maxCmdHelpLen = SHELLMATTA_MAX(maxCmdHelpLen, strlen(cmd->helpText)); + cmd = cmd->next; + } + + /** -# loop through all commands and print all possible information */ + cmd = inst->cmdList; + while(NULL != cmd) + { + /** -# determine the length of each field to add padding */ + cmdLen = strlen(cmd->cmd); + cmdAliasLen = strlen(cmd->cmdAlias); + cmdHelpLen = strlen(cmd->helpText); + + inst->write(cmd->cmd, strlen(cmd->cmd)); + tabCnt = (maxCmdLen - cmdLen) + 2u; + SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write); + + inst->write(cmd->cmdAlias, strlen(cmd->cmdAlias)); + tabCnt = (maxCmdAliasLen - cmdAliasLen) + 2u; + SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write); + + inst->write(cmd->helpText, strlen(cmd->helpText)); + tabCnt = (maxCmdHelpLen - cmdHelpLen) + 2u; + SHELLMATTA_PRINT_BUFFER(tabBuffer, tabCnt, inst->write); + + inst->write(cmd->usageText, strlen(cmd->usageText)); + inst->write("\r\n", 2u); + + cmd = cmd->next; + } + + (void)arguments; + (void)length; + + return ret; +} +shellmatta_cmd_t helpCmd = {"help", "h", "Print this help text", "help", helpCmdFct, NULL}; + +/** + * @brief terminates an input and prints the prompt again + * @param[in] inst pointer to a shellmatta instance + */ +void utils_terminateInput(shellmatta_instance_t *inst) +{ + inst->inputCount = 0u; + inst->cursor = 0u; + inst->write("\r\n", 2u); + inst->write(inst->prompt, strlen(inst->prompt)); +} + +/** + * @} + */ diff --git a/src/shellmatta_utils.h b/src/shellmatta_utils.h new file mode 100644 index 0000000..9c3a5e4 --- /dev/null +++ b/src/shellmatta_utils.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Stefan Strobel + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/** + * @file shellmatta_utils.h + * @brief util/helper functions of shellmatta + * @author Stefan Strobel + */ + +/** + * @addtogroup shellmatta_utils + * @{ + */ +#ifndef _SHELLMATTA_UTILS_H_ +#define _SHELLMATTA_UTILS_H_ + +#include "shellmatta.h" +#include + + +#define SHELLMATTA_MIN(a,b) (((a) > (b)) ? (b) : (a)) +#define SHELLMATTA_MAX(a,b) (((a) < (b)) ? (b) : (a)) +#define SHELLMATTA_PRINT_BUFFER(buffer,cnt,fct) \ + while((cnt) > sizeof((buffer))) \ + { \ + (cnt) -= sizeof((buffer)); \ + (fct)((buffer), sizeof((buffer))); \ + } \ + if((cnt) != 0u) \ + { \ + (fct)((buffer), (cnt)); \ + } + +extern shellmatta_cmd_t helpCmd; + +#define SHELLMATTA_MAGIC 0x5101E110u + +#ifndef SHELLMATTA_OUTPUT_BUFFER_SIZE +#define SHELLMATTA_OUTPUT_BUFFER_SIZE 128u +#endif + +void utils_writeEcho( shellmatta_instance_t *inst, + const char *data, + uint32_t length); +uint32_t utils_shellItoa(int32_t value, char *buffer, uint32_t base); +void utils_saveCursorPos(shellmatta_instance_t *inst); +void utils_restoreCursorPos(shellmatta_instance_t *inst); +void utils_eraseLine(shellmatta_instance_t *inst); +void utils_rewindCursor(shellmatta_instance_t *inst, uint32_t length); +void utils_forwardCursor(shellmatta_instance_t *inst, uint32_t length); +void utils_insertChars( shellmatta_instance_t *inst, + char *data, + uint32_t length); +void utils_removeChars( shellmatta_instance_t *inst, + uint32_t length, + bool backspace); +void utils_clearInput(shellmatta_instance_t *inst); +void utils_terminateInput(shellmatta_instance_t *inst); + +#endif + +/** @} */ +