Dear list,

This is a series of patches that bring various improvements to the
cursor<->row correspondence tracking (TexRow). It is now extended to
arbitrary nestings of math, and to tables and subcaptions.

Jean-Marc, no improvement to error reporting and reverse search yet,
sorry :) I had to postpone that, but it would be very simple now to
extend this patch with e.g. accurate error reporting inside arbitrary
nestings of math (as accurate as Latex's line number reporting actually
would let us be).

I submit it on the list to get your agreement on several fundamental
changes which I kept, as you will see, as little intrusive as possible.

The sensitive changes (i.e. where I have been careful to make the
simplest changes so as to not affect latex output) are indicated with *.

#1: Add math cell positions to TexRow. It mainly consists in new code
that generalises the old one, and it is not output-related. (But this is
where I had the most fun.)

*#2: Add a unique id to math insets. I also print this information in
the DEVEL_VERSION in the status bar similarly to what is done currently
for paragraphs. It is interesting that while the paragraph id is usually
below 10000, this new math inset id immediately goes into the millions
on opening an actual LyX document and it increases very quickly (which I
suspect is due to updateMacros()).

*#3: Splitting otexstream into otexrowstream and otexstream.
otexrowstream focuses on the line counting feature.

#4: Adding TexRow information on math latex output. WriteStream now uses
the new otexrowstream instead of odocstream. (This should not be
sensitive because the dangerous work has been done in #3.) This finishes
implementing row tracking for math output.

#5: TexRow info in source panel and gotoCursor() for debugging. These
are debugging features that are currently activated with Debug::LATEX in
DEVEL_VERSION and appear in the source panel. Please, tell me if there
are more appropriate conditions to enable these features. They will
continue to be useful for debugging anything TexRow-related.

*#6: Remove a deep copy of MathData in lyx::write that interfered with
TexRow. If anybody can explain to me the purpose of this antediluvian
call to extractStrings, I am all ears. In the meanwhile, I wrote
equivalent code in case it was actually useful. I noticed a major
decrease of the math inset ids after this patch; hopefully this id is a
new metrics that will allow us to observe further improvements to the
implementation of maths.

*#8: Add cursor<->row correspondance tracking for tables and
subcaptions. Here, the less intrusive solution was to implement the
concatenation of TexRow objects so that we can write to an
odocstringstream buffer and still keep the correct tracking information.
If you know of anything else that relies on writing to an
odocstringstream buffer before actual output please let me know so I can
fix it.

There are also probably many corner cases that would be trivial to fix
now, please report any problems you still encounter.


Sincerely
Guillaume
>From 492bad9f3f3ebc6d4b3064465e82a45b015eca4b Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Wed, 14 Oct 2015 00:17:05 +0100
Subject: [PATCH 1/8] Add math cell positions to TexRow

This is preliminary work for extending the cursor<->row tracking to math.

TexRow used to associate, to each row, a location id/pos where id determines a
paragraph and pos the position in the paragraph.

TexRow now associates to each row a list of entries, text or math. A math is a
pair uid/idx where uid will determine a math inset and idx is the number of the
cell.

The analogy id/pos<->inset/idx works better than the analogy id/pos<->idx/pos,
because what matters for the TexRow algorithm(TM) is the behaviour in terms of
line breaks.

This only improves the source view and the forward search, not the error report
and the reverse search (though this could be easily added now).
---
 src/Paragraph.cpp |   2 +
 src/TexRow.cpp    | 405 +++++++++++++++++++++++++++++++++++++++++++++++-------
 src/TexRow.h      | 127 ++++++++++++-----
 3 files changed, 455 insertions(+), 79 deletions(-)

diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp
index 8ec57a5..5cea164 100644
--- a/src/Paragraph.cpp
+++ b/src/Paragraph.cpp
@@ -510,6 +510,8 @@ Paragraph::Private::Private(Paragraph * owner, Layout const & layout)
 // FIXME: There should be a more intelligent way to generate and use the
 // paragraph ids per buffer instead a global static counter for all InsetText
 // in the running program.
+// However, this per-session id is used in LFUN_PARAGRAPH_GOTO to
+// switch to a different buffer, as used in the outliner for instance.
 static int paragraph_id = -1;
 
 Paragraph::Private::Private(Private const & p, Paragraph * owner)
diff --git a/src/TexRow.cpp b/src/TexRow.cpp
index 173634b..0ffdc29 100644
--- a/src/TexRow.cpp
+++ b/src/TexRow.cpp
@@ -6,6 +6,7 @@
  * \author Matthias Ettrich
  * \author Lars Gullik Bjønnes
  * \author John Levon
+ * \author Guillaume Munch
  *
  * Full author contact details are available in file CREDITS.
  */
@@ -16,7 +17,10 @@
 #include "Paragraph.h"
 #include "TexRow.h"
 
+#include "mathed/InsetMath.h"
+
 #include "support/debug.h"
+#include "support/docstring_list.h"
 
 #include <algorithm>
 
@@ -24,22 +28,110 @@
 namespace lyx {
 
 
+
+bool TexRow::RowEntryList::addEntry(RowEntry const & entry)
+{
+	if (!entry.is_math) {
+		if (text_entry_ < size())
+			return false;
+		else
+			text_entry_ = size();
+	}
+	if (size() == 0 || !(operator[](size() - 1) == entry))
+		push_back(RowEntry(entry));
+	return true;
+}
+
+
+TexRow::TextEntry TexRow::RowEntryList::getTextEntry() const
+{
+	if (text_entry_ < size())
+		return operator[](text_entry_).text;
+	return TexRow::text_none;
+}
+
+
+TexRow::RowEntry TexRow::RowEntryList::entry() const
+{
+	if (0 < size())
+		return operator[](0);
+	return TexRow::row_none;
+}
+
+
+TexRow::TextEntry const TexRow::text_none = { -1, 0 };
+TexRow::RowEntry const TexRow::row_none = { false, TexRow::text_none };
+
+
+bool TexRow::isNone(TextEntry const & t)
+{
+	return t.id < 0;
+}
+
+
+bool TexRow::isNone(RowEntry const & r)
+{
+	return !r.is_math && isNone(r.text);
+}
+
+
 void TexRow::reset(bool enable)
 {
-	rowlist.clear();
-	lastid = -1;
-	lastpos = -1;
+	rowlist_.clear();
+	current_row_ = RowEntryList();
 	enabled_ = enable;
 }
 
 
-void TexRow::start(int id, int pos)
+TexRow::RowEntry TexRow::textEntry(int id, int pos)
 {
-	if (!enabled_ || started)
-		return;
-	lastid = id;
-	lastpos = pos;
-	started = true;
+	RowEntry entry;
+	entry.is_math = false;
+	entry.text.pos = pos;
+	entry.text.id = id;
+	return entry;
+}
+
+
+TexRow::RowEntry TexRow::mathEntry(uid_type id, idx_type cell)
+{
+	RowEntry entry;
+	entry.is_math = true;
+	entry.math.cell = cell;
+	entry.math.id = id;
+	return entry;
+}
+
+
+bool operator==(TexRow::RowEntry const & entry1,
+				TexRow::RowEntry const & entry2)
+{
+	return entry1.is_math == entry2.is_math
+		&& (entry1.is_math
+			? (entry1.math.id == entry2.math.id
+			   && entry1.math.cell == entry2.math.cell)
+			: (entry1.text.id == entry2.text.id
+			   && entry1.text.pos == entry2.text.pos));
+}
+
+
+bool TexRow::start(RowEntry entry)
+{
+	if (!enabled_)
+		return false;
+	return current_row_.addEntry(entry);
+}
+
+
+bool TexRow::start(int id, int pos)
+{
+	return start(textEntry(id,pos));
+}
+
+
+bool TexRow::startMath(uid_type id, idx_type cell)
+{
+	return start(mathEntry(id,cell));
 }
 
 
@@ -47,9 +139,8 @@ void TexRow::newline()
 {
 	if (!enabled_)
 		return;
-	RowList::value_type tmp(lastid, lastpos);
-	rowlist.push_back(tmp);
-	started = false;
+	rowlist_.push_back(current_row_);
+	current_row_ = RowEntryList();
 }
 
 void TexRow::newlines(int num_lines)
@@ -68,73 +159,295 @@ void TexRow::finalize()
 	newline();
 }
 
+
 bool TexRow::getIdFromRow(int row, int & id, int & pos) const
 {
-	if (row <= 0 || row > int(rowlist.size())) {
+	while (row > 0
+		   && (row > int(rowlist_.size())
+			   || isNone(rowlist_[row - 1].getTextEntry()))) {
+		--row;
+	}
+	if (row <= 0) {
 		id = -1;
 		pos = 0;
 		return false;
 	}
-
-	id = rowlist[row - 1].id();
-	pos = rowlist[row - 1].pos();
+	TextEntry t = rowlist_[row - 1].getTextEntry();
+	id = t.id;
+	pos = t.pos;
 	return true;
 }
 
 
+TexRow::RowEntry TexRow::rowEntryFromCursorSlice(CursorSlice const & slice)
+{
+	RowEntry entry;
+	InsetMath * insetMath = slice.asInsetMath();
+	if (insetMath) {
+		entry.is_math = 1;
+		entry.math.id = insetMath->id();
+		entry.math.cell = slice.idx();
+	} else if (slice.text()) {
+		entry.is_math = 0;
+		entry.text.id = slice.paragraph().id();
+		entry.text.pos = slice.pos();
+	} else {
+		// should not happen
+		entry = row_none;
+	}
+	return entry;
+}
+
+
+bool TexRow::sameParOrInsetMath(RowEntry const & entry1,
+								RowEntry const & entry2)
+{
+	return entry1.is_math == entry2.is_math
+		&& (entry1.is_math
+			? (entry1.math.id == entry2.math.id)
+			: (entry1.text.id == entry2.text.id));
+}
+
+
+// assumes it is sameParOrInsetMath
+int TexRow::comparePos(RowEntry const & entry1,
+					   RowEntry const & entry2)
+{
+	if (entry1.is_math)
+		return entry2.math.cell - entry1.math.cell;
+	else
+		return entry2.text.pos - entry1.text.pos;
+}
+
+
+// An iterator on RowList that goes top-down, left-right
+//
+// We assume that the end of RowList does not change, which makes things simpler
+//
+// Records a pair of iterators on the RowEntryList (row_it_, row_end_) and a
+// pair of iterators on the current row (it_, it_end_).
+//
+// it_ always points to a valid position unless row_it_ == row_end_.
+//
+// We could turn this into a proper bidirectional iterator, but we don't need as
+// much.
+//
+class TexRow::RowListIterator
+{
+public:
+	RowListIterator(RowList::const_iterator r,
+					RowList::const_iterator r_end)
+		: row_it_(r), row_end_(r_end),
+		  it_(r == r_end ? RowEntryList::const_iterator() : r->begin()),
+		  it_end_(r == r_end ? RowEntryList::const_iterator() : r->end())
+	{
+		normalize();
+	}
+
+
+	RowListIterator() :
+		row_it_(RowList::const_iterator()),
+		row_end_(RowList::const_iterator()),
+		it_(RowEntryList::const_iterator()),
+		it_end_(RowEntryList::const_iterator()) { }
+
+
+	RowEntry const & operator*()
+	{
+		return *it_;
+	}
+
+
+	RowListIterator & operator++()
+	{
+		++it_;
+		normalize();
+		return *this;
+	}
+
+
+	bool atEnd() const
+	{
+		return row_it_ == row_end_;
+	}
+	
+	
+	bool operator==(RowListIterator const & a) const
+	{
+		return row_it_ == a.row_it_ && ((atEnd() && a.atEnd()) || it_ == a.it_);
+	}
+
+
+	bool operator!=(RowListIterator const & a) const { return !operator==(a); }
+
+
+	// Current row.
+	RowList::const_iterator const & row() const
+	{
+		return row_it_;
+	}
+private:
+	// ensures that it_ points to a valid value unless row_it_ == row_end_
+	void normalize()
+	{
+		if (row_it_ == row_end_)
+			return;
+		while (it_ == it_end_) {
+			++row_it_;
+			if (row_it_ != row_end_) {
+				it_ = row_it_->begin();
+				it_end_ = row_it_->end();
+			} else
+				return;
+		}
+	}
+	//
+	RowList::const_iterator row_it_;
+	//
+	RowList::const_iterator row_end_;
+	//
+	RowEntryList::const_iterator it_;
+	//
+	RowEntryList::const_iterator it_end_;
+};
+
+
+TexRow::RowListIterator TexRow::begin() const
+{
+	return RowListIterator(rowlist_.begin(), rowlist_.end());
+}
+
+
+TexRow::RowListIterator TexRow::end() const
+{
+	return RowListIterator(rowlist_.end(), rowlist_.end());
+}
+
+
 std::pair<int,int> TexRow::rowFromDocIterator(DocIterator const & dit) const
 {
-	bool found = false;
+	bool beg_found = false;
+	bool end_is_next = true;
+	int end_offset = 1;
 	size_t best_slice = 0;
+	RowEntry best_entry = row_none;
 	size_t const n = dit.depth();
-	// this loop finds the last row of the topmost possible CursorSlice
-	RowList::const_iterator best_beg_row = rowlist.begin();
-	RowList::const_iterator best_end_row = rowlist.begin();
-	RowList::const_iterator it = rowlist.begin();
-	RowList::const_iterator const end = rowlist.end();
+	// this loop finds a pair (best_beg_row,best_end_row) where best_beg_row is
+	// the first row of the topmost possible CursorSlice, and best_end_row is
+	// the one just before the first row matching the next CursorSlice.
+	RowListIterator const begin = this->begin();//necessary disambiguation
+	RowListIterator const end = this->end();
+	RowListIterator best_beg_entry;
+	//best last entry with same pos as the beg_entry, or first entry with pos
+	//immediately following the beg_entry
+	RowListIterator best_end_entry;
+	RowListIterator it = begin;
 	for (; it != end; ++it) {
-		if (found) {
-			// Compute the best end row. It is the one that matches pos+1.
-			CursorSlice const & best = dit[best_slice];
-			if (best.text()
-				&& it->id() == best.paragraph().id()
-				&& it->pos() == best.pos() + 1
-				&& (best_end_row->id() != it->id()
-					|| best_end_row->pos() < it->pos()))
-					best_end_row = it;
+		// Compute the best end row.
+		if (beg_found
+			&& (!sameParOrInsetMath(*it, *best_end_entry)
+				|| comparePos(*it, *best_end_entry) <= 0)
+			&& sameParOrInsetMath(*it, best_entry)) {
+		    switch (comparePos(*it, best_entry)) {
+			case 0:
+				// Either it is the last one that matches pos...
+				best_end_entry = it;
+				end_is_next = false;
+				end_offset = 1;
+				break;
+			case -1: {
+				// ...or it is the row preceding the first that matches pos+1 
+				if (!end_is_next) {
+					end_is_next = true;
+					if (it.row() != best_end_entry.row())
+						end_offset = 0;
+					best_end_entry = it;
+				}
+				break;
+			}
+			}
 		}
-		for (size_t i = best_slice; i < n && dit[i].text(); ++i) {
-			int const id = dit[i].paragraph().id();
-			if (it->id() == id) {
-				if (it->pos() <= dit[i].pos()
-					&& (best_beg_row->id() != id
-						|| it->pos() > best_beg_row->pos())) {
-					found = true;
+		// Compute the best begin row. It is better than the previous one if it
+		// matches either at a deeper level, or at the same level but not
+		// before.
+		for (size_t i = best_slice; i < n; ++i) {
+			TexRow::RowEntry entry_i = rowEntryFromCursorSlice(dit[i]);
+			if (sameParOrInsetMath(*it, entry_i)) {
+				if (comparePos(*it, entry_i) >= 0
+					&& (i > best_slice
+						|| !beg_found
+						|| !sameParOrInsetMath(*it, *best_beg_entry)
+						|| (comparePos(*it, *best_beg_entry) <= 0
+							&& comparePos(entry_i, *best_beg_entry) != 0)
+						)
+					) {
+					beg_found = true;
+					end_is_next = false;
+					end_offset = 1;
 					best_slice = i;
-					best_beg_row = best_end_row = it;
+					best_entry = entry_i;
+					best_beg_entry = best_end_entry = it;
 				}
 				//found CursorSlice
 				break;
 			}
 		}
 	}
-	if (!found)
+	if (!beg_found)
 		return std::make_pair(-1,-1);
-	int const beg_i = distance(rowlist.begin(), best_beg_row) + 1;
-	// remove one to the end
-	int const end_i = std::max(beg_i,
-							   (int)distance(rowlist.begin(), best_end_row));
-	return std::make_pair(beg_i,end_i);
+	int const best_beg_row = distance(rowlist_.begin(),
+									  best_beg_entry.row()) + 1;
+	int const best_end_row = distance(rowlist_.begin(),
+									  best_end_entry.row()) + end_offset;
+	return std::make_pair(best_beg_row, best_end_row);
 }
 
 
+// debugging functions
+
+///
+docstring TexRow::asString(RowEntry const & entry)
+{
+	odocstringstream os;
+	if (entry.is_math)
+		os << "(1," << entry.math.id << "," << entry.math.cell << ")";
+	else
+		os << "(0," << entry.text.id << "," << entry.text.pos << ")";
+	return os.str();
+}
+
+
+///prepends the texrow to the source given by tex, for debugging purpose
+void TexRow::prepend(docstring_list & tex) const
+{
+	int const prefix_length = 25;
+	if (tex.size() < rowlist_.size())
+		tex.resize(rowlist_.size());
+	std::vector<RowEntryList>::const_iterator it = rowlist_.begin();
+	std::vector<RowEntryList>::const_iterator const beg = rowlist_.begin();
+	std::vector<RowEntryList>::const_iterator const end = rowlist_.end();
+	for (; it < end; ++it) {
+		docstring entry;
+		std::vector<RowEntry>::const_iterator it2 = it->begin();
+		std::vector<RowEntry>::const_iterator const end2 = it->end();
+		for (; it2 != end2; ++it2)
+			entry += asString(*it2);
+		if (entry.length() < prefix_length)
+			entry = entry + docstring(prefix_length - entry.length(), L' ');
+		int i = it - beg;
+		tex[i] = entry + "  " + tex[i];
+	}
+}
+
+
+
 LyXErr & operator<<(LyXErr & l, TexRow & texrow)
 {
 	if (l.enabled()) {
 		for (int i = 0; i < texrow.rows(); i++) {
 			int id,pos;
 			if (texrow.getIdFromRow(i+1,id,pos) && id>0)
-				l << i+1 << ":" << id << ":" << pos << "\n";
+			l << i+1 << ":" << id << ":" << pos << "\n";
 		}
 	}
 	return l;
diff --git a/src/TexRow.h b/src/TexRow.h
index 254f446..e56a2e4 100644
--- a/src/TexRow.h
+++ b/src/TexRow.h
@@ -7,6 +7,7 @@
  * \author Matthias Ettrich
  * \author Lars Gullik Bjønnes
  * \author John Levon
+ * \author Guillaume Munch
  *
  * Full author contact details are available in file CREDITS.
  */
@@ -18,27 +19,99 @@
 
 #include <vector>
 
+
 namespace lyx {
 
 class LyXErr;
+class CursorSlice;
 class DocIterator;
+class docstring_list;
 
-/// Represents the correspondence between paragraphs and the generated
-/// LaTeX file
+/// type for cell indices
+typedef size_t idx_type;
+// Negative values denote invalid ids or 'not found'
+typedef int uid_type;
 
+
+/// Represents the correspondence between paragraphs or math cells and the
+/// generated LaTeX file
 class TexRow {
 public:
+	/// an individual par id/pos <=> row mapping
+	struct TextEntry { int id; int pos; };
+	/// an individual math id/cell <=> row mapping
+	struct MathEntry { uid_type id; idx_type cell; };
+	/// a container for passing entries around
+	struct RowEntry {
+		bool is_math;// true iff the union is a math
+		union {
+			struct TextEntry text;
+			struct MathEntry math;
+		};
+	};
+	// For each row we store a list of one TextEntry and several
+	// MathEntries. (The order is important.)  We only want one text entry
+	// because we do not want to store every position in the lyx file. On the
+	// other hand we want to record all math cells positions for enough
+	// precision. Usually the count of math cells is easier to handle.
+	class RowEntryList : public std::vector<RowEntry> {
+	public:
+		RowEntryList() : std::vector<RowEntry>(), text_entry_(-1) {}
+		// returns true if the row entry will appear in the row entry list
+		bool addEntry(RowEntry const &);
+		// returns true if the text entry will appear in the row entry list
+		//bool addTextEntry(TextEntry const &);
+		// returns true because the math entry will always appears
+		//bool addMathEntry(MathEntry const &);
+		// returns the TextEntry or TexRow::text_none if none
+		TextEntry getTextEntry() const;
+		// returns the first entry, or TexRow::row_none if none
+		RowEntry entry() const;
+	private:
+		size_t text_entry_;
+	};
+	/*private:
+	typedef std::vector<RowEntryList> RowList;
+	public:*/
+	static const TextEntry text_none;
+	static const RowEntry row_none;
+	/// Returns true if RowEntry is devoid of information
+	static bool isNone(RowEntry const &);
+	/// Returns true if TextEntry is devoid of information
+	static bool isNone(TextEntry const &);
+	/// Converts a CursorSlice into a RowEntry
+	static RowEntry rowEntryFromCursorSlice(CursorSlice const & slice);
+	/// Encapsulates the paragraph and position for later use
+	static RowEntry textEntry(int id, int pos);
+	/// Encapsulates a cell and position for later use
+	static RowEntry mathEntry(uid_type id, idx_type cell);
+	/// true iff same paragraph or math inset
+	static bool sameParOrInsetMath(RowEntry const &, RowEntry const &);
+	/// computes the distance
+	/// assumes it is the sameParOrInsetMath
+	static int comparePos(RowEntry const & entry1, RowEntry const & entry2);
+	/// for debugging purpose
+	static docstring asString(RowEntry const &);
 	///
 	TexRow(bool enable = true)
-		: lastid(-1), lastpos(-1), started(false), enabled_(enable) {}
+		: current_row_(RowEntryList()), enabled_(enable) {}
 
 	/// Clears structure
 	/// TexRow is often computed to be immediately discarded. Set enable to
-	/// false if texrow is not needed
+	/// false if texrow is not needed.
 	void reset(bool enable = true);
 
-	/// Define what paragraph and position the next row will represent
-	void start(int id, int pos);
+	/// Defines the row information for the current line
+	/// returns true if this entry will appear on the current row
+	bool start(RowEntry entry);
+
+	/// Defines the paragraph and position for the current line
+	/// returns true if this entry will appear on the current row
+	bool start(int id, int pos);
+
+	/// Defines a cell and position for the current line
+	/// returns true if this entry will appear on the current row
+	bool startMath(uid_type id, idx_type cell);
 
 	/// Insert node when line is completed
 	void newline();
@@ -66,41 +139,29 @@ public:
 	std::pair<int,int> rowFromDocIterator(DocIterator const & dit) const;
 	
 	/// Returns the number of rows contained
-	int rows() const { return rowlist.size(); }
+	int rows() const { return rowlist_.size(); }
 
-	/// an individual id/pos <=> row mapping
-	class RowItem {
-	public:
-		RowItem(int id, int pos)
-			: id_(id), pos_(pos)
-		{}
+	/// for debugging purpose
+	void prepend(docstring_list &) const;
 
-		/// paragraph id
-		int id() const { return id_; }
-		/// set paragraph position
-		void pos(int p) { pos_ = p; }
-		/// paragraph position
-		int pos() const { return pos_; }
-	private:
-		RowItem();
-		int id_;
-		int pos_;
-	};
-	///
-	typedef std::vector<RowItem> RowList;
 private:
+	typedef std::vector<RowEntryList> RowList;
+	///
+	class RowListIterator;
+	///
+	RowListIterator begin() const;
+	///
+	RowListIterator end() const;
 	/// container of id/pos <=> row mapping
-	RowList rowlist;
-	/// Last paragraph
-	int lastid;
-	/// Last position
-	int lastpos;
-	/// Is id/pos already registered for current row?
-	bool started;
+	RowList rowlist_;
+	/// Entry of current line
+	RowEntryList current_row_;
 	/// 
 	bool enabled_;
 };
 
+bool operator==(TexRow::RowEntry const &, TexRow::RowEntry const &);
+
 LyXErr & operator<<(LyXErr &, TexRow &);
 
 
-- 
2.1.4

>From 72635e1db8207a8ad2bde4794e7bd92d99a36365 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Wed, 14 Oct 2015 00:23:12 +0100
Subject: [PATCH 2/8] Add a unique id to math insets

To do this non-intrusively we create a new UniqueId class with special copy and
comparison semantics. We make it a template in case we need to reuse it later.

The status bar now spits out the math inset uid information when in a math cell
in DEVEL_VERSION, like it already does when in a paragraph.

Notice that we use 64bit integers for uids. This is because the uid goes into
the millions just after opening a file (medium-sized, but with a lot of macros;
I suspect that macro update is the culprit here).

This is preliminary work for extending the cursor<->row tracking to math.
---
 src/Cursor.cpp           | 11 +++++++++--
 src/TexRow.h             | 45 ++++++++++++++++++++++++++++++++++++++++++++-
 src/mathed/InsetMath.cpp |  4 ++++
 src/mathed/InsetMath.h   |  8 +++++++-
 4 files changed, 64 insertions(+), 4 deletions(-)

diff --git a/src/Cursor.cpp b/src/Cursor.cpp
index fe90f98..4ea99ff 100644
--- a/src/Cursor.cpp
+++ b/src/Cursor.cpp
@@ -1179,8 +1179,6 @@ void Cursor::info(odocstream & os) const
 		if (inset)
 			prevInset()->infoize2(os);
 	}
-	// overwite old message
-	os << "                    ";
 }
 
 
@@ -2106,6 +2104,15 @@ docstring Cursor::currentState() const
 	if (inMathed()) {
 		odocstringstream os;
 		info(os);
+#ifdef DEVEL_VERSION
+		InsetMath * math = inset().asInsetMath();
+		os << _(", Cell: ") << idx();
+		if (math)
+			os << _(", Id: ") << math->id();
+		else
+			os << _(", No id");
+		os << _(", Position: ") << pos();
+#endif
 		return os.str();
 	}
 
diff --git a/src/TexRow.h b/src/TexRow.h
index e56a2e4..1710574 100644
--- a/src/TexRow.h
+++ b/src/TexRow.h
@@ -17,7 +17,14 @@
 
 #include "support/debug.h"
 
+#ifdef LYX_ENABLE_CXX11
+#include <cstdint>
+#else
+#include <boost/cstdint.hpp>
+#endif
+
 #include <vector>
+#include <boost/lexical_cast.hpp>
 
 
 namespace lyx {
@@ -29,8 +36,44 @@ class docstring_list;
 
 /// type for cell indices
 typedef size_t idx_type;
+
 // Negative values denote invalid ids or 'not found'
-typedef int uid_type;
+// Do we need 64 bits? The unique identifier for math insets went above
+// 3'000'000 just after opening a document, and 6'000'000 after a few
+// copy-pastes...
+#ifdef LYX_ENABLE_CXX11
+typedef std::int64_t uid_type;
+#else
+typedef boost::int64_t uid_type;
+#endif
+
+// Implements a unique identifier for T (analoguous to Paragraph::id())
+template<typename T>
+class UniqueId {
+	static uid_type prev;
+public:
+	UniqueId() : n_(++prev) { }
+	// force equality
+	UniqueId(uid_type n) : n_(n) { }
+	// copy : force uniqueness
+	UniqueId(UniqueId const &) : n_(++prev) { }
+	UniqueId & operator=(UniqueId const &) { return *this; }
+	~UniqueId() { }
+	// comparison operator -- objects can be equal even if they do not have
+	// the same id. 
+	bool operator==(UniqueId const &) { return true; }
+	//
+	uid_type id() const { return n_; }
+	//
+	docstring asString() const { return boost::lexical_cast<docstring>(n_); };
+private:
+	uid_type const n_;
+};
+
+// global static uid_type counter.
+template<typename T>
+uid_type UniqueId<T>::prev = -1;
+
 
 
 /// Represents the correspondence between paragraphs or math cells and the
diff --git a/src/mathed/InsetMath.cpp b/src/mathed/InsetMath.cpp
index 738f634..2e142e2 100644
--- a/src/mathed/InsetMath.cpp
+++ b/src/mathed/InsetMath.cpp
@@ -27,6 +27,10 @@ using namespace std;
 
 namespace lyx {
 
+
+template class UniqueId<InsetMath>;
+
+
 docstring InsetMath::name() const
 {
 	return from_utf8("Unknown");
diff --git a/src/mathed/InsetMath.h b/src/mathed/InsetMath.h
index 917011f..d11cf13 100644
--- a/src/mathed/InsetMath.h
+++ b/src/mathed/InsetMath.h
@@ -15,6 +15,8 @@
 
 #include "MathData.h"
 
+#include "TexRow.h"
+
 #include "insets/Inset.h"
 
 
@@ -44,7 +46,7 @@ Abstract base class for all math objects.  A math insets is for use of the
 math editor only, it isn't a general LyX inset. It's used to represent all
 the math objects.
 
-Math insets do not know there parents, a cursor position or things
+Math insets do not know their parents, a cursor position or things
 like that. They are dumb objects that are contained in other math insets
 (InsetMathNests, in fact) thus forming a tree. The root of this tree is
 always a InsetMathHull, which provides an interface to the Outer World by
@@ -225,6 +227,10 @@ public:
 	bool isInToc() const { return true; }
 	///
 	InsetCode lyxCode() const { return MATH_CODE; }
+	///
+	uid_type id() const { return uid_.id(); };
+private:
+	UniqueId<InsetMath> uid_;
 };
 
 ///
-- 
2.1.4

>From 0d2ff3798ae04e5b8b9c85e0e91969f4664ddcc3 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Wed, 7 Oct 2015 04:02:40 +0100
Subject: [PATCH 3/8] Splitting otexstream into otexrowstream and otexstream.

otexstream used to count lines to build a TexRow, and some other things. The new
class otexrowstream has the line counting feature of the previous otexstream
without other stuff. otexstream is now a subclass of otexrowstream that has the
same features as before.

This is preliminary work for extending the cursor<->row tracking to math.
---
 src/texstream.cpp | 99 +++++++++++++++++++++++++++++++++++++++++--------------
 src/texstream.h   | 51 +++++++++++++++++++++-------
 2 files changed, 114 insertions(+), 36 deletions(-)

diff --git a/src/texstream.cpp b/src/texstream.cpp
index e1c0fe7..84fb060 100644
--- a/src/texstream.cpp
+++ b/src/texstream.cpp
@@ -29,17 +29,22 @@ using lyx::support::split;
 
 namespace lyx {
 
+void otexrowstream::put(char_type const & c)
+{
+	os_.put(c);
+	if (c == '\n')
+		texrow_.newline();
+}
+
 void otexstream::put(char_type const & c)
 {
 	if (protectspace_) {
 		if (!canbreakline_ && c == ' ')
-			os_ << "{}";
+			os() << "{}";
 		protectspace_ = false;
 	}
-	os_.put(c);
+	otexrowstream::put(c);
 	lastChar(c);
-	if (c == '\n')
-		texrow_.newline();
 }
 
 
@@ -50,9 +55,8 @@ SafeBreakLine safebreakln;
 otexstream & operator<<(otexstream & ots, BreakLine)
 {
 	if (ots.canBreakLine()) {
-		ots.os().put('\n');
+		ots.otexrowstream::put('\n');
 		ots.lastChar('\n');
-		ots.texrow().newline();
 	}
 	ots.protectSpace(false);
 	return ots;
@@ -61,26 +65,43 @@ otexstream & operator<<(otexstream & ots, BreakLine)
 
 otexstream & operator<<(otexstream & ots, SafeBreakLine)
 {
+	otexrowstream & otrs = ots;
 	if (ots.canBreakLine()) {
-		ots.os() << "%\n";
+		otrs << "%\n";
 		ots.lastChar('\n');
-		ots.texrow().newline();
 	}
 	ots.protectSpace(false);
 	return ots;
 }
 
 
-otexstream & operator<<(otexstream & ots, odocstream_manip pf)
+otexrowstream & operator<<(otexrowstream & ots, odocstream_manip pf)
 {
 	ots.os() << pf;
 	if (pf == static_cast<odocstream_manip>(endl)) {
-		ots.lastChar('\n');
 		ots.texrow().newline();
 	}
 	return ots;
 }
 
+otexstream & operator<<(otexstream & ots, odocstream_manip pf)
+{
+	otexrowstream & otrs = ots;
+	otrs << pf;
+	if (pf == static_cast<odocstream_manip>(endl)) {
+		ots.lastChar('\n');
+	}
+	return ots;
+}
+
+
+otexrowstream & operator<<(otexrowstream & ots, docstring const & s)
+{
+	ots.os() << s;
+	ots.texrow().newlines(count(s.begin(), s.end(), '\n'));
+	return ots;
+}
+
 
 otexstream & operator<<(otexstream & ots, docstring const & s)
 {
@@ -89,10 +110,10 @@ otexstream & operator<<(otexstream & ots, docstring const & s)
 	// Check whether there's something to output
 	if (len == 0)
 		return ots;
-
+	otexrowstream & otrs = ots;
 	if (ots.protectSpace()) {
 		if (!ots.canBreakLine() && s[0] == ' ')
-			ots.os() << "{}";
+			otrs << "{}";
 		ots.protectSpace(false);
 	}
 
@@ -106,7 +127,7 @@ otexstream & operator<<(otexstream & ots, docstring const & s)
 		docstring s2 = split(s, s1, 0xF0000);
 		while (true) {
 			if (!s1.empty())
-				ots.os() << s1;
+				otrs << s1;
 			if (s2.empty())
 				break;
 			docstring enc;
@@ -114,17 +135,23 @@ otexstream & operator<<(otexstream & ots, docstring const & s)
 			if (!contains(s2, 0xF0001))
 				s2 = split(enc, s1, 0xF0000);
 			else {
-				ots.os() << setEncoding(to_ascii(enc));
+				otrs << setEncoding(to_ascii(enc));
 				s2 = split(s3, s1, 0xF0000);
 			}
 		}
 	} else
-		ots.os() << s;
+		otrs << s;
 
 	if (len > 1)
 		ots.canBreakLine(s[len - 2] != '\n');
 	ots.lastChar(s[len - 1]);
-	ots.texrow().newlines(count(s.begin(), s.end(), '\n'));
+	return ots;
+}
+
+
+otexrowstream & operator<<(otexrowstream & ots, string const & s)
+{
+	ots << from_utf8(s);
 	return ots;
 }
 
@@ -136,6 +163,13 @@ otexstream & operator<<(otexstream & ots, string const & s)
 }
 
 
+otexrowstream & operator<<(otexrowstream & ots, char const * s)
+{
+	ots << from_utf8(s);
+	return ots;
+}
+
+
 otexstream & operator<<(otexstream & ots, char const * s)
 {
 	ots << from_utf8(s);
@@ -143,22 +177,37 @@ otexstream & operator<<(otexstream & ots, char const * s)
 }
 
 
+otexrowstream & operator<<(otexrowstream & ots, char c)
+{
+	ots.put(c);
+	return ots;
+}
+
+
 otexstream & operator<<(otexstream & ots, char c)
 {
-	if (ots.protectSpace()) {
-		if (!ots.canBreakLine() && c == ' ')
-			ots.os() << "{}";
-		ots.protectSpace(false);
-	}
-	ots.os() << c;
-	ots.lastChar(c);
-	if (c == '\n')
-		ots.texrow().newline();
+	ots.put(c);
 	return ots;
 }
 
 
 template <typename Type>
+otexrowstream & operator<<(otexrowstream & ots, Type value)
+{
+	ots.os() << value;
+	return ots;
+}
+
+template otexrowstream & operator<< <SetEnc>(otexrowstream & os, SetEnc);
+template otexrowstream & operator<< <double>(otexrowstream &, double);
+template otexrowstream & operator<< <int>(otexrowstream &, int);
+template otexrowstream & operator<< <unsigned int>(otexrowstream &,
+												   unsigned int);
+template otexrowstream & operator<< <unsigned long>(otexrowstream &,
+													unsigned long);
+
+
+template <typename Type>
 otexstream & operator<<(otexstream & ots, Type value)
 {
 	ots.os() << value;
diff --git a/src/texstream.h b/src/texstream.h
index 282d210..4fff211 100644
--- a/src/texstream.h
+++ b/src/texstream.h
@@ -19,7 +19,44 @@ namespace lyx {
 
 /** Wrapper class for odocstream.
     This class is used to automatically count the lines of the exported latex
-    code and also to ensure that no blank lines may be inadvertently output.
+    code.
+  */
+
+class otexrowstream {
+public:
+	///
+	otexrowstream(odocstream & os, TexRow & texrow)
+		: os_(os), texrow_(texrow) {}
+	///
+	odocstream & os() { return os_; }
+	///
+	TexRow & texrow() { return texrow_; }
+	///
+	void put(char_type const & c);
+private:
+	///
+	odocstream & os_;
+	///
+	TexRow & texrow_;
+};
+
+///
+otexrowstream & operator<<(otexrowstream &, odocstream_manip);
+///
+otexrowstream & operator<<(otexrowstream &, docstring const &);
+///
+otexrowstream & operator<<(otexrowstream &, std::string const &);
+///
+otexrowstream & operator<<(otexrowstream &, char const *);
+///
+otexrowstream & operator<<(otexrowstream &, char);
+///
+template <typename Type>
+otexrowstream & operator<<(otexrowstream & ots, Type value);
+
+
+/** Subclass for otexrowstream.
+    This class is used to ensure that no blank lines may be inadvertently output.
     To this end, use the special variables "breakln" and "safebreakln" as if
     they were iomanip's to ensure that the next output will start at the
     beginning of a line. Using "breakln", a '\n' char will be output if needed,
@@ -28,17 +65,13 @@ namespace lyx {
     a paragraph break was just output.
   */
 
-class otexstream {
+class otexstream : public otexrowstream {
 public:
 	///
 	otexstream(odocstream & os, TexRow & texrow)
-		: os_(os), texrow_(texrow), canbreakline_(false),
+		: otexrowstream(os, texrow), canbreakline_(false),
 		  protectspace_(false), parbreak_(true), lastchar_(0) {}
 	///
-	odocstream & os() { return os_; }
-	///
-	TexRow & texrow() { return texrow_; }
-	///
 	void put(char_type const & c);
 	///
 	void canBreakLine(bool breakline) { canbreakline_ = breakline; }
@@ -61,10 +94,6 @@ public:
 	bool afterParbreak() const { return parbreak_; }
 private:
 	///
-	odocstream & os_;
-	///
-	TexRow & texrow_;
-	///
 	bool canbreakline_;
 	///
 	bool protectspace_;
-- 
2.1.4

>From 41d65e57273b16c0b416e5d3b143de6c0fd25d69 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Wed, 7 Oct 2015 04:13:21 +0100
Subject: [PATCH 4/8] Adding TexRow information on math latex output (#4725)

WriteStream is now built from an otexstream instead of an odocstream, and
therefore counts lines in a TexRow. Calls to TexRow are added in relevant places
in math insets.

This finishes adding line tracking for math in the source panel and for forward
search.
---
 src/Cursor.cpp                   |  4 ++-
 src/lyxfind.cpp                  |  4 +--
 src/mathed/InsetMath.cpp         | 12 +++++--
 src/mathed/InsetMathAMSArray.cpp |  3 ++
 src/mathed/InsetMathArray.cpp    |  3 ++
 src/mathed/InsetMathCases.cpp    |  3 ++
 src/mathed/InsetMathDiagram.cpp  |  3 ++
 src/mathed/InsetMathGrid.cpp     | 10 +++++-
 src/mathed/InsetMathHull.cpp     | 75 ++++++++++++++++++++++++++++------------
 src/mathed/InsetMathNest.cpp     | 31 +++++++++--------
 src/mathed/InsetMathSplit.cpp    |  3 ++
 src/mathed/InsetMathSubstack.cpp |  3 ++
 src/mathed/InsetMathTabular.cpp  |  3 ++
 src/mathed/InsetMathXYMatrix.cpp |  6 +++-
 src/mathed/MacroTable.cpp        |  4 ++-
 src/mathed/MathExtern.cpp        |  1 +
 src/mathed/MathMacroTemplate.cpp |  4 ++-
 src/mathed/MathStream.cpp        | 31 ++++++++++++++---
 src/mathed/MathStream.h          | 24 ++++++++++---
 src/mathed/MathSupport.cpp       | 12 +++++--
 20 files changed, 180 insertions(+), 59 deletions(-)

diff --git a/src/Cursor.cpp b/src/Cursor.cpp
index 4ea99ff..b3de69b 100644
--- a/src/Cursor.cpp
+++ b/src/Cursor.cpp
@@ -1684,7 +1684,9 @@ void Cursor::normalize()
 			<< pos() << ' ' << lastpos() <<  " in idx: " << idx()
 		       << " in atom: '";
 		odocstringstream os;
-		WriteStream wi(os, false, true, WriteStream::wsDefault);
+		TexRow texrow(false);
+		otexrowstream ots(os,texrow);
+		WriteStream wi(ots, false, true, WriteStream::wsDefault);
 		inset().asInsetMath()->write(wi);
 		lyxerr << to_utf8(os.str()) << endl;
 		pos() = lastpos();
diff --git a/src/lyxfind.cpp b/src/lyxfind.cpp
index 85b66d0..2f7a0b4 100644
--- a/src/lyxfind.cpp
+++ b/src/lyxfind.cpp
@@ -1072,7 +1072,7 @@ docstring latexifyFromCursor(DocIterator const & cur, int len)
 		for (int s = cur.depth() - 1; s >= 0; --s) {
 			CursorSlice const & cs = cur[s];
 			if (cs.asInsetMath() && cs.asInsetMath()->asHullInset()) {
-				WriteStream ws(ods);
+				WriteStream ws(os);
 				cs.asInsetMath()->asHullInset()->header_write(ws);
 				break;
 			}
@@ -1094,7 +1094,7 @@ docstring latexifyFromCursor(DocIterator const & cur, int len)
 			CursorSlice const & cs = cur[s];
 			InsetMath * inset = cs.asInsetMath();
 			if (inset && inset->asHullInset()) {
-				WriteStream ws(ods);
+				WriteStream ws(os);
 				inset->asHullInset()->footer_write(ws);
 				break;
 			}
diff --git a/src/mathed/InsetMath.cpp b/src/mathed/InsetMath.cpp
index 2e142e2..87f7681 100644
--- a/src/mathed/InsetMath.cpp
+++ b/src/mathed/InsetMath.cpp
@@ -57,7 +57,9 @@ void InsetMath::dump() const
 {
 	lyxerr << "---------------------------------------------" << endl;
 	odocstringstream os;
-	WriteStream wi(os, false, true, WriteStream::wsDefault);
+	TexRow texrow(false);
+	otexrowstream ots(os,texrow);
+	WriteStream wi(ots, false, true, WriteStream::wsDefault);
 	write(wi);
 	lyxerr << to_utf8(os.str());
 	lyxerr << "\n---------------------------------------------" << endl;
@@ -160,7 +162,9 @@ HullType InsetMath::getType() const
 ostream & operator<<(ostream & os, MathAtom const & at)
 {
 	odocstringstream oss;
-	WriteStream wi(oss, false, false, WriteStream::wsDefault);
+	TexRow texrow(false);
+	otexrowstream ots(oss,texrow);
+	WriteStream wi(ots, false, false, WriteStream::wsDefault);
 	at->write(wi);
 	return os << to_utf8(oss.str());
 }
@@ -168,7 +172,9 @@ ostream & operator<<(ostream & os, MathAtom const & at)
 
 odocstream & operator<<(odocstream & os, MathAtom const & at)
 {
-	WriteStream wi(os, false, false, WriteStream::wsDefault);
+	TexRow texrow(false);
+	otexrowstream ots(os,texrow);
+	WriteStream wi(ots, false, false, WriteStream::wsDefault);
 	at->write(wi);
 	return os;
 }
diff --git a/src/mathed/InsetMathAMSArray.cpp b/src/mathed/InsetMathAMSArray.cpp
index e615e1d..12a92da 100644
--- a/src/mathed/InsetMathAMSArray.cpp
+++ b/src/mathed/InsetMathAMSArray.cpp
@@ -134,8 +134,11 @@ void InsetMathAMSArray::write(WriteStream & os) const
 {
 	MathEnsurer ensurer(os);
 	os << "\\begin{" << name_ << '}';
+	bool open = os.startOuterRow();
 	InsetMathGrid::write(os);
 	os << "\\end{" << name_ << '}';
+	if (open)
+		os.startOuterRow();
 }
 
 
diff --git a/src/mathed/InsetMathArray.cpp b/src/mathed/InsetMathArray.cpp
index 821f460..d5f9164 100644
--- a/src/mathed/InsetMathArray.cpp
+++ b/src/mathed/InsetMathArray.cpp
@@ -103,6 +103,7 @@ void InsetMathArray::write(WriteStream & os) const
 	if (os.fragile())
 		os << "\\protect";
 	os << "\\begin{" << name_ << '}';
+	bool open = os.startOuterRow();
 
 	char const v = verticalAlignment();
 	if (v == 't' || v == 'b')
@@ -114,6 +115,8 @@ void InsetMathArray::write(WriteStream & os) const
 	if (os.fragile())
 		os << "\\protect";
 	os << "\\end{" << name_ << '}';
+	if (open)
+		os.startOuterRow();
 	// adding a \n here is bad if the array is the last item
 	// in an \eqnarray...
 }
diff --git a/src/mathed/InsetMathCases.cpp b/src/mathed/InsetMathCases.cpp
index d6f7133..17b4fb1 100644
--- a/src/mathed/InsetMathCases.cpp
+++ b/src/mathed/InsetMathCases.cpp
@@ -132,11 +132,14 @@ void InsetMathCases::write(WriteStream & os) const
 	MathEnsurer ensurer(os);
 	if (os.fragile())
 		os << "\\protect";
+	bool open = os.startOuterRow();
 	os << "\\begin{cases}\n";
 	InsetMathGrid::write(os);
 	if (os.fragile())
 		os << "\\protect";
 	os << "\\end{cases}";
+	if (open)
+		os.startOuterRow();
 }
 
 
diff --git a/src/mathed/InsetMathDiagram.cpp b/src/mathed/InsetMathDiagram.cpp
index 25ad63c..5c8887c 100644
--- a/src/mathed/InsetMathDiagram.cpp
+++ b/src/mathed/InsetMathDiagram.cpp
@@ -56,9 +56,12 @@ void InsetMathDiagram::write(WriteStream & os) const
 {
 	MathEnsurer ensurer(os);
 	os << "\\Diagram";
+	bool open = os.startOuterRow();
 	os << '{';
 	InsetMathGrid::write(os);
 	os << "}\n";
+	if (open)
+		os.startOuterRow();
 }
 
 
diff --git a/src/mathed/InsetMathGrid.cpp b/src/mathed/InsetMathGrid.cpp
index 781d921..8ea3940 100644
--- a/src/mathed/InsetMathGrid.cpp
+++ b/src/mathed/InsetMathGrid.cpp
@@ -1266,9 +1266,16 @@ void InsetMathGrid::write(WriteStream & os,
 				emptyline = false;
 			}
 		}
-		for (col_type col = beg_col; col < lastcol;) {
+		for (col_type col = beg_col; col < end_col;) {
 			int nccols = 1;
 			idx_type const idx = index(row, col);
+			TexRow::RowEntry entry = os.texrow().mathEntry(id(),idx);
+			os.texrow().startMath(id(),idx);
+			if (col >= lastcol) {
+				++col;
+				continue;
+			}
+			os.pushRowEntry(entry);
 			if (cellinfo_[idx].multi_ == CELL_BEGIN_OF_MULTICOLUMN) {
 				size_t s = col + 1;
 				while (s < ncols() &&
@@ -1286,6 +1293,7 @@ void InsetMathGrid::write(WriteStream & os,
 				os << '}';
 			os << eocString(col + nccols - 1, lastcol);
 			col += nccols;
+			os.popRowEntry();
 		}
 		eol = eolString(row, os.fragile(), os.latex(), last_eoln);
 		os << eol;
diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp
index 560c999..08c0dec 100644
--- a/src/mathed/InsetMathHull.cpp
+++ b/src/mathed/InsetMathHull.cpp
@@ -575,7 +575,9 @@ void InsetMathHull::metricsT(TextMetricsInfo const & mi, Dimension & dim) const
 		InsetMathGrid::metricsT(mi, dim);
 	} else {
 		odocstringstream os;
-		WriteStream wi(os, false, true, WriteStream::wsDefault);
+		TexRow texrow(false);
+		otexrowstream ots(os,texrow);
+		WriteStream wi(ots, false, true, WriteStream::wsDefault);
 		write(wi);
 		dim.wid = os.str().size();
 		dim.asc = 1;
@@ -590,7 +592,9 @@ void InsetMathHull::drawT(TextPainter & pain, int x, int y) const
 		InsetMathGrid::drawT(pain, x, y);
 	} else {
 		odocstringstream os;
-		WriteStream wi(os, false, true, WriteStream::wsDefault);
+		TexRow texrow(false);
+		otexrowstream ots(os,texrow);
+		WriteStream wi(ots, false, true, WriteStream::wsDefault);
 		write(wi);
 		pain.draw(x, y, os.str().c_str());
 	}
@@ -608,7 +612,9 @@ static docstring latexString(InsetMathHull const & inset)
 	static Encoding const * encoding = 0;
 	if (inset.isBufferValid())
 		encoding = &(inset.buffer().params().encoding());
-	WriteStream wi(ls, false, true, WriteStream::wsPreview, encoding);
+	TexRow texrow(false);
+	otexrowstream ots(ls,texrow);
+	WriteStream wi(ots, false, true, WriteStream::wsPreview, encoding);
 	inset.write(wi);
 	return ls.str();
 }
@@ -921,22 +927,25 @@ void InsetMathHull::validate(LaTeXFeatures & features) const
 void InsetMathHull::header_write(WriteStream & os) const
 {
 	bool n = numberedType();
-
+	
 	switch(type_) {
 	case hullNone:
 		break;
 
 	case hullSimple:
 		os << '$';
+		os.startOuterRow();
 		if (cell(0).empty())
 			os << ' ';
 		break;
 
 	case hullEquation:
+		os << "\n";
+		os.startOuterRow();
 		if (n)
-			os << "\n\\begin{equation" << star(n) << "}\n";
+			os << "\\begin{equation" << star(n) << "}\n";
 		else
-			os << "\n\\[\n";
+			os << "\\[\n";
 		break;
 
 	case hullEqnArray:
@@ -944,17 +953,23 @@ void InsetMathHull::header_write(WriteStream & os) const
 	case hullFlAlign:
 	case hullGather:
 	case hullMultline:
-		os << "\n\\begin{" << hullName(type_) << star(n) << "}\n";
+		os << "\n";
+		os.startOuterRow();
+		os << "\\begin{" << hullName(type_) << star(n) << "}\n";
 		break;
 
 	case hullAlignAt:
 	case hullXAlignAt:
-		os << "\n\\begin{" << hullName(type_) << star(n) << '}'
+		os << "\n";
+		os.startOuterRow();
+		os << "\\begin{" << hullName(type_) << star(n) << '}'
 		  << '{' << static_cast<unsigned int>((ncols() + 1)/2) << "}\n";
 		break;
 
 	case hullXXAlignAt:
-		os << "\n\\begin{" << hullName(type_) << '}'
+		os << "\n";
+		os.startOuterRow();
+		os << "\\begin{" << hullName(type_) << '}'
 		  << '{' << static_cast<unsigned int>((ncols() + 1)/2) << "}\n";
 		break;
 
@@ -963,7 +978,9 @@ void InsetMathHull::header_write(WriteStream & os) const
 		break;
 
 	default:
-		os << "\n\\begin{unknown" << star(n) << "}\n";
+		os << "\n";
+		os.startOuterRow();
+		os << "\\begin{unknown" << star(n) << "}\n";
 		break;
 	}
 }
@@ -983,10 +1000,12 @@ void InsetMathHull::footer_write(WriteStream & os) const
 		break;
 
 	case hullEquation:
+		os << "\n";
+		os.startOuterRow();
 		if (n)
-			os << "\n\\end{equation" << star(n) << "}\n";
+			os << "\\end{equation" << star(n) << "}\n";
 		else
-			os << "\n\\]\n";
+			os << "\\]\n";
 		break;
 
 	case hullEqnArray:
@@ -996,11 +1015,15 @@ void InsetMathHull::footer_write(WriteStream & os) const
 	case hullXAlignAt:
 	case hullGather:
 	case hullMultline:
-		os << "\n\\end{" << hullName(type_) << star(n) << "}\n";
+		os << "\n";
+		os.startOuterRow();
+		os << "\\end{" << hullName(type_) << star(n) << "}\n";
 		break;
 
 	case hullXXAlignAt:
-		os << "\n\\end{" << hullName(type_) << "}\n";
+		os << "\n";
+		os.startOuterRow();
+		os << "\\end{" << hullName(type_) << "}\n";
 		break;
 
 	case hullRegexp:
@@ -1009,7 +1032,9 @@ void InsetMathHull::footer_write(WriteStream & os) const
 		break;
 
 	default:
-		os << "\n\\end{unknown" << star(n) << "}\n";
+		os << "\n";
+		os.startOuterRow();
+		os << "\\end{unknown" << star(n) << "}\n";
 		break;
 	}
 }
@@ -1367,7 +1392,6 @@ docstring InsetMathHull::eolString(row_type row, bool fragile, bool latex,
 	return res + InsetMathGrid::eolString(row, fragile, latex, last_eoln);
 }
 
-
 void InsetMathHull::write(WriteStream & os) const
 {
 	ModeSpecifier specifier(os, MATH_MODE);
@@ -2004,7 +2028,9 @@ bool InsetMathHull::searchForward(BufferView * bv, string const & str,
 void InsetMathHull::write(ostream & os) const
 {
 	odocstringstream oss;
-	WriteStream wi(oss, false, false, WriteStream::wsDefault);
+	TexRow texrow(false);
+	otexrowstream ots(oss,texrow);
+	WriteStream wi(ots, false, false, WriteStream::wsDefault);
 	oss << "Formula ";
 	write(wi);
 	os << to_utf8(oss.str());
@@ -2046,8 +2072,10 @@ int InsetMathHull::plaintext(odocstringstream & os,
 	}
 
 	odocstringstream oss;
+	TexRow texrow(false);
+	otexrowstream ots(oss,texrow);
 	Encoding const * const enc = encodings.fromLyXName("utf8");
-	WriteStream wi(oss, false, true, WriteStream::wsDefault, enc);
+	WriteStream wi(ots, false, true, WriteStream::wsDefault, enc);
 
 	// Fix Bug #6139
 	if (type_ == hullRegexp)
@@ -2087,12 +2115,14 @@ int InsetMathHull::docbook(odocstream & os, OutputParams const & runparams) cons
 	++ms.tab(); ms.cr(); ms.os() << '<' << bname << '>';
 
 	odocstringstream ls;
+	TexRow texrow;
+	otexstream ols(ls, texrow);
 	if (runparams.flavor == OutputParams::XML) {
 		ms << MTag("alt role='tex' ");
 		// Workaround for db2latex: db2latex always includes equations with
 		// \ensuremath{} or \begin{display}\end{display}
 		// so we strip LyX' math environment
-		WriteStream wi(ls, false, false, WriteStream::wsDefault, runparams.encoding);
+		WriteStream wi(ols, false, false, WriteStream::wsDefault, runparams.encoding);
 		InsetMathGrid::write(wi);
 		ms << from_utf8(subst(subst(to_utf8(ls.str()), "&", "&amp;"), "<", "&lt;"));
 		ms << ETag("alt");
@@ -2102,9 +2132,6 @@ int InsetMathHull::docbook(odocstream & os, OutputParams const & runparams) cons
 		InsetMathGrid::mathmlize(ms);
 		ms << ETag("math");
 	} else {
-		TexRow texrow;
-		texrow.reset();
-		otexstream ols(ls, texrow);
 		ms << MTag("alt role='tex'");
 		latex(ols, runparams);
 		res = texrow.rows();
@@ -2354,7 +2381,9 @@ docstring InsetMathHull::xhtml(XHTMLStream & xs, OutputParams const & op) const
 		// Unfortunately, we cannot use latexString() because we do not want
 		// $...$ or whatever.
 		odocstringstream ls;
-		WriteStream wi(ls, false, true, WriteStream::wsPreview);
+		TexRow texrow(false);
+		otexrowstream ots(ls,texrow);
+		WriteStream wi(ots, false, true, WriteStream::wsPreview);
 		ModeSpecifier specifier(wi, MATH_MODE);
 		mathAsLatex(wi);
 		docstring const latex = ls.str();
diff --git a/src/mathed/InsetMathNest.cpp b/src/mathed/InsetMathNest.cpp
index 94b4d68..473457d 100644
--- a/src/mathed/InsetMathNest.cpp
+++ b/src/mathed/InsetMathNest.cpp
@@ -250,7 +250,9 @@ bool InsetMathNest::idxLast(Cursor & cur) const
 void InsetMathNest::dump() const
 {
 	odocstringstream oss;
-	WriteStream os(oss);
+	TexRow texrow(false);
+	otexrowstream ots(oss,texrow);
+	WriteStream os(ots);
 	os << "---------------------------------------------\n";
 	write(os);
 	os << "\n";
@@ -376,8 +378,11 @@ void InsetMathNest::write(WriteStream & os) const
 	ModeSpecifier specifier(os, currentMode(), lockedMode());
 	docstring const latex_name = name();
 	os << '\\' << latex_name;
-	for (size_t i = 0; i < nargs(); ++i)
+	for (size_t i = 0; i < nargs(); ++i) {
+		os.pushRowEntry(TexRow::mathEntry(id(),i));
 		os << '{' << cell(i) << '}';
+		os.popRowEntry();
+	}
 	if (nargs() == 0)
 		os.pendingSpace(true);
 	if (lock_ && !os.latex()) {
@@ -398,22 +403,20 @@ void InsetMathNest::normalize(NormalStream & os) const
 
 void InsetMathNest::latex(otexstream & os, OutputParams const & runparams) const
 {
-	WriteStream wi(os.os(), runparams.moving_arg, true,
-		       runparams.dryrun ? WriteStream::wsDryrun : WriteStream::wsDefault,
-		       runparams.encoding);
+	WriteStream wi(os, runparams.moving_arg, true,
+			runparams.dryrun ? WriteStream::wsDryrun : WriteStream::wsDefault,
+			runparams.encoding);
 	wi.canBreakLine(os.canBreakLine());
-	write(wi);
+	if (runparams.lastid != -1) {
+		wi.pushRowEntry(os.texrow().textEntry(runparams.lastid,
+											  runparams.lastpos));
+		write(wi);
+		wi.popRowEntry();
+	} else
+		write(wi);
 	// Reset parbreak status after a math inset.
 	os.lastChar(0);
 	os.canBreakLine(wi.canBreakLine());
-
-	int lf = wi.line();
-	if (lf > 0 && runparams.lastid != -1) {
-		--lf;
-		os.texrow().newline();
-		os.texrow().start(runparams.lastid, runparams.lastpos);
-	}
-	os.texrow().newlines(lf);
 }
 
 
diff --git a/src/mathed/InsetMathSplit.cpp b/src/mathed/InsetMathSplit.cpp
index de4ddc0..5c425fb 100644
--- a/src/mathed/InsetMathSplit.cpp
+++ b/src/mathed/InsetMathSplit.cpp
@@ -104,6 +104,7 @@ void InsetMathSplit::write(WriteStream & ws) const
 	if (!numbered_ && name_ == "align")
 		suffix = from_ascii("*");
 	ws << "\\begin{" << name_ << suffix << '}';
+	bool open = ws.startOuterRow();
 	if (name_ != "split" && name_ != "align" && verticalAlignment() != 'c')
 		ws << '[' << verticalAlignment() << ']';
 	if (name_ == "alignedat")
@@ -112,6 +113,8 @@ void InsetMathSplit::write(WriteStream & ws) const
 	if (ws.fragile())
 		ws << "\\protect";
 	ws << "\\end{" << name_ << suffix << "}\n";
+	if (open)
+		ws.startOuterRow();
 }
 
 
diff --git a/src/mathed/InsetMathSubstack.cpp b/src/mathed/InsetMathSubstack.cpp
index d385137..7810ff4 100644
--- a/src/mathed/InsetMathSubstack.cpp
+++ b/src/mathed/InsetMathSubstack.cpp
@@ -107,8 +107,11 @@ void InsetMathSubstack::write(WriteStream & os) const
 {
 	MathEnsurer ensurer(os);
 	os << "\\substack{";
+	bool open = os.startOuterRow();
 	InsetMathGrid::write(os);
 	os << "}\n";
+	if (open)
+		os.startOuterRow();
 }
 
 
diff --git a/src/mathed/InsetMathTabular.cpp b/src/mathed/InsetMathTabular.cpp
index 083b0e5..2c814a3 100644
--- a/src/mathed/InsetMathTabular.cpp
+++ b/src/mathed/InsetMathTabular.cpp
@@ -71,6 +71,7 @@ void InsetMathTabular::write(WriteStream & os) const
 	if (os.fragile())
 		os << "\\protect";
 	os << "\\begin{" << name_ << '}';
+	bool open = os.startOuterRow();
 
 	char const v = verticalAlignment();
 	if (v == 't' || v == 'b')
@@ -82,6 +83,8 @@ void InsetMathTabular::write(WriteStream & os) const
 	if (os.fragile())
 		os << "\\protect";
 	os << "\\end{" << name_ << '}';
+	if (open)
+		os.startOuterRow();
 	// adding a \n here is bad if the tabular is the last item
 	// in an \eqnarray...
 }
diff --git a/src/mathed/InsetMathXYMatrix.cpp b/src/mathed/InsetMathXYMatrix.cpp
index d04d8f4..95941b8 100644
--- a/src/mathed/InsetMathXYMatrix.cpp
+++ b/src/mathed/InsetMathXYMatrix.cpp
@@ -57,6 +57,7 @@ void InsetMathXYMatrix::write(WriteStream & os) const
 {
 	MathEnsurer ensurer(os);
 	os << "\\xymatrix";
+	bool open = os.startOuterRow();
 	if (equal_spacing_) {
 		os << "@!";
 		switch (spacing_code_) {
@@ -83,7 +84,10 @@ void InsetMathXYMatrix::write(WriteStream & os) const
 	}
 	os << '{';
 	InsetMathGrid::write(os);
-	os << "}\n";
+	os << "}";
+	if (open)
+		os.startOuterRow();
+	os << "\n";
 }
 
 
diff --git a/src/mathed/MacroTable.cpp b/src/mathed/MacroTable.cpp
index 919edd4..9ff4bfe 100644
--- a/src/mathed/MacroTable.cpp
+++ b/src/mathed/MacroTable.cpp
@@ -200,7 +200,9 @@ int MacroData::write(odocstream & os, bool overwriteRedefinition) const
 	// output template
 	MathMacroTemplate const & tmpl =
 		static_cast<MathMacroTemplate const &>(*inset);
-	WriteStream wi(os, false, true, WriteStream::wsDefault);
+	TexRow texrow(false);
+	otexrowstream ots(os,texrow);
+	WriteStream wi(ots, false, true, WriteStream::wsDefault);
 	return tmpl.write(wi, overwriteRedefinition);
 }
 
diff --git a/src/mathed/MathExtern.cpp b/src/mathed/MathExtern.cpp
index 1904843..b98097a 100644
--- a/src/mathed/MathExtern.cpp
+++ b/src/mathed/MathExtern.cpp
@@ -1386,6 +1386,7 @@ void write(MathData const & dat, WriteStream & wi)
 	extractStrings(ar);
 	wi.firstitem() = true;
 	for (MathData::const_iterator it = ar.begin(); it != ar.end(); ++it) {
+		//		wi.startOuterRow();
 		(*it)->write(wi);
 		wi.firstitem() = false;
 	}
diff --git a/src/mathed/MathMacroTemplate.cpp b/src/mathed/MathMacroTemplate.cpp
index f3ba2b4..30909f2 100644
--- a/src/mathed/MathMacroTemplate.cpp
+++ b/src/mathed/MathMacroTemplate.cpp
@@ -1168,7 +1168,9 @@ void MathMacroTemplate::read(Lexer & lex)
 void MathMacroTemplate::write(ostream & os) const
 {
 	odocstringstream oss;
-	WriteStream wi(oss, false, false, WriteStream::wsDefault);
+	TexRow texrow(false);
+	otexrowstream ots(oss,texrow);
+	WriteStream wi(ots, false, false, WriteStream::wsDefault);
 	oss << "FormulaMacro\n";
 	write(wi);
 	os << to_utf8(oss.str());
diff --git a/src/mathed/MathStream.cpp b/src/mathed/MathStream.cpp
index ed23eb4..dabfc06 100644
--- a/src/mathed/MathStream.cpp
+++ b/src/mathed/MathStream.cpp
@@ -121,8 +121,8 @@ WriteStream & operator<<(WriteStream & ws, docstring const & s)
 }
 
 
-WriteStream::WriteStream(odocstream & os, bool fragile, bool latex, OutputType output,
-			Encoding const * encoding)
+WriteStream::WriteStream(otexrowstream & os, bool fragile, bool latex,
+						 OutputType output, Encoding const * encoding)
 	: os_(os), fragile_(fragile), firstitem_(false), latex_(latex),
 	  output_(output), pendingspace_(false), pendingbrace_(false),
 	  textmode_(false), locked_(0), ascii_(0), canbreakline_(true),
@@ -130,11 +130,11 @@ WriteStream::WriteStream(odocstream & os, bool fragile, bool latex, OutputType o
 {}
 
 
-WriteStream::WriteStream(odocstream & os)
+WriteStream::WriteStream(otexrowstream & os)
 	: os_(os), fragile_(false), firstitem_(false), latex_(false),
 	  output_(wsDefault), pendingspace_(false), pendingbrace_(false),
 	  textmode_(false), locked_(0), ascii_(0), canbreakline_(true),
-	  line_(0), encoding_(0)
+	  line_(0), encoding_(0) 
 {}
 
 
@@ -183,6 +183,29 @@ void WriteStream::asciiOnly(bool ascii)
 }
 
 
+void WriteStream::pushRowEntry(TexRow::RowEntry entry)
+{
+	outer_row_entries_.push_back(entry);
+}
+
+
+void WriteStream::popRowEntry()
+{
+	if (!outer_row_entries_.empty())
+		outer_row_entries_.pop_back();
+}
+
+
+bool WriteStream::startOuterRow()
+{
+	size_t n = outer_row_entries_.size();
+	if (n > 0)
+		return texrow().start(outer_row_entries_[n - 1]);
+	else
+		return false;
+}
+
+
 WriteStream & operator<<(WriteStream & ws, MathAtom const & at)
 {
 	at->write(ws);
diff --git a/src/mathed/MathStream.h b/src/mathed/MathStream.h
index ad1baad..6cfd585 100644
--- a/src/mathed/MathStream.h
+++ b/src/mathed/MathStream.h
@@ -17,6 +17,7 @@
 #include "InsetMath.h"
 // FIXME: Move to individual insets
 #include "MetricsInfo.h"
+#include "texstream.h"
 
 
 namespace lyx {
@@ -39,10 +40,10 @@ public:
 		wsPreview
 	};
 	///
-	WriteStream(odocstream & os, bool fragile, bool latex, OutputType output,
-		Encoding const * encoding = 0);
+	WriteStream(otexrowstream & os, bool fragile, bool latex, OutputType output,
+				Encoding const * encoding = 0);
 	///
-	explicit WriteStream(odocstream & os);
+	explicit WriteStream(otexrowstream & os);
 	///
 	~WriteStream();
 	///
@@ -54,7 +55,9 @@ public:
 	///
 	OutputType output() const { return output_; }
 	///
-	odocstream & os() { return os_; }
+	otexrowstream & os() { return os_; }
+	///
+	TexRow & texrow() { return os_.texrow(); }
 	///
 	bool & firstitem() { return firstitem_; }
 	///
@@ -85,9 +88,18 @@ public:
 	bool asciiOnly() const { return ascii_; }
 	/// LaTeX encoding
 	Encoding const * encoding() const { return encoding_; }
+
+	/// maintains a stack of texrow informations about outer math insets.
+	/// push an entry
+	void pushRowEntry(TexRow::RowEntry entry);
+	/// pop an entry
+	void popRowEntry();
+	/// TexRow::starts the innermost outer math inset
+	/// returns true if the outer row entry will appear at this line
+	bool startOuterRow();
 private:
 	///
-	odocstream & os_;
+	otexrowstream & os_;
 	/// do we have to write \\protect sometimes
 	bool fragile_;
 	/// are we at the beginning of an MathData?
@@ -112,6 +124,8 @@ private:
 	int line_;
 	///
 	Encoding const * encoding_;
+	///
+	std::vector<TexRow::RowEntry> outer_row_entries_;
 };
 
 ///
diff --git a/src/mathed/MathSupport.cpp b/src/mathed/MathSupport.cpp
index 1f90873..9801151 100644
--- a/src/mathed/MathSupport.cpp
+++ b/src/mathed/MathSupport.cpp
@@ -877,7 +877,9 @@ bool isAlphaSymbol(MathAtom const & at)
 docstring asString(MathData const & ar)
 {
 	odocstringstream os;
-	WriteStream ws(os);
+	TexRow texrow(false);
+	otexrowstream ots(os,texrow);
+	WriteStream ws(ots);
 	ws << ar;
 	return os.str();
 }
@@ -894,7 +896,9 @@ void asArray(docstring const & str, MathData & ar, Parse::flags pf)
 docstring asString(InsetMath const & inset)
 {
 	odocstringstream os;
-	WriteStream ws(os);
+	TexRow texrow(false);
+	otexrowstream ots(os,texrow);
+	WriteStream ws(ots);
 	inset.write(ws);
 	return os.str();
 }
@@ -903,7 +907,9 @@ docstring asString(InsetMath const & inset)
 docstring asString(MathAtom const & at)
 {
 	odocstringstream os;
-	WriteStream ws(os);
+	TexRow texrow(false);
+	otexrowstream ots(os,texrow);
+	WriteStream ws(ots);
 	at->write(ws);
 	return os.str();
 }
-- 
2.1.4

>From b877d914d2d14ccd355b0f5b136370b99cdd1bf1 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Sun, 11 Oct 2015 14:50:32 +0100
Subject: [PATCH 5/8] TexRow info in source panel and gotoCursor() for
 debugging

These features are active in DEVEL_VERSION when Debug is set to LATEX.

1. The TexRow information is prepended to the source panel.

2. Clicking on any line in the source triggers reverse search. (This would be an
interesting feature to implement on the user side, but we need a proper LFUN.)
---
 src/BufferView.cpp                  |  8 ++++-
 src/BufferView.h                    |  3 ++
 src/frontends/qt4/GuiViewSource.cpp | 60 ++++++++++++++++++++++++++++++-------
 src/frontends/qt4/GuiViewSource.h   | 11 ++++---
 src/mathed/InsetMathHull.cpp        |  2 +-
 5 files changed, 68 insertions(+), 16 deletions(-)

diff --git a/src/BufferView.cpp b/src/BufferView.cpp
index b07eb2b..0bc3c05 100644
--- a/src/BufferView.cpp
+++ b/src/BufferView.cpp
@@ -2357,12 +2357,18 @@ int BufferView::scrollUp(int offset)
 
 void BufferView::setCursorFromRow(int row)
 {
+	setCursorFromRow(row, buffer_.texrow());
+}
+
+
+void BufferView::setCursorFromRow(int row, TexRow const & texrow)
+{
 	int tmpid;
 	int tmppos;
 	pit_type newpit = 0;
 	pos_type newpos = 0;
 
-	buffer_.texrow().getIdFromRow(row, tmpid, tmppos);
+	texrow.getIdFromRow(row, tmpid, tmppos);
 
 	bool posvalid = (tmpid != -1);
 	if (posvalid) {
diff --git a/src/BufferView.h b/src/BufferView.h
index bfdb0ba..15d2c6b 100644
--- a/src/BufferView.h
+++ b/src/BufferView.h
@@ -44,6 +44,7 @@ class PainterInfo;
 class ParIterator;
 class ParagraphMetrics;
 class Point;
+class TexRow;
 class Text;
 class TextMetrics;
 
@@ -159,6 +160,8 @@ public:
 
 	/// set the cursor based on the given TeX source row.
 	void setCursorFromRow(int row);
+	///
+	void setCursorFromRow(int row, TexRow const & texrow);
 
 	/// set cursor to the given inset. Return true if found.
 	bool setCursorFromInset(Inset const *);
diff --git a/src/frontends/qt4/GuiViewSource.cpp b/src/frontends/qt4/GuiViewSource.cpp
index 1695664..c908a2e 100644
--- a/src/frontends/qt4/GuiViewSource.cpp
+++ b/src/frontends/qt4/GuiViewSource.cpp
@@ -26,6 +26,7 @@
 #include "support/debug.h"
 #include "support/lassert.h"
 #include "support/docstream.h"
+#include "support/docstring_list.h"
 #include "support/gettext.h"
 
 #include <boost/crc.hpp>
@@ -64,6 +65,11 @@ ViewSourceWidget::ViewSourceWidget()
 		this, SLOT(setViewFormat(int)));
 	connect(outputFormatCO, SIGNAL(activated(int)),
 		this, SLOT(contentsChanged()));
+#ifdef DEVEL_VERSION
+	if (lyx::lyxerr.debugging(Debug::LATEX))
+		connect(viewSourceTV, SIGNAL(cursorPositionChanged()),
+				this, SLOT(gotoCursor()));
+#endif
 
 	// setting the update timer
 	update_timer_->setSingleShot(true);
@@ -89,7 +95,7 @@ ViewSourceWidget::ViewSourceWidget()
 }
 
 
-auto_ptr<TexRow> ViewSourceWidget::getContent(BufferView const * view,
+void ViewSourceWidget::getContent(BufferView const * view,
 			Buffer::OutputWhat output, docstring & str, string const & format,
 			bool master)
 {
@@ -108,11 +114,10 @@ auto_ptr<TexRow> ViewSourceWidget::getContent(BufferView const * view,
 	if (par_begin > par_end)
 		swap(par_begin, par_end);
 	odocstringstream ostr;
-	auto_ptr<TexRow> texrow = view->buffer().getSourceCode(ostr, format,
-								    par_begin, par_end + 1, output, master);
+	texrow_ = view->buffer().getSourceCode(ostr, format,
+										par_begin, par_end + 1, output, master);
 	//ensure that the last line can always be selected in its full width
 	str = ostr.str() + "\n";
-	return texrow;
 }
 
 
@@ -164,6 +169,23 @@ void ViewSourceWidget::updateViewNow()
 	update_timer_->start(0);
 }
 
+namespace {
+
+QString prependTexRow(TexRow const & texrow, QString const & content)
+{
+	QStringList list = content.split(QChar('\n'));
+	docstring_list dlist;
+	for (QStringList::const_iterator it = list.begin(); it != list.end(); ++it)
+		dlist.push_back(from_utf8(fromqstr(*it)));
+	texrow.prepend(dlist);
+	QString qstr;
+	for (docstring_list::iterator it = dlist.begin(); it != dlist.end(); ++it)
+		qstr += toqstr(*it) + '\n';
+	return qstr;
+}
+
+} // anon namespace
+
 void ViewSourceWidget::realUpdateView()
 {
 	if (!bv_) {
@@ -191,13 +213,18 @@ void ViewSourceWidget::realUpdateView()
 		output = Buffer::OnlyBody;
 
 	docstring content;
-	auto_ptr<TexRow> texrow = getContent(bv_, output, content, format,
-									   masterPerspectiveCB->isChecked());
+	getContent(bv_, output, content, format, masterPerspectiveCB->isChecked());
 	QString old = document_->toPlainText();
 	QString qcontent = toqstr(content);
+#ifdef DEVEL_VERSION
+	if (texrow_.get() && lyx::lyxerr.debugging(Debug::LATEX))
+		qcontent = prependTexRow(*texrow_, qcontent);
+#endif
+	// prevent gotoCursor()
+	viewSourceTV->blockSignals(true);
 	bool const changed = setText(qcontent);
 
-	if (changed && !texrow.get()) {
+	if (changed && !texrow_.get()) {
 		// position-to-row is unavailable
 		// we jump to the first modification
 		const QChar * oc = old.constData();
@@ -227,18 +254,18 @@ void ViewSourceWidget::realUpdateView()
 		//c.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor,1);
 		viewSourceTV->setTextCursor(c);
 
-	} else if (texrow.get()) {
+	} else if (texrow_.get()) {
 		// Use the available position-to-row conversion to highlight
 		// the current selection in the source
 		int beg_row, end_row;
 		{
 			DocIterator beg = bv_->cursor().selectionBegin();
 			DocIterator end = bv_->cursor().selectionEnd();
-			std::pair<int,int> beg_rows = texrow->rowFromDocIterator(beg);
+			std::pair<int,int> beg_rows = texrow_->rowFromDocIterator(beg);
 			beg_row = beg_rows.first;
 			if (beg != end) {
 				end.backwardChar();
-				std::pair<int,int> end_rows = texrow->rowFromDocIterator(end);
+				std::pair<int,int> end_rows = texrow_->rowFromDocIterator(end);
 				end_row = end_rows.second;
 			} else {
 				end_row = beg_rows.second;
@@ -290,9 +317,22 @@ void ViewSourceWidget::realUpdateView()
 		viewSourceTV->setTextCursor(c);
 		viewSourceTV->horizontalScrollBar()->setValue(h_scroll);
 	} // else if (texrow)
+	viewSourceTV->blockSignals(false);
 }
 
 
+// only used in DEVEL_MODE for debugging
+// need a proper LFUN if we want to implement it in release mode
+void ViewSourceWidget::gotoCursor()
+{
+	if (!bv_ || !texrow_.get())
+		return;
+	int row = viewSourceTV->textCursor().blockNumber() + 1;
+	const_cast<BufferView *>(bv_)->setCursorFromRow(row, *texrow_);
+}
+
+
+
 void ViewSourceWidget::updateDefaultFormat()
 {
 	if (!bv_)
diff --git a/src/frontends/qt4/GuiViewSource.h b/src/frontends/qt4/GuiViewSource.h
index 7b451a6..086bb9d 100644
--- a/src/frontends/qt4/GuiViewSource.h
+++ b/src/frontends/qt4/GuiViewSource.h
@@ -62,6 +62,8 @@ public Q_SLOTS:
 	void updateDefaultFormat();
 	///
 	void contentsChanged();
+	///
+	void gotoCursor();
 
 private Q_SLOTS:
 	/// update content
@@ -69,10 +71,8 @@ private Q_SLOTS:
 
 private:
 	/// Get the source code of selected paragraphs, or the whole document.
-	/// If TexRow is unavailable for the format then t is null.
-	std::auto_ptr<TexRow> getContent(BufferView const * view,
-									 Buffer::OutputWhat output, docstring & str,
-									 std::string const & format, bool master);
+	void getContent(BufferView const * view, Buffer::OutputWhat output,
+			   docstring & str, std::string const & format, bool master);
 	///
 	BufferView const * bv_;
 	///
@@ -83,6 +83,9 @@ private:
 	QString view_format_;
 	///
 	QTimer * update_timer_;
+	/// TexRow information from the last source view. If TexRow is unavailable
+	/// for the last format then texrow_ is null.
+	std::auto_ptr<TexRow> texrow_; 
 };
 
 
diff --git a/src/mathed/InsetMathHull.cpp b/src/mathed/InsetMathHull.cpp
index 08c0dec..6ff8833 100644
--- a/src/mathed/InsetMathHull.cpp
+++ b/src/mathed/InsetMathHull.cpp
@@ -927,7 +927,7 @@ void InsetMathHull::validate(LaTeXFeatures & features) const
 void InsetMathHull::header_write(WriteStream & os) const
 {
 	bool n = numberedType();
-	
+
 	switch(type_) {
 	case hullNone:
 		break;
-- 
2.1.4

>From 8962cbd78a2272762505cdb20f837b9d52418143 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Mon, 12 Oct 2015 23:31:15 +0100
Subject: [PATCH 6/8] Remove a deep copy of MathData in lyx::write

This deep copy messes with the unique identifier: what TexRow saw was different
from the original uid.

In case this call to extractStrings is just for optimisation, then it is
probably quicker not do anything at all, but in doubt about its purpose, I
inlined the string extraction so as to avoid the deep copy.

This improves performance, in the same scale as if the call to extractStrings
was removed naively. I do not know if the improvement is noticeable, but after
this optimisation, the uid counter grew half as fast.
---
 src/TexRow.h              |  2 +-
 src/mathed/MathExtern.cpp | 17 +++++++++++++----
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/src/TexRow.h b/src/TexRow.h
index 1710574..0eb8f18 100644
--- a/src/TexRow.h
+++ b/src/TexRow.h
@@ -39,7 +39,7 @@ typedef size_t idx_type;
 
 // Negative values denote invalid ids or 'not found'
 // Do we need 64 bits? The unique identifier for math insets went above
-// 3'000'000 just after opening a document, and 6'000'000 after a few
+// 1'400'000 just after opening a document, and 2'000'000 after a few
 // copy-pastes...
 #ifdef LYX_ENABLE_CXX11
 typedef std::int64_t uid_type;
diff --git a/src/mathed/MathExtern.cpp b/src/mathed/MathExtern.cpp
index b98097a..4e8f230 100644
--- a/src/mathed/MathExtern.cpp
+++ b/src/mathed/MathExtern.cpp
@@ -1382,11 +1382,20 @@ namespace {
 
 void write(MathData const & dat, WriteStream & wi)
 {
-	MathData ar = dat;
-	extractStrings(ar);
 	wi.firstitem() = true;
-	for (MathData::const_iterator it = ar.begin(); it != ar.end(); ++it) {
-		//		wi.startOuterRow();
+	for (MathData::const_iterator it = dat.begin(); it != dat.end(); ++it) {
+		// extracting strings when possible
+		docstring s;
+		for (; it != dat.end() && (*it)->asCharInset(); ++it) {
+			s += (*it)->getChar();
+		}
+		if (!s.empty()) {
+			InsetMathString ms(s);
+			ms.write(wi);
+			if (it == dat.end())
+				break;
+		}
+		// here: it != dat.end() && !(*it)->asCharInset()
 		(*it)->write(wi);
 		wi.firstitem() = false;
 	}
-- 
2.1.4

>From 14d09e4f8a9517b82be5f9d29658270a028191dd Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Tue, 13 Oct 2015 20:26:21 +0100
Subject: [PATCH 7/8] Improve the TexRow Cursor->Row algorithm for selections.

Selections were incorrect after the addition of math due to the invalidation of
an invariant.
---
 src/TexRow.cpp                      | 18 +++++++++++++++++-
 src/TexRow.h                        |  6 ++++++
 src/frontends/qt4/GuiViewSource.cpp | 17 +++--------------
 3 files changed, 26 insertions(+), 15 deletions(-)

diff --git a/src/TexRow.cpp b/src/TexRow.cpp
index 0ffdc29..2efb1f8 100644
--- a/src/TexRow.cpp
+++ b/src/TexRow.cpp
@@ -13,7 +13,7 @@
 
 #include <config.h>
 
-#include "DocIterator.h"
+#include "Cursor.h"
 #include "Paragraph.h"
 #include "TexRow.h"
 
@@ -403,6 +403,22 @@ std::pair<int,int> TexRow::rowFromDocIterator(DocIterator const & dit) const
 }
 
 
+std::pair<int,int> TexRow::rowFromCursor(Cursor const & cur) const
+{
+	DocIterator beg = cur.selectionBegin();
+	std::pair<int,int> beg_rows = rowFromDocIterator(beg);
+	if (cur.selection()) {
+		DocIterator end = cur.selectionEnd();
+		if (!cur.selIsMultiCell())
+			end.top().backwardPos();	
+		std::pair<int,int> end_rows = rowFromDocIterator(end);
+		return std::make_pair(std::min(beg_rows.first, end_rows.first),
+							  std::max(beg_rows.second, end_rows.second));
+	} else
+		return std::make_pair(beg_rows.first, beg_rows.second);
+}
+
+
 // debugging functions
 
 ///
diff --git a/src/TexRow.h b/src/TexRow.h
index 0eb8f18..87581ce 100644
--- a/src/TexRow.h
+++ b/src/TexRow.h
@@ -30,6 +30,7 @@
 namespace lyx {
 
 class LyXErr;
+class Cursor;
 class CursorSlice;
 class DocIterator;
 class docstring_list;
@@ -180,6 +181,11 @@ public:
 	/// Finds the best pair of rows for dit
 	/// returns (-1,-1) if not found.
 	std::pair<int,int> rowFromDocIterator(DocIterator const & dit) const;
+
+	/// Finds the best pair of rows for cursor, taking the selection into
+	/// account
+	/// returns (-1,-1) if not found.
+	std::pair<int,int> rowFromCursor(Cursor const & dit) const;
 	
 	/// Returns the number of rows contained
 	int rows() const { return rowlist_.size(); }
diff --git a/src/frontends/qt4/GuiViewSource.cpp b/src/frontends/qt4/GuiViewSource.cpp
index c908a2e..4a0381d 100644
--- a/src/frontends/qt4/GuiViewSource.cpp
+++ b/src/frontends/qt4/GuiViewSource.cpp
@@ -257,20 +257,9 @@ void ViewSourceWidget::realUpdateView()
 	} else if (texrow_.get()) {
 		// Use the available position-to-row conversion to highlight
 		// the current selection in the source
-		int beg_row, end_row;
-		{
-			DocIterator beg = bv_->cursor().selectionBegin();
-			DocIterator end = bv_->cursor().selectionEnd();
-			std::pair<int,int> beg_rows = texrow_->rowFromDocIterator(beg);
-			beg_row = beg_rows.first;
-			if (beg != end) {
-				end.backwardChar();
-				std::pair<int,int> end_rows = texrow_->rowFromDocIterator(end);
-				end_row = end_rows.second;
-			} else {
-				end_row = beg_rows.second;
-			}
-		}
+		std::pair<int,int> rows = texrow_->rowFromCursor(bv_->cursor());
+		int const beg_row = rows.first;
+		int const end_row = rows.second;
 
 		QTextCursor c = QTextCursor(viewSourceTV->document());
 
-- 
2.1.4

>From 7b2a23c7dd937308a153347ab64d7eda9335d910 Mon Sep 17 00:00:00 2001
From: Guillaume Munch <g...@lyx.org>
Date: Tue, 13 Oct 2015 23:51:50 +0100
Subject: [PATCH 8/8] Add cursor<->row correspondance tracking for tables and
 subcaptions.

Please use the new function otexstream.append(str, texrow) to append an
odocstringstream with texrow information to the output when outputing to a
string buffer (e.g. case of subcaptions).
---
 src/TexRow.cpp              | 53 +++++++++++++++++++++++++++++++++++++++++----
 src/TexRow.h                | 34 +++++++++++++++++++----------
 src/insets/InsetCaption.cpp |  2 ++
 src/insets/InsetFloat.cpp   | 40 ++++++++++++++++++++--------------
 src/insets/InsetFloat.h     |  2 ++
 src/insets/InsetTabular.cpp |  3 +++
 src/texstream.cpp           |  9 ++++++++
 src/texstream.h             |  2 ++
 8 files changed, 114 insertions(+), 31 deletions(-)

diff --git a/src/TexRow.cpp b/src/TexRow.cpp
index 2efb1f8..a3fdd40 100644
--- a/src/TexRow.cpp
+++ b/src/TexRow.cpp
@@ -37,9 +37,15 @@ bool TexRow::RowEntryList::addEntry(RowEntry const & entry)
 		else
 			text_entry_ = size();
 	}
+	forceAddEntry(entry);
+	return true;
+}
+
+
+void TexRow::RowEntryList::forceAddEntry(RowEntry const & entry)
+{
 	if (size() == 0 || !(operator[](size() - 1) == entry))
 		push_back(RowEntry(entry));
-	return true;
 }
 
 
@@ -59,6 +65,14 @@ TexRow::RowEntry TexRow::RowEntryList::entry() const
 }
 
 
+void TexRow::RowEntryList::append(RowEntryList const & row)
+{
+	if (text_entry_ >= size())
+		text_entry_ = row.text_entry_ + size();
+	insert(end(), row.begin(), row.end());
+}
+
+
 TexRow::TextEntry const TexRow::text_none = { -1, 0 };
 TexRow::RowEntry const TexRow::row_none = { false, TexRow::text_none };
 
@@ -129,9 +143,17 @@ bool TexRow::start(int id, int pos)
 }
 
 
-bool TexRow::startMath(uid_type id, idx_type cell)
+void TexRow::forceStart(int id, int pos)
 {
-	return start(mathEntry(id,cell));
+	if (!enabled_)
+		return;
+	return current_row_.forceAddEntry(textEntry(id,pos));
+}
+
+
+void TexRow::startMath(uid_type id, idx_type cell)
+{
+	start(mathEntry(id,cell));
 }
 
 
@@ -160,6 +182,24 @@ void TexRow::finalize()
 }
 
 
+void TexRow::append(TexRow const & texrow)
+{
+	if (!enabled_ || !texrow.enabled_)
+		return;
+	RowList::const_iterator it = texrow.rowlist_.begin();
+	RowList::const_iterator const end = texrow.rowlist_.end();
+	if (it == end) {
+		current_row_.append(texrow.current_row_);
+	} else {
+		current_row_.append(*it++);
+		rowlist_.push_back(current_row_);
+		rowlist_.insert(rowlist_.end(), it, end);
+		current_row_ = texrow.current_row_;
+	}
+}
+
+
+
 bool TexRow::getIdFromRow(int row, int & id, int & pos) const
 {
 	while (row > 0
@@ -409,7 +449,12 @@ std::pair<int,int> TexRow::rowFromCursor(Cursor const & cur) const
 	std::pair<int,int> beg_rows = rowFromDocIterator(beg);
 	if (cur.selection()) {
 		DocIterator end = cur.selectionEnd();
-		if (!cur.selIsMultiCell())
+		if (!cur.selIsMultiCell()
+			// backwardPos asserts without the following test, IMO it's not my
+			// duty to check this.
+			&& (end.top().pit() != 0
+				|| end.top().idx() != 0
+				|| end.top().pos() != 0))
 			end.top().backwardPos();	
 		std::pair<int,int> end_rows = rowFromDocIterator(end);
 		return std::make_pair(std::min(beg_rows.first, end_rows.first),
diff --git a/src/TexRow.h b/src/TexRow.h
index 87581ce..03a4535 100644
--- a/src/TexRow.h
+++ b/src/TexRow.h
@@ -93,24 +93,25 @@ public:
 			struct MathEntry math;
 		};
 	};
-	// For each row we store a list of one TextEntry and several
-	// MathEntries. (The order is important.)  We only want one text entry
+	// For each row we store a list of one special TextEntry and several
+	// RowEntries. (The order is important.)  We only want one text entry
 	// because we do not want to store every position in the lyx file. On the
-	// other hand we want to record all math cells positions for enough
-	// precision. Usually the count of math cells is easier to handle.
+	// other hand we want to record all math and table cells positions for
+	// enough precision. Usually the count of cells is easier to handle.
 	class RowEntryList : public std::vector<RowEntry> {
 	public:
 		RowEntryList() : std::vector<RowEntry>(), text_entry_(-1) {}
 		// returns true if the row entry will appear in the row entry list
 		bool addEntry(RowEntry const &);
-		// returns true if the text entry will appear in the row entry list
-		//bool addTextEntry(TextEntry const &);
-		// returns true because the math entry will always appears
-		//bool addMathEntry(MathEntry const &);
+		// the row entry will appear in the row entry list, but it never counts
+		// as a proper text entry.
+		void forceAddEntry(RowEntry const &);
 		// returns the TextEntry or TexRow::text_none if none
 		TextEntry getTextEntry() const;
 		// returns the first entry, or TexRow::row_none if none
 		RowEntry entry() const;
+		// appends a row
+		void append(RowEntryList const &);
 	private:
 		size_t text_entry_;
 	};
@@ -152,10 +153,17 @@ public:
 	/// Defines the paragraph and position for the current line
 	/// returns true if this entry will appear on the current row
 	bool start(int id, int pos);
+	
+	/// Defines a cell and position for the current line.  Always appear in the
+	/// current row.
+	void startMath(uid_type id, idx_type cell);
 
-	/// Defines a cell and position for the current line
-	/// returns true if this entry will appear on the current row
-	bool startMath(uid_type id, idx_type cell);
+	/// Defines the paragraph for the current cell-like inset.  Always appears
+	/// in the current row like a math cell, but is detached from the normal
+	/// text flow. Note: since the cell idx is not recorded it does not work as
+	/// well as for math grids; if we were to do that properly we would need to
+	/// access the id of the parent Tabular inset from the CursorSlice.
+	void forceStart(int id, int pos);
 
 	/// Insert node when line is completed
 	void newline();
@@ -190,6 +198,10 @@ public:
 	/// Returns the number of rows contained
 	int rows() const { return rowlist_.size(); }
 
+	/// appends texrow. the final line of this is merged with the first line of
+	/// texrow.
+	void append(TexRow const & texrow);
+
 	/// for debugging purpose
 	void prepend(docstring_list &) const;
 
diff --git a/src/insets/InsetCaption.cpp b/src/insets/InsetCaption.cpp
index 93abfe4..21b834a 100644
--- a/src/insets/InsetCaption.cpp
+++ b/src/insets/InsetCaption.cpp
@@ -329,6 +329,8 @@ void InsetCaption::getArgument(otexstream & os,
 	rp.par_end = paragraphs().size();
 
 	// Output the contents of the inset
+	if (!paragraphs().empty())
+		os.texrow().forceStart(paragraphs()[0].id(), 0);
 	latexParagraphs(buffer(), text(), os, rp);
 	runparams.encoding = rp.encoding;
 
diff --git a/src/insets/InsetFloat.cpp b/src/insets/InsetFloat.cpp
index 1cda8d9..03fdf03 100644
--- a/src/insets/InsetFloat.cpp
+++ b/src/insets/InsetFloat.cpp
@@ -327,12 +327,11 @@ void InsetFloat::latex(otexstream & os, OutputParams const & runparams_in) const
 
 		OutputParams rp = runparams_in;
 		rp.moving_arg = true;
-		docstring const caption = getCaption(rp);
-		if (!caption.empty()) {
-			os << caption;
-		}
+		getCaption(os, rp);
 		os << '{';
 		// The main argument is the contents of the float. This is not a moving argument.
+		if (!paragraphs().empty())
+			os.texrow().forceStart(paragraphs()[0].id(), 0);
 		rp.moving_arg = false;
 		rp.inFloat = OutputParams::SUBFLOAT;
 		InsetText::latex(os, rp);
@@ -494,28 +493,37 @@ bool InsetFloat::allowsCaptionVariation(std::string const & newtype) const
 
 docstring InsetFloat::getCaption(OutputParams const & runparams) const
 {
+	TexRow texrow(false);
+	odocstringstream ods;
+	otexstream os(ods, texrow);
+	getCaption(os, runparams);
+	return ods.str();
+}
+
+
+void InsetFloat::getCaption(otexstream & os,
+							OutputParams const & runparams) const
+{
 	if (paragraphs().empty())
-		return docstring();
+		return;
 
 	InsetCaption const * ins = getCaptionInset();
 	if (ins == 0)
-		return docstring();
+		return;
 
-	TexRow texrow;
-	odocstringstream ods;
-	otexstream os(ods, texrow);
 	ins->getArgs(os, runparams);
-	ods << '[';
-	odocstringstream odss;
-	otexstream oss(odss, texrow);
+	
+	os << '[';
+	TexRow texrow;
+	odocstringstream ods;
+	otexstream oss(ods, texrow);
 	ins->getArgument(oss, runparams);
-	docstring arg = odss.str();
+	docstring arg = ods.str();
 	// Protect ']'
 	if (arg.find(']') != docstring::npos)
 		arg = '{' + arg + '}';
-	ods << arg;
-	ods << ']';
-	return ods.str();
+	os.append(arg, texrow);
+	os << ']';
 }
 
 
diff --git a/src/insets/InsetFloat.h b/src/insets/InsetFloat.h
index c58be58..9c20267 100644
--- a/src/insets/InsetFloat.h
+++ b/src/insets/InsetFloat.h
@@ -109,6 +109,8 @@ private:
 	///
 	docstring getCaption(OutputParams const &) const;
 	///
+	void getCaption(otexstream & os, OutputParams const & runparams) const;
+
 	InsetFloatParams params_;
 };
 
diff --git a/src/insets/InsetTabular.cpp b/src/insets/InsetTabular.cpp
index d92acf2..e827bfd 100644
--- a/src/insets/InsetTabular.cpp
+++ b/src/insets/InsetTabular.cpp
@@ -2572,6 +2572,9 @@ void Tabular::TeXRow(otexstream & os, row_type row,
 		shared_ptr<InsetTableCell> inset = cellInset(cell);
 
 		Paragraph const & par = inset->paragraphs().front();
+
+		os.texrow().forceStart(par.id(), 0);
+
 		bool rtl = par.isRTL(buffer().params())
 			&& !par.empty()
 			&& getPWidth(cell).zero()
diff --git a/src/texstream.cpp b/src/texstream.cpp
index 84fb060..3268dca 100644
--- a/src/texstream.cpp
+++ b/src/texstream.cpp
@@ -29,6 +29,14 @@ using lyx::support::split;
 
 namespace lyx {
 
+
+void otexrowstream::append(docstring const & str, TexRow const & texrow)
+{
+	os_ << str;
+	texrow_.append(texrow);
+}
+
+
 void otexrowstream::put(char_type const & c)
 {
 	os_.put(c);
@@ -36,6 +44,7 @@ void otexrowstream::put(char_type const & c)
 		texrow_.newline();
 }
 
+
 void otexstream::put(char_type const & c)
 {
 	if (protectspace_) {
diff --git a/src/texstream.h b/src/texstream.h
index 4fff211..c81bbf9 100644
--- a/src/texstream.h
+++ b/src/texstream.h
@@ -33,6 +33,8 @@ public:
 	TexRow & texrow() { return texrow_; }
 	///
 	void put(char_type const & c);
+	///
+	void append(docstring const &, TexRow const &);
 private:
 	///
 	odocstream & os_;
-- 
2.1.4

Reply via email to