diff --git a/src/base/system.cpp b/src/base/system.cpp index 56df4589c..91d4e7cd7 100644 --- a/src/base/system.cpp +++ b/src/base/system.cpp @@ -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); diff --git a/src/base/system.h b/src/base/system.h index 704e89356..f3bf7b749 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -1605,6 +1605,21 @@ const char *str_find_nocase(const char *haystack, const char *needle); */ const char *str_find(const char *haystack, const char *needle); +/* + Function: str_delimiters_around_offset + Parameters: + haystack - String to search in + needle - String to search for + + Returns: + true if both delimiters were found + false if a delimiter is missing (it uses haystart start and end as fallback) + + Remarks: + - 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 * diff --git a/src/game/client/components/console.cpp b/src/game/client/components/console.cpp index 9410e602a..d2b1b2d3b 100644 --- a/src/game/client/components/console.cpp +++ b/src/game/client/components/console.cpp @@ -322,19 +322,19 @@ void CGameConsole::CInstance::PossibleCommandsCompleteCallback(int Index, const CGameConsole::CInstance *pInstance = (CGameConsole::CInstance *)pUser; if(pInstance->m_CompletionChosen == Index) { - char aCurrent[512]; - str_truncate(aCurrent, sizeof(aCurrent), pInstance->m_aCompletionBuffer, pInstance->m_CompletionCommandStart); + char aBefore[512]; + str_truncate(aBefore, sizeof(aBefore), pInstance->m_aCompletionBuffer, pInstance->m_CompletionCommandStart); char aBuf[512]; - str_format(aBuf, sizeof(aBuf), "%s%s", aCurrent, pStr); + 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); } } -const char *CGameConsole::CInstance::GetCommand(const char *pInput) const +void CGameConsole::CInstance::GetCommand(const char *pInput, char *pCmd, size_t CmdSize) { - while(str_find(pInput, ";")) - pInput = str_find(pInput, ";") + 1; - return pInput; + str_delimiters_around_offset(pInput, ";", m_Input.GetCursorOffset(), &m_CompletionCommandStart, &m_CompletionCommandEnd); + str_truncate(pCmd, CmdSize, pInput + m_CompletionCommandStart, m_CompletionCommandEnd - m_CompletionCommandStart); } static void StrCopyUntilSpace(char *pDest, size_t DestSize, const char *pSrc) @@ -458,12 +458,12 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) if(!m_Searching) { - const char *pSearch = GetCommand(m_aCompletionBuffer); - m_CompletionCommandStart = pSearch - m_aCompletionBuffer; + char aSearch[128]; + GetCommand(m_aCompletionBuffer, aSearch, sizeof(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(pSearch, 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) @@ -472,7 +472,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(pSearch, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this); + m_pGameConsole->m_pConsole->PossibleCommands(aSearch, m_CompletionFlagmask, UseTempCommands, PossibleCommandsCompleteCallback, this); } else if(m_CompletionChosen != -1) { @@ -597,8 +597,10 @@ bool CGameConsole::CInstance::OnInput(const IInput::CEvent &Event) // find the current command { + char aCmd[128]; + GetCommand(GetString(), aCmd, sizeof(aCmd)); char aBuf[IConsole::CMDLINE_LENGTH]; - StrCopyUntilSpace(aBuf, sizeof(aBuf), GetCommand(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()); @@ -1128,7 +1130,9 @@ void CGameConsole::OnRender() Info.m_pOffsetChange = &pConsole->m_CompletionRenderOffsetChange; Info.m_Width = Screen.w; Info.m_TotalWidth = 0.0f; - Info.m_pCurrentCmd = pConsole->GetCommand(pConsole->m_aCompletionBuffer); + char aCmd[128]; + pConsole->GetCommand(pConsole->m_aCompletionBuffer, aCmd, sizeof(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::max(); diff --git a/src/game/client/components/console.h b/src/game/client/components/console.h index 7e3df0143..c54809c05 100644 --- a/src/game/client/components/console.h +++ b/src/game/client/components/console.h @@ -70,6 +70,7 @@ class CGameConsole : public CComponent float m_CompletionRenderOffsetChange; int m_CompletionArgumentPosition; int m_CompletionCommandStart = 0; + int m_CompletionCommandEnd = 0; char m_aUser[32]; bool m_UserGot; @@ -114,7 +115,20 @@ class CGameConsole : public CComponent void Dump() REQUIRES(!m_BacklogPendingLock); const char *GetString() const { return m_Input.GetString(); } - void GetCommand(char *pBuf, size_t Size) const; + /** + * 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 pCmd the command the cursor is at + * @param CmdSize size of the pCmd buffer + */ + void GetCommand(const char *pInput, char *pCmd, size_t CmdSize); static void PossibleCommandsCompleteCallback(int Index, const char *pStr, void *pUser); static void PossibleArgumentsCompleteCallback(int Index, const char *pStr, void *pUser); diff --git a/src/test/str.cpp b/src/test/str.cpp index dde5edd27..9dcbb6bbe 100644 --- a/src/test/str.cpp +++ b/src/test/str.cpp @@ -4,6 +4,62 @@ #include +TEST(Str, StrDelim) +{ + int Start, End; + // 0123456 + // 01234567891111111 + str_delimiters_around_offset("123;123456789;aaa", ";", 5, &Start, &End); + EXPECT_EQ(Start, 4); + EXPECT_EQ(End, 13); + + str_delimiters_around_offset("123;123", ";", 1, &Start, &End); + EXPECT_EQ(Start, 0); + EXPECT_EQ(End, 3); + + str_delimiters_around_offset("---foo---bar---baz---hello", "---", 1, &Start, &End); + EXPECT_EQ(Start, 0); + EXPECT_EQ(End, 0); + + str_delimiters_around_offset("---foo---bar---baz---hello", "---", 2, &Start, &End); + EXPECT_EQ(Start, 0); + EXPECT_EQ(End, 0); + + str_delimiters_around_offset("---foo---bar---baz---hello", "---", 3, &Start, &End); + EXPECT_EQ(Start, 3); + EXPECT_EQ(End, 6); + + str_delimiters_around_offset("---foo---bar---baz---hello", "---", 4, &Start, &End); + EXPECT_EQ(Start, 3); + EXPECT_EQ(End, 6); + + str_delimiters_around_offset("---foo---bar---baz---hello", "---", 9, &Start, &End); + EXPECT_EQ(Start, 9); + EXPECT_EQ(End, 12); + + str_delimiters_around_offset("---foo---bar---baz---hello", "---", 22, &Start, &End); + EXPECT_EQ(Start, 21); + EXPECT_EQ(End, 26); + + str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 2, &Start, &End); + EXPECT_EQ(Start, 0); + EXPECT_EQ(End, 3); + + str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 3, &Start, &End); + EXPECT_EQ(Start, 0); + EXPECT_EQ(End, 3); + + bool Found = str_delimiters_around_offset("foo;;;;bar;;;;;;", ";", 4, &Start, &End); + EXPECT_EQ(Found, true); + EXPECT_EQ(Start, 4); + EXPECT_EQ(End, 4); + + Found = str_delimiters_around_offset("", ";", 4, &Start, &End); + EXPECT_EQ(Found, false); + EXPECT_EQ(Start, 0); + EXPECT_EQ(End, 0); +} + TEST(Str, StrIsNum) { EXPECT_EQ(str_isnum('/'), false);