[LyX/2.5.x] Implement LFUN_REFERENCE_TO_PARAGRAPH for mathed (#13206)

Juergen Spitzmueller <[email protected]> Thu, 23 Apr 2026 16:49:43 +0000
Newsgroups gmane.editors.lyx.cvs
Message-ID <[email protected]>
commit 4287c94d0bf5aafeed1cc1927964cbbb067c144f
Author: Juergen Spitzmueller <[email protected]>
Date:   Sun Oct 12 13:47:06 2025 +0200

    Implement LFUN_REFERENCE_TO_PARAGRAPH for mathed (#13206)
    
    This allows to enter references to numbered math formulae directly
    from the outliner (or the list of existing equations in the cross
    ref dialog), whether they already have a label or not (as for text
    elements).
    
    (cherry picked from commit 1b82fc4d8eb50dac7eef743a0ac95002f5709589)
---
 lib/ui/stdcontext.inc        |  9 ++++++
 src/BufferView.cpp           | 72 ++++++++++++++++++++++++++++++--------------
 src/TocBuilder.cpp           | 28 +++++++++++++++--
 src/TocBuilder.h             |  4 ++-
 src/frontends/qt/GuiRef.cpp  |  5 ++-
 src/mathed/InsetMathHull.cpp | 19 ++++++------
 src/mathed/InsetMathHull.h   |  2 +-
 7 files changed, 101 insertions(+), 38 deletions(-)

diff --git a/lib/ui/stdcontext.inc b/lib/ui/stdcontext.inc
index ad8571d0ed..316be4031b 100644
--- a/lib/ui/stdcontext.inc
+++ b/lib/ui/stdcontext.inc
@@ -593,6 +593,15 @@ Menuset
 	End
 
 
+#
+# Toc Equation context menu
+#
+
+	Menu "context-toc-equation"
+		SubMenu "Insert Cross-Reference to this Item|C" "context-toc-ref-to-par"
+	End
+
+
 #
 # Toc Footnotes context menu
 #
diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index 7dc58b000d..2a4a6707ed 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -53,6 +53,7 @@
 #include "insets/InsetRef.h"
 #include "insets/InsetText.h"
 
+#include "mathed/InsetMathHull.h"
 #include "mathed/InsetMathNest.h"
 #include "mathed/InsetMathRef.h"
 #include "mathed/MathData.h"
@@ -1723,6 +1724,7 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 		vector<string> const pids = getVectorFromString(cmd.getArg(0));
 		string const type = cmd.getArg(1);
 		int id = convert<int>(pids.back());
+		row_type row = -1;
 		inserted_label_.clear();
 		if (id < 0)
 			break;
@@ -1735,43 +1737,69 @@ void BufferView::dispatch(FuncRequest const & cmd, DispatchResult & dr)
 				++i;
 				continue;
 			}
-			string label = dit.innerParagraph().getLabelForXRef();
+			string label;
+			if (dit.nextInset() && dit.nextInset()->asInsetMath() && dit.nextInset()->asInsetMath()->asHullInset()) {
+				row = convert<row_type>(pids.front());
+				label = to_utf8(dit.nextInset()->asInsetMath()->asHullInset()->label(row));
+			} else
+				label = dit.innerParagraph().getLabelForXRef();
 			if (!label.empty()) {
 				// if the paragraph has a label, we use this
 				if (type == "forrefdialog")
+					// only store the label for the dialog to take it
 					inserted_label_ = label;
 				else {
+					// insert a reference to the label (e.g., from outliner)
 					string const arg = (type.empty()) ? label : label + " " + type;
 					lyx::dispatch(FuncRequest(LFUN_REFERENCE_INSERT, arg));
 					cur.forceBufferUpdate();
 				}
 				break;
 			} else {
-				// if there is not a label yet
-				// go to the paragraph (including nested insets) ...
-				lyx::dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
-				for (string const & s : pids) {
-					id = convert<int>(s);
-					if (id < 0)
-						break;
-					dit = b->getParFromID(id);
-					lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_GOTO, s));
+				// if there is not a label yet, insert one
+				// this categorically differes in texted and mathed
+				docstring new_label;
+				if (dit.nextInset() && dit.nextInset()->asInsetMath() && dit.nextInset()->asInsetMath()->asHullInset()) {
+					// in mathed, we set the label via InsetMathHull::label()
+					// but we need to assure it is unique
+					new_label = from_ascii("eq:equation");
+					int j = 1;
+					while (buffer().activeLabel(new_label)) {
+						new_label = new_label + '-' + convert<docstring>(j);
+						++j;
+					}
+					// record undo for the math inset next to dit
+					cur.buffer()->undo().recordUndoInset(CursorData(dit), dit.nextInset());
+					// insert the label to the desired row of the hull inset
+					dit.nextInset()->asInsetMath()->asHullInset()->label(row, new_label, true);
+					// needed to get the new label into the buffer list
+					cur.buffer()->updateBuffer();
+				} else {
+					// in texted, go to the paragraph (including nested insets) ...
+					lyx::dispatch(FuncRequest(LFUN_BOOKMARK_SAVE, "0"));
+					for (string const & s : pids) {
+						id = convert<int>(s);
+						if (id < 0)
+							break;
+						dit = b->getParFromID(id);
+						lyx::dispatch(FuncRequest(LFUN_PARAGRAPH_GOTO, s));
+					}
+					// insert a new label
+					// we do not want to open the dialog, hence we
+					// do not employ LFUN_LABEL_INSERT
+					InsetCommandParams p(LABEL_CODE);
+					new_label = dit.getPossibleLabel();
+					p["name"] = new_label;
+					string const data = InsetCommand::params2string(p);
+					lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data));
+					// ... go back to the original position
+					lyx::dispatch(FuncRequest(LFUN_BOOKMARK_GOTO, "0"));
 				}
