From 4a58887cb1fea83db221f28885ea4599ffc6eaed Mon Sep 17 00:00:00 2001 From: Martino Ferrari Date: Thu, 8 Oct 2026 10:25:27 +0200 Subject: [PATCH] fix: the search label no longer claims an off-screen selected hit is in view place_search_marks() places the selected hit unconditionally to keep it marked when stepping to it, but the label must distinguish this case from hits that are actually visible on screen, so it does not lie about what is in view. add sel_off_view to track when the selected hit is off screen, introduce a shown count that excludes it, and emit a third label case when only the selected hit is off screen. --- src/ui.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/ui.cpp b/src/ui.cpp index 1c7e8ed..8d9a19f 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -3358,6 +3358,7 @@ void place_search_marks(App& app) { std::vector xs; int current = -1; std::size_t in_view = 0; + bool sel_off_view = false; const std::vector& hits = app.search.hits(); if (!hits.empty()) { const double s0 = app.model->stream_at_x0(); @@ -3377,22 +3378,33 @@ void place_search_marks(App& app) { std::sort(cand.begin(), cand.end()); const std::size_t cap = static_cast(RowSet::kSearchMarks); + // xs.reserve(cap) is only a hint; the actual cap is enforced by xs.size() < cap in the fill loop. xs.reserve(cap); if (sel >= 0 && sel < static_cast(hits.size())) { current = 0; - xs.push_back(hits[static_cast(sel)] - s0); + const double x = hits[static_cast(sel)] - s0; + // The selected hit is placed whether or not it is on screen, so the label must not fold it + // into the in-view count: it is the one mark `in_view` above deliberately did not see. + sel_off_view = x < view.min || x > view.max; + xs.push_back(x); } for (std::size_t i = 0; i < cand.size() && xs.size() < cap; ++i) xs.push_back(cand[i].second); } if (app.search_marks_label != nullptr) { + // The marks that are *in view*: `xs` also carries the selected hit when it is off screen. + const std::size_t shown = xs.size() - (sel_off_view ? 1u : 0u); char text[128]; if (hits.empty()) { std::snprintf(text, sizeof(text), " "); - } else if (in_view > xs.size()) { + } else if (in_view > shown) { // A truncated display says so: "16 of 38" with no qualifier reads as "there are 38 and you // are seeing 16 of them *everywhere*", which is not the claim. std::snprintf(text, sizeof(text), "%zu of %zu marks shown \xE2\x80\x94 zoom in to see the rest", - xs.size(), in_view); + shown, in_view); + } else if (sel_off_view) { + std::snprintf(text, sizeof(text), + "%zu of %zu in view \xE2\x80\x94 the selected hit is outside the window", shown, + hits.size()); } else { std::snprintf(text, sizeof(text), "%zu of %zu in view", xs.size(), hits.size()); }