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()); }