Skip to content

Commit

Permalink
Merge pull request ddnet#8352 from ChillerDragon/pr_multi_cmd_helptext
Browse files Browse the repository at this point in the history
Show help text when chaining multiple commands
  • Loading branch information
Robyt3 authored Jul 10, 2024
2 parents 89992f2 + 7b540dd commit 8e83049
Show file tree
Hide file tree
Showing 5 changed files with 158 additions and 5 deletions.
32 changes: 32 additions & 0 deletions src/base/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3155,6 +3155,38 @@ const char *str_find(const char *haystack, const char *needle)
return 0;
}

bool str_delimiters_around_offset(const char *haystack, const char *delim, int offset, int *start, int *end)
{
bool found = true;
const char *search = haystack;
const int delim_len = str_length(delim);
*start = 0;
while(str_find(search, delim))
{
const char *test = str_find(search, delim) + delim_len;
int distance = test - haystack;
if(distance > offset)
break;

*start = distance;
search = test;
}
if(search == haystack)
found = false;

if(str_find(search, delim))
{
*end = str_find(search, delim) - haystack;
}
else
{
*end = str_length(haystack);
found = false;
}

return found;
}

const char *str_rchr(const char *haystack, char needle)
{
return strrchr(haystack, needle);
Expand Down
16 changes: 16 additions & 0 deletions src/base/system.h
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,22 @@ const char *str_find_nocase(const char *haystack, const char *needle);
*/
const char *str_find(const char *haystack, const char *needle);

/**
* @ingroup Strings
*
* @param haystack String to search in
* @param delim String to search for
* @param offset Number of characters into the haystack
* @param start Will be set to the first delimiter on the left side of the offset (or haystack start)
* @param end Will be set to the furst delimiter on the right side of the offset (or haystack end)
*
* @return `true` if both delimiters were found
* @return 'false' if a delimiter is missing (it uses haystack start and end as fallback)
*
* @remark The strings are treated as zero-terminated strings.
*/
bool str_delimiters_around_offset(const char *haystay, const char *delim, int offset, int *start, int *end);

/**
* Finds the last occurrence of a character
*
Expand Down
46 changes: 41 additions & 5 deletions src/game/client/components/console.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,34 @@ void CGameConsole::CInstance::PossibleCommandsCompleteCallback(int Index, const
{
CGameConsole::CInstance *pInstance = (CGameConsole::CInstance *)pUser;
if(pInstance->m_CompletionChosen == Index)
pInstance->m_Input.Set(pStr);
{
char aBefore[IConsole::CMDLINE_LENGTH];
str_truncate(aBefore, sizeof(aBefore), pInstance->m_aCompletionBuffer, pInstance->m_CompletionCommandStart);
char aBuf[IConsole::CMDLINE_LENGTH];
str_format(aBuf, sizeof(aBuf), "%s%s%s", aBefore, pStr, pInstance->m_aCompletionBuffer + pInstance->m_CompletionCommandEnd);
pInstance->m_Input.Set(aBuf);
pInstance->m_Input.SetCursorOffset(str_length(pStr) + pInstance->m_CompletionCommandStart);
}
}

void CGameConsole::CInstance::GetCommand(const char *pInput, char (&aCmd)[IConsole::CMDLINE_LENGTH])
{
char aInput[IConsole::CMDLINE_LENGTH];
str_copy(aInput, pInput);
m_CompletionCommandStart = 0;
m_CompletionCommandEnd = 0;

char aaSeparators[][2] = {";", "\""};
for(auto *pSeparator : aaSeparators)
{
int Start, End;
str_delimiters_around_offset(aInput + m_CompletionCommandStart, pSeparator, m_Input.GetCursorOffset() - m_CompletionCommandStart, &Start, &End);
m_CompletionCommandStart += Start;
m_CompletionCommandEnd = m_CompletionCommandStart + (End - Start);
aInput[m_CompletionCommandEnd] = '\0';
}

str_copy(aCmd, aInput + m_CompletionCommandStart, sizeof(aCmd));
}

static void StrCopyUntilSpace(char *pDest, size_t DestSize, const char *pSrc)
Expand Down Expand Up @@ -445,9 +472,12 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)

if(!m_Searching)
{
char aSearch[IConsole::CMDLINE_LENGTH];
GetCommand(m_aCompletionBuffer, aSearch);

// command completion
const bool UseTempCommands = m_Type == CGameConsole::CONSOLETYPE_REMOTE && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands();
int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands);
int CompletionEnumerationCount = m_pGameConsole->m_pConsole->PossibleCommands(aSearch, m_CompletionFlagmask, UseTempCommands);
if(m_Type == CGameConsole::CONSOLETYPE_LOCAL || m_pGameConsole->Client()->RconAuthed())
{
if(CompletionEnumerationCount)
Expand All @@ -456,7 +486,7 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)
m_CompletionChosen = 0;
m_CompletionChosen = (m_CompletionChosen + Direction + CompletionEnumerationCount) % CompletionEnumerationCount;
m_CompletionArgumentPosition = 0;
m_pGameConsole->m_pConsole->PossibleCommands(m_aCompletionBuffer, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this);
m_pGameConsole->m_pConsole->PossibleCommands(aSearch, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this);
}
else if(m_CompletionChosen != -1)
{
Expand Down Expand Up @@ -581,8 +611,11 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event)

// find the current command
{
char aCmd[IConsole::CMDLINE_LENGTH];
GetCommand(GetString(), aCmd);
char aBuf[IConsole::CMDLINE_LENGTH];
StrCopyUntilSpace(aBuf, sizeof(aBuf), GetString());
StrCopyUntilSpace(aBuf, sizeof(aBuf), aCmd);

const IConsole::CCommandInfo *pCommand = m_pGameConsole->m_pConsole->GetCommandInfo(aBuf, m_CompletionFlagmask,
m_Type != CGameConsole::CONSOLETYPE_LOCAL && m_pGameConsole->Client()->RconAuthed() && m_pGameConsole->Client()->UseTempRconCommands());
if(pCommand)
Expand Down Expand Up @@ -1111,7 +1144,10 @@ void CGameConsole::OnRender()
Info.m_pOffsetChange = &pConsole->m_CompletionRenderOffsetChange;
Info.m_Width = Screen.w;
Info.m_TotalWidth = 0.0f;
Info.m_pCurrentCmd = pConsole->m_aCompletionBuffer;
char aCmd[IConsole::CMDLINE_LENGTH];
pConsole->GetCommand(pConsole->m_aCompletionBuffer, aCmd);
Info.m_pCurrentCmd = aCmd;

TextRender()->SetCursor(&Info.m_Cursor, InitialX - Info.m_Offset, InitialY + RowHeight + 2.0f, FONT_SIZE, TEXTFLAG_RENDER | TEXTFLAG_STOP_AT_END);
Info.m_Cursor.m_LineWidth = std::numeric_limits<float>::max();
const int NumCommands = m_pConsole->PossibleCommands(Info.m_pCurrentCmd, pConsole->m_CompletionFlagmask, m_ConsoleType != CGameConsole::CONSOLETYPE_LOCAL && Client()->RconAuthed() && Client()->UseTempRconCommands(), PossibleCommandsRenderCallback, &Info);
Expand Down
15 changes: 15 additions & 0 deletions src/game/client/components/console.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ class CGameConsole : public CComponent
float m_CompletionRenderOffset;
float m_CompletionRenderOffsetChange;
int m_CompletionArgumentPosition;
int m_CompletionCommandStart = 0;
int m_CompletionCommandEnd = 0;

char m_aUser[32];
bool m_UserGot;
Expand Down Expand Up @@ -113,6 +115,19 @@ class CGameConsole : public CComponent
void Dump() REQUIRES(!m_BacklogPendingLock);

const char *GetString() const { return m_Input.GetString(); }
/**
* Gets the command at the current cursor including surrounding spaces.
* Commands are split by semicolons.
*
* So if the current console input is for example "hello; world ;foo"
* ^
* and the cursor is here -------------/
* The result would be " world "
*
* @param pInput the console input line
* @param aCmd the command the cursor is at
*/
void GetCommand(const char *pInput, char (&aCmd)[IConsole::CMDLINE_LENGTH]);
static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser);
static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser);

