From 65d61e7a2786172da95ed9433ed0c49a7398f405 Mon Sep 17 00:00:00 2001 From: Guillaume Munch Date: Wed, 14 Oct 2015 00:17:05 +0100 Subject: [PATCH] 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 | 408 ++++++++++++++++++++++++++++++++++++++++------ src/TexRow.h | 135 +++++++++++---- 3 files changed, 462 insertions(+), 83 deletions(-) diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 8ec57a5511..5cea164d63 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 173634b003..a34acf0874 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,30 +17,122 @@ #include "Paragraph.h" #include "TexRow.h" +#include "mathed/InsetMath.h" + #include "support/debug.h" +#include "support/docstring_list.h" #include +#include 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 +140,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 +160,291 @@ void TexRow::finalize() newline(); } + bool TexRow::getIdFromRow(int row, int & id, int & pos) const { - if (row <= 0 || row > int(rowlist.size())) { - id = -1; - pos = 0; - return false; + TextEntry t = text_none; + bool ret = false; + if (row <= int(rowlist_.size())) + while (row > 0 && isNone(t = rowlist_[row - 1].getTextEntry())) + --row; + if (row > 0) + ret = true; + id = t.id; + pos = t.pos; + return ret; +} + + +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; +} - id = rowlist[row - 1].id(); - pos = rowlist[row - 1].pos(); - return true; + +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 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::const_iterator it = rowlist_.begin(); + std::vector::const_iterator const beg = rowlist_.begin(); + std::vector::const_iterator const end = rowlist_.end(); + for (; it < end; ++it) { + docstring entry; + std::vector::const_iterator it2 = it->begin(); + std::vector::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 9b21d45a64..47a7d74bee 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. */ @@ -14,6 +15,7 @@ #ifndef TEXROW_H #define TEXROW_H +#include "support/types.h" #include "support/debug.h" #include @@ -21,26 +23,103 @@ namespace lyx { class LyXErr; +class CursorSlice; class DocIterator; +class docstring_list; +/// types for cells and math insets typedef void const * uid_type; +typedef size_t idx_type; + /// Represents the correspondence between paragraphs 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 { + public: + RowEntryList() : std::vector(), text_entry_(-1) {} + + // returns true if the row entry will appear in the row entry list + bool addEntry(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; + + private: + size_t text_entry_; + }; + + /// Returns true if RowEntry is devoid of information + static bool isNone(RowEntry const &); + static const TextEntry text_none; + static const RowEntry row_none; + + /// 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 in pos or cell index + /// assumes it is the sameParOrInsetMath + static int comparePos(RowEntry const & entry1, RowEntry const & entry2); + + /// for debugging purposes + 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 + /// Clears structure. Set enable to false if texrow is not needed, to avoid + /// computing TexRow when it is going to be immediately discarded. 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(); @@ -68,41 +147,29 @@ public: std::pair rowFromDocIterator(DocIterator const & dit) const; /// Returns the number of rows contained - int rows() const { return rowlist.size(); } + int rows() const { return rowlist_.size(); } + + /// for debugging purpose + void prepend(docstring_list &) const; - /// an individual id/pos <=> row mapping - class RowItem { - public: - RowItem(int id, int pos) - : id_(id), pos_(pos) - {} - - /// 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 RowList; private: + typedef std::vector 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.39.5