3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Matthias Ettrich
7 * \author Lars Gullik Bjønnes
9 * \author Guillaume Munch
11 * Full author contact details are available in file CREDITS.
18 #include "FuncRequest.h"
19 #include "Paragraph.h"
22 #include "mathed/InsetMath.h"
24 #include "support/convert.h"
25 #include "support/debug.h"
26 #include "support/docstring_list.h"
27 #include "support/lassert.h"
39 TexString::TexString(docstring s)
40 : str(move(s)), texrow(TexRow())
42 texrow.setRows(1 + count(str.begin(), str.end(), '\n'));
46 TexString::TexString(docstring s, TexRow t)
47 : str(move(s)), texrow(move(t))
53 void TexString::validate()
55 size_t lines = 1 + count(str.begin(), str.end(), '\n');
56 size_t rows = texrow.rows();
57 bool valid = lines == rows;
59 LYXERR0("TexString has " << lines << " lines but " << rows << " rows." );
60 // Assert in devel mode. This is important to catch bugs early, otherwise
61 // they might be hard to notice and find. Recover gracefully in release
63 LASSERT(valid, texrow.setRows(lines));
67 bool TexRow::RowEntryList::addEntry(RowEntry entry)
71 if (isNone(text_entry_))
72 text_entry_ = entry.text;
73 else if (!v_.empty() && TexRow::sameParOrInsetMath(v_.back(), entry))
84 void TexRow::RowEntryList::forceAddEntry(RowEntry entry)
86 if (v_.empty() || !(v_.back() == entry))
91 TexRow::TextEntry TexRow::RowEntryList::getTextEntry() const
93 if (!isNone(text_entry_))
95 return TexRow::text_none;
99 void TexRow::RowEntryList::append(RowEntryList row)
101 if (isNone(text_entry_))
102 text_entry_ = row.text_entry_;
103 move(row.begin(), row.end(), back_inserter(v_));
113 TexRow::TextEntry const TexRow::text_none = { -1, 0 };
114 TexRow::RowEntry const TexRow::row_none = TexRow::textEntry(-1, 0);
118 bool TexRow::isNone(TextEntry t)
125 bool TexRow::isNone(RowEntry r)
127 return r.type == text_entry && isNone(r.text);
138 TexRow::RowEntryList & TexRow::currentRow()
140 return rowlist_.back();
145 TexRow::RowEntry TexRow::textEntry(int id, pos_type pos)
148 entry.type = text_entry;
149 entry.text.pos = pos;
156 TexRow::RowEntry TexRow::mathEntry(uid_type id, idx_type cell)
159 entry.type = math_entry;
160 entry.math.cell = cell;
167 TexRow::RowEntry TexRow::beginDocument()
170 entry.type = begin_document;
171 entry.begindocument = {};
176 bool operator==(TexRow::RowEntry entry1, TexRow::RowEntry entry2)
178 if (entry1.type != entry2.type)
180 switch (entry1.type) {
181 case TexRow::text_entry:
182 return entry1.text.id == entry2.text.id
183 && entry1.text.pos == entry2.text.pos;
184 case TexRow::math_entry:
185 return entry1.math.id == entry2.math.id
186 && entry1.math.cell == entry2.math.cell;
187 case TexRow::begin_document:
195 bool TexRow::start(RowEntry entry)
197 return currentRow().addEntry(entry);
201 bool TexRow::start(int id, pos_type pos)
203 return start(textEntry(id,pos));
207 void TexRow::forceStart(int id, pos_type pos)
209 return currentRow().forceAddEntry(textEntry(id,pos));
213 void TexRow::startMath(uid_type id, idx_type cell)
215 start(mathEntry(id,cell));
219 void TexRow::newline()
221 rowlist_.push_back(RowEntryList());
225 void TexRow::newlines(size_t num_lines)
232 void TexRow::append(TexRow other)
234 RowList::iterator it = other.rowlist_.begin();
235 RowList::iterator const end = other.rowlist_.end();
236 LASSERT(it != end, return);
237 currentRow().append(move(*it++));
238 move(it, end, back_inserter(rowlist_));
242 pair<TexRow::TextEntry, TexRow::TextEntry>
243 TexRow::getEntriesFromRow(int const row) const
245 // FIXME: Take math entries into account, take table cells into account and
246 // get rid of the ad hoc special text entry for each row.
248 // FIXME: A yellow note alone on its paragraph makes the reverse-search on
249 // the subsequent line inaccurate. Get rid of text entries that
250 // correspond to no output by delaying their addition, at the level
251 // of otexrowstream, until a character is actually output.
253 LYXERR(Debug::LATEX, "getEntriesFromRow: row " << row << " requested");
255 // check bounds for row - 1, our target index
257 return {text_none, text_none};
258 size_t const i = static_cast<size_t>(row - 1);
259 if (i >= rowlist_.size())
260 return {text_none, text_none};
262 // find the start entry
263 TextEntry const start = [&]() {
264 for (size_t j = i; j > 0; --j) {
265 if (!isNone(rowlist_[j].getTextEntry()))
266 return rowlist_[j].getTextEntry();
267 // Check the absence of begin_document at row j. The begin_document row
268 // entry is used to prevent mixing of body and preamble.
269 for (RowEntry entry : rowlist_[j])
270 if (entry.type == begin_document)
276 // find the end entry
277 TextEntry end = [&]() {
280 // select up to the last position of the starting paragraph as a
282 TextEntry last_pos = {start.id, -1};
283 // find the next occurence of paragraph start.id
284 for (size_t j = i + 1; j < rowlist_.size(); ++j) {
285 for (RowEntry entry : rowlist_[j]) {
286 if (entry.type == begin_document)
287 // what happens in the preamble remains in the preamble
289 if (entry.type == text_entry && entry.text.id == start.id)
296 // The following occurs for a displayed math inset for instance (for good
297 // reasons involving subtleties of the algorithm in getRowFromDocIterator).
298 // We want this inset selected.
299 if (start.id == end.id && start.pos == end.pos)
306 pair<DocIterator, DocIterator> TexRow::getDocIteratorsFromRow(
308 Buffer const & buf) const
310 TextEntry start, end;
311 tie(start,end) = getEntriesFromRow(row);
312 return getDocIteratorsFromEntries(start, end, buf);
317 pair<DocIterator, DocIterator> TexRow::getDocIteratorsFromEntries(
322 auto set_pos = [](DocIterator & dit, pos_type pos) {
323 dit.pos() = (pos >= 0) ? min(pos, dit.lastpos())
324 // negative pos values are counted from the end
325 : max(dit.lastpos() + pos + 1, pos_type(0));
328 DocIterator dit_start = buf.getParFromID(start.id);
330 set_pos(dit_start, start.pos);
332 DocIterator dit_end = buf.getParFromID(end.id);
334 set_pos(dit_end, end.pos);
335 // Step backwards to prevent selecting the beginning of another
337 if (dit_end.pos() == 0 && !dit_end.top().at_cell_begin()) {
338 CursorSlice end_top = dit_end.top();
339 end_top.backwardPos();
340 if (dit_start && end_top != dit_start.top())
341 dit_end.top() = end_top;
343 dit_end.boundary(true);
345 return {dit_start, dit_end};
350 FuncRequest TexRow::goToFunc(TextEntry start, TextEntry end)
352 return {LFUN_PARAGRAPH_GOTO,
353 convert<string>(start.id) + " " + convert<string>(start.pos) + " " +
354 convert<string>(end.id) + " " + convert<string>(end.pos)};
358 FuncRequest TexRow::goToFuncFromRow(int const row) const
360 TextEntry start, end;
361 tie(start,end) = getEntriesFromRow(row);
363 "goToFuncFromRow: for row " << row << ", TexRow has found "
364 "start (id=" << start.id << ",pos=" << start.pos << "), "
365 "end (id=" << end.id << ",pos=" << end.pos << ")");
366 return goToFunc(start, end);
371 TexRow::RowEntry TexRow::rowEntryFromCursorSlice(CursorSlice const & slice)
374 InsetMath * insetMath = slice.asInsetMath();
376 entry.type = math_entry;
377 entry.math.id = insetMath->id();
378 entry.math.cell = slice.idx();
379 } else if (slice.text()) {
380 entry.type = text_entry;
381 entry.text.id = slice.paragraph().id();
382 entry.text.pos = slice.pos();
384 LASSERT(false, return row_none);
390 bool TexRow::sameParOrInsetMath(RowEntry entry1, RowEntry entry2)
392 if (entry1.type != entry2.type)
394 switch (entry1.type) {
395 case TexRow::text_entry:
396 return entry1.text.id == entry2.text.id;
397 case TexRow::math_entry:
398 return entry1.math.id == entry2.math.id;
399 case TexRow::begin_document:
408 int TexRow::comparePos(RowEntry entry1, RowEntry entry2)
410 // assume it is sameParOrInsetMath
411 switch (entry1.type /* equal to entry2.type */) {
412 case TexRow::text_entry:
413 return entry2.text.pos - entry1.text.pos;
414 case TexRow::math_entry:
415 return entry2.math.cell - entry1.math.cell;
416 case TexRow::begin_document:
424 // An iterator on RowList that goes top-down, left-right
426 // We assume that the end of RowList does not change, which makes things simpler
428 // Records a pair of iterators on the RowEntryList (row_it_, row_end_) and a
429 // pair of iterators on the current row (it_, it_end_).
431 // it_ always points to a valid position unless row_it_ == row_end_.
433 // We could turn this into a proper bidirectional iterator, but we don't need as
436 class TexRow::RowListIterator
439 RowListIterator(RowList::const_iterator r,
440 RowList::const_iterator r_end)
441 : row_it_(r), row_end_(r_end),
442 it_(r == r_end ? RowEntryList::const_iterator() : r->begin()),
443 it_end_(r == r_end ? RowEntryList::const_iterator() : r->end())
450 row_it_(RowList::const_iterator()),
451 row_end_(RowList::const_iterator()),
452 it_(RowEntryList::const_iterator()),
453 it_end_(RowEntryList::const_iterator()) { }
456 RowEntry const & operator*()
462 RowListIterator & operator++()
472 return row_it_ == row_end_;
476 bool operator==(RowListIterator const & a) const
478 return row_it_ == a.row_it_ && ((atEnd() && a.atEnd()) || it_ == a.it_);
482 bool operator!=(RowListIterator const & a) const { return !operator==(a); }
486 RowList::const_iterator const & row() const
491 // ensures that it_ points to a valid value unless row_it_ == row_end_
494 if (row_it_ == row_end_)
496 while (it_ == it_end_) {
498 if (row_it_ != row_end_) {
499 it_ = row_it_->begin();
500 it_end_ = row_it_->end();
506 RowList::const_iterator row_it_;
508 RowList::const_iterator row_end_;
510 RowEntryList::const_iterator it_;
512 RowEntryList::const_iterator it_end_;
516 TexRow::RowListIterator TexRow::begin() const
518 return RowListIterator(rowlist_.begin(), rowlist_.end());
522 TexRow::RowListIterator TexRow::end() const
524 return RowListIterator(rowlist_.end(), rowlist_.end());
528 pair<int,int> TexRow::rowFromDocIterator(DocIterator const & dit) const
530 // Do not change anything in this algorithm if unsure.
531 bool beg_found = false;
532 bool end_is_next = true;
534 size_t best_slice = 0;
535 RowEntry best_entry = row_none;
536 size_t const n = dit.depth();
537 // this loop finds a pair (best_beg_row,best_end_row) where best_beg_row is
538 // the first row of the topmost possible CursorSlice, and best_end_row is
539 // the one just before the first row matching the next CursorSlice.
540 RowListIterator const begin = this->begin();//necessary disambiguation
541 RowListIterator const end = this->end();
542 RowListIterator best_beg_entry;
543 //best last entry with same pos as the beg_entry, or first entry with pos
544 //immediately following the beg_entry
545 RowListIterator best_end_entry;
546 RowListIterator it = begin;
547 for (; it != end; ++it) {
548 // Compute the best end row.
550 && (!sameParOrInsetMath(*it, *best_end_entry)
551 || comparePos(*it, *best_end_entry) <= 0)
552 && sameParOrInsetMath(*it, best_entry)) {
553 switch (comparePos(*it, best_entry)) {
555 // Either it is the last one that matches pos...
561 // ...or it is the row preceding the first that matches pos+1
564 if (it.row() != best_end_entry.row())
572 // Compute the best begin row. It is better than the previous one if it
573 // matches either at a deeper level, or at the same level but not
575 for (size_t i = best_slice; i < n; ++i) {
576 RowEntry entry_i = rowEntryFromCursorSlice(dit[i]);
577 if (sameParOrInsetMath(*it, entry_i)) {
578 if (comparePos(*it, entry_i) >= 0
581 || !sameParOrInsetMath(*it, *best_beg_entry)
582 || (comparePos(*it, *best_beg_entry) <= 0
583 && comparePos(entry_i, *best_beg_entry) != 0)
590 best_entry = entry_i;
591 best_beg_entry = best_end_entry = it;
599 return make_pair(-1,-1);
600 int const best_beg_row = distance(rowlist_.begin(),
601 best_beg_entry.row()) + 1;
602 int const best_end_row = distance(rowlist_.begin(),
603 best_end_entry.row()) + end_offset;
604 return make_pair(best_beg_row, best_end_row);
608 pair<int,int> TexRow::rowFromCursor(Cursor const & cur) const
610 DocIterator beg = cur.selectionBegin();
611 pair<int,int> beg_rows = rowFromDocIterator(beg);
612 if (cur.selection()) {
613 DocIterator end = cur.selectionEnd();
614 if (!cur.selIsMultiCell() && !end.top().at_cell_begin())
615 end.top().backwardPos();
616 pair<int,int> end_rows = rowFromDocIterator(end);
617 return make_pair(min(beg_rows.first, end_rows.first),
618 max(beg_rows.second, end_rows.second));
620 return make_pair(beg_rows.first, beg_rows.second);
624 size_t TexRow::rows() const
626 return rowlist_.size();
630 void TexRow::setRows(size_t r)
632 rowlist_.resize(r, RowEntryList());
636 // debugging functions
639 docstring TexRow::asString(RowEntry entry)
642 switch (entry.type) {
643 case TexRow::text_entry:
644 os << "(par " << entry.text.id << "," << entry.text.pos << ")";
646 case TexRow::math_entry:
647 os << "(" << entry.math.id << "," << entry.math.cell << ")";
649 case TexRow::begin_document:
650 os << "(begin_document)";
659 ///prepends the texrow to the source given by tex, for debugging purpose
660 void TexRow::prepend(docstring_list & tex) const
662 size_type const prefix_length = 25;
663 if (tex.size() < rowlist_.size())
664 tex.resize(rowlist_.size());
665 auto it = rowlist_.cbegin();
666 auto const beg = rowlist_.cbegin();
667 auto const end = rowlist_.cend();
668 for (; it < end; ++it) {
670 for (RowEntry const & e : *it)
671 entry += asString(e);
672 if (entry.length() < prefix_length)
673 entry = entry + docstring(prefix_length - entry.length(), ' ');
674 ptrdiff_t i = it - beg;
675 tex[i] = entry + " " + tex[i];