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).
This commit is contained in:
Guillaume Munch 2015-10-14 00:17:05 +01:00
parent e2d7a4534a
commit 65d61e7a27
3 changed files with 461 additions and 82 deletions

View File

@ -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)

View File

@ -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 <algorithm>
#include <sstream>
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;
}
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();
}
id = rowlist[row - 1].id();
pos = rowlist[row - 1].pos();
return true;
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;

View File

@ -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 <vector>
@ -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<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 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<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 &);