mirror of
https://github.com/WerWolv/ImHex.git
synced 2026-04-01 21:17:44 -05:00
fix: Crashes and usability issues with the pattern tree filter
This commit is contained in:
@@ -32,6 +32,8 @@
|
||||
#include <imgui.h>
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <fonts/vscode_icons.hpp>
|
||||
#include <hex/api/tutorial_manager.hpp>
|
||||
#include <pl/core/ast/ast_node_mathematical_expression.hpp>
|
||||
|
||||
#include <wolv/io/file.hpp>
|
||||
|
||||
@@ -197,6 +199,61 @@ namespace hex::ui {
|
||||
|
||||
}
|
||||
|
||||
std::optional<PatternDrawer::Filter> PatternDrawer::parseComparison(const Filter &currFilter, std::string filterString) {
|
||||
auto result = currFilter;
|
||||
|
||||
pl::core::Lexer lexer;
|
||||
|
||||
filterString = wolv::util::trim(filterString);
|
||||
if (filterString.empty())
|
||||
return std::nullopt;
|
||||
else if (filterString.starts_with("===")) {
|
||||
result.operation = std::strong_ordering::equal;
|
||||
result.inverted = false;
|
||||
result.typeMatch = true;
|
||||
filterString = filterString.substr(3);
|
||||
} else if (filterString.starts_with("==")) {
|
||||
result.operation = std::strong_ordering::equal;
|
||||
result.inverted = false;
|
||||
filterString = filterString.substr(2);
|
||||
} else if (filterString.starts_with("!=")) {
|
||||
result.operation = std::strong_ordering::equal;
|
||||
result.inverted = true;
|
||||
filterString = filterString.substr(2);
|
||||
} else if (filterString.starts_with(">=")) {
|
||||
result.operation = std::strong_ordering::less;
|
||||
result.inverted = true;
|
||||
filterString = filterString.substr(2);
|
||||
} else if (filterString.starts_with("<=")) {
|
||||
result.operation = std::strong_ordering::greater;
|
||||
result.inverted = true;
|
||||
filterString = filterString.substr(2);
|
||||
} else if (filterString.starts_with(">")) {
|
||||
result.operation = std::strong_ordering::greater;
|
||||
result.inverted = false;
|
||||
filterString = filterString.substr(1);
|
||||
} else if (filterString.starts_with("<")) {
|
||||
result.operation = std::strong_ordering::less;
|
||||
result.inverted = false;
|
||||
filterString = filterString.substr(1);
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
pl::api::Source source(filterString);
|
||||
auto tokens = lexer.lex(&source);
|
||||
|
||||
if (!tokens.isOk() || tokens.unwrap().size() != 2)
|
||||
return std::nullopt;
|
||||
|
||||
auto literal = std::get_if<pl::core::Token::Literal>(&tokens.unwrap().front().value);
|
||||
if (literal == nullptr)
|
||||
return std::nullopt;
|
||||
result.value = *literal;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<PatternDrawer::Filter> PatternDrawer::parseRValueFilter(const std::string &filter) {
|
||||
Filter result;
|
||||
|
||||
@@ -208,30 +265,30 @@ namespace hex::ui {
|
||||
for (size_t i = 0; i < filter.size(); i += 1) {
|
||||
char c = filter[i];
|
||||
|
||||
if (i < filter.size() - 1 && c == '=' && filter[i + 1] == '=') {
|
||||
pl::core::Lexer lexer;
|
||||
|
||||
pl::api::Source source(filter.substr(i + 2));
|
||||
auto tokens = lexer.lex(&source);
|
||||
|
||||
if (!tokens.isOk() || tokens.unwrap().size() != 2)
|
||||
if (c == '.') {
|
||||
if (result.path.back().empty())
|
||||
return std::nullopt;
|
||||
|
||||
auto literal = std::get_if<pl::core::Token::Literal>(&tokens.unwrap().front().value);
|
||||
if (literal == nullptr)
|
||||
return std::nullopt;
|
||||
result.value = *literal;
|
||||
|
||||
break;
|
||||
} else if (c == '.') {
|
||||
result.path.emplace_back();
|
||||
} else if (c == '*') {
|
||||
if (!result.path.back().empty())
|
||||
return std::nullopt;
|
||||
|
||||
result.path.back() = "*";
|
||||
} else if (c == '[') {
|
||||
result.path.emplace_back();
|
||||
result.path.back() += c;
|
||||
} else if (c == ']') {
|
||||
result.path.back() += c;
|
||||
} else if (c == ' ') {
|
||||
// Skip whitespace
|
||||
} else {
|
||||
} else if (std::isalnum(c) || c == '_') {
|
||||
if (result.path.back() == "*")
|
||||
return std::nullopt;
|
||||
|
||||
result.path.back() += c;
|
||||
} else {
|
||||
return parseComparison(result, filter.substr(i));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,7 +298,10 @@ namespace hex::ui {
|
||||
void PatternDrawer::updateFilter() {
|
||||
m_filteredPatterns.clear();
|
||||
|
||||
if (m_filter.path.empty()) {
|
||||
if (!m_filter.has_value())
|
||||
return;
|
||||
|
||||
if (m_filter->path.empty()) {
|
||||
m_filteredPatterns = m_sortedPatterns;
|
||||
return;
|
||||
}
|
||||
@@ -251,13 +311,23 @@ namespace hex::ui {
|
||||
if (m_filteredPatterns.size() > m_maxFilterDisplayItems)
|
||||
break;
|
||||
|
||||
traversePatternTree(*pattern, treePath, [this, &treePath](auto &pattern) {
|
||||
traversePatternTree(pattern, treePath, [this, &treePath](auto &pattern) {
|
||||
if (m_filteredPatterns.size() > m_maxFilterDisplayItems)
|
||||
return;
|
||||
|
||||
if (matchesFilter(m_filter.path, treePath, false)) {
|
||||
if (!m_filter.value.has_value() || pattern.getValue() == m_filter.value)
|
||||
m_filteredPatterns.push_back(&pattern);
|
||||
if (matchesFilter(m_filter->path, treePath, false)) {
|
||||
if (!m_filter->value.has_value()) {
|
||||
m_filteredPatterns.push_back(pattern);
|
||||
} else {
|
||||
auto patternValue = pattern->getValue();
|
||||
if (!m_filter->inverted && (patternValue <=> m_filter->value) == m_filter->operation) {
|
||||
if (!m_filter->typeMatch || (m_filter->value->index() == patternValue.index()))
|
||||
m_filteredPatterns.push_back(pattern);
|
||||
} else if (m_filter->inverted && (patternValue <=> m_filter->value) != m_filter->operation) {
|
||||
if (!m_filter->typeMatch || (m_filter->value->index() == patternValue.index()))
|
||||
m_filteredPatterns.push_back(pattern);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -608,7 +678,7 @@ namespace hex::ui {
|
||||
}
|
||||
|
||||
int id = 1;
|
||||
pattern.forEachEntry(0, pattern.getEntryCount(), [&] (u64, auto *field) {
|
||||
pattern.forEachEntry(0, pattern.getEntryCount(), [&] (u64, const auto &field) {
|
||||
ImGui::PushID(id);
|
||||
this->draw(*field);
|
||||
ImGui::PopID();
|
||||
@@ -804,7 +874,7 @@ namespace hex::ui {
|
||||
}
|
||||
|
||||
int id = 1;
|
||||
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, auto *member){
|
||||
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, const auto &member){
|
||||
ImGui::PushID(id);
|
||||
this->draw(*member);
|
||||
ImGui::PopID();
|
||||
@@ -849,7 +919,7 @@ namespace hex::ui {
|
||||
}
|
||||
|
||||
int id = 1;
|
||||
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, auto *member) {
|
||||
pattern.forEachEntry(0, pattern.getEntryCount(), [&](u64, const auto &member) {
|
||||
ImGui::PushID(id);
|
||||
this->draw(*member);
|
||||
ImGui::PopID();
|
||||
@@ -1051,7 +1121,7 @@ namespace hex::ui {
|
||||
}
|
||||
|
||||
int id = 1;
|
||||
iterable.forEachEntry(i, endIndex, [&](u64, auto *entry){
|
||||
iterable.forEachEntry(i, endIndex, [&](u64, const auto &entry){
|
||||
ImGui::PushID(id);
|
||||
this->draw(*entry);
|
||||
ImGui::PopID();
|
||||
@@ -1100,7 +1170,7 @@ namespace hex::ui {
|
||||
return sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending ? result == std::strong_ordering::less : result == std::strong_ordering::greater;
|
||||
}
|
||||
|
||||
bool PatternDrawer::beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<pl::ptrn::Pattern*> &sortedPatterns, float height) const {
|
||||
bool PatternDrawer::beginPatternTable(const std::vector<std::shared_ptr<pl::ptrn::Pattern>> &patterns, std::vector<std::shared_ptr<pl::ptrn::Pattern>> &sortedPatterns, float height) const {
|
||||
if (!ImGui::BeginTable("##Patterntable", 9, ImGuiTableFlags_Borders | ImGuiTableFlags_Resizable | ImGuiTableFlags_Sortable | ImGuiTableFlags_Hideable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(0, height))) {
|
||||
return false;
|
||||
}
|
||||
@@ -1128,13 +1198,10 @@ namespace hex::ui {
|
||||
}
|
||||
|
||||
if (!m_favoritesUpdateTask.isRunning()) {
|
||||
sortedPatterns.clear();
|
||||
std::transform(patterns.begin(), patterns.end(), std::back_inserter(sortedPatterns), [](const std::shared_ptr<pl::ptrn::Pattern> &pattern) {
|
||||
return pattern.get();
|
||||
});
|
||||
sortedPatterns = patterns;
|
||||
|
||||
std::stable_sort(sortedPatterns.begin(), sortedPatterns.end(), [this, &sortSpecs](const pl::ptrn::Pattern *left, const pl::ptrn::Pattern *right) -> bool {
|
||||
return this->sortPatterns(sortSpecs, left, right);
|
||||
std::stable_sort(sortedPatterns.begin(), sortedPatterns.end(), [this, &sortSpecs](const std::shared_ptr<pl::ptrn::Pattern> &left, const std::shared_ptr<pl::ptrn::Pattern> &right) -> bool {
|
||||
return this->sortPatterns(sortSpecs, left.get(), right.get());
|
||||
});
|
||||
|
||||
for (auto &pattern : sortedPatterns) {
|
||||
@@ -1149,14 +1216,18 @@ namespace hex::ui {
|
||||
return true;
|
||||
}
|
||||
|
||||
void PatternDrawer::traversePatternTree(pl::ptrn::Pattern &pattern, std::vector<std::string> &patternPath, const std::function<void(pl::ptrn::Pattern&)> &callback) {
|
||||
patternPath.push_back(pattern.getVariableName());
|
||||
void PatternDrawer::traversePatternTree(const std::shared_ptr<pl::ptrn::Pattern> &pattern, std::vector<std::string> &patternPath, const std::function<void(const std::shared_ptr<pl::ptrn::Pattern>&)> &callback) {
|
||||
patternPath.push_back(pattern->getVariableName());
|
||||
ON_SCOPE_EXIT { patternPath.pop_back(); };
|
||||
|
||||
callback(pattern);
|
||||
if (auto iterable = dynamic_cast<pl::ptrn::IIterable*>(&pattern); iterable != nullptr) {
|
||||
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, pl::ptrn::Pattern *entry) {
|
||||
traversePatternTree(*entry, patternPath, callback);
|
||||
if (auto iterable = dynamic_cast<pl::ptrn::IIterable*>(pattern.get()); iterable != nullptr) {
|
||||
// Don't index individual characters of strings
|
||||
if (dynamic_cast<pl::ptrn::PatternString*>(pattern.get()) || dynamic_cast<pl::ptrn::PatternWideString*>(pattern.get()))
|
||||
return;
|
||||
|
||||
iterable->forEachEntry(0, iterable->getEntryCount(), [&](u64, const auto &entry) {
|
||||
traversePatternTree(entry, patternPath, callback);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1199,10 +1270,48 @@ namespace hex::ui {
|
||||
|
||||
auto &style = ImGui::GetStyle();
|
||||
ImGui::PushItemWidth(-(style.ItemSpacing.x * 2 + style.WindowPadding.x * 2 + ImGui::GetTextLineHeightWithSpacing() * 5 + 15_scaled));
|
||||
if (ImGuiExt::InputTextIcon("##Search", ICON_VS_FILTER, m_filterText)) {
|
||||
m_filter = parseRValueFilter(m_filterText).value_or(Filter{ });
|
||||
updateFilter();
|
||||
|
||||
const bool filterError = !m_filterText.empty() && !m_filter.has_value();
|
||||
|
||||
if (filterError) {
|
||||
ImGui::PushStyleColor(ImGuiCol_Border, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_LoggerError));
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1_scaled);
|
||||
}
|
||||
|
||||
if (ImGuiExt::InputTextIcon("##Search", ICON_VS_FILTER, m_filterText)) {
|
||||
auto newFilter = parseRValueFilter(m_filterText);
|
||||
|
||||
if (m_filterText.empty()) {
|
||||
m_filter.reset();
|
||||
updateFilter();
|
||||
} else if (!newFilter.has_value()) {
|
||||
m_filter.reset();
|
||||
m_filteredPatterns.clear();
|
||||
} else {
|
||||
m_filter = newFilter;
|
||||
updateFilter();
|
||||
}
|
||||
}
|
||||
|
||||
TutorialManager::setLastItemInteractiveHelpPopup([] {
|
||||
ImGuiExt::TextFormattedWrapped("{}",
|
||||
"Allows filtering of the pattern tree using the following syntax:\n\n"
|
||||
"- variable_name: Shows all patterns with this name\n"
|
||||
"- container.var: Shows all patterns named var that are inside a pattern called container\n"
|
||||
"- a.*.b: Searches all children of pattern a for a pattern named b\n"
|
||||
"- a[10].b: Shows patterns named b inside the 10th array entry of a pattern named a\n"
|
||||
"- x > 10: Shows all patterns named x that have a value greater than 10\n"
|
||||
"- a.b == 100: Shows all patterns named b with a value of 100 that are inside a pattern named a\n"
|
||||
"- a == \"Hello\": Shows all patterns named a whose value is the string \"Hello\"\n\n"
|
||||
"If nothing was found, the tree will be empty. If there was a parsing error, the filter text field will be highlighted in red."
|
||||
);
|
||||
});
|
||||
|
||||
if (filterError) {
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleVar();
|
||||
}
|
||||
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::SameLine();
|
||||
@@ -1322,7 +1431,7 @@ namespace hex::ui {
|
||||
|
||||
m_showFavoriteStars = true;
|
||||
|
||||
for (auto &pattern : m_filter.path.empty() ? m_sortedPatterns : m_filteredPatterns) {
|
||||
for (auto &pattern : m_filterText.empty() ? m_sortedPatterns : m_filteredPatterns) {
|
||||
ImGui::PushID(id);
|
||||
this->draw(*pattern);
|
||||
ImGui::PopID();
|
||||
@@ -1372,14 +1481,14 @@ namespace hex::ui {
|
||||
continue;
|
||||
|
||||
patternPath.clear();
|
||||
traversePatternTree(*pattern, patternPath, [&, this](const pl::ptrn::Pattern &currPattern) {
|
||||
traversePatternTree(pattern, patternPath, [&, this](const std::shared_ptr<pl::ptrn::Pattern> &currPattern) {
|
||||
for (auto &[path, favoritePattern] : m_favorites) {
|
||||
if (updatedFavorites == m_favorites.size())
|
||||
task.interrupt();
|
||||
task.update();
|
||||
|
||||
if (matchesFilter(patternPath, path, true)) {
|
||||
favoritePattern = currPattern.clone();
|
||||
favoritePattern = currPattern->clone();
|
||||
updatedFavorites += 1;
|
||||
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user