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 Cédric Mocquillon
Chris Boyce (slothlife) Chris Boyce (slothlife)
Chris Robinson (KittyCat) Chris Robinson (KittyCat)
Chris Vigil
Cody Glassman (Wazabear) Cody Glassman (Wazabear)
Coleman Smith (olcoal) Coleman Smith (olcoal)
Cory F. Cohen (cfcohen) Cory F. Cohen (cfcohen)

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

@ -7,7 +7,9 @@
#include <filesystem> #include <filesystem>
#include <fstream> #include <fstream>
#include <regex>
#include <apps/openmw/mwgui/textcolours.hpp>
#include <components/compiler/exception.hpp> #include <components/compiler/exception.hpp>
#include <components/compiler/extensions0.hpp> #include <components/compiler/extensions0.hpp>
#include <components/compiler/lineparser.hpp> #include <components/compiler/lineparser.hpp>
@ -144,6 +146,8 @@ namespace MWGui
Console::Console(int w, int h, bool consoleOnlyScripts, Files::ConfigurationManager& cfgMgr) Console::Console(int w, int h, bool consoleOnlyScripts, Files::ConfigurationManager& cfgMgr)
: WindowBase("openmw_console.layout") : WindowBase("openmw_console.layout")
, mCaseSensitiveSearch(false)
, mRegExSearch(false)
, mCompilerContext(MWScript::CompilerContext::Type_Console) , mCompilerContext(MWScript::CompilerContext::Type_Console)
, mConsoleOnlyScripts(consoleOnlyScripts) , mConsoleOnlyScripts(consoleOnlyScripts)
, mCfgMgr(cfgMgr) , mCfgMgr(cfgMgr)
@ -155,6 +159,8 @@ namespace MWGui
getWidget(mSearchTerm, "edit_SearchTerm"); getWidget(mSearchTerm, "edit_SearchTerm");
getWidget(mNextButton, "button_Next"); getWidget(mNextButton, "button_Next");
getWidget(mPreviousButton, "button_Previous"); getWidget(mPreviousButton, "button_Previous");
getWidget(mCaseSensitiveToggleButton, "button_CaseSensitive");
getWidget(mRegExSearchToggleButton, "button_RegExSearch");
// Set up the command line box // Set up the command line box
mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand); mCommandLine->eventEditSelectAccept += newDelegate(this, &Console::acceptCommand);
@ -163,7 +169,9 @@ namespace MWGui
// Set up the search term box // Set up the search term box
mSearchTerm->eventEditSelectAccept += newDelegate(this, &Console::acceptSearchTerm); mSearchTerm->eventEditSelectAccept += newDelegate(this, &Console::acceptSearchTerm);
mNextButton->eventMouseButtonClick += newDelegate(this, &Console::findNextOccurrence); 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 // Set up the log window
mHistory->setOverflowToTheLeft(true); mHistory->setOverflowToTheLeft(true);
@ -387,6 +395,37 @@ namespace MWGui
execute(cm); 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) void Console::acceptSearchTerm(MyGUI::EditBox* _sender)
{ {
const std::string& searchTerm = mSearchTerm->getOnlyText(); const std::string& searchTerm = mSearchTerm->getOnlyText();
@ -396,43 +435,84 @@ namespace MWGui
return; return;
} }
mCurrentSearchTerm = Utf8Stream::lowerCaseUtf8(searchTerm); std::string newSearchTerm = mCaseSensitiveSearch ? searchTerm : Utf8Stream::lowerCaseUtf8(searchTerm);
mCurrentOccurrence = std::string::npos;
// If new search term reset position, otherwise continue from current position
if (newSearchTerm != mCurrentSearchTerm)
{
mCurrentSearchTerm = newSearchTerm;
mCurrentOccurrenceIndex = std::string::npos;
}
findNextOccurrence(nullptr); findNextOccurrence(nullptr);
} }
enum class Console::SearchDirection
{
Forward,
Reverse
};
void Console::findNextOccurrence(MyGUI::Widget* _sender) 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()) if (mCurrentSearchTerm.empty())
{ {
return; 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 // Setup default search range
size_t startIndex = 0; size_t firstIndex{ 0 };
size_t lastIndex{ historyText.length() };
// If this is not the first search, we start right AFTER the last occurrence. // If search is not the first adjust the range based on the direction and previous occurrence.
if (mCurrentOccurrence != std::string::npos && historyText.length() - mCurrentOccurrence > 1) 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 // If the last search did not find anything AND...
// the beginning, we repeat the search one time for wrapping around the text. if (mCurrentOccurrenceIndex == std::string::npos)
if (mCurrentOccurrence == std::string::npos && startIndex != 0)
{ {
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 // 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 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()); void Console::findWithRegex(const std::string& historyText, const SearchDirection direction,
const size_t firstIndex, const size_t lastIndex)
// Search starts at the end {
size_t startIndex = historyText.length(); // 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 reverse search get last result in interval
if (mCurrentOccurrence != std::string::npos && mCurrentOccurrence > 1) 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 void Console::findWithStringSearch(const std::string& historyText, const SearchDirection direction,
// the end, we repeat the search one time for wrapping around the text. const size_t firstIndex, const size_t lastIndex)
if (mCurrentOccurrence == std::string::npos && startIndex != historyText.length()) {
// 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 search term is found in text, set new current occurrence values
if (mCurrentOccurrence != std::string::npos) if (mCurrentOccurrenceIndex != std::string::npos)
{ {
markOccurrence(mCurrentOccurrence, mCurrentSearchTerm.length()); mCurrentOccurrenceIndex += firstIndex;
mCurrentOccurrenceLength = mCurrentSearchTerm.length();
} }
else else
{ {
markOccurrence(0, 0); mCurrentOccurrenceLength = 0;
} }
} }
void Console::markOccurrence(const size_t textPosition, const size_t length) void Console::markOccurrence(const size_t textPosition, const size_t length)
{ {
if (textPosition == 0 && length == 0) if (textPosition == 0 && length == 0)

@ -31,6 +31,8 @@ namespace MWGui
MyGUI::EditBox* mSearchTerm; MyGUI::EditBox* mSearchTerm;
MyGUI::Button* mNextButton; MyGUI::Button* mNextButton;
MyGUI::Button* mPreviousButton; MyGUI::Button* mPreviousButton;
MyGUI::Button* mCaseSensitiveToggleButton;
MyGUI::Button* mRegExSearchToggleButton;
typedef std::list<std::string> StringList; typedef std::list<std::string> StringList;
@ -83,12 +85,25 @@ namespace MWGui
void commandBoxKeyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char); void commandBoxKeyPress(MyGUI::Widget* _sender, MyGUI::KeyCode key, MyGUI::Char _char);
void acceptCommand(MyGUI::EditBox* _sender); void acceptCommand(MyGUI::EditBox* _sender);
enum class SearchDirection;
void toggleCaseSensitiveSearch(MyGUI::Widget* _sender);
void toggleRegExSearch(MyGUI::Widget* _sender);
void acceptSearchTerm(MyGUI::EditBox* _sender); void acceptSearchTerm(MyGUI::EditBox* _sender);
void findNextOccurrence(MyGUI::Widget* _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); 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; std::string mCurrentSearchTerm;
bool mCaseSensitiveSearch;
bool mRegExSearch;
std::string complete(std::string input, std::vector<std::string>& matches); std::string complete(std::string input, std::vector<std::string>& matches);

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

Loading…
Cancel
Save