Skip to content

feat: New improved Search Tool - #999

Merged
karolak6612 merged 6 commits into
masterfrom
Search-Tool
Mar 28, 2026
Merged

feat: New improved Search Tool#999
karolak6612 merged 6 commits into
masterfrom
Search-Tool

Conversation

@karolak6612

@karolak6612 karolak6612 commented Mar 28, 2026

Copy link
Copy Markdown
Collaborator
- More filters
- List / Grid view
- Fuzzy search by name/ cid/ sid
- creature search

Summary by CodeRabbit

  • New Features
    • Revamped Advanced Item Finder: text search with wildcards, multi-dimension filters (type/property/interaction/visual), list/grid results, live previews, keyboard/mouse navigation, and double-click/Enter activation.
    • Creature search integrated alongside items.
    • Persistent finder state (query, filters, selection, window position/size) and session restore.
    • Paginated map-wide search with navigation and exportable results; configurable results limit in settings.

…arch

Replaced legacy find item dialog with new advanced finder featuring fuzzy name search, type/property/interaction/visual filters, creature search support, and state persistence. Added ForceUse/MultiUse/FullTile item flags with parser support across DAT/OTB/XML formats. Updated C++ standard to C++23.
Extracted results rendering into new find_item_window_views module with List/Grid view modes using NanoVG. Replaced legacy radio-button search modes with unified token-based fuzzy search supporting wildcards and numeric matching. Updated CMakeLists.txt to register new source files. Removed deprecated search mode persistence from navigation_menu_handler.cpp and search_handler.cpp. Refactored find_item_window_model.cpp scoring logic to use multi-token matching with prefix/suffix wildcard support. Modernized find_item_window.cpp layout with left-side filter panel and tabbed results view.
@github-actions

Copy link
Copy Markdown

🤖 Hi @karolak6612, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant overhaul of the search functionality within the application. By replacing the legacy search dialog with a more robust and flexible 'Advanced Search Tool', users can now perform more complex queries with better filtering and visualization options. The changes also include necessary backend updates to support these new features, including updated item parsing logic and modern C++ standards.

Highlights

  • Advanced Search Tool: Implemented a completely redesigned and improved Search Tool with support for fuzzy searching by name, CID, or SID, as well as creature search capabilities.
  • UI/UX Enhancements: Added a new list/grid view for search results, expanded filtering options, and improved the overall layout for better usability.
  • Technical Upgrades: Updated the project to C++23 and added new item flags (ForceUse, MultiUse, FullTile) to support the new search functionality and improved item parsing.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Mar 28, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Replaces the legacy Find Item dialog with an Advanced Item Finder (model, views, dialog); adds paginated item/creature search operations, new item flags and parser mappings, paged search result UI, settings persistence, and raises C++ target standard to C++23.

Changes

Cohort / File(s) Summary
Build
CMakeLists.txt, source/CMakeLists.txt
Set rme C++ standard to C++23 and add new finder source/header files to the build.
Settings
source/app/settings.h, source/app/settings.cpp
Add unsigned-integer storage/getter/setter, replace old FIND/JUMP keys with Advanced Finder keys, and persist advanced finder window geometry and search limits.
Item flags & parsers
source/item_definitions/core/item_definition_types.h, source/item_definitions/formats/dat/dat_item_parser.cpp, source/item_definitions/formats/otb/otb_item_parser.cpp, source/item_definitions/formats/xml/xml_item_parser.cpp
Add ForceUse, MultiUse, FullTile flags; map/use these flags and decay attributes across DAT/OTB/XML parsers.
Search ops
source/editor/operations/search_operations.h
Add pagination/offset to ItemSearcher; introduce CreatureSearcher with offset, totalMatches tracking and progress updates.
Advanced Finder model
source/ui/find_item_window_model.h, source/ui/find_item_window_model.cpp
New catalog model: catalog builder, tokenized/fuzzy matcher, filter masks, selection key helpers, and persistence APIs.
Advanced Finder views
source/ui/find_item_window_views.h, source/ui/find_item_window_views.cpp, source/ui/find_item_window_views_paint.cpp
New NanoVG-backed AdvancedFinderResultsView (List/Grid) with virtualized rendering, hit-testing, keyboard/mouse navigation, hover/tooltips, and painting.
Find dialog & layout/internal
source/ui/find_item_window.h, source/ui/find_item_window.cpp, source/ui/find_item_window_layout.cpp, source/ui/find_item_window_internal.h
Refactor dialog to model/view architecture; expand constructor API (ActionSet/ResultAction/default_action/include_creatures), add persistence, deferred selection, controls↔query sync, and session/shared state.
Result window (search results)
source/ui/result_window.h, source/ui/result_window.cpp
Replace listbox with ResultCanvas, add row-based SetResults(...) with pagination metadata and page loaders, navigation and export changes.
Integration & handlers
source/ui/menubar/navigation_menu_handler.cpp, source/ui/menubar/search_handler.cpp
Branch on dialog ResultAction (SearchMap vs SelectItem), run item/creature paged searches, populate SearchResultWindow with paginated results, remove old mode persistence.
Search manager / GUI
source/ui/managers/search_manager.h, source/ui/managers/search_manager.cpp, source/ui/gui.h, source/ui/main_frame.cpp
Replace HideSearchWindow() with DestroySearchWindow() and ensure explicit destruction on exit/manager teardown.
Added files
source/ui/find_item_window_model.*, source/ui/find_item_window_views.*, source/ui/find_item_window_internal.h, source/ui/find_item_window_layout.cpp
New model/view/layout/internal implementation files added to project.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Dialog as FindItemDialog
    participant Model as AdvancedFinderModel
    participant View as AdvancedFinderResultsView
    participant Handler as SearchHandler
    participant Editor as EditorOperations

    User->>Dialog: Enter query and set filters
    Dialog->>Model: FilterAdvancedFinderCatalog(catalog, query)
    Model-->>Dialog: Matched & scored indices
    Dialog->>View: SetRows(filtered rows, preferred selection)
    User->>View: Select / activate / right-activate
    View-->>Dialog: Selection/activation event
    Dialog->>Dialog: Determine ResultAction
    alt ResultAction == SearchMap
        Dialog->>Handler: Request paged search (kind, id/brush, offset)
        Handler->>Editor: Run ItemSearcher/CreatureSearcher (with offset)
        Editor-->>Handler: Return paged results + total
        Handler->>SearchResultWindow: SetResults(rows, total, offset, limit, page_loader)
    else ResultAction == SelectItem / ConfirmSelection
        Dialog->>Handler: Request selection handling
        Handler->>Editor: Select brush / update palette
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through tokens, masks, and light,
Built catalogs, grids, and fuzzy sight,
Creatures and items in a tidy view,
Pages to flip and selections true,
A tiny rabbit cheers — the finder is new!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.54% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat: New improved Search Tool' is highly related to the changeset, which comprehensively redesigns the search functionality with new filters, list/grid views, fuzzy search, creature search, and pagination.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch Search-Tool

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request upgrades the project to the C++23 standard and introduces a comprehensive "Advanced Item Finder" UI, refactoring the legacy item search into a model-view architecture. The update includes new NanoVG-based list and grid views, expanded item flag parsing for OTB, DAT, and XML formats, and a new creature search capability. Review feedback highlights several violations of the repository's 500-line file limit in the new UI components and the requirement to use std::format instead of wxString::Format. Additionally, suggestions were made to improve architectural robustness by encapsulating session state and using explicit configuration for dialog instances.

// along with this program. If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////////

#include "app/main.h"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This file has been significantly refactored and is now over 700 lines long. The repository style guide (line 25) suggests splitting files that exceed 500 lines. Please consider refactoring this into smaller, more focused components. For example, the layout logic in buildLayout could be broken down into more helper functions, or parts of the dialog's logic could be extracted into separate helper classes.

Comment thread source/ui/find_item_window.cpp Outdated
input_timer.Bind(wxEVT_TIMER, &FindItemDialog::OnInputTimer, this);
ok_button->Bind(wxEVT_BUTTON, &FindItemDialog::OnClickOK, this);
cancel_button->Bind(wxEVT_BUTTON, &FindItemDialog::OnClickCancel, this);
std::optional<SessionFinderState> g_session_finder_state;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The use of a file-scoped global variable g_session_finder_state to manage state between dialog invocations can make the code harder to reason about and maintain. While it's scoped to this file via an anonymous namespace, a better approach would be to encapsulate this session state within a manager class that can be passed to the dialog or accessed via a service locator. This would make the data flow more explicit.

