Show completion options for bind key names in console

Show completion options for key names for all bind commands (`bind`, `binds`, `bind_reset`) in the local console.

At the moment this only works for completing the first key name but not for composite binds that use `+`, which would work differently than other console argument completion.

Unnamed keys (starting with `&`) are not shown as completion options.

Refactor console argument completion to reduce duplicate code.
This commit is contained in:
Robert Müller 2024-03-02 23:26:53 +01:00
parent 1afdf47362
commit 3073934df1

View file

@ -73,7 +73,37 @@ void CConsoleLogger::OnConsoleDeletion()
m_pConsole = nullptr; m_pConsole = nullptr;
} }
static int ArgumentPosition(const char *pStr, const std::vector<std::pair<const char *, int>> &vCommands) enum class EArgumentCompletionType
{
NONE,
TUNE,
SETTING,
KEY,
};
class CArgumentCompletionEntry
{
public:
EArgumentCompletionType m_Type;
const char *m_pCommandName;
int m_ArgumentIndex;
};
static const CArgumentCompletionEntry gs_aArgumentCompletionEntries[] = {
{EArgumentCompletionType::TUNE, "tune", 0},
{EArgumentCompletionType::TUNE, "tune_reset", 0},
{EArgumentCompletionType::TUNE, "toggle_tune", 0},
{EArgumentCompletionType::TUNE, "tune_zone", 1},
{EArgumentCompletionType::SETTING, "reset", 0},
{EArgumentCompletionType::SETTING, "toggle", 0},
{EArgumentCompletionType::SETTING, "access_level", 0},
{EArgumentCompletionType::SETTING, "+toggle", 0},
{EArgumentCompletionType::KEY, "bind", 0},
{EArgumentCompletionType::KEY, "binds", 0},
{EArgumentCompletionType::KEY, "unbind", 0},
};
static std::pair<EArgumentCompletionType, int> ArgumentCompletion(const char *pStr)
{ {
const char *pCommandStart = pStr; const char *pCommandStart = pStr;
const char *pIt = pStr; const char *pIt = pStr;
@ -82,50 +112,37 @@ static int ArgumentPosition(const char *pStr, const std::vector<std::pair<const
const char *pCommandEnd = pIt; const char *pCommandEnd = pIt;
if(!CommandLength) if(!CommandLength)
return -1; return {EArgumentCompletionType::NONE, -1};
pIt = str_skip_whitespaces_const(pIt); pIt = str_skip_whitespaces_const(pIt);
if(pIt == pCommandEnd) if(pIt == pCommandEnd)
return -1; return {EArgumentCompletionType::NONE, -1};
for(const auto &[pCommand, ArgIndex] : vCommands) for(const auto &Entry : gs_aArgumentCompletionEntries)
{ {
int Length = maximum(str_length(pCommand), CommandLength); int Length = maximum(str_length(Entry.m_pCommandName), CommandLength);
if(str_comp_nocase_num(pCommand, pCommandStart, Length) == 0) if(str_comp_nocase_num(Entry.m_pCommandName, pCommandStart, Length) == 0)
{ {
int CurrentArg = 0; int CurrentArg = 0;
const char *pArgStart = nullptr, *pArgEnd = nullptr; const char *pArgStart = nullptr, *pArgEnd = nullptr;
while(CurrentArg < ArgIndex) while(CurrentArg < Entry.m_ArgumentIndex)
{ {
pArgStart = pIt; pArgStart = pIt;
pIt = str_skip_to_whitespace_const(pIt); // Skip argument value pIt = str_skip_to_whitespace_const(pIt); // Skip argument value
pArgEnd = pIt; pArgEnd = pIt;
if(!pIt[0] || pArgStart == pIt) // Check that argument is not empty if(!pIt[0] || pArgStart == pIt) // Check that argument is not empty
return -1; return {EArgumentCompletionType::NONE, -1};
pIt = str_skip_whitespaces_const(pIt); // Go to next argument position pIt = str_skip_whitespaces_const(pIt); // Go to next argument position
CurrentArg++; CurrentArg++;
} }
if(pIt == pArgEnd) if(pIt == pArgEnd)
return -1; // Check that there is at least one space after return {EArgumentCompletionType::NONE, -1}; // Check that there is at least one space after
return pIt - pStr; return {Entry.m_Type, pIt - pStr};
} }
} }
return -1; return {EArgumentCompletionType::NONE, -1};
}
// Vector of pair, where each pair is <command, index to autocomplete>
static const std::vector<std::pair<const char *, int>> gs_vTuningCommands{
{"tune", 0},
{"tune_reset", 0},
{"toggle_tune", 0},
{"tune_zone", 1},
};
// Returns the position of the start of the autocompletion, or -1
static int TuningCommandArgumentPos(const char *pStr)
{
return ArgumentPosition(pStr, gs_vTuningCommands);
} }
static int PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCallback = IConsole::EmptyPossibleCommandCallback, void *pUser = nullptr) static int PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCallback = IConsole::EmptyPossibleCommandCallback, void *pUser = nullptr)
@ -142,15 +159,20 @@ static int PossibleTunings(const char *pStr, IConsole::FPossibleCallback pfnCall
return Index; return Index;
} }
static const std::vector<std::pair<const char *, int>> gs_vSettingCommands{ static int PossibleKeys(const char *pStr, IInput *pInput, IConsole::FPossibleCallback pfnCallback = IConsole::EmptyPossibleCommandCallback, void *pUser = nullptr)
{"reset", 0},
{"toggle", 0},
{"access_level", 0},
{"+toggle", 0},
};
static int SettingCommandArgumentPos(const char *pStr)
{ {
return ArgumentPosition(pStr, gs_vSettingCommands); int Index = 0;
for(int Key = KEY_A; Key < KEY_JOY_AXIS_11_RIGHT; Key++)
{
// Ignore unnamed keys starting with '&'
const char *pKeyName = pInput->KeyName(Key);
if(pKeyName[0] != '&' && str_find_nocase(pKeyName, pStr))
{
pfnCallback(Index, pKeyName, pUser);
Index++;
}
}
return Index;
} }
const ColorRGBA CGameConsole::ms_SearchHighlightColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f); const ColorRGBA CGameConsole::ms_SearchHighlightColor = ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f);
@ -435,28 +457,26 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)
} }
// Argument completion // Argument completion
const int SettingCompletionPos = SettingCommandArgumentPos(GetString()); const auto [CompletionType, CompletionPos] = ArgumentCompletion(GetString());
const int TuningCompletionPos = TuningCommandArgumentPos(GetString()); if(CompletionType == EArgumentCompletionType::TUNE)
if(TuningCompletionPos != -1)
CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument); CompletionEnumerationCount = PossibleTunings(m_aCompletionBufferArgument);
else if(SettingCompletionPos != -1) else if(CompletionType == EArgumentCompletionType::SETTING)
CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands); CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands);
else if(CompletionType == EArgumentCompletionType::KEY)
CompletionEnumerationCount = PossibleKeys(m_aCompletionBufferArgument, m_pGameConsole->Input());
if(CompletionEnumerationCount) if(CompletionEnumerationCount)
{ {
if(m_CompletionChosenArgument == -1 && Direction < 0) if(m_CompletionChosenArgument == -1 && Direction < 0)
m_CompletionChosenArgument = 0; m_CompletionChosenArgument = 0;
m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount; m_CompletionChosenArgument = (m_CompletionChosenArgument + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
if(TuningCompletionPos != -1 && m_pGameConsole->Client()->RconAuthed() && m_Type == CGameConsole::CONSOLETYPE_REMOTE) m_CompletionArgumentPosition = CompletionPos;
{ if(CompletionType == EArgumentCompletionType::TUNE)
m_CompletionArgumentPosition = TuningCompletionPos;
PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this); PossibleTunings(m_aCompletionBufferArgument, PossibleArgumentsCompleteCallback, this);
} else if(CompletionType == EArgumentCompletionType::SETTING)
else if(SettingCompletionPos != -1)
{
m_CompletionArgumentPosition = SettingCompletionPos;
m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this); m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBufferArgument, m_CompletionFlagmask, UseTempCommands, PossibleArgumentsCompleteCallback, this);
} else if(CompletionType == EArgumentCompletionType::KEY)
PossibleKeys(m_aCompletionBufferArgument, m_pGameConsole->Input(), PossibleArgumentsCompleteCallback, this);
} }
else if(m_CompletionChosenArgument != -1) else if(m_CompletionChosenArgument != -1)
{ {
@ -526,30 +546,24 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)
{ {
if(Event.m_Key != KEY_TAB && Event.m_Key != KEY_LSHIFT && Event.m_Key != KEY_RSHIFT) if(Event.m_Key != KEY_TAB && Event.m_Key != KEY_LSHIFT && Event.m_Key != KEY_RSHIFT)
{ {
m_CompletionChosen = -1;
str_copy(m_aCompletionBuffer, m_Input.GetString());
const char *pInputStr = m_Input.GetString(); const char *pInputStr = m_Input.GetString();
const int TuningCompletionPos = TuningCommandArgumentPos(GetString()); m_CompletionChosen = -1;
for(const auto &[pCmd, _] : gs_vTuningCommands) str_copy(m_aCompletionBuffer, pInputStr);
{
const int Len = str_length(pCmd);
if(str_comp_nocase_num(pInputStr, pCmd, Len) == 0 && str_isspace(pInputStr[Len]))
{
m_CompletionChosenArgument = -1;
str_copy(m_aCompletionBufferArgument, &pInputStr[TuningCompletionPos]);
}
}
const int SettingCompletionPos = SettingCommandArgumentPos(GetString()); const auto [CompletionType, CompletionPos] = ArgumentCompletion(GetString());
for(const auto &[pCmd, _] : gs_vSettingCommands) if(CompletionType != EArgumentCompletionType::NONE)
{ {
const int Len = str_length(pCmd); for(const auto &Entry : gs_aArgumentCompletionEntries)
if(str_comp_nocase_num(pInputStr, pCmd, Len) == 0 && str_isspace(pInputStr[Len]))
{ {
m_CompletionChosenArgument = -1; if(Entry.m_Type != CompletionType)
str_copy(m_aCompletionBufferArgument, &pInputStr[SettingCompletionPos]); continue;
const int Len = str_length(Entry.m_pCommandName);
if(str_comp_nocase_num(pInputStr, Entry.m_pCommandName, Len) == 0 && str_isspace(pInputStr[Len]))
{
m_CompletionChosenArgument = -1;
str_copy(m_aCompletionBufferArgument, &pInputStr[CompletionPos]);
}
} }
} }
@ -1072,18 +1086,19 @@ void CGameConsole::OnRender()
if(NumCommands <= 0 && pConsole->m_IsCommand) if(NumCommands <= 0 && pConsole->m_IsCommand)
{ {
const int TuningCompletionPos = TuningCommandArgumentPos(Info.m_pCurrentCmd); const auto [CompletionType, _] = ArgumentCompletion(Info.m_pCurrentCmd);
const int SettingCompletionPos = SettingCommandArgumentPos(Info.m_pCurrentCmd);
int NumArguments = 0; int NumArguments = 0;
if(TuningCompletionPos != -1 || SettingCompletionPos != -1) if(CompletionType != EArgumentCompletionType::NONE)
{ {
Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument; Info.m_WantedCompletion = pConsole->m_CompletionChosenArgument;
Info.m_TotalWidth = 0.0f; Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBufferArgument; Info.m_pCurrentCmd = pConsole->m_aCompletionBufferArgument;
if(TuningCompletionPos != -1) if(CompletionType == EArgumentCompletionType::TUNE)
NumArguments = PossibleTunings(Info.m_pCurrentCmd, PossibleCommandsRenderCallback, &Info); NumArguments = PossibleTunings(Info.m_pCurrentCmd, PossibleCommandsRenderCallback, &Info);
else if(SettingCompletionPos != -1) else if(CompletionType == EArgumentCompletionType::SETTING)
NumArguments = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info); NumArguments = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info);
else if(CompletionType == EArgumentCompletionType::KEY)
NumArguments = PossibleKeys(Info.m_pCurrentCmd, Input(), PossibleCommandsRenderCallback, &Info);
pConsole->m_CompletionRenderOffset = Info.m_Offset; pConsole->m_CompletionRenderOffset = Info.m_Offset;
} }