Skip to content

File wsh_shell_autocomplete.c

File List > src > wsh_shell_autocomplete.c

Go to the documentation of this file

#include "wsh_shell_autocomplete.h"

#if WSH_SHELL_AUTOCOMPLETE

WshShell_Bool_t WshShellAutocomplete_Try(WshShell_Char_t* pInBuff, WshShell_Size_t inBuffLen,
                                         WshShellCmd_Table_t* pShellCommands) {
    WSH_SHELL_ASSERT(pInBuff && pShellCommands);
    if (!pInBuff || !pShellCommands)
        return false;

    WshShell_Char_t inputCopy[WSH_SHELL_INTR_BUFF_LEN] = {0};
    WSH_SHELL_STRNCPY(inputCopy, pInBuff, inBuffLen);

    const WshShell_Char_t* pcInputCopyTrimmed =
        WshShellStr_TrimString(inputCopy, WSH_SHELL_STRLEN(inputCopy));
    WshShell_Size_t inputCopyTrimmedLen = WSH_SHELL_STRLEN(pcInputCopyTrimmed);

    const WshShellCmd_t* pcDefCmd = WshShellDefCmd_GetPtr();

    WshShell_Size_t cmdNum = WshShellCmd_GetCmdNum(pShellCommands);
    WshShell_Char_t candidates[cmdNum + 1][WSH_SHELL_CMD_NAME_LEN];  // + one more for default cmd
    WshShell_Size_t matchCount      = 0;
    const WshShellCmd_t* pcCmdMatch = NULL;

    // Check default command first
    if (WSH_SHELL_STRNCMP(pcInputCopyTrimmed, pcDefCmd->Name, inputCopyTrimmedLen) == 0) {
        WSH_SHELL_STRNCPY(candidates[matchCount], pcDefCmd->Name, WSH_SHELL_CMD_NAME_LEN - 1);
        candidates[matchCount][WSH_SHELL_CMD_NAME_LEN - 1] = '\0';  // safety null-term
        pcCmdMatch                                         = pcDefCmd;
        matchCount++;
    }

    // Collect other matching commands
    for (WshShell_Size_t cmdIdx = 0; cmdIdx < cmdNum; cmdIdx++) {
        const WshShellCmd_t* pcCmd = WshShellCmd_GetCmdByIndex(pShellCommands, cmdIdx);
        if (WSH_SHELL_STRNCMP(pcInputCopyTrimmed, pcCmd->Name, inputCopyTrimmedLen) == 0) {
            WSH_SHELL_STRNCPY(candidates[matchCount], pcCmd->Name, WSH_SHELL_CMD_NAME_LEN);
            pcCmdMatch = pcCmd;
            matchCount++;
        }
    }

    // Create padding buff for pretty message print
    WshShell_Char_t sysMsgShift[WSH_SHELL_AUTOCOMPLETE_PAD_LEN + 1];
    WshShell_Size_t padLen = (inBuffLen < WSH_SHELL_AUTOCOMPLETE_PAD_LEN)
                                 ? (WSH_SHELL_AUTOCOMPLETE_PAD_LEN - inBuffLen)
                                 : 0;
    WSH_SHELL_MEMSET((void*)sysMsgShift, WSH_SHELL_AUTOCOMPLETE_PAD_SYM, padLen);
    sysMsgShift[0]      = ' ';
    sysMsgShift[padLen] = '\0';

    // No matches found
    if (matchCount == 0) {
        WSH_SHELL_PRINT_SYS("%s /autocomplete: no matches\r\n", sysMsgShift);
        return false;
    }

    // Exactly one match found — autocomplete to full command
    if (matchCount == 1) {
        //Fetch options if command already autocomplited
        if (inputCopyTrimmedLen == WSH_SHELL_STRLEN(candidates[0]) &&
            WSH_SHELL_MEMCMP(pcInputCopyTrimmed, candidates[0], inputCopyTrimmedLen) == 0) {
            WSH_SHELL_PRINT_SYS("%s /autocomplete flags found: ", sysMsgShift);

            const WshShellOption_t* pOpt = pcCmdMatch->Options;
            WshShell_Bool_t optFound     = false;
            if (pOpt != NULL) {
                for (; pOpt->Type != WSH_SHELL_OPTION_END; pOpt++) {
                    if (pOpt->Type == WSH_SHELL_OPTION_NO ||
                        pOpt->Type == WSH_SHELL_OPTION_WAITS_INPUT)
                        continue;

                    optFound = true;
                    WSH_SHELL_PRINT_INFO("[%s/%s] ", pOpt->ShortName, pOpt->LongName);
                }
            }

            WSH_SHELL_PRINT_INFO("%s\r\n", optFound ? "" : "none");
            return false;
        }

        WshShell_Size_t candLen  = WSH_SHELL_STRLEN(candidates[0]);
        candidates[0][candLen++] = ' ';  //add extra space if command found
        WSH_SHELL_STRNCPY(pInBuff, candidates[0], candLen);
        pInBuff[candLen] = '\0';
        return true;
    }

    // Determine longest common prefix among all matches
    WshShell_Size_t prefixLen = inBuffLen;
    WshShell_Bool_t match     = true;

    while (match && prefixLen < WSH_SHELL_CMD_NAME_LEN) {
        WshShell_Char_t nextChar = candidates[0][prefixLen];
        for (WshShell_Size_t cnt = 1; cnt < matchCount; cnt++) {
            if (candidates[cnt][prefixLen] != nextChar) {
                match = false;
                break;
            }
        }

        if (match)
            prefixLen++;
    }

    // If common prefix is longer than input, extend input
    if (prefixLen > inBuffLen) {
        WSH_SHELL_MEMCPY((void*)&pInBuff[inBuffLen], (void*)&candidates[0][inBuffLen],
                         prefixLen - inBuffLen);
        pInBuff[prefixLen] = '\0';
        return true;
    }

    // Print all candidates as options
    WSH_SHELL_PRINT_SYS("%s /autocomplete cmd found: ", sysMsgShift);
    for (WshShell_Size_t cnt = 0; cnt < matchCount; cnt++)
        WSH_SHELL_PRINT_INFO("%s ", candidates[cnt]);

    WSH_SHELL_PRINT("\r\n");

    return false;
}

#else /* WSH_SHELL_AUTOCOMPLETE */

WshShell_Bool_t WshShellAutocomplete_Try(WshShell_Char_t* pInBuff, WshShell_Size_t inBuffLen,
                                         WshShellCmd_Table_t* pShellCommands) {
    WSH_SHELL_ASSERT(pInBuff && pShellCommands);
    if (!pInBuff || !pShellCommands)
        return false;

    WshShell_Char_t sysMsgShift[WSH_SHELL_AUTOCOMPLETE_PAD_LEN + 1];
    WshShell_Size_t padLen = (inBuffLen < WSH_SHELL_AUTOCOMPLETE_PAD_LEN)
                                 ? (WSH_SHELL_AUTOCOMPLETE_PAD_LEN - inBuffLen)
                                 : 0;
    WSH_SHELL_MEMSET((void*)sysMsgShift, WSH_SHELL_AUTOCOMPLETE_PAD_SYM, padLen);
    sysMsgShift[0]      = ' ';
    sysMsgShift[padLen] = '\0';

    WSH_SHELL_PRINT_WARN("%s /autocomplete disabled", sysMsgShift);
    WSH_SHELL_PRINT("\r\n");

    return false;
}

#endif /* WSH_SHELL_AUTOCOMPLETE */