-				// ... if not, insert a new label
-				// we do not want to open the dialog, hence we
-				// do not employ LFUN_LABEL_INSERT
-				InsetCommandParams p(LABEL_CODE);
-				docstring const new_label = dit.getPossibleLabel();
-				p["name"] = new_label;
-				string const data = InsetCommand::params2string(p);
-				lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data));
-				// ... go back to the original position
-				lyx::dispatch(FuncRequest(LFUN_BOOKMARK_GOTO, "0"));
 				if (type == "forrefdialog")
-					// ... and save for the ref dialog to insert
+					// save for the ref dialog to insert
 					inserted_label_ = to_utf8(new_label);
 				else {
-					// ... or insert the ref directly (from outliner)
+					// or insert the ref directly (from outliner)
 					string const arg = (type.empty()) ? to_utf8(new_label)
 									  : to_utf8(new_label) + " " + type;
 					lyx::dispatch(FuncRequest(LFUN_REFERENCE_INSERT, arg));
diff --git a/src/TocBuilder.cpp b/src/TocBuilder.cpp
index fb688bd58e..458a95db66 100644
--- a/src/TocBuilder.cpp
+++ b/src/TocBuilder.cpp
@@ -15,6 +15,7 @@
 #include "DocIterator.h"
 #include "TocBackend.h"
 
+#include "support/convert.h"
 #include "support/lassert.h"
 
 using namespace std;
@@ -32,9 +33,16 @@ TocBuilder::TocBuilder(shared_ptr<Toc> const & toc)
 }
 
 void TocBuilder::pushItem(DocIterator const & dit, docstring const & s,
-                          bool output_active, bool is_captioned)
+                          bool output_active, bool is_captioned, int row)
 {
-	toc_->push_back(TocItem(dit, stack_.size(), s, output_active));
+	TocItem item(dit, stack_.size(), s, output_active);
+	if (row != -1) {
+		docstring parID = item.parIDs();
+		if (!stack_.empty())
+			parID = (*toc_)[stack_.top().pos].dit().paragraphGotoArgument(true);
+		item.setParIDs(convert<docstring>(row) + ',' + parID);
+	}
+	toc_->push_back(item);
 	frame f = {
 		toc_->size() - 1, //pos
 		is_captioned, //is_captioned
@@ -87,6 +95,22 @@ void TocBuilder::argumentItem(docstring const & arg_str)
 	stack_.top().is_captioned = true;
 }
 
+void TocBuilder::mathMultilineItem(docstring const & arg_str, Toc::size_type row)
+{
+	if (stack_.empty() || arg_str.empty())
+		return;
+	TocItem & item = (*toc_)[stack_.top().pos];
+	docstring const & str = item.str();
+	string const & delim =
+		(str.empty() || !stack_.top().is_captioned) ? "" :  ", ";
+	item.str(str + from_ascii(delim) + arg_str);
+	docstring parID = item.parIDs();
+	if (!stack_.empty())
+		parID = (*toc_)[stack_.top().pos].dit().paragraphGotoArgument(true);
+	item.setParIDs(convert<docstring>(row) + ',' + parID);
+	stack_.top().is_captioned = true;
+}
+
 void TocBuilder::pop()
 {
 	if (!stack_.empty())
diff --git a/src/TocBuilder.h b/src/TocBuilder.h
index af9c41cb24..23d5fe8329 100644
--- a/src/TocBuilder.h
+++ b/src/TocBuilder.h
@@ -31,7 +31,7 @@ public:
 	/// Open a level.
 	/// When entering a float or flex or paragraph (with AddToToc)
 	void pushItem(DocIterator const & dit, docstring const & s,
-	              bool output_active, bool is_captioned = false);
+	              bool output_active, bool is_captioned = false, int row = -1);
 	/// Edit entry at current level. Add new entry if already captioned.
 	/// When encountering a float caption
 	void captionItem(DocIterator const & dit, docstring const & s,
@@ -39,6 +39,8 @@ public:
 	/// Edit entry at current level (always).
 	/// When encountering an argument (with isTocCaption) for flex or paragraph
 	void argumentItem(docstring const & arg_str);
+	///
+	void mathMultilineItem(docstring const & arg_str, Toc::size_type row);
 	/// Close a level.
 	/// When exiting a float or flex or paragraph
 	void pop();
diff --git a/src/frontends/qt/GuiRef.cpp b/src/frontends/qt/GuiRef.cpp
index 3cf94b7ae2..86136e1cb9 100644
--- a/src/frontends/qt/GuiRef.cpp
+++ b/src/frontends/qt/GuiRef.cpp
@@ -566,9 +566,8 @@ void GuiRef::updateTargets()
 				targetCO->addItem(toqstr(translateIfPossible(name.second)), toqstr(name.first));
 		}
 	}