Expand Down
54 changes: 54 additions & 0 deletions src/test/str.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,60 @@

#include <game/gamecore.h>

TEST(Str, StrDelim)
{
int Start, End;
// 0123456
// 01234567891111111
EXPECT_EQ(str_delimiters_around_offset("123;123456789;aaa", ";", 5, &Start, &End), true);
EXPECT_EQ(Start, 4);
EXPECT_EQ(End, 13);

EXPECT_EQ(str_delimiters_around_offset("123;123", ";", 1, &Start, &End), false);
EXPECT_EQ(Start, 0);
EXPECT_EQ(End, 3);

EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 1, &Start, &End), false);
EXPECT_EQ(Start, 0);
EXPECT_EQ(End, 0);

EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 2, &Start, &End), false);
EXPECT_EQ(Start, 0);
EXPECT_EQ(End, 0);

EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 3, &Start, &End), true);
EXPECT_EQ(Start, 3);
EXPECT_EQ(End, 6);

EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 4, &Start, &End), true);
EXPECT_EQ(Start, 3);
EXPECT_EQ(End, 6);

EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 9, &Start, &End), true);
EXPECT_EQ(Start, 9);
EXPECT_EQ(End, 12);

EXPECT_EQ(str_delimiters_around_offset("---foo---bar---baz---hello", "---", 22, &Start, &End), false);
EXPECT_EQ(Start, 21);
EXPECT_EQ(End, 26);

EXPECT_EQ(str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 2, &Start, &End), false);
EXPECT_EQ(Start, 0);
EXPECT_EQ(End, 3);

EXPECT_EQ(str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 3, &Start, &End), false);
EXPECT_EQ(Start, 0);
EXPECT_EQ(End, 3);

EXPECT_EQ(str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 4, &Start, &End), true);
EXPECT_EQ(Start, 4);
EXPECT_EQ(End, 4);

EXPECT_EQ(str_delimiters_around_offset("", ";", 4, &Start, &End), false);
EXPECT_EQ(Start, 0);
EXPECT_EQ(End, 0);
}

TEST(Str, StrIsNum)
{
EXPECT_EQ(str_isnum('/'), false);
Expand Down

0 comments on commit 8e83049

Please sign in to comment.