1 /**
2 * \file TexRow.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
5 *
6 * \author Matthias Ettrich
7 * \author Lars Gullik Bjønnes
8 * \author John Levon
9 * \author Guillaume Munch
10 *
11 * Full author contact details are available in file CREDITS.
12 */
13
14 #include <config.h>
15
16 #include "Buffer.h"
17 #include "Cursor.h"
18 #include "FuncRequest.h"
19 #include "Paragraph.h"
20 #include "TexRow.h"
21
22 #include "mathed/InsetMath.h"
23
24 #include "support/convert.h"
25 #include "support/debug.h"
26 #include "support/docstring_list.h"
27 #include "support/lassert.h"
28
29 #include <algorithm>
30 #include <iterator>
31 #include <sstream>
32
33 using namespace std;
34
35
36 namespace lyx {
37
38
TexString(docstring s)39 TexString::TexString(docstring s)
40 : str(move(s)), texrow(TexRow())
41 {
42 texrow.setRows(1 + count(str.begin(), str.end(), '\n'));
43 }
44
45
TexString(docstring s,TexRow t)46 TexString::TexString(docstring s, TexRow t)
47 : str(move(s)), texrow(move(t))
48 {
49 validate();
50 }
51
52
validate()53 void TexString::validate()
54 {
55 size_t lines = 1 + count(str.begin(), str.end(), '\n');
56 size_t rows = texrow.rows();
57 bool valid = lines == rows;
58 if (!valid)
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
62 // mode.
63 LASSERT(valid, texrow.setRows(lines));
64 }
65
66
addEntry(RowEntry entry)67 bool TexRow::RowEntryList::addEntry(RowEntry entry)
68 {
69 switch (entry.type) {
70 case text_entry:
71 if (isNone(text_entry_))
72 text_entry_ = entry.text;
73 else if (!v_.empty() && TexRow::sameParOrInsetMath(v_.back(), entry))
74 return false;
75 break;
76 default:
77 break;
78 }
79 forceAddEntry(entry);
80 return true;
81 }
82
83
forceAddEntry(RowEntry entry)84 void TexRow::RowEntryList::forceAddEntry(RowEntry entry)
85 {
86 if (v_.empty() || !(v_.back() == entry))
87 v_.push_back(entry);
88 }
89
90
getTextEntry() const91 TexRow::TextEntry TexRow::RowEntryList::getTextEntry() const
92 {
93 if (!isNone(text_entry_))
94 return text_entry_;
95 return TexRow::text_none;
96 }
97
98
append(RowEntryList row)99 void TexRow::RowEntryList::append(RowEntryList row)
100 {
101 if (isNone(text_entry_))
102 text_entry_ = row.text_entry_;
103 move(row.begin(), row.end(), back_inserter(v_));
104 }
105
106
TexRow()107 TexRow::TexRow()
108 {
109 reset();
110 }
111
112
113 TexRow::TextEntry const TexRow::text_none = { -1, 0 };
114 TexRow::RowEntry const TexRow::row_none = TexRow::textEntry(-1, 0);
115
116
117 //static
isNone(TextEntry t)118 bool TexRow::isNone(TextEntry t)
119 {
120 return t.id < 0;
121 }
122
123
124 //static
isNone(RowEntry r)125 bool TexRow::isNone(RowEntry r)
126 {
127 return r.type == text_entry && isNone(r.text);
128 }
129
130
reset()131 void TexRow::reset()
132 {
133 rowlist_.clear();
134 newline();
135 }
136
137
currentRow()138 TexRow::RowEntryList & TexRow::currentRow()
139 {
140 return rowlist_.back();
141 }
142
143
144 //static
textEntry(int id,pos_type pos)145 TexRow::RowEntry TexRow::textEntry(int id, pos_type pos)
146 {
147 RowEntry entry;
148 entry.type = text_entry;
149 entry.text.pos = pos;
150 entry.text.id = id;
151 return entry;
152 }
153
154
155 //static
mathEntry(uid_type id,idx_type cell)156 TexRow::RowEntry TexRow::mathEntry(uid_type id, idx_type cell)
157 {
158 RowEntry entry;
159 entry.type = math_entry;
160 entry.math.cell = cell;
161 entry.math.id = id;
162 return entry;
163 }
164
165
166 //static
beginDocument()167 TexRow::RowEntry TexRow::beginDocument()
168 {
169 RowEntry entry;
170 entry.type = begin_document;
171 entry.begindocument = {};
172 return entry;
173 }
174
175
operator ==(TexRow::RowEntry entry1,TexRow::RowEntry entry2)176 bool operator==(TexRow::RowEntry entry1, TexRow::RowEntry entry2)
177 {
178 if (entry1.type != entry2.type)
179 return false;
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:
188 return true;
189 default:
190 return false;
191 }
192 }
193
194
start(RowEntry entry)195 bool TexRow::start(RowEntry entry)
196 {
197 return currentRow().addEntry(entry);
198 }
199
200
start(int id,pos_type pos)201 bool TexRow::start(int id, pos_type pos)
202 {
203 return start(textEntry(id,pos));
204 }
205
206
forceStart(int id,pos_type pos)207 void TexRow::forceStart(int id, pos_type pos)
208 {
209 return currentRow().forceAddEntry(textEntry(id,pos));
210 }
211
212
startMath(uid_type id,idx_type cell)213 void TexRow::startMath(uid_type id, idx_type cell)
214 {
215 start(mathEntry(id,cell));
216 }
217
218
newline()219 void TexRow::newline()
220 {
221 rowlist_.push_back(RowEntryList());
222 }
223
224
newlines(size_t num_lines)225 void TexRow::newlines(size_t num_lines)
226 {
227 while (num_lines--)
228 newline();
229 }
230
231
append(TexRow other)232 void TexRow::append(TexRow other)
233 {
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_));
239 }
240
241
242 pair<TexRow::TextEntry, TexRow::TextEntry>
getEntriesFromRow(int const row) const243 TexRow::getEntriesFromRow(int const row) const
244 {
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.
247 //
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.
252 //
253 LYXERR(Debug::LATEX, "getEntriesFromRow: row " << row << " requested");
254
255 // check bounds for row - 1, our target index
256 if (row <= 0)
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};
261
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)
271 return text_none;
272 }
273 return text_none;
274 } ();
275
276 // find the end entry
277 TextEntry end = [&]() {
278 if (isNone(start))
279 return text_none;
280 // select up to the last position of the starting paragraph as a
281 // fallback
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
288 return last_pos;
289 if (entry.type == text_entry && entry.text.id == start.id)
290 return entry.text;
291 }
292 }
293 return last_pos;
294 } ();
295
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)
300 ++end.pos;
301
302 return {start, end};
303 }
304
305
getDocIteratorsFromRow(int const row,Buffer const & buf) const306 pair<DocIterator, DocIterator> TexRow::getDocIteratorsFromRow(
307 int const row,
308 Buffer const & buf) const
309 {
310 TextEntry start, end;
311 tie(start,end) = getEntriesFromRow(row);
312 return getDocIteratorsFromEntries(start, end, buf);
313 }
314
315
316 //static
getDocIteratorsFromEntries(TextEntry start,TextEntry end,Buffer const & buf)317 pair<DocIterator, DocIterator> TexRow::getDocIteratorsFromEntries(
318 TextEntry start,
319 TextEntry end,
320 Buffer const & buf)
321 {
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));
326 };
327 // Finding start
328 DocIterator dit_start = buf.getParFromID(start.id);
329 if (dit_start)
330 set_pos(dit_start, start.pos);
331 // Finding end
332 DocIterator dit_end = buf.getParFromID(end.id);
333 if (dit_end) {
334 set_pos(dit_end, end.pos);
335 // Step backwards to prevent selecting the beginning of another
336 // paragraph.
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;
342 }
343 dit_end.boundary(true);
344 }
345 return {dit_start, dit_end};
346 }
347
348
349 //static
goToFunc(TextEntry start,TextEntry end)350 FuncRequest TexRow::goToFunc(TextEntry start, TextEntry end)
351 {
352 return {LFUN_PARAGRAPH_GOTO,
353 convert<string>(start.id) + " " + convert<string>(start.pos) + " " +
354 convert<string>(end.id) + " " + convert<string>(end.pos)};
355 }
356
357
goToFuncFromRow(int const row) const358 FuncRequest TexRow::goToFuncFromRow(int const row) const
359 {
360 TextEntry start, end;
361 tie(start,end) = getEntriesFromRow(row);
362 LYXERR(Debug::LATEX,
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);
367 }
368
369
370 //static
rowEntryFromCursorSlice(CursorSlice const & slice)371 TexRow::RowEntry TexRow::rowEntryFromCursorSlice(CursorSlice const & slice)
372 {
373 RowEntry entry;
374 InsetMath * insetMath = slice.asInsetMath();
375 if (insetMath) {
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();
383 } else
384 LASSERT(false, return row_none);
385 return entry;
386 }
387
388
389 //static
sameParOrInsetMath(RowEntry entry1,RowEntry entry2)390 bool TexRow::sameParOrInsetMath(RowEntry entry1, RowEntry entry2)
391 {
392 if (entry1.type != entry2.type)
393 return false;
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:
400 return true;
401 default:
402 return false;
403 }
404 }
405
406
407 //static
comparePos(RowEntry entry1,RowEntry entry2)408 int TexRow::comparePos(RowEntry entry1, RowEntry entry2)
409 {
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:
417 return 0;
418 default:
419 return 0;
420 }
421 }
422
423
424 // An iterator on RowList that goes top-down, left-right
425 //
426 // We assume that the end of RowList does not change, which makes things simpler
427 //
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_).
430 //
431 // it_ always points to a valid position unless row_it_ == row_end_.
432 //
433 // We could turn this into a proper bidirectional iterator, but we don't need as
434 // much.
435 //
436 class TexRow::RowListIterator
437 {
438 public:
RowListIterator(RowList::const_iterator r,RowList::const_iterator r_end)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())
444 {
445 normalize();
446 }
447
448
RowListIterator()449 RowListIterator() :
450 row_it_(RowList::const_iterator()),
451 row_end_(RowList::const_iterator()),
452 it_(RowEntryList::const_iterator()),
453 it_end_(RowEntryList::const_iterator()) { }
454
455
operator *()456 RowEntry const & operator*()
457 {
458 return *it_;
459 }
460
461
operator ++()462 RowListIterator & operator++()
463 {
464 ++it_;
465 normalize();
466 return *this;
467 }
468
469
atEnd() const470 bool atEnd() const
471 {
472 return row_it_ == row_end_;
473 }
474
475
operator ==(RowListIterator const & a) const476 bool operator==(RowListIterator const & a) const
477 {
478 return row_it_ == a.row_it_ && ((atEnd() && a.atEnd()) || it_ == a.it_);
479 }
480
481
operator !=(RowListIterator const & a) const482 bool operator!=(RowListIterator const & a) const { return !operator==(a); }
483
484
485 // Current row.
row() const486 RowList::const_iterator const & row() const
487 {
488 return row_it_;
489 }
490 private:
491 // ensures that it_ points to a valid value unless row_it_ == row_end_
normalize()492 void normalize()
493 {
494 if (row_it_ == row_end_)
495 return;
496 while (it_ == it_end_) {
497 ++row_it_;
498 if (row_it_ != row_end_) {
499 it_ = row_it_->begin();
500 it_end_ = row_it_->end();
501 } else
502 return;
503 }
504 }
505 //
506 RowList::const_iterator row_it_;
507 //
508 RowList::const_iterator row_end_;
509 //
510 RowEntryList::const_iterator it_;
511 //
512 RowEntryList::const_iterator it_end_;
513 };
514
515
begin() const516 TexRow::RowListIterator TexRow::begin() const
517 {
518 return RowListIterator(rowlist_.begin(), rowlist_.end());
519 }
520
521
end() const522 TexRow::RowListIterator TexRow::end() const
523 {
524 return RowListIterator(rowlist_.end(), rowlist_.end());
525 }
526
527
rowFromDocIterator(DocIterator const & dit) const528 pair<int,int> TexRow::rowFromDocIterator(DocIterator const & dit) const
529 {
530 // Do not change anything in this algorithm if unsure.
531 bool beg_found = false;
532 bool end_is_next = true;
533 int end_offset = 1;
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.
549 if (beg_found
550 && (!sameParOrInsetMath(*it, *best_end_entry)
551 || comparePos(*it, *best_end_entry) <= 0)
552 && sameParOrInsetMath(*it, best_entry)) {
553 switch (comparePos(*it, best_entry)) {
554 case 0:
555 // Either it is the last one that matches pos...
556 best_end_entry = it;
557 end_is_next = false;
558 end_offset = 1;
559 break;
560 case -1: {
561 // ...or it is the row preceding the first that matches pos+1
562 if (!end_is_next) {
563 end_is_next = true;
564 if (it.row() != best_end_entry.row())
565 end_offset = 0;
566 best_end_entry = it;
567 }
568 break;
569 }
570 }
571 }
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
574 // before.
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
579 && (i > best_slice
580 || !beg_found
581 || !sameParOrInsetMath(*it, *best_beg_entry)
582 || (comparePos(*it, *best_beg_entry) <= 0
583 && comparePos(entry_i, *best_beg_entry) != 0)
584 )
585 ) {
586 beg_found = true;
587 end_is_next = false;
588 end_offset = 1;
589 best_slice = i;
590 best_entry = entry_i;
591 best_beg_entry = best_end_entry = it;
592 }
593 //found CursorSlice
594 break;
595 }
596 }
597 }
598 if (!beg_found)
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);
605 }
606
607
rowFromCursor(Cursor const & cur) const608 pair<int,int> TexRow::rowFromCursor(Cursor const & cur) const
609 {
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));
619 } else
620 return make_pair(beg_rows.first, beg_rows.second);
621 }
622
623
rows() const624 size_t TexRow::rows() const
625 {
626 return rowlist_.size();
627 }
628
629
setRows(size_t r)630 void TexRow::setRows(size_t r)
631 {
632 rowlist_.resize(r, RowEntryList());
633 }
634
635
636 // debugging functions
637
638 ///
asString(RowEntry entry)639 docstring TexRow::asString(RowEntry entry)
640 {
641 odocstringstream os;
642 switch (entry.type) {
643 case TexRow::text_entry:
644 os << "(par " << entry.text.id << "," << entry.text.pos << ")";
645 break;
646 case TexRow::math_entry:
647 os << "(" << entry.math.id << "," << entry.math.cell << ")";
648 break;
649 case TexRow::begin_document:
650 os << "(begin_document)";
651 break;
652 default:
653 break;
654 }
655 return os.str();
656 }
657
658
659 ///prepends the texrow to the source given by tex, for debugging purpose
prepend(docstring_list & tex) const660 void TexRow::prepend(docstring_list & tex) const
661 {
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) {
669 docstring entry;
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];
676 }
677 }
678
679
680 } // namespace lyx
681