-// FIXME This does not work yet with REFERENCE_TO_PARAGRAPH
-//	if (isTargetAvailable("equation"))
-//		targetCO->addItem(qt_("Equations"), "equation");
+	if (isTargetAvailable("equation"))
+		targetCO->addItem(qt_("Equations"), "equation");
 	// restore previous setting
 	int const i = targetCO->findData(target);
 	if (i != -1)
diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp
index 33656e12f7..b7c5749f53 100644
--- a/src/mathed/InsetMathHull.cpp
+++ b/src/mathed/InsetMathHull.cpp
@@ -354,8 +354,8 @@ void InsetMathHull::addToToc(DocIterator const & pit, bool output_active,
 	TocBuilder & b = backend.builder("equation");
 	b.pushItem(pit, docstring(), output_active);
 	if (first != last)
-		b.argumentItem(bformat(from_ascii("(%1$s-%2$s)"),
-		                       numbers_[first], numbers_[last]));
+		b.mathMultilineItem(bformat(from_ascii("(%1$s-%2$s)"),
+		                       numbers_[first], numbers_[last]), 0);
 
 	odocstringstream ods;
 	Encoding const * enc = encodings.fromLyXName("utf8");
@@ -373,7 +373,7 @@ void InsetMathHull::addToToc(DocIterator const & pit, bool output_active,
 		if (first == last) {
 			// this is the only equation
 			plaintext(ods, ops, max_length);
-			b.argumentItem(label + " " + ods.str());
+			b.mathMultilineItem(label + " " + ods.str(), row);
 		} else {
 			// insert as sub-items
 			otexrowstream ots(ods);
@@ -387,7 +387,7 @@ void InsetMathHull::addToToc(DocIterator const & pit, bool output_active,
 					break;
 				}
 			}
-			b.pushItem(pit, label+ " " + d, output_active);
+			b.pushItem(pit, label+ " " + d, output_active, false, row);
 			// clear the stringstream
 			odocstringstream().swap(ods);
 			b.pop();
@@ -1002,7 +1002,7 @@ docstring InsetMathHull::label(row_type row) const
 }
 
 
-void InsetMathHull::label(row_type row, docstring const & label)
+void InsetMathHull::label(row_type row, docstring const & label, bool const init)
 {
 	//lyxerr << "setting label '" << label << "' for row " << row << endl;
 	if (labels_[row]) {
@@ -1022,6 +1022,9 @@ void InsetMathHull::label(row_type row, docstring const & label)
 	labels_[row] = new InsetLabel(buffer_, p);
 	if (buffer_)
 		labels_[row]->setBuffer(buffer());
+	if (init)
+		// Newly created inset so initialize it.
+		labels_[row]->initView();
 }
 
 
@@ -2087,11 +2090,9 @@ void InsetMathHull::doDispatch(Cursor & cur, FuncRequest & cmd)
 				if (labels_[r])
 					// The label will take care of the reference update.
 					label(r, str);
-				else {
-					label(r, str);
+				else
 					// Newly created inset so initialize it.
-					labels_[r]->initView();
-				}
+					label(r, str, true);
 			}
 			cur.forceBufferUpdate();
 			break;
diff --git a/src/mathed/InsetMathHull.h b/src/mathed/InsetMathHull.h
index e25e516e0c..b5cfe92f03 100644
--- a/src/mathed/InsetMathHull.h
+++ b/src/mathed/InsetMathHull.h
@@ -71,7 +71,7 @@ public:
 	///
 	docstring label(row_type row) const;
 	///
-	void label(row_type row, docstring const & label);
+	void label(row_type row, docstring const & label, bool const init = false);
 	///
 	std::vector<InsetLabel *> const & getLabels() { return labels_; }
 	///
-- 
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs