commit c60ec0a3009955b049d9303855db59e6cb0a2327
Author: Juergen Spitzmueller <[email protected]>
Date:   Sat Mar 28 12:44:10 2026 +0100

    Improve simple search within selection
    
    When searching/replacing within a selection, remember the selection range
    beyond the first match.
    
    To this end, we record whether a selection has been made in result of
    a search match and differentiate this kind of selection from others
    (those within which we ought to search).
    
    In result, LyX (simple) search/replace within selection does no longer
    work in the restricted way (like LibreOffice) to only provide one hit
    within a selection, it keeps on searching for more hits until the
    selection end is reached and then asks whether it should continue
    searching outside (like M$ Word).
    It also does not moan about selection end being reached when it made the
    selection itself (to highlight a match).
    
    (cherry picked from commit 643e2bb0f9207f52ca6bee6257ffd0221a089c73)
---
 src/BufferView.cpp |  5 +++-
 src/BufferView.h   |  3 +-
 src/Cursor.cpp     |  9 ++++--
 src/Cursor.h       |  6 ++++
 src/lyxfind.cpp    | 80 +++++++++++++++++++++++++++++++++++-------------------
 src/lyxfind.h      |  2 +-
 status.25x         |  3 ++
 7 files changed, 74 insertions(+), 34 deletions(-)

diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index f6c672fe02..48bcea3fc3 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -3235,7 +3235,8 @@ bool BufferView::mouseSetCursor(Cursor & cur, bool const 
select)
 
 
 void BufferView::putSelectionAt(DocIterator const & cur,
-                               int length, bool backwards)
+                               int length, bool backwards,
+                               bool const searchhit)
 {
        d->cursor_.clearSelection();
 
@@ -3248,6 +3249,8 @@ void BufferView::putSelectionAt(DocIterator const & cur,
                } else
                        d->cursor_.setSelection(d->cursor_, length);
        }
+       if (searchhit)
+               d->cursor_.setSearchMatchSelection(true);
 }
 
 
diff --git a/src/BufferView.h b/src/BufferView.h
index eca7f84ccc..50f783f37e 100644
--- a/src/BufferView.h
+++ b/src/BufferView.h
@@ -306,7 +306,8 @@ public:
         * cur + \c length.
         */
        void putSelectionAt(DocIterator const & cur,
-               int length, bool backwards);
+               int length, bool backwards,
+               bool const searchhit = false);
        /// set a selection between \p from and \p to
        void setSelection(DocIterator const & from,
                         DocIterator const & to);
diff --git a/src/Cursor.cpp b/src/Cursor.cpp
index cc06268483..52c8bf2821 100644
--- a/src/Cursor.cpp
+++ b/src/Cursor.cpp
@@ -123,19 +123,19 @@ DocIterator bruteFind(Cursor const & c, int x, int y)
 
 CursorData::CursorData()
        : DocIterator(), anchor_(), selection_(false), mark_(false),
-         word_selection_(false), current_font(inherit_font)
+         word_selection_(false), search_match_selection_(false), 
current_font(inherit_font)
 {}
 
 
 CursorData::CursorData(Buffer * buffer)
        : DocIterator(buffer), anchor_(), selection_(false), mark_(false),
-         word_selection_(false), current_font(inherit_font)
+         word_selection_(false), search_match_selection_(false), 
current_font(inherit_font)
 {}
 
 
 CursorData::CursorData(DocIterator const & dit)
        : DocIterator(dit), anchor_(), selection_(false), mark_(false),
-         word_selection_(false), current_font(inherit_font)
+         word_selection_(false), search_match_selection_(false), 
current_font(inherit_font)
 {}
 
 
