[LyX/master] Improve simple search within selection

Juergen Spitzmueller <[email protected]>
Newsgroups gmane.editors.lyx.cvs
Message-ID <[email protected]>
commit 643e2bb0f9207f52ca6bee6257ffd0221a089c73
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).
---
 src/BufferView.cpp |  5 +++-
 src/BufferView.h   |  3 +-
 src/Cursor.cpp     |  9 ++++--
 src/Cursor.h       |  6 ++++
 src/lyxfind.cpp    | 80 +++++++++++++++++++++++++++++++++++-------------------
 src/lyxfind.h      |  2 +-
 6 files changed, 71 insertions(+), 34 deletions(-)

diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index 0dda44b131..d3a457ffd3 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -3236,7 +3236,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();
 
@@ -3249,6 +3250,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 ecd66e2ef0..0a5aa0a735 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.
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.