Resolve "Extend searching in the console with regex and toggleable case-sensitivity"

macos_ci_fix
Chris Vigil 1 year ago committed by psi29a
parent d2f16774d9
commit 996f5fd7ad

@ -54,6 +54,7 @@ Programmers
Cédric Mocquillon
Chris Boyce (slothlife)
Chris Robinson (KittyCat)
Chris Vigil
Cody Glassman (Wazabear)
Coleman Smith (olcoal)
Cory F. Cohen (cfcohen)

@ -85,6 +85,7 @@
Feature #7148: Optimize string literal lookup in mwscript
Feature #7194: Ori to show texture paths
Feature #7214: Searching in the in-game console
Feature #7284: Searching in the console with regex and toggleable case-sensitivity
Feature #7477: NegativeLight Magic Effect flag
Feature #7499: OpenMW-CS: Generate record filters by drag & dropping cell content to the filters field
Task #7113: Move from std::atoi to std::from_char

@ -7,7 +7,9 @@
#include <filesystem>
#include <fstream>
#include <regex>
#include <apps/openmw/mwgui/textcolours.hpp>
#include <components/compiler/exception.hpp>
#include <components/compiler/extensions0.hpp>
#include <components/compiler/lineparser.hpp>
@ -144,6 +146,8 @@ namespace MWGui
Console::Console(int w, int h, bool consoleOnlyScripts, Files::ConfigurationManager& cfgMgr)
: WindowBase("openmw_console.layout")
, mCaseSensitiveSearch(false)
, mRegExSearch(false)
, mCompilerContext(MWScript::CompilerContext::Type_Console)
, mConsoleOnlyScripts(consoleOnlyScripts)
, mCfgMgr(cfgMgr)
@ -155,6 +159,8 @@ namespace MWGui
getWidget(mSearchTerm, "edit_SearchTerm");
getWidget(mNextButton, "button_Next");
getWidget(mPreviousButton, "button_Previous");
getWidget(mCaseSensitiveToggleButton, "button_CaseSensitive");
getWidget(mRegExSearchToggleButton, "button_RegExSearch");
// Set up the command line box
mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand);
@ -163,7 +169,9 @@ namespace MWGui
// Set up the search term box
mSearchTerm->eventEditSelectAccept += newDelegate(this, &Console::acceptSearchTerm);
mNextButton->eventMouseButtonClick += newDelegate(this, &Console::findNextOccurrence);
mPreviousButton->eventMouseButtonClick += newDelegate(this, &Console::findPreviousOccurence);
mPreviousButton->eventMouseButtonClick += newDelegate(this, &Console::findPreviousOccurrence);
mCaseSensitiveToggleButton->eventMouseButtonClick += newDelegate(this, &Console::toggleCaseSensitiveSearch);
mRegExSearchToggleButton->eventMouseButtonClick += newDelegate(this, &Console::toggleRegExSearch);
// Set up the log window
mHistory->setOverflowToTheLeft(true);
@ -387,6 +395,37 @@ namespace MWGui
execute(cm);
}
void Console::toggleCaseSensitiveSearch(MyGUI::Widget* _sender)
{
mCaseSensitiveSearch = !mCaseSensitiveSearch;
// Reset console search highlight position search parameters have changed
mCurrentOccurrenceIndex = std::string::npos;
// Adjust color to reflect toggled status
const TextColours& textColours{ MWBase::Environment::get().getWindowManager()->getTextColours() };
mCaseSensitiveToggleButton->setTextColour(mCaseSensitiveSearch ? textColours.link : textColours.normal);
}
void Console::toggleRegExSearch(MyGUI::Widget* _sender)
{
mRegExSearch = !mRegExSearch;
// Reset console search highlight position search parameters have changed
mCurrentOccurrenceIndex = std::string::npos;
// Adjust color to reflect toggled status
const TextColours& textColours{ MWBase::Environment::get().getWindowManager()->getTextColours() };
mRegExSearchToggleButton->setTextColour(mRegExSearch ? textColours.link : textColours.normal);
// RegEx searches are always case sensitive
mCaseSensitiveSearch = mRegExSearch;
// Dim case sensitive and set disabled if regex search toggled on, restore when toggled off
mCaseSensitiveToggleButton->setTextColour(mCaseSensitiveSearch ? textColours.linkPressed : textColours.normal);
mCaseSensitiveToggleButton->setEnabled(!mRegExSearch);
}
void Console::acceptSearchTerm(MyGUI::EditBox* _sender)
{
const std::string& searchTerm = mSearchTerm->getOnlyText();
@ -396,43 +435,84 @@ namespace MWGui
return;
}
mCurrentSearchTerm = Utf8Stream::lowerCaseUtf8(searchTerm);
mCurrentOccurrence = std::string::npos;
std::string newSearchTerm = mCaseSensitiveSearch ? searchTerm : Utf8Stream::lowerCaseUtf8(searchTerm);
// If new search term reset position, otherwise continue from current position
if (newSearchTerm != mCurrentSearchTerm)
{
mCurrentSearchTerm = newSearchTerm;
mCurrentOccurrenceIndex = std::string::npos;
}
findNextOccurrence(nullptr);
}
enum class Console::SearchDirection
{
Forward,
Reverse
};
void Console::findNextOccurrence(MyGUI::Widget* _sender)
{
findOccurrence(SearchDirection::Forward);
}
void Console::findPreviousOccurrence(MyGUI::Widget* _sender)
{
findOccurrence(SearchDirection::Reverse);
}
void Console::findOccurrence(const SearchDirection direction)
{
if (mCurrentSearchTerm.empty())
{
return;
}
const auto historyText = Utf8Stream::lowerCaseUtf8(mHistory->getOnlyText().asUTF8());
const auto historyText{ mCaseSensitiveSearch ? mHistory->getOnlyText().asUTF8()
: Utf8Stream::lowerCaseUtf8(mHistory->getOnlyText().asUTF8()) };
// Search starts at the beginning
size_t startIndex = 0;
// Setup default search range
size_t firstIndex{ 0 };
size_t lastIndex{ historyText.length() };
// If this is not the first search, we start right AFTER the last occurrence.
if (mCurrentOccurrence != std::string::npos && historyText.length() - mCurrentOccurrence > 1)
// If search is not the first adjust the range based on the direction and previous occurrence.
if (mCurrentOccurrenceIndex != std::string::npos)
{
startIndex = mCurrentOccurrence + 1;
if (direction == SearchDirection::Forward && mCurrentOccurrenceIndex > 1)
{
firstIndex = mCurrentOccurrenceIndex + mCurrentOccurrenceLength;
}
else if (direction == SearchDirection::Reverse
&& (historyText.length() - mCurrentOccurrenceIndex) > mCurrentOccurrenceLength)
{
lastIndex = mCurrentOccurrenceIndex - 1;
}
}
mCurrentOccurrence = historyText.find(mCurrentSearchTerm, startIndex);
findInHistoryText(historyText, direction, firstIndex, lastIndex);
// If the last search did not find anything AND we didn't start at
// the beginning, we repeat the search one time for wrapping around the text.
if (mCurrentOccurrence == std::string::npos && startIndex != 0)
// If the last search did not find anything AND...
if (mCurrentOccurrenceIndex == std::string::npos)
{
mCurrentOccurrence = historyText.find(mCurrentSearchTerm);
if (direction == SearchDirection::Forward && firstIndex != 0)
{
// ... We didn't start at the beginning, we apply the search to the other half of the text.
findInHistoryText(historyText, direction, 0, firstIndex);
}
else if (direction == SearchDirection::Reverse && lastIndex != historyText.length())
{
// ... We didn't search to the end, we apply the search to the other half of the text.
findInHistoryText(historyText, direction, lastIndex, historyText.length());
}
}
// Only scroll & select if we actually found something
if (mCurrentOccurrence != std::string::npos)
if (mCurrentOccurrenceIndex != std::string::npos)
{
markOccurrence(mCurrentOccurrence, mCurrentSearchTerm.length());
markOccurrence(mCurrentOccurrenceIndex, mCurrentOccurrenceLength);
}
else
{
@ -440,44 +520,78 @@ namespace MWGui
}
}
void Console::findPreviousOccurence(MyGUI::Widget* _sender)
void Console::findInHistoryText(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
if (mCurrentSearchTerm.empty())
if (mRegExSearch)
{
return;
findWithRegex(historyText, direction, firstIndex, lastIndex);
}
else
{
findWithStringSearch(historyText, direction, firstIndex, lastIndex);
}
}
const auto historyText = Utf8Stream::lowerCaseUtf8(mHistory->getOnlyText().asUTF8());
// Search starts at the end
size_t startIndex = historyText.length();
void Console::findWithRegex(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
// Search text for regex match in given interval
const std::regex pattern{ mCurrentSearchTerm };
std::sregex_iterator match{ (historyText.cbegin() + firstIndex), (historyText.cbegin() + lastIndex), pattern };
const std::sregex_iterator end{};
// If this is not the first search, we start right BEFORE the last occurrence.
if (mCurrentOccurrence != std::string::npos && mCurrentOccurrence > 1)
// If reverse search get last result in interval
if (direction == SearchDirection::Reverse)
{
startIndex = mCurrentOccurrence - 1;
std::sregex_iterator lastMatch{ end };
while (match != end)
{
lastMatch = match;
++match;
}
match = lastMatch;
}
mCurrentOccurrence = historyText.rfind(mCurrentSearchTerm, startIndex);
// If regex match is found in text, set new current occurrence values
if (match != end)
{
mCurrentOccurrenceIndex = match->position() + firstIndex;
mCurrentOccurrenceLength = match->length();
}
else
{
mCurrentOccurrenceIndex = std::string::npos;
mCurrentOccurrenceLength = 0;
}
}
// If the last search did not find anything AND we didn't start at
// the end, we repeat the search one time for wrapping around the text.
if (mCurrentOccurrence == std::string::npos && startIndex != historyText.length())
void Console::findWithStringSearch(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
{
// Search in given text interval for search term
const size_t substringLength{ (lastIndex - firstIndex) + 1 };
const std::string_view historyTextView((historyText.c_str() + firstIndex), substringLength);
if (direction == SearchDirection::Forward)
{
mCurrentOccurrenceIndex = historyTextView.find(mCurrentSearchTerm);
}
else
{
mCurrentOccurrence = historyText.rfind(mCurrentSearchTerm, historyText.length());
mCurrentOccurrenceIndex = historyTextView.rfind(mCurrentSearchTerm);
}
// Only scroll & select if we actually found something
if (mCurrentOccurrence != std::string::npos)
// If search term is found in text, set new current occurrence values
if (mCurrentOccurrenceIndex != std::string::npos)
{
markOccurrence(mCurrentOccurrence, mCurrentSearchTerm.length());
mCurrentOccurrenceIndex += firstIndex;
mCurrentOccurrenceLength = mCurrentSearchTerm.length();
}
else
{
markOccurrence(0, 0);
mCurrentOccurrenceLength = 0;
}
}
void Console::markOccurrence(const size_t textPosition, const size_t length)
{
if (textPosition == 0 && length == 0)

@ -31,6 +31,8 @@ namespace MWGui
MyGUI::EditBox* mSearchTerm;
MyGUI::Button* mNextButton;
MyGUI::Button* mPreviousButton;
MyGUI::Button* mCaseSensitiveToggleButton;
MyGUI::Button* mRegExSearchToggleButton;
typedef std::list<std::string> StringList;
@ -83,12 +85,25 @@ namespace MWGui
void commandBoxKeyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char);
void acceptCommand(MyGUI::EditBox* _sender);
enum class SearchDirection;
void toggleCaseSensitiveSearch(MyGUI::Widget* _sender);
void toggleRegExSearch(MyGUI::Widget* _sender);
void acceptSearchTerm(MyGUI::EditBox* _sender);
void findNextOccurrence(MyGUI::Widget* _sender);
void findPreviousOccurence(MyGUI::Widget* _sender);
void findPreviousOccurrence(MyGUI::Widget* _sender);
void findOccurrence(SearchDirection direction);
void findInHistoryText(
const std::string& historyText, SearchDirection direction, size_t firstIndex, size_t lastIndex);
void findWithRegex(
const std::string& historyText, SearchDirection direction, size_t firstIndex, size_t lastIndex);
void findWithStringSearch(
const std::string& historyText, SearchDirection direction, size_t firstIndex, size_t lastIndex);
void markOccurrence(size_t textPosition, size_t length);
size_t mCurrentOccurrence = std::string::npos;
size_t mCurrentOccurrenceIndex = std::string::npos;
size_t mCurrentOccurrenceLength = 0;
std::string mCurrentSearchTerm;
bool mCaseSensitiveSearch;
bool mRegExSearch;
std::string complete(std::string input, std::vector<std::string>& matches);

@ -6,7 +6,7 @@
<Property key="Visible" value="false"/>
<!-- Log window -->
<Widget type="EditBox" skin="MW_TextBoxEdit" position="5 5 380 328" align="Stretch" name="list_History">
<Widget type="EditBox" skin="MW_TextBoxEdit" position="5 30 380 303" align="Stretch" name="list_History">
<Property key="MultiLine" value="1"/>
<Property key="ReadOnly" value="true"/>
<Property key="FontName" value="MonoFont"/>
@ -17,26 +17,38 @@
<Property key="MaxTextLength" value="10000"/>
</Widget>
<!-- Command line -->
<Widget type="EditBox" skin="MW_ConsoleCommand" position="0 338 384 28" align="HStretch Bottom" name="edit_Command">
<Property key="InvertSelected" value="false"/>
<UserString key="AcceptTab" value="true"/>
</Widget>
<Widget type="HBox" position="1 0 379 28" align="Left Top HStretch">
<!-- "Previous" button -->
<Widget type="Button" skin="MW_Button" position="0 0 28 28" name="button_Previous" align="Top Right">
<Property key="Caption" value="<"/>
</Widget>
<!-- Search box -->
<Widget type="EditBox" skin="MW_ConsoleCommand" position="250 1 115 28" align="Top Right" name="edit_SearchTerm">
<Property key="InvertSelected" value="false"/>
</Widget>
<!-- "Next" button -->
<Widget type="Button" skin="MW_Button" position="0 0 28 28" name="button_Next" align="Top Right">
<Property key="Caption" value=">"/>
</Widget>
<!-- "Next" button -->
<Widget type="Button" skin="MW_Button" position="220 0 28 28" name="button_Next" align="Top Right">
<Property key="Caption" value=">"/>
</Widget>
<!-- Search box -->
<Widget type="EditBox" skin="MW_ConsoleCommand" position="0 0 0 28" align="Top Right" name="edit_SearchTerm">
<Property key="InvertSelected" value="false"/>
<UserString key="HStretch" value="true"/>
</Widget>
<!-- "CaseSensitive" toggle -->
<Widget type="Button" skin="MW_Button" position="0 0 28 28" name="button_CaseSensitive" align="Top Right">
<Property key="Caption" value="Aa"/>
</Widget>
<!-- "Previous" button -->
<Widget type="Button" skin="MW_Button" position="190 0 28 28" name="button_Previous" align="Top Right">
<Property key="Caption" value="<"/>
<!-- "RegEx" toggle -->
<Widget type="Button" skin="MW_Button" position="0 0 28 28" name="button_RegExSearch" align="Top Right">
<Property key="Caption" value=".*"/>
</Widget>
</Widget>
<!-- Command line -->
<Widget type="EditBox" skin="MW_ConsoleCommand" position="0 338 384 28" align="HStretch Bottom" name="edit_Command">
<Property key="InvertSelected" value="false"/>
<UserString key="AcceptTab" value="true"/>
</Widget>
</Widget>
</MyGUI>

Loading…
Cancel
Save