@@ -251,6 +251,7 @@ CursorSlice CursorData::normalAnchor() const
 void CursorData::setSelection()
 {
        selection(true);
+       setSearchMatchSelection(false);
        if (idx() == normalAnchor().idx() &&
            pit() == normalAnchor().pit() &&
            pos() == normalAnchor().pos())
@@ -262,6 +263,7 @@ void CursorData::setSelection(DocIterator const & where, 
int n)
 {
        setCursor(where);
        selection(true);
+       setSearchMatchSelection(false);
        anchor_ = where;
        pos() += n;
 }
@@ -500,6 +502,7 @@ void CursorData::clearSelection()
 {
        selection(false);
        setWordSelection(false);
+       setSearchMatchSelection(false);
        setMark(false);
        resetAnchor();
 }
diff --git a/src/Cursor.h b/src/Cursor.h
index c2fcf63135..879dce1033 100644
--- a/src/Cursor.h
+++ b/src/Cursor.h
@@ -123,6 +123,10 @@ public:
        bool mark() const { return mark_; }
        /// did we place the anchor?
        void setMark(bool mark) { mark_ = mark; }
+       /// Set search match selection mode
+       void setSearchMatchSelection(bool set) { search_match_selection_ = set; 
}
+       /// Are we in search match selection mode?
+       bool searchMatchSelection() const { return search_match_selection_; }
        ///
        void setSelection();
        /// set selection at given position
@@ -229,6 +233,8 @@ private:
        bool mark_;
        /// are we in word-selection mode? This is set when double clicking.
        bool word_selection_;
+       /// are we in search match selection mode?
+       bool search_match_selection_;
 
        /// the start of the new born word
        DocIterator new_word_;
diff --git a/src/lyxfind.cpp b/src/lyxfind.cpp
index 46180ceea4..62a8fa15fe 100644
--- a/src/lyxfind.cpp
+++ b/src/lyxfind.cpp
@@ -239,8 +239,9 @@ int findForward(DocIterator & cur, DocIterator const & 
endcur,
                bool find_del = true, bool onlysel = false)
 {
        for (; cur; cur.forwardChar()) {
-               if (onlysel && cur.inTexted() && endcur.pit() == cur.pit()
-                   && endcur.idx() == cur.idx() && endcur.pos() < cur.pos())
+               if (onlysel && cur.inTexted()
+                   && (endcur.pit() < cur.pit() || (endcur.pit() == cur.pit()
+                   && endcur.idx() == cur.idx() && endcur.pos() < cur.pos())))
                        break;
                if (cur.inTexted()) {
                        int len = match(cur.paragraph(), cur.pos(), find_del);
@@ -258,8 +259,9 @@ int findBackwards(DocIterator & cur, DocIterator const & 
endcur,
 {
        while (cur) {
                cur.backwardChar();
-               if (onlysel && cur.inTexted() && endcur.pit() == cur.pit()
-                   && endcur.idx() == cur.idx() && endcur.pos() > cur.pos())
+               if (onlysel && cur.inTexted()
+                   && (endcur.pit() > cur.pit() || (endcur.pit() == cur.pit()
+                   && endcur.idx() == cur.idx() && endcur.pos() > cur.pos())))
                        break;
                if (cur.inTexted()) {
                        int len = match(cur.paragraph(), cur.pos(), find_del);
@@ -283,10 +285,13 @@ bool searchAllowed(docstring const & str)
 } // namespace
 
 
+DocIterator selection_search_start;
+DocIterator selection_search_end;
+
 bool findOne(BufferView * bv, docstring const & searchstr,
             bool case_sens, bool whole, bool forward,
             bool find_del, bool check_wrap, bool const auto_wrap,
-            bool instant, bool onlysel)
+            bool instant, bool onlysel, bool fromrep)
 {
        bool const had_selection = bv->cursor().selection();
 
@@ -302,10 +307,14 @@ bool findOne(BufferView * bv, docstring const & searchstr,
        if (!searchAllowed(searchstr))
                return false;
 
-       DocIterator const startcur = bv->cursor().selectionBegin();
-       DocIterator const endcur = bv->cursor().selectionEnd();
+       if (!fromrep && (!had_selection || 
!bv->cursor().searchMatchSelection())) {
+               selection_search_start = bv->cursor().selectionBegin();
+               selection_search_end = bv->cursor().selectionEnd();
+       }
+
+       bool const in_new_selection = onlysel && had_selection && 
!bv->cursor().searchMatchSelection();
 
-       if (onlysel && had_selection) {
+       if (in_new_selection) {
                docstring const matchstring = 
bv->cursor().selectionAsString(false);
                docstring const lcmatchsting = support::lowercase(matchstring);
                if (matchstring == searchstr || (!case_sens && lcmatchsting == 
lowercase(searchstr))) {
@@ -324,28 +333,35 @@ bool findOne(BufferView * bv, docstring const & searchstr,
        }
 
        DocIterator cur = forward
-               ? ((instant || onlysel) ? bv->cursor().selectionBegin() : 
bv->cursor().selectionEnd())
-               : ((instant || onlysel) ? bv->cursor().selectionEnd() : 
bv->cursor().selectionBegin());
+               ? ((instant || in_new_selection) ? 
bv->cursor().selectionBegin() : bv->cursor().selectionEnd())
+               : ((instant || in_new_selection) ? bv->cursor().selectionEnd() 
: bv->cursor().selectionBegin());
 
        MatchString const match(searchstr, case_sens, whole);
 
+       // Set only_selection to false if we search outside a set selection 
range
+       onlysel &= selection_search_start != selection_search_end;
+
        int match_len = forward
-               ? findForward(cur, endcur, match, find_del, onlysel)
-               : findBackwards(cur, startcur, match, find_del, onlysel);
+               ? findForward(cur, selection_search_end, match, find_del, 
onlysel)
+               : findBackwards(cur, selection_search_start, match, find_del, 
onlysel);
 
        if (match_len > 0)
-               bv->putSelectionAt(cur, match_len, !forward);
-       else if (onlysel && had_selection) {
+               bv->putSelectionAt(cur, match_len, !forward, true);
+       else if (onlysel) {
                docstring q = _("The search string was not found within the 
selection.\n"
                                "Continue search outside?");
                int search_answer = frontend::Alert::prompt(_("Search outside 
selection?"),
                        q, 0, 1, _("&Yes"), _("&No"));
                if (search_answer == 0) {
                        bv->clearSelection();
+                       selection_search_start = bv->cursor().selectionBegin();
+                       selection_search_end = bv->cursor().selectionEnd();
                        if (findOne(bv, searchstr, case_sens, whole, forward,
                                    find_del, check_wrap, auto_wrap, false, 
false))
                                return true;
-               }
+               } else
+                       // restore original selection
+                       bv->setSelection(selection_search_start, 
selection_search_end);
                return false;
        }
        else if (check_wrap) {
@@ -381,12 +397,12 @@ bool findOne(BufferView * bv, docstring const & searchstr,
                                    find_del, false, false, false, false))
                                return true;
                }
-               bv->setCursor(startcur);
+               bv->setCursor(selection_search_start);
 
                // restore original selection
                if (had_selection) {
                        bv->cursor().resetAnchor();
-                       bv->setSelection(startcur, endcur);
+                       bv->setSelection(selection_search_start, 
selection_search_end);
                }
                return false;
        }
@@ -470,18 +486,18 @@ int replaceAll(BufferView * bv,
 }
 
 
-// the idea here is that we are going to replace the string that
+// The idea here is that we are going to replace the string that
 // is selected IF it is the search string.
-// if there is a selection, but it is not the search string, then
-// we basically ignore it. (FIXME We ought to replace only within
-// the selection.)
-// if there is no selection, then:
-//  (i) if some search string has been provided, then we find it.
+// If there is a selection, but it is not the search string, then
+// we search for a hit in this selection if "only selection" is set,
+// otherwise we search after the selection.
+// If there is no selection, then:
+//  (i) If some search string has been provided, then we find it.
 //      (think of how the dialog works when you hit "replace" the
 //      first time.)
-// (ii) if no search string has been provided, then we treat the
-//      word the cursor is in as the search string. (why? i have no
-//      idea.) but this only works in text?
+// (ii) If no search string has been provided, then we treat the
+//      word the cursor is in as the search string. ("Why? I have no
+//      idea.) But this only works in text?
 //
 // returns the number of replacements made (one, if any) and
 // whether anything at all was done.
@@ -491,7 +507,8 @@ pair<bool, int> replaceOne(BufferView * bv, docstring 
searchstr,
                           bool onlysel)
 {
        Cursor & cur = bv->cursor();
-       if (!cur.selection() || onlysel) {
+       bool const in_new_selection = onlysel && 
!bv->cursor().searchMatchSelection();
+       if (!cur.selection() || in_new_selection) {
                // no selection, non-empty search string: find it
                if (!searchstr.empty()) {
                        bool const found = findOne(bv, searchstr, case_sens, 
whole,
@@ -535,6 +552,13 @@ pair<bool, int> replaceOne(BufferView * bv, docstring 
searchstr,
                return make_pair(false, 0);
 
        cap::replaceSelectionWithString(cur, replacestr);
+       if (onlysel && selection_search_start != selection_search_end
+           && selection_search_end.pit() == cur.pit()
+           && selection_search_end.idx() == cur.idx()) {
+               selection_search_end.pos() += replacestr.length() - 
searchstr.length();
+               LASSERT(selection_search_end.pos() >= 0 && 
selection_search_end.pos() <= cur.lastpos(),
+                       selection_search_end.pos() = 0);
+       }
        if (forward) {
                cur.pos() += replacestr.length();
                LASSERT(cur.pos() <= cur.lastpos(),
@@ -542,7 +566,7 @@ pair<bool, int> replaceOne(BufferView * bv, docstring 
searchstr,
        }
        if (findnext)
                findOne(bv, searchstr, case_sens, whole,
-                       forward, false, findnext, wrap, false, onlysel);
+                       forward, false, findnext, wrap, false, onlysel, true);
 
        return make_pair(true, 1);
 }
diff --git a/src/lyxfind.h b/src/lyxfind.h
index 84d2400e2b..bb0c354ace 100644
--- a/src/lyxfind.h
+++ b/src/lyxfind.h
@@ -73,7 +73,7 @@ bool findOne(BufferView * bv, docstring const & searchstr,
             bool case_sens, bool whole, bool forward,
             bool find_del = true, bool check_wrap = false,
             bool const auto_wrap = false, bool instant = false,
-            bool onlysel = false);
+            bool onlysel = false, bool fromrep = false);
 
 /** Parse the string encoding of the replace request that is found in
  *  \c ev.argument and act on it.
diff --git a/status.25x b/status.25x
index 708f54886a..5d8ad627ae 100644
--- a/status.25x
+++ b/status.25x
@@ -35,6 +35,9 @@ What's new
 - It is now possible in the tabular creation dialog (Inset > Table) to
   insert a multi-page table (optionally with caption) directly.
 
+- Simple search within selection now remembers the initial selection
+  beyond the first match (bug 13293).
+
 
 * DOCUMENTATION AND LOCALIZATION
 
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs

Reply via email to