@@ -0,0 +1,558 @@
#include "ui/find_item_window_model.h"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This new file is over 550 lines long, which exceeds the 500-line limit suggested by the repository style guide (line 25). To improve maintainability, please consider splitting this file into smaller, more focused modules. For example, the query parsing logic, catalog building, and state persistence could each be extracted into their own files.

@@ -0,0 +1,842 @@
#include "ui/find_item_window_views.h"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This new file is over 840 lines long, and the AdvancedFinderResultsView class is very large. This violates the repository style guide's 500-line limit for files (line 25). To improve maintainability and adhere to the Single Responsibility Principle, please refactor this. For instance, AdvancedFinderResultsView could be split into a base class with separate List and Grid view implementations. The rendering logic for different item types could also be extracted into helper classes or functions.

Comment thread source/ui/menubar/search_handler.cpp Outdated
@@ -108,7 +142,6 @@ void SearchHandler::OnSearchForItemOnSelection(wxCommandEvent& WXUNUSED(event))
}

FindItemDialog dialog(frame, "Search on Selection");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The new FindItemDialog is being used here with its default parameters. This is fragile because it relies on the implicit behavior of the dialog's constructor and doesn't clearly state the intent for this specific use case. The new dialog is much more powerful than the old one, and its behavior can be configured.

To make the code more robust and readable, please explicitly configure the FindItemDialog for the needs of OnSearchForItemOnSelection. For example, if it should only allow selecting an item and not searching the map, you should pass the appropriate ActionSet and other parameters to the constructor.

FindItemDialog dialog(frame, "Search on Selection", false, FindItemDialog::ActionSet::ConfirmOnly);

Comment thread source/ui/find_item_window.cpp Outdated

void FindItemDialog::updateResultTitle(size_t count) const {
if (result_count_label_ != nullptr) {
result_count_label_->SetLabel(wxString::Format("Results: %zu |", count));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

low

The style guide (line 84) mandates the use of std::format over wxString::Format. Please update this line to use std::format for consistency and adherence to the project's coding standards. You will need to #include <format> in this file.

result_count_label_->SetLabel(std::format("Results: {} |", count));

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This Pull Request introduces a modern, feature-rich "Advanced Item Finder" to the editor, significantly enhancing the search capabilities with fuzzy matching, extensive filtering (by type, properties, interactions, and visuals), and a polished NanoVG-based UI with list and grid views. The implementation is well-structured and follows modern C++ practices.

🔍 General Feedback

  • Visual Polish: The addition of the NanoVG-based results view and preview panel provides a very professional and responsive experience.
  • C++23 Migration: The upgrade to C++23 is a bold move that allows for cleaner code (like std::to_underlying), but should be double-checked for toolchain compatibility across all target platforms.
  • Performance Considerations: While the current implementation is efficient for thousands of items, caching the catalog and adding a small debounce to the search field would make it even snappier and more robust on lower-end hardware.
  • Improved Parsers: The updates to the DAT and OTB parsers to include new flags like ForceUse and FullTile are important for the search tool's advanced filtering logic.

}
}

std::vector<AdvancedFinderCatalogRow> BuildAdvancedFinderCatalog(bool include_creatures) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 `BuildAdvancedFinderCatalog` is called every time the search dialog is opened. This iterates over thousands of item definitions and performs numerous allocations. Caching this catalog (e.g., in a static variable or a session-wide service) would significantly improve the dialog's opening speed and overall responsiveness.

Comment thread CMakeLists.txt
add_executable(rme ${rme_H} ${rme_SRC})

set_target_properties(rme PROPERTIES CXX_STANDARD 20)
set_target_properties(rme PROPERTIES CXX_STANDARD 23)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Upgrading to C++23 is a significant change for the project's toolchain requirements. While it allows for nice features like `std::to_underlying` used elsewhere in this PR, ensure that all target platforms and CI environments support modern compilers (GCC 13+, Clang 16+, MSVC 2022).

return std::string(value.substr(first, last - first + 1));
}

[[nodiscard]] std::vector<std::string> tokenizeLower(std::string_view value) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 `std::isalnum` is locale-dependent and does not handle UTF-8 multibyte characters. If item names contain non-ASCII characters (common in localized versions), the tokenization might fail or produce incorrect results. Consider using a UTF-8 aware utility for tokenization or a simple custom check for space/punctuation if you only want to split by words.


void FindItemDialog::OnTypeChange(wxCommandEvent& WXUNUSED(event)) {
RefreshContentsInternal();
void FindItemDialog::OnTextChanged(wxCommandEvent& WXUNUSED(event)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The search now filters the entire catalog on every keystroke. While fast for small datasets, it might introduce typing lag with larger item sets. Re-introducing a small debouncing timer (e.g., 200-300ms) before calling `refreshResults()` would ensure a smoother typing experience, especially on slower machines.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (3)
source/app/settings.cpp (1)

324-327: Persist these filter masks as unsigned values.

These keys hold AdvancedFinderFilterMask values, but Int(...) routes them through getInteger() / setInteger(), which are signed. That gives the new settings a 31-bit ceiling, and any future filter using bit 31 will round-trip as negative and then load back as 0 in source/ui/find_item_window_model.cpp. I’d switch these to uint32-aware settings accessors or a string representation before the filter enums grow further.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/app/settings.cpp` around lines 324 - 327, The four Int(...) calls
store AdvancedFinderFilterMask values as signed ints causing overflow for bit31;
change them to a uint32-aware settings accessor (e.g., replace
Int(ADVANCED_ITEM_FINDER_TYPE_FILTERS, ...) /
Int(ADVANCED_ITEM_FINDER_PROPERTY_FILTERS, ...) /
Int(ADVANCED_ITEM_FINDER_INTERACTION_FILTERS, ...) /
Int(ADVANCED_ITEM_FINDER_VISUAL_FILTERS, ...) with the project’s unsigned
setting API such as UInt/UnsignedInt/GetUnsigned/SetUnsigned that round-trips a
32-bit value) or else serialize the mask to a string; also update the load/save
paths used by source/ui/find_item_window_model.cpp to use the same unsigned
reader so AdvancedFinderFilterMask bits >=31 survive.
source/ui/menubar/search_handler.cpp (1)

16-60: Extract these advanced-finder helpers into one shared utility.

This block now duplicates selectFoundBrush, item search result wiring, and creature search result wiring from source/ui/menubar/navigation_menu_handler.cpp. Keeping the result-window plumbing in two places will make the next behavior tweak drift between the two menus.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/menubar/search_handler.cpp` around lines 16 - 60, These three
functions (selectFoundBrush, showItemSearchResults, showCreatureSearchResults)
duplicate result-window and load-bar plumbing found in
navigation_menu_handler.cpp; extract that common logic into a single shared
helper (e.g., a utility function or small class in a common UI helper namespace)
that accepts the searcher/result types (or a callable adapter), the
load_bar_label, and the result-to-display mapping, reusing
EditorOperations::ItemSearcher/CreatureSearcher and SearchResultWindow
interactions (CreateLoadBar, DestroyLoadBar, limitReached/maxCount messaging,
window->Clear(), and window->AddPosition). Then replace the local
implementations in both files to call the new shared helper while keeping the
existing function names/signatures used by callers (selectFoundBrush,
showItemSearchResults, showCreatureSearchResults) to minimize call-site changes.
source/ui/find_item_window_model.h (1)

83-86: Consider simplifying the cast chain.

The outer cast to AdvancedFinderFilterMask for the shift amount is semantically unusual—shift amounts are typically plain integers. The std::underlying_type_t<Enum> result (uint8_t) already promotes naturally for the shift operation.

♻️ Simplified version
 template <typename Enum>
 constexpr AdvancedFinderFilterMask advancedFinderBit(Enum value) {
-	return AdvancedFinderFilterMask { 1 } << static_cast<AdvancedFinderFilterMask>(static_cast<std::underlying_type_t<Enum>>(value));
+	return AdvancedFinderFilterMask{1} << static_cast<std::underlying_type_t<Enum>>(value);
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window_model.h` around lines 83 - 86, Simplify
advancedFinderBit by removing the outer cast to AdvancedFinderFilterMask for the
shift amount: compute the shift using the enum's underlying integer (e.g.,
std::underlying_type_t<Enum> or an int/unsigned) and let ordinary integer
promotion perform the shift, then construct/return the AdvancedFinderFilterMask
from (AdvancedFinderFilterMask{1} << that_integer); update the expression in
advancedFinderBit accordingly so the shift operand is a plain integer rather
than an AdvancedFinderFilterMask.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/editor/operations/search_operations.h`:
- Around line 154-156: The traversal isn't short-circuited because
CreatureSearcher::operator()(Map&, Tile*, long long) returns early but
foreach_TileOnMap (the foreach wrapper in source/map/map.h that currently uses
std::ranges::for_each and discards the visitor return) never observes that;
change the iteration contract so the visitor can signal stop (e.g., make the
visitor return bool) and update foreach_TileOnMap to use a loop that checks the
visitor's return and breaks when it returns false (replace the
std::ranges::for_each usage with a manual loop or a std::any_of/for loop that
respects the boolean), and update CreatureSearcher::operator() to return the
appropriate boolean (true to continue, false to stop) so maxCount actually stops
traversal.

In `@source/ui/find_item_window_model.cpp`:
- Around line 153-185: Both scoreTextTokenMatch and scoreNumericTokenMatch fail
to treat queries with both prefix_wildcard and suffix_wildcard as "contains"
matches; update these functions (scoreTextTokenMatch and scoreNumericTokenMatch)
to check if query_token.prefix_wildcard && query_token.suffix_wildcard and then
test name_token.contains(query_token.value) and return an appropriate score
(e.g., a distinct base like 30 + length difference or similar to other wildcard
branches) before falling back to prefix/suffix rules so queries like *term*
match infix occurrences such as "sword".

In `@source/ui/find_item_window_views.cpp`:
- Around line 590-600: The hit-testing currently treats the first pixel of the
gap as part of the adjacent card because the checks use '>' instead of '>=';
update the boundary checks in the hit-test logic around row_height/row_offset
and column_width/column_offset (the block computing row, row_offset, column,
column_offset in find_item_window_views.cpp) to treat offsets equal to
card_height_ or card_width_ as outside the card (i.e., replace the row_offset >
card_height_ and column_offset > card_width_ conditions with >= so gap pixels
are non-interactive).

In `@source/ui/find_item_window.cpp`:
- Around line 395-403: The dialog currently ignores the persisted
state.last_action when restoring; update the restore/initialization logic to set
default_action_ from state.last_action before applying button defaults so the
user's previous "SearchMap" vs "SelectItem" choice is restored. Concretely, in
the code paths that set button defaults (the block using action_set_ ==
ActionSet::SearchAndSelect that calls search_map_button_->SetDefault() or
select_item_button_->SetDefault(), and the similar block at the 413-417
location), assign default_action_ = state.last_action (or read state.last_action
into default_action_ or a local used to choose the default) and then call
SetDefault() on the matching widget; ensure the other branch that sets
ok_button_->SetDefault() is unchanged.
- Line 148: g_session_finder_state (SessionFinderState) is being restored too
early and saved back unconditionally, so transient dialogs (e.g., ConfirmOnly)
overwrite the shared finder session; fix by ensuring the global
g_session_finder_state is only updated/restored from a window instance when
persist_shared_state_ is true and only written back on close when
persist_shared_state_ is true, and adjust the restore order so the persisted
shared state is loaded before any transient restoration; apply these guards in
the FindItemWindow constructor/restore logic and in the close/save path (the
places that currently touch g_session_finder_state and persist_shared_state_,
including ConfirmOnly handling) so one-off dialogs never replace the main
finder's query/selection/view mode.
- Around line 234-237: The Hints box in buildLayout() always shows both
"double-left-click to search the map" and "double-right-click to select in the
tileset", which is wrong for dialogs with action_set_ == ConfirmOnly; change
buildLayout() so the two createHintText() calls are conditional based on
action_set_ (only add the left-click hint when the action_set_ enables map
search and only add the right-click hint when it enables tileset selection).
Locate buildLayout(), the hints_box variable and the createHintText(...) calls
and wrap each Add(...) in a check against action_set_ (or use the existing
action_set_ query methods/flags) so the hint text matches the actual available
actions.

In `@source/ui/menubar/navigation_menu_handler.cpp`:
- Around line 125-133: The SearchMap branch in the Jump to Item handler can run
without an open editor; before calling showCreatureSearchResults or
showItemSearchResults for the FindItemDialog ResultAction::SearchMap, add the
same editor-open guard used elsewhere (check g_gui.IsEditorOpen()) and bail out
(or treat as cancel) if no editor is open; locate the branch handling
dialog.getResultAction() == FindItemDialog::ResultAction::SearchMap in the code
around FindItemDialog usage and wrap the calls to
showCreatureSearchResults/showItemSearchResults with that guard (or redirect to
the normal non-map search flow).

---

Nitpick comments:
In `@source/app/settings.cpp`:
- Around line 324-327: The four Int(...) calls store AdvancedFinderFilterMask
values as signed ints causing overflow for bit31; change them to a uint32-aware
settings accessor (e.g., replace Int(ADVANCED_ITEM_FINDER_TYPE_FILTERS, ...) /
Int(ADVANCED_ITEM_FINDER_PROPERTY_FILTERS, ...) /
Int(ADVANCED_ITEM_FINDER_INTERACTION_FILTERS, ...) /
Int(ADVANCED_ITEM_FINDER_VISUAL_FILTERS, ...) with the project’s unsigned
setting API such as UInt/UnsignedInt/GetUnsigned/SetUnsigned that round-trips a
32-bit value) or else serialize the mask to a string; also update the load/save
paths used by source/ui/find_item_window_model.cpp to use the same unsigned
reader so AdvancedFinderFilterMask bits >=31 survive.

In `@source/ui/find_item_window_model.h`:
- Around line 83-86: Simplify advancedFinderBit by removing the outer cast to
AdvancedFinderFilterMask for the shift amount: compute the shift using the
enum's underlying integer (e.g., std::underlying_type_t<Enum> or an
int/unsigned) and let ordinary integer promotion perform the shift, then
construct/return the AdvancedFinderFilterMask from (AdvancedFinderFilterMask{1}
<< that_integer); update the expression in advancedFinderBit accordingly so the
shift operand is a plain integer rather than an AdvancedFinderFilterMask.

In `@source/ui/menubar/search_handler.cpp`:
- Around line 16-60: These three functions (selectFoundBrush,
showItemSearchResults, showCreatureSearchResults) duplicate result-window and
load-bar plumbing found in navigation_menu_handler.cpp; extract that common
logic into a single shared helper (e.g., a utility function or small class in a
common UI helper namespace) that accepts the searcher/result types (or a
callable adapter), the load_bar_label, and the result-to-display mapping,
reusing EditorOperations::ItemSearcher/CreatureSearcher and SearchResultWindow
interactions (CreateLoadBar, DestroyLoadBar, limitReached/maxCount messaging,
window->Clear(), and window->AddPosition). Then replace the local
implementations in both files to call the new shared helper while keeping the
existing function names/signatures used by callers (selectFoundBrush,
showItemSearchResults, showCreatureSearchResults) to minimize call-site changes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 36ceeada-2a1b-450d-9568-0176b52abe2f

📥 Commits

Reviewing files that changed from the base of the PR and between 2bd899e and ae50bd0.

📒 Files selected for processing (17)
  • CMakeLists.txt
  • source/CMakeLists.txt
  • source/app/settings.cpp
  • source/app/settings.h
  • source/editor/operations/search_operations.h
  • source/item_definitions/core/item_definition_types.h
  • source/item_definitions/formats/dat/dat_item_parser.cpp
  • source/item_definitions/formats/otb/otb_item_parser.cpp
  • source/item_definitions/formats/xml/xml_item_parser.cpp
  • source/ui/find_item_window.cpp
  • source/ui/find_item_window.h
  • source/ui/find_item_window_model.cpp
  • source/ui/find_item_window_model.h
  • source/ui/find_item_window_views.cpp
  • source/ui/find_item_window_views.h
  • source/ui/menubar/navigation_menu_handler.cpp
  • source/ui/menubar/search_handler.cpp

Comment on lines +154 to +156
void operator()(Map& map, Tile* tile, long long done) {
if (result.size() >= static_cast<size_t>(maxCount)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -i 'map_search*' source
rg -n -C5 '\bforeach_TileOnMap\b' source

Repository: karolak6612/remeres-map-editor-redux

Length of output: 3434


The traversal is not short-circuited; maxCount only caps displayed results.

The foreach_TileOnMap function at source/map/map.h:238-243 uses std::ranges::for_each and discards the return value from the visitor:

std::ranges::for_each(map.tiles(), [&](auto& tile_loc) {
    foreach (map, tile_loc.get(), ++done)
        ;
});

Early return from CreatureSearcher::operator() does not stop iteration. The entire map is always traversed even after result.size() >= maxCount is reached. The maxCount guard caps displayed results only, not runtime. Fix foreach_TileOnMap to respect early termination (e.g., by checking a return value or using a different iteration mechanism).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/editor/operations/search_operations.h` around lines 154 - 156, The
traversal isn't short-circuited because CreatureSearcher::operator()(Map&,
Tile*, long long) returns early but foreach_TileOnMap (the foreach wrapper in
source/map/map.h that currently uses std::ranges::for_each and discards the
visitor return) never observes that; change the iteration contract so the
visitor can signal stop (e.g., make the visitor return bool) and update
foreach_TileOnMap to use a loop that checks the visitor's return and breaks when
it returns false (replace the std::ranges::for_each usage with a manual loop or
a std::any_of/for loop that respects the boolean), and update
CreatureSearcher::operator() to return the appropriate boolean (true to
continue, false to stop) so maxCount actually stops traversal.

Comment thread source/ui/find_item_window_model.cpp
Comment thread source/ui/find_item_window_views.cpp Outdated
Comment thread source/ui/find_item_window.cpp Outdated
Comment thread source/ui/find_item_window.cpp Outdated
Comment thread source/ui/find_item_window.cpp Outdated
Comment on lines +395 to +403
if (action_set_ == ActionSet::SearchAndSelect) {
if (default_action_ == AdvancedFinderDefaultAction::SearchMap) {
search_map_button_->SetDefault();
} else {
select_item_button_->SetDefault();
}
} else if (selection == SearchMode::ClientIDs) {
uint16_t clientID = (uint16_t)client_id_spin->GetValue();
for (ServerItemId id : g_item_definitions.findAllByClientId(clientID)) {
const auto item = g_item_definitions.get(id);
RAWBrush* raw_brush = item.editorData().raw_brush;
if (!raw_brush) {
continue;
}
} else if (ok_button_ != nullptr) {
ok_button_->SetDefault();
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Apply the stored last action when restoring the shared finder.

You persist state.last_action, but the default button is still chosen entirely from the constructor argument. Reopening the dialog never restores the user's previous "Search Map" vs "Select Item" preference.

💡 Suggested fix
+ if (persist_shared_state_) {
+ 	default_action_ = persisted_state_.last_action;
+ }
+
 if (action_set_ == ActionSet::SearchAndSelect) {
 	if (default_action_ == AdvancedFinderDefaultAction::SearchMap) {
 		search_map_button_->SetDefault();
 	} else {

Also applies to: 413-417

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window.cpp` around lines 395 - 403, The dialog currently
ignores the persisted state.last_action when restoring; update the
restore/initialization logic to set default_action_ from state.last_action
before applying button defaults so the user's previous "SearchMap" vs
"SelectItem" choice is restored. Concretely, in the code paths that set button
defaults (the block using action_set_ == ActionSet::SearchAndSelect that calls
search_map_button_->SetDefault() or select_item_button_->SetDefault(), and the
similar block at the 413-417 location), assign default_action_ =
state.last_action (or read state.last_action into default_action_ or a local
used to choose the default) and then call SetDefault() on the matching widget;
ensure the other branch that sets ok_button_->SetDefault() is unchanged.

Comment thread source/ui/menubar/navigation_menu_handler.cpp
- source/app/settings.cpp — removed FIND_ITEM_MODE, JUMP_TO_ITEM_MODE, ADVANCED_ITEM_FINDER_FIND_BY, ADVANCED_ITEM_FINDER_LAST_ACTION settings
- source/app/settings.h — removed corresponding Config enum entries for deleted settings
- source/ui/find_item_window.cpp — removed last_action persistence logic from savePersistedState()
- source/ui/find_item_window_model.cpp — removed last_action load/save, removed secondary_label assignments for items and creatures
- source/ui/find_item_window_model.h — removed secondary_label field from AdvancedFinderCatalogRow, removed last_action from AdvancedFinderPersistedState
- source/ui/find_item_window_views.cpp — removed <format> import [minor], deleted entire AdvancedFinderPreviewPanel class, renamed rowSummary to hoverSummary and switched to string concatenation
- source/ui/find_item_window_views.h — removed AdvancedFinderPreviewPanel class declaration
Implemented pagination for search results to handle large result sets efficiently. Replaced wxListBox with custom NanoVG-based ResultCanvas for better performance and theming. Added SEARCH_RESULTS_LIMIT config (default 100000). Updated ItemSearcher/CreatureSearcher to track totalMatches and support offset-based paging. Added DestroySearchWindow() for proper cleanup. Search handlers now load results in pages with prev/next navigation.
@github-actions

Copy link
Copy Markdown

🤖 Hi @karolak6612, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

The new Search Tool provides a significant enhancement to the editor's capabilities, including creature search, pagination, and a much-improved result list. The move towards value-based row storage and better state management in settings is a positive highlight.

🔍 General Feedback

  • The custom NanoVGCanvas implementation for search results is clean and provides a modern feel to the UI.
  • Pagination is a great addition, though the current per-page full map scan might be slow on exceptionally large maps. Consider caching search results in the future if performance becomes an issue.
  • The C++23 upgrade is noted; ensure this doesn't impact CI or developer environments without a strong functional requirement for the new standard.

[[nodiscard]] uint32_t searchResultsLimit() {
return static_cast<uint32_t>(std::max(100000, g_settings.getInteger(Config::SEARCH_RESULTS_LIMIT)));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The use of std::max(100000, ...) for the search results limit is problematic. It prevents users from setting a limit lower than 100,000, effectively forcing a very large minimum. If the intention was to provide a default value or a maximum cap, this logic should be reconsidered.

If you want 100,000 to be the default when the setting is not present or invalid, you should handle it differently. If you want to cap it at 100,000, you should use std::min.

Suggested change
[[nodiscard]] uint32_t searchResultsLimit() {
return static_cast<uint32_t>(std::max(1, g_settings.getInteger(Config::SEARCH_RESULTS_LIMIT)));
}

Comment thread CMakeLists.txt
add_executable(rme ${rme_H} ${rme_SRC})

set_target_properties(rme PROPERTIES CXX_STANDARD 20)
set_target_properties(rme PROPERTIES CXX_STANDARD 23)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Upgrading the C++ standard to C++23 is a major change. Unless there are specific C++23 features being utilized that aren't available in C++20, it's generally better to remain on a more widely supported standard like C++20 to ensure broader compatibility with developer environments and CI tools.

If there are C++23 features used that I missed, please disregard this comment. Otherwise, consider if C++20 is sufficient.

Suggested change
set_target_properties(rme PROPERTIES CXX_STANDARD 23)
set_target_properties(rme PROPERTIES CXX_STANDARD 20)

@@ -122,14 +125,16 @@ namespace {
case DatFlagTranslucent:
case DatFlagLyingCorpse:
case DatFlagAnimateAlways:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 DatFlagFullGround was removed from the list of handled flags in the parser. If this flag is still used in Tibia .dat files (which it typically is in some versions), this could cause an "unknown flag" warning or regression for items that utilize it. If DatFlagFullTile is meant to replace it or if it's no longer relevant for the versions supported by this PR, please clarify. Otherwise, it should likely remain in the switch statement even if it's a no-op to avoid the default "unknown" case.

Suggested change
case DatFlagAnimateAlways:
case DatFlagFullGround:
case DatFlagAnimateAlways:

}

if (rows_.empty() || total_count_ == 0) {
summary_label->SetLabel("Results: 0");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 The export functionality currently only exports results from the current page (stored in rows_). If there are more results available (indicated by total_count_), the user might expect all of them to be exported.

Consider either:

  1. Warning the user that only the current page is being exported.
  2. Implementing a full export that performs a dedicated map scan to find and write all matches directly to the file.
	for (size_t i = 0; i < count; ++i) {
		const SearchResultRow& row = rows_[i];
		file.Write(wxString::Format("%u\t%s\t%d\t%d\t%d\n", row.index, row.name, row.position.x, row.position.y, row.position.z));

- source/editor/operations/search_operations.h — removed limitReached() method from ItemSearchOperation and CreatureSearchOperation structs
- source/ui/gui.h — removed HideSearchWindow() wrapper method
- source/ui/managers/search_manager.cpp — removed HideSearchWindow() implementation
- source/ui/managers/search_manager.h — removed HideSearchWindow() declaration from public API
- source/ui/menubar/navigation_menu_handler.cpp — removed unused dialog_util.h include [minor]
@github-actions

Copy link
Copy Markdown

🤖 Hi @karolak6612, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
source/ui/menubar/navigation_menu_handler.cpp (1)

151-159: ⚠️ Potential issue | 🟠 Major

Block SearchMap when no editor is open.

This handler still exposes the map-search flow even though the SearchMap branch immediately walks g_gui.GetCurrentMap(). With no editor open, the dialog can still return SearchMap and then dereference invalid editor/map state.

💡 Suggested fix
-	FindItemDialog dialog(frame, "Jump to Item", false, FindItemDialog::ActionSet::SearchAndSelect, AdvancedFinderDefaultAction::SelectItem, true);
+	const auto action_set = g_gui.IsEditorOpen()
+		? FindItemDialog::ActionSet::SearchAndSelect
+		: FindItemDialog::ActionSet::ConfirmOnly;
+	FindItemDialog dialog(frame, "Jump to Item", false, action_set, AdvancedFinderDefaultAction::SelectItem, true);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/menubar/navigation_menu_handler.cpp` around lines 151 - 159, The
SearchMap branch can run when no editor/map exists; before handling
FindItemDialog::ResultAction::SearchMap (after dialog.ShowModal() and
dialog.getResultAction()), check that an editor/map is open via
g_gui.GetCurrentMap() (or the existing editor-check helper) and early-return or
treat as cancelled if it is null; only call showCreatureSearchResults or
showItemSearchResults when the map/editor is valid to avoid dereferencing
invalid state.
🧹 Nitpick comments (5)
source/ui/find_item_window_views.cpp (1)

576-583: const_cast used to call non-const base class method.

The drawSpriteBadge method is const but needs to call GetOrCreateSpriteTexture which modifies texture cache state. This is acceptable given the caching semantics, but could be documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window_views.cpp` around lines 576 - 583, The const_cast
in drawSpriteBadge calling GetOrCreateSpriteTexture on AdvancedFinderResultsView
should be justified or eliminated: either change the texture cache members used
by GetOrCreateSpriteTexture to be mutable and keep GetOrCreateSpriteTexture
logically const, or if leaving the method non-const, add a clear comment
immediately above the const_cast explaining why mutation is safe (texture cache
only, idempotent, thread-affinity, etc.) and reference GetOrCreateSpriteTexture
and AdvancedFinderResultsView; ensure the comment documents the caching
semantics and why const correctness is relaxed.
source/ui/result_window.h (1)

38-38: Consider documenting the page_loader callback contract.

The SetResults signature accepts a page_loader callback but its expected behavior (called with new offset when pagination buttons are clicked) isn't documented. A brief comment would help future maintainers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/result_window.h` at line 38, Document the page_loader parameter on
the SetResults declaration: explain that page_loader is an optional callback
invoked when pagination controls are used, it receives the new page_offset
(uint32_t) to request more SearchResultRow items, is allowed to be empty
(default {}), and should perform loading/updating of results (e.g., call
SetResults again) when the user requests a different page; add this brief
comment above the SetResults prototype so maintainers understand the contract.
source/ui/find_item_window_model.cpp (1)

354-383: Dynamic cast per item definition is expensive.

Line 356 performs a dynamic_cast to GameSprite* for every item definition when building the catalog. This is O(n) casts for n items.

💡 Consider caching or avoiding dynamic_cast

If the sprite type is known at retrieval time, consider providing a typed accessor in the graphics system to avoid runtime type checking for every item.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window_model.cpp` around lines 354 - 383, The
buildVisualMask function does a dynamic_cast<GameSprite*> via
g_gui.gfx.getSprite(definition.clientId()) for every item which is expensive;
change the graphics API or usage to avoid per-item dynamic_casts by exposing a
typed accessor or caching sprite metadata: add or use a getGameSprite(clientId)
or a boolean/flag-based query on g_gui.gfx that returns either a GameSprite* or
lightweight sprite properties, then update buildVisualMask to call that typed
accessor once (or read cached metadata) instead of dynamic_casting each time,
keeping checks for hasLight(), animator/frames, and other property queries
against the new accessor (referencing buildVisualMask, GameSprite,
g_gui.gfx.getSprite, and AdvancedFinderVisualFilter).
source/ui/find_item_window.cpp (1)

543-551: const_cast to call Layout() is a code smell.

Using const_cast<FindItemDialog*>(this)->Layout() in a const method indicates the method shouldn't be const, or Layout() should be called elsewhere.

♻️ Consider making the method non-const
-void FindItemDialog::updateResultTitle(size_t count) const {
+void FindItemDialog::updateResultTitle(size_t count) {
 	if (result_count_label_ != nullptr) {
 		result_count_label_->SetLabel(wxString::Format("Results: %zu |", count));
 		result_count_label_->SetMinSize(result_count_label_->GetBestSize());
 		if (wxWindow* parent = result_count_label_->GetParent(); parent != nullptr) {
 			parent->Layout();
 		}
-		const_cast<FindItemDialog*>(this)->Layout();
+		Layout();
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window.cpp` around lines 543 - 551, updateResultTitle
currently uses const_cast to call Layout() which indicates the method should not
be const; change the method to be non-const (remove the trailing const from
FindItemDialog::updateResultTitle in both declaration and definition), remove
the const_cast<FindItemDialog*>(this)->Layout() call and call this->Layout()
directly, and update any callers or references as needed so the method compiles
without casting; ensure the existing result_count_label_ parent Layout() call
remains unchanged.
source/ui/result_window.cpp (1)

392-405: Potential integer overflow in progress bar callback.

The SetLoadScale call on line 400 casts size_t values to int32_t. For very large result sets (approaching SEARCH_RESULTS_LIMIT of 100,000), this is fine, but if the limit is increased significantly in the future, this could overflow.

💡 Consider using safer casts
-		g_gui.SetLoadScale(static_cast<int32_t>(i), static_cast<int32_t>(count));
+		g_gui.SetLoadScale(static_cast<int32_t>(std::min(i, static_cast<size_t>(INT32_MAX))), 
+		                   static_cast<int32_t>(std::min(count, static_cast<size_t>(INT32_MAX))));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/result_window.cpp` around lines 392 - 405, The progress scaling
currently casts size_t to int32_t in the export loop (rows_, count ->
g_gui.SetLoadScale(static_cast<int32_t>(i), static_cast<int32_t>(count))), which
can overflow if result limits grow; fix by using a wider integer or clamping:
either change SetLoadScale to accept a 64-bit type (e.g. int64_t) and pass i and
count as int64_t, or compute safe int values by converting to int64_t and then
clamping to INT32_MAX (or the GUI max) before calling SetLoadScale; update all
references to SetLoadScale and the loop variables (i, count, rows_) accordingly
to ensure no narrowing cast from size_t to int32_t occurs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/ui/managers/search_manager.cpp`:
- Around line 12-14: The SearchManager destructor (~SearchManager) must not call
DestroySearchWindow because that function touches g_gui.aui_manager and global
destruction order between g_search and g_gui is unspecified; remove the call
from ~SearchManager (make the destructor a no-op) and rely on the existing
explicit call to g_search.DestroySearchWindow() in MainFrame::OnExit to perform
cleanup at application shutdown; ensure DestroySearchWindow remains available
for explicit teardown and consider leaving a comment in ~SearchManager
referencing MainFrame::OnExit for future maintainers.

In `@source/ui/menubar/navigation_menu_handler.cpp`:
- Around line 37-59: The pagination callback is re-querying
g_gui.GetCurrentMap() at click time, so if the user switches or closes the map
the paging runs against the wrong map; modify showItemSearchResults (and the
analogous showCreatureSearchResults at lines 62-84) to capture and use the
specific map instance (or map ID) used for the initial search: add a parameter
for the map pointer/ID to the function signature, construct
EditorOperations::ItemSearcher and call foreach_ItemOnMap with that captured map
instead of g_gui.GetCurrentMap(), and change the lambda passed into
window->SetResults to capture the map by value and call the updated
showItemSearchResults(map, item_id, load_bar_label, next_offset) so paging
always queries the original map.
- Around line 18-20: searchResultsLimit currently forces a 100000 minimum by
using std::max(100000, g_settings.getInteger(Config::SEARCH_RESULTS_LIMIT)),
preventing smaller configured page sizes; change the logic in searchResultsLimit
to clamp the configured value into a valid range (for example using std::clamp)
so the returned uint32_t is bounded between a sensible minimum (e.g. 1) and the
maximum 100000, and use g_settings.getInteger(Config::SEARCH_RESULTS_LIMIT) as
the source value so smaller configured limits are respected.

In `@source/ui/menubar/search_handler.cpp`:
- Around line 277-283: The call to result->SetResults(std::move(rows),
static_cast<uint32_t>(rows.size())) uses rows.size() after rows has been
moved-from (undefined); change it to pass the known size from found (or capture
size before moving). Specifically, in the block that builds rows using
makeSearchResultRow and found, replace the second argument to SetResults with
static_cast<uint32_t>(found.size()) (or store auto n = rows.size(); and use n)
so you don't query rows after std::move; keep the call to
result->SetResults(std::move(rows), ... ) otherwise.

---

Duplicate comments:
In `@source/ui/menubar/navigation_menu_handler.cpp`:
- Around line 151-159: The SearchMap branch can run when no editor/map exists;
before handling FindItemDialog::ResultAction::SearchMap (after
dialog.ShowModal() and dialog.getResultAction()), check that an editor/map is
open via g_gui.GetCurrentMap() (or the existing editor-check helper) and
early-return or treat as cancelled if it is null; only call
showCreatureSearchResults or showItemSearchResults when the map/editor is valid
to avoid dereferencing invalid state.

---

Nitpick comments:
In `@source/ui/find_item_window_model.cpp`:
- Around line 354-383: The buildVisualMask function does a
dynamic_cast<GameSprite*> via g_gui.gfx.getSprite(definition.clientId()) for
every item which is expensive; change the graphics API or usage to avoid
per-item dynamic_casts by exposing a typed accessor or caching sprite metadata:
add or use a getGameSprite(clientId) or a boolean/flag-based query on g_gui.gfx
that returns either a GameSprite* or lightweight sprite properties, then update
buildVisualMask to call that typed accessor once (or read cached metadata)
instead of dynamic_casting each time, keeping checks for hasLight(),
animator/frames, and other property queries against the new accessor
(referencing buildVisualMask, GameSprite, g_gui.gfx.getSprite, and
AdvancedFinderVisualFilter).

In `@source/ui/find_item_window_views.cpp`:
- Around line 576-583: The const_cast in drawSpriteBadge calling
GetOrCreateSpriteTexture on AdvancedFinderResultsView should be justified or
eliminated: either change the texture cache members used by
GetOrCreateSpriteTexture to be mutable and keep GetOrCreateSpriteTexture
logically const, or if leaving the method non-const, add a clear comment
immediately above the const_cast explaining why mutation is safe (texture cache
only, idempotent, thread-affinity, etc.) and reference GetOrCreateSpriteTexture
and AdvancedFinderResultsView; ensure the comment documents the caching
semantics and why const correctness is relaxed.

In `@source/ui/find_item_window.cpp`:
- Around line 543-551: updateResultTitle currently uses const_cast to call
Layout() which indicates the method should not be const; change the method to be
non-const (remove the trailing const from FindItemDialog::updateResultTitle in
both declaration and definition), remove the
const_cast<FindItemDialog*>(this)->Layout() call and call this->Layout()
directly, and update any callers or references as needed so the method compiles
without casting; ensure the existing result_count_label_ parent Layout() call
remains unchanged.

In `@source/ui/result_window.cpp`:
- Around line 392-405: The progress scaling currently casts size_t to int32_t in
the export loop (rows_, count -> g_gui.SetLoadScale(static_cast<int32_t>(i),
static_cast<int32_t>(count))), which can overflow if result limits grow; fix by
using a wider integer or clamping: either change SetLoadScale to accept a 64-bit
type (e.g. int64_t) and pass i and count as int64_t, or compute safe int values
by converting to int64_t and then clamping to INT32_MAX (or the GUI max) before
calling SetLoadScale; update all references to SetLoadScale and the loop
variables (i, count, rows_) accordingly to ensure no narrowing cast from size_t
to int32_t occurs.

In `@source/ui/result_window.h`:
- Line 38: Document the page_loader parameter on the SetResults declaration:
explain that page_loader is an optional callback invoked when pagination
controls are used, it receives the new page_offset (uint32_t) to request more
SearchResultRow items, is allowed to be empty (default {}), and should perform
loading/updating of results (e.g., call SetResults again) when the user requests
a different page; add this brief comment above the SetResults prototype so
maintainers understand the contract.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 37185da5-742c-47c4-a830-286d82a2cf8d

📥 Commits

Reviewing files that changed from the base of the PR and between ae50bd0 and 46cf203.

📒 Files selected for processing (16)
  • source/app/settings.cpp
  • source/app/settings.h
  • source/editor/operations/search_operations.h
  • source/ui/find_item_window.cpp
  • source/ui/find_item_window_model.cpp
  • source/ui/find_item_window_model.h
  • source/ui/find_item_window_views.cpp
  • source/ui/find_item_window_views.h
  • source/ui/gui.h
  • source/ui/main_frame.cpp
  • source/ui/managers/search_manager.cpp
  • source/ui/managers/search_manager.h
  • source/ui/menubar/navigation_menu_handler.cpp
  • source/ui/menubar/search_handler.cpp
  • source/ui/result_window.cpp
  • source/ui/result_window.h
🚧 Files skipped from review as they are similar to previous changes (2)
  • source/ui/find_item_window_views.h
  • source/editor/operations/search_operations.h

Comment thread source/ui/managers/search_manager.cpp Outdated
Comment thread source/ui/menubar/navigation_menu_handler.cpp
Comment thread source/ui/menubar/navigation_menu_handler.cpp Outdated
Comment thread source/ui/menubar/search_handler.cpp Outdated

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

## 📋 Review Summary

This Pull Request introduces a significantly improved Advanced Item Finder and creature search functionality, including a complete overhaul of the search results UI using custom NanoVG rendering and pagination. The new features provide much-needed filtering capabilities and a more responsive, visually rich experience for map editing.

🔍 General Feedback

  • New Advanced Finder: The implementation of the Advanced Item Finder is very comprehensive, offering a wide range of filters (types, properties, interactions, visuals) and both List and Grid views.
  • Improved Performance: Replacing the standard wxListBox with a custom NanoVG-based canvas for search results and the finder dialog significantly improves UI responsiveness when handling large result sets.
  • Pagination Support: The addition of pagination for map-wide search results is a great improvement for scalability and usability.
  • Consistency: The move to C++23 allows for cleaner code (e.g., using std::to_underlying), and the developer has maintained good consistency with existing architectural patterns.
  • Shared Utilities: There is some code duplication between search_handler.cpp and navigation_menu_handler.cpp for search-related helper functions which should be refactored into a common utility.

void SearchHandler::OnSearchForItem(wxCommandEvent& WXUNUSED(event)) {
if (!g_gui.IsEditorOpen()) {
return;
namespace {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 High. The searchResultsLimit() function enforces a minimum limit of 100,000, which ignores the user setting if it is set to a lower value. This can lead to performance issues when searching large maps if a smaller limit was intended.

Suggested change
namespace {
[[nodiscard]] uint32_t searchResultsLimit() {
return static_cast<uint32_t>(std::max(1, g_settings.getInteger(Config::SEARCH_RESULTS_LIMIT)));
}

Comment thread source/ui/find_item_window_views.cpp Outdated
}

void AdvancedFinderResultsView::OnNanoVGPaint(NVGcontext* vg, int width, int height) {
updateLayoutMetrics(width);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium. OnNanoVGPaint calls updateLayoutMetrics (which calls UpdateScrollbar and SetScrollStep) every paint cycle. This is inefficient as layout metrics and scrollbar configuration should only be updated when the window size or data changes (which is already handled in onSize, SetRows, etc.).

Suggested change
updateLayoutMetrics(width);
void AdvancedFinderResultsView::OnNanoVGPaint(NVGcontext* vg, int width, int height) {
if (cached_width_ != width || cached_height_ != height) {
updateLayoutMetrics(width);
}
if (empty_state_ != EmptyState::Rows || rows_.empty()) {

@@ -13,41 +13,126 @@
#include "map/map.h"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium. The search-related helper functions (searchResultsLimit, makeSearchResultRow, showItemSearchResults, showCreatureSearchResults) are duplicated between search_handler.cpp and navigation_menu_handler.cpp. This duplication makes the code harder to maintain and prone to inconsistencies. Consider moving these shared utilities to a common location.

… settings

Refactored FindItemDialog by extracting layout code to find_item_window_layout.cpp and painting logic to find_item_window_views_paint.cpp, with internal helpers moved to find_item_window_internal.h. Added unsigned integer support to Settings for filter masks, replacing signed integers. Fixed search pagination to use correct map instance and clamp limits properly. Added prefix wildcard matching to item search scoring.
@github-actions

Copy link
Copy Markdown

🤖 Hi @karolak6612, I've received your request, and I'm working on it now! You can track my progress in the logs for more details.

@github-actions

Copy link
Copy Markdown

🤖 I'm sorry @karolak6612, but I was unable to process your request. Please see the logs for more details.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
source/ui/find_item_window.cpp (1)

73-75: Minor: Cache activeResultsView() result to avoid redundant call.

activeResultsView() is called twice on line 74. While it's a simple getter, caching the result improves clarity.

♻️ Suggested improvement
 void FindItemDialog::updateButtons() {
-	const bool has_selection = activeResultsView() != nullptr && activeResultsView()->GetSelectedRow() != nullptr;
+	const auto* view = activeResultsView();
+	const bool has_selection = view != nullptr && view->GetSelectedRow() != nullptr;
 
 	if (search_map_button_ != nullptr) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window.cpp` around lines 73 - 75, In updateButtons(),
activeResultsView() is called twice; cache its result in a local variable (e.g.,
auto* view = activeResultsView()) and use view for the null-check and the
subsequent GetSelectedRow() call so has_selection becomes something like view !=
nullptr && view->GetSelectedRow() != nullptr; this avoids the redundant getter
call and improves clarity.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@source/app/settings.cpp`:
- Around line 72-84: The bounds check in Settings::getUnsignedInteger is too
loose and allows key == Config::LAST to index past the end of store; change the
guard from "key > Config::LAST" to "key >= Config::LAST" (i.e. ensure key is
strictly less than Config::LAST before accessing store[key]) and make the same
correction in the other unsigned accessor(s) referenced (the unsigned accessors
around lines 118-126) so all checks use >= against Config::LAST to prevent
out-of-range indexing of store.

In `@source/ui/find_item_window_views_paint.cpp`:
- Around line 18-23: List-mode rendering prints row.server_id and row.client_id
for creature rows, producing misleading 0 values; update the list rendering
logic that formats rows (the same place where hoverSummary is used) to check
AdvancedFinderCatalogRow::isCreature() and, when true, render "SID: -   CID: -  
" (or use hoverSummary's output) instead of printing row.server_id and
row.client_id; locate the code that formats/display rows (references:
hoverSummary, AdvancedFinderCatalogRow, row.isCreature(), row.server_id,
row.client_id) and add the conditional branch so creatures show dashes rather
than numeric IDs.

In `@source/ui/result_window.cpp`:
- Around line 374-380: SearchResultWindow::activateRow currently recenters the
current tab regardless of the result origin; attach the originating Map*/tab
identifier to the result set/rows_ (the paged loaders in search_handler.cpp
already know the Map*) and, inside SearchResultWindow::activateRow, first
switch/reactivate that stored map/tab before calling
g_gui.SetScreenCenterPosition(rows_[index].position); update the code paths that
construct rows_ to populate the new source map/tab field and use the app's
existing API to activate that map/tab prior to centering so the recenter happens
on the correct map.
- Around line 314-330: The footer buttons (previous_page_button_,
next_page_button_, export_button_, clear_button_) are bitmap-only and lack
accessible names for screen readers; update their creation to provide
accessibility by calling SetToolTip() and/or SetName() (or SetLabel()) with
descriptive text for each button (e.g., "Previous page", "Next page", "Export
current page", "Clear results"), and ensure previous_page_button_ and
next_page_button_ at minimum get tooltips or names as a fallback so assistive
technology can expose them.

---

Nitpick comments:
In `@source/ui/find_item_window.cpp`:
- Around line 73-75: In updateButtons(), activeResultsView() is called twice;
cache its result in a local variable (e.g., auto* view = activeResultsView())
and use view for the null-check and the subsequent GetSelectedRow() call so
has_selection becomes something like view != nullptr && view->GetSelectedRow()
!= nullptr; this avoids the redundant getter call and improves clarity.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f60b033-d3e6-4275-a020-8f0b63b5b1c5

📥 Commits

Reviewing files that changed from the base of the PR and between 46cf203 and d160862.

📒 Files selected for processing (19)
  • source/CMakeLists.txt
  • source/app/settings.cpp
  • source/app/settings.h
  • source/editor/operations/search_operations.h
  • source/ui/find_item_window.cpp
  • source/ui/find_item_window.h
  • source/ui/find_item_window_internal.h
  • source/ui/find_item_window_layout.cpp
  • source/ui/find_item_window_model.cpp
  • source/ui/find_item_window_model.h
  • source/ui/find_item_window_views.cpp
  • source/ui/find_item_window_views_paint.cpp
  • source/ui/gui.h
  • source/ui/managers/search_manager.cpp
  • source/ui/managers/search_manager.h
  • source/ui/menubar/navigation_menu_handler.cpp
  • source/ui/menubar/search_handler.cpp
  • source/ui/result_window.cpp
  • source/ui/result_window.h
✅ Files skipped from review due to trivial changes (1)
  • source/CMakeLists.txt
🚧 Files skipped from review as they are similar to previous changes (6)
  • source/ui/managers/search_manager.h
  • source/ui/gui.h
  • source/ui/menubar/navigation_menu_handler.cpp
  • source/ui/managers/search_manager.cpp
  • source/ui/find_item_window.h
  • source/ui/find_item_window_model.h

Comment thread source/app/settings.cpp
Comment on lines +72 to +84
uint32_t Settings::getUnsignedInteger(uint32_t key) const {
if (key > Config::LAST) {
return 0;
}
const DynamicValue& dv = store[key];
if (auto val = std::get_if<uint32_t>(&dv.val)) {
return *val;
}
if (auto val = std::get_if<int>(&dv.val)) {
return static_cast<uint32_t>(std::max(0, *val));
}
return 0;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten the bounds check in the new unsigned accessors.

store is sized with Config::LAST, so key == Config::LAST is already out of range. These guards still fall through and index past the vector end.

Suggested fix
uint32_t Settings::getUnsignedInteger(uint32_t key) const {
-	if (key > Config::LAST) {
+	if (key >= Config::LAST) {
		return 0;
	}
	...
}

void Settings::setUnsignedInteger(uint32_t key, uint32_t newval) {
-	if (key > Config::LAST) {
+	if (key >= Config::LAST) {
		return;
	}
	...
}

Also applies to: 118-126

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/app/settings.cpp` around lines 72 - 84, The bounds check in
Settings::getUnsignedInteger is too loose and allows key == Config::LAST to
index past the end of store; change the guard from "key > Config::LAST" to "key
>= Config::LAST" (i.e. ensure key is strictly less than Config::LAST before
accessing store[key]) and make the same correction in the other unsigned
accessor(s) referenced (the unsigned accessors around lines 118-126) so all
checks use >= against Config::LAST to prevent out-of-range indexing of store.

Comment on lines +18 to +23
[[nodiscard]] std::string hoverSummary(const AdvancedFinderCatalogRow& row) {
if (row.isCreature()) {
return "SID: - CID: - " + row.label;
}
return "SID: " + std::to_string(row.server_id) + " CID: " + std::to_string(row.client_id) + " " + row.label;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Render creature rows without bogus SID/CID values.

hoverSummary() already treats creatures as having no SID/CID, but list mode always prints row.server_id and row.client_id. Creature results will show misleading 0 values here.

Suggested fix
+	const std::string sid_text = row.isCreature() ? "-" : std::to_string(row.server_id);
+	const std::string cid_text = row.isCreature() ? "-" : std::to_string(row.client_id);
+
 	nvgSave(vg);
 	nvgScissor(vg, column_image + 6.0f, rect.y, rect.width - column_image - 12.0f, rect.height);
-	nvgText(vg, column_image + 12.0f, text_y, std::to_string(row.server_id).c_str(), nullptr);
-	nvgText(vg, column_image + column_sid + 12.0f, text_y, std::to_string(row.client_id).c_str(), nullptr);
+	nvgText(vg, column_image + 12.0f, text_y, sid_text.c_str(), nullptr);
+	nvgText(vg, column_image + column_sid + 12.0f, text_y, cid_text.c_str(), nullptr);
 	nvgText(vg, column_image + column_sid + column_cid + 12.0f, text_y, row.label.c_str(), nullptr);
 	nvgRestore(vg);

Also applies to: 221-226

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/find_item_window_views_paint.cpp` around lines 18 - 23, List-mode
rendering prints row.server_id and row.client_id for creature rows, producing
misleading 0 values; update the list rendering logic that formats rows (the same
place where hoverSummary is used) to check
AdvancedFinderCatalogRow::isCreature() and, when true, render "SID: -   CID: -  
" (or use hoverSummary's output) instead of printing row.server_id and
row.client_id; locate the code that formats/display rows (references:
hoverSummary, AdvancedFinderCatalogRow, row.isCreature(), row.server_id,
row.client_id) and add the conditional branch so creatures show dashes rather
than numeric IDs.

Comment on lines +314 to +330
previous_page_button_ = newd wxButton(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
previous_page_button_->SetBitmap(IMAGE_MANAGER.GetBitmapBundle(ICON_ARROW_LEFT));
action_sizer->Add(previous_page_button_, wxSizerFlags(0).CenterVertical().Border(wxRIGHT, FromDIP(6)));

next_page_button_ = newd wxButton(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
next_page_button_->SetBitmap(IMAGE_MANAGER.GetBitmapBundle(ICON_ARROW_RIGHT));
action_sizer->Add(next_page_button_, wxSizerFlags(0).CenterVertical().Border(wxRIGHT, FromDIP(12)));

export_button_ = newd wxButton(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
export_button_->SetBitmap(IMAGE_MANAGER.GetBitmapBundle(ICON_FILE_EXPORT));
export_button_->SetToolTip("Export current page");
action_sizer->Add(export_button_, wxSizerFlags(0).CenterVertical().Border(wxRIGHT, FromDIP(6)));

clear_button_ = newd wxButton(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxBU_EXACTFIT);
clear_button_->SetBitmap(IMAGE_MANAGER.GetBitmapBundle(ICON_TRASH_CAN));
clear_button_->SetToolTip("Clear results");
action_sizer->Add(clear_button_, wxSizerFlags(0).CenterVertical());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's confirm the exact code at the specified lines
cat -n source/ui/result_window.cpp | sed -n '314,330p'

Repository: karolak6612/remeres-map-editor-redux

Length of output: 1447


🏁 Script executed:

# Let's check if there's any accessibility-related setup in the file
rg -A 5 -B 5 "SetToolTip|SetAccessible|SetName" source/ui/result_window.cpp | head -50

Repository: karolak6612/remeres-map-editor-redux

Length of output: 1044


🏁 Script executed:

# Check wxButton usage patterns in the codebase to see if buttons typically get labels
rg "wxButton.*wxEmptyString" source/ -A 2 | head -30

Repository: karolak6612/remeres-map-editor-redux

Length of output: 1824


🏁 Script executed:

# Look for any accessibility setup or configuration in the project
fd -t f -e cpp -e h source/ | xargs rg -l "accessible|Accessible|ARIA|a11y" 2>/dev/null | head -20

Repository: karolak6612/remeres-map-editor-redux

Length of output: 537


🏁 Script executed:

# Search for SetName() and SetAccessible() usage on buttons
rg "SetName\(|SetAccessible\(" source/ui/ -B 2 -A 2 | head -40

Repository: karolak6612/remeres-map-editor-redux

Length of output: 1531


🏁 Script executed:

# Check if there's any accessibility documentation or standards in the project
find source -name "*.md" -o -name "README*" | xargs grep -l "access" 2>/dev/null

Repository: karolak6612/remeres-map-editor-redux

Length of output: 101


🏁 Script executed:

# Look at the position_toolbar example more closely to understand the pattern
cat -n source/ui/toolbar/position_toolbar.cpp | sed -n '1,50p' | grep -A 10 "go_button"

Repository: karolak6612/remeres-map-editor-redux

Length of output: 1085


Give bitmap-only footer buttons accessible names.

All four buttons are created with empty labels, and the pagination buttons (previous_page_button_ and next_page_button_) have no tooltip fallback, making them inaccessible to screen reader users. Add either a visible label, explicit tooltip, or accessible name (via SetName() or similar wxWidgets accessibility API) for each button to ensure they're properly exposed to assistive technology.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/result_window.cpp` around lines 314 - 330, The footer buttons
(previous_page_button_, next_page_button_, export_button_, clear_button_) are
bitmap-only and lack accessible names for screen readers; update their creation
to provide accessibility by calling SetToolTip() and/or SetName() (or
SetLabel()) with descriptive text for each button (e.g., "Previous page", "Next
page", "Export current page", "Clear results"), and ensure previous_page_button_
and next_page_button_ at minimum get tooltips or names as a fallback so
assistive technology can expose them.

Comment on lines +374 to 380
void SearchResultWindow::activateRow(int index) {
if (index < 0 || index >= static_cast<int>(rows_.size())) {
return;
}

g_gui.SetScreenCenterPosition(rows_[static_cast<size_t>(index)].position);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep result activation bound to the map that produced the page.

The paged loaders in source/ui/menubar/search_handler.cpp are already tied to the originating Map*, but activateRow() recenters the current tab via g_gui.SetScreenCenterPosition(). If the user switches tabs before clicking a row, navigation jumps on the wrong map. Store the source map/tab with the result set and reactivate that tab before centering.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@source/ui/result_window.cpp` around lines 374 - 380,
SearchResultWindow::activateRow currently recenters the current tab regardless
of the result origin; attach the originating Map*/tab identifier to the result
set/rows_ (the paged loaders in search_handler.cpp already know the Map*) and,
inside SearchResultWindow::activateRow, first switch/reactivate that stored
map/tab before calling g_gui.SetScreenCenterPosition(rows_[index].position);
update the code paths that construct rows_ to populate the new source map/tab
field and use the app's existing API to activate that map/tab prior to centering
so the recenter happens on the correct map.

@karolak6612
karolak6612 merged commit 6c05a3b into master Mar 28, 2026
37 of 38 checks passed
@karolak6612
karolak6612 deleted the Search-Tool branch April 6, 2026 15:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant