]> git.lyx.org Git - lyx.git/blob - src/TexRow.cpp
Avoid full metrics computation with Update:FitCursor
[lyx.git] / src / TexRow.cpp
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 "TexRow.h"
17
18 #include "Buffer.h"
19 #include "Cursor.h"
20 #include "FuncRequest.h"
21 #include "Paragraph.h"
22
23 #include "mathed/InsetMath.h"
24
25 #include "support/convert.h"
26 #include "support/debug.h"
27 #include "support/docstring_list.h"
28 #include "support/lassert.h"
29
30 #include <algorithm>
31 #include <iterator>
32 #include <sstream>
33
34 using namespace std;
35
36
37 namespace lyx {
38
39
40 TexString::TexString(docstring s)
41         : str(std::move(s)), texrow(TexRow())
42 {
43         texrow.setRows(1 + count(str.begin(), str.end(), '\n'));
44 }
45
46
47 TexString::TexString(docstring s, TexRow t)
48         : str(std::move(s)), texrow(std::move(t))
49 {
50         validate();
51 }
52
53
54 void TexString::validate()
55 {
56         size_t lines = 1 + count(str.begin(), str.end(), '\n');
57         size_t rows = texrow.rows();
58         bool valid = lines == rows;
59         if (!valid)
60                 LYXERR0("TexString has " << lines << " lines but " << rows << " rows." );
61         // Assert in devel mode.  This is important to catch bugs early, otherwise
62         // they might be hard to notice and find.  Recover gracefully in release
63         // mode.
64         LASSERT(valid, texrow.setRows(lines));
65 }
66
67
68 bool TexRow::RowEntryList::addEntry(RowEntry entry)
69 {
70         switch (entry.type) {
71         case text_entry:
72                 if (isNone(text_entry_))
73                         text_entry_ = entry.text;
74                 else if (!v_.empty() && TexRow::sameParOrInsetMath(v_.back(), entry))
75                         return false;
76                 break;
77         default:
78                 break;
79         }
80         forceAddEntry(entry);
81         return true;
82 }
83
84
85 void TexRow::RowEntryList::forceAddEntry(RowEntry entry)
86 {
87         if (v_.empty() || !(v_.back() == entry))
88                 v_.push_back(entry);
89 }
90
91
92 TexRow::TextEntry TexRow::RowEntryList::getTextEntry() const
93 {
94         if (!isNone(text_entry_))
95                 return text_entry_;
96         return TexRow::text_none;
97 }
98
99
100 void TexRow::RowEntryList::append(RowEntryList row)
101 {
102         if (isNone(text_entry_))
103                 text_entry_ = row.text_entry_;
104         move(row.begin(), row.end(), back_inserter(v_));
105 }
106
107
108 TexRow::TexRow()
109 {
110         reset();
111 }
112
113
114 TexRow::TextEntry const TexRow::text_none = { -1, 0 };
115 TexRow::RowEntry const TexRow::row_none = TexRow::textEntry(-1, 0);
116
117
118 //static
119 bool TexRow::isNone(TextEntry t)
120 {
121         return t.id < 0;
122 }
123
124
125 //static
126 bool TexRow::isNone(RowEntry r)
127 {
128         return r.type == text_entry && isNone(r.text);
129 }
130
131
132 void TexRow::reset()
133 {
134         rowlist_.clear();
135         newline();
136 }
137
138
139 TexRow::RowEntryList & TexRow::currentRow()
140 {
141         return rowlist_.back();
142 }
143
144
145 //static
146 TexRow::RowEntry TexRow::textEntry(int id, pos_type pos)
147 {
148         RowEntry entry;
149         entry.type = text_entry;
150         entry.text.pos = pos;
151         entry.text.id = id;
152         return entry;
153 }
154
155
156 //static
157 TexRow::RowEntry TexRow::mathEntry(uid_type id, idx_type cell)
158 {
159         RowEntry entry;
160         entry.type = math_entry;
161         entry.math.cell = cell;
162         entry.math.id = id;
163         return entry;
164 }
165
166
167 //static
168 TexRow::RowEntry TexRow::beginDocument()
169 {
170         RowEntry entry;
171         entry.type = begin_document;
172         entry.begindocument = {};
173         return entry;
174 }
175
176
177 bool operator==(TexRow::RowEntry entry1, TexRow::RowEntry entry2)
178 {
179         if (entry1.type != entry2.type)
180                 return false;
181         switch (entry1.type) {
182         case TexRow::text_entry:
183                 return entry1.text.id == entry2.text.id
184                         && entry1.text.pos == entry2.text.pos;
185         case TexRow::math_entry:
186                 return entry1.math.id == entry2.math.id
187                         && entry1.math.cell == entry2.math.cell;
188         case TexRow::begin_document:
189                 return true;
190         default:
191                 return false;
192         }
193 }
194
195
196 bool TexRow::start(RowEntry entry)
197 {
198         return currentRow().addEntry(entry);
199 }
200
201
202 bool TexRow::start(int id, pos_type pos)
203 {
204         return start(textEntry(id,pos));
205 }
206
207
208 void TexRow::forceStart(int id, pos_type pos)
209 {
210         return currentRow().forceAddEntry(textEntry(id,pos));
211 }
212
213
214 void TexRow::startMath(uid_type id, idx_type cell)
215 {
216         start(mathEntry(id,cell));
217 }
218
219
220 void TexRow::newline()
221 {
222         rowlist_.push_back(RowEntryList());
223 }
224
225
226 void TexRow::newlines(size_t num_lines)
227 {
228         while (num_lines--)
229                 newline();
230 }
231
232
233 void TexRow::append(TexRow other)
234 {
235         RowList::iterator it = other.rowlist_.begin();
236         RowList::iterator const end = other.rowlist_.end();
237         LASSERT(it != end, return);
238         currentRow().append(std::move(*it++));
239         move(it, end, back_inserter(rowlist_));
240 }
241
242
243 pair<TexRow::TextEntry, TexRow::TextEntry>
244 TexRow::getEntriesFromRow(int const row) const
245 {
246         // FIXME: Take math entries into account, take table cells into account and
247         //        get rid of the ad hoc special text entry for each row.
248         //
249         // FIXME: A yellow note alone on its paragraph makes the reverse-search on
250         //        the subsequent line inaccurate. Get rid of text entries that
251         //        correspond to no output by delaying their addition, at the level
252         //        of otexrowstream, until a character is actually output.
253         //
254         LYXERR(Debug::OUTFILE, "getEntriesFromRow: row " << row << " requested");
255
256         // check bounds for row - 1, our target index
257         if (row <= 0)
258                 return {text_none, text_none};
259         size_t const i = static_cast<size_t>(row - 1);
260         if (i >= rowlist_.size())
261                 return {text_none, text_none};
262
263         // find the start entry
264         TextEntry const start = [&]() {
265                 for (size_t j = i; j > 0; --j) {
266                         if (!isNone(rowlist_[j].getTextEntry()))
267                                 return rowlist_[j].getTextEntry();
268                         // Check the absence of begin_document at row j. The begin_document row
269                         // entry is used to prevent mixing of body and preamble.
270                         for (RowEntry entry : rowlist_[j])
271                                 if (entry.type == begin_document)
272                                         return text_none;
273                 }
274                 return text_none;
275         } ();
276
277         // find the end entry
278         TextEntry end = [&]() {
279                 if (isNone(start))
280                         return text_none;
281                 // select up to the last position of the starting paragraph as a
282                 // fallback
283                 TextEntry last_pos = {start.id, -1};
284                 // find the next occurence of paragraph start.id
285                 for (size_t j = i + 1; j < rowlist_.size(); ++j) {
286                         for (RowEntry entry : rowlist_[j]) {
287                                 if (entry.type == begin_document)
288                                         // what happens in the preamble remains in the preamble
289                                         return last_pos;
290                                 if (entry.type == text_entry && entry.text.id == start.id)
291                                         return entry.text;
292                         }
293                 }
294                 return last_pos;
295         } ();
296
297         // The following occurs for a displayed math inset for instance (for good
298         // reasons involving subtleties of the algorithm in getRowFromDocIterator).
299         // We want this inset selected.
300         if (start.id == end.id && start.pos == end.pos)
301                 ++end.pos;
302
303         return {start, end};
304 }
305
306
307 pair<DocIterator, DocIterator> TexRow::getDocIteratorsFromRow(
308     int const row,
309     Buffer const & buf) const
310 {
311         TextEntry start, end;
312         tie(start,end) = getEntriesFromRow(row);
313         return getDocIteratorsFromEntries(start, end, buf);
314 }
315
316
317 //static
318 pair<DocIterator, DocIterator> TexRow::getDocIteratorsFromEntries(
319             TextEntry start,
320             TextEntry end,
321             Buffer const & buf)
322 {
323         auto set_pos = [](DocIterator & dit, pos_type pos) {
324                 dit.pos() = (pos >= 0) ? min(pos, dit.lastpos())
325                                        // negative pos values are counted from the end
326                                        : max(dit.lastpos() + pos + 1, pos_type(0));
327         };
328         // Finding start
329         DocIterator dit_start = buf.getParFromID(start.id);
330         if (dit_start)
331                 set_pos(dit_start, start.pos);
332         // Finding end
333         DocIterator dit_end = buf.getParFromID(end.id);
334         if (dit_end) {
335                 set_pos(dit_end, end.pos);
336                 // Step backwards to prevent selecting the beginning of another
337                 // paragraph.
338                 if (dit_end.pos() == 0 && !dit_end.top().at_cell_begin()) {
339                         CursorSlice end_top = dit_end.top();
340                         end_top.backwardPos();
341                         if (dit_start && end_top != dit_start.top())
342                                 dit_end.top() = end_top;
343                 }
344                 dit_end.boundary(true);
345         }
346         return {dit_start, dit_end};
347 }
348
349
350 //static
351 FuncRequest TexRow::goToFunc(TextEntry start, TextEntry end)
352 {
353         return {LFUN_PARAGRAPH_GOTO,
354                         convert<string>(start.id) + " " + convert<string>(start.pos) + " " +
355                         convert<string>(end.id) + " " + convert<string>(end.pos)};
356 }
357
358
359 FuncRequest TexRow::goToFuncFromRow(int const row) const
360 {
361         TextEntry start, end;
362         tie(start,end) = getEntriesFromRow(row);
363         LYXERR(Debug::OUTFILE,
364                "goToFuncFromRow: for row " << row << ", TexRow has found "
365                "start (id=" << start.id << ",pos=" << start.pos << "), "
366                "end (id=" << end.id << ",pos=" << end.pos << ")");
367         return goToFunc(start, end);
368 }
369
370
371 //static
372 TexRow::RowEntry TexRow::rowEntryFromCursorSlice(CursorSlice const & slice)
373 {
374         RowEntry entry;
375         InsetMath * insetMath = slice.asInsetMath();
376         if (insetMath) {
377                 entry.type = math_entry;
378                 entry.math.id = insetMath->id();
379                 entry.math.cell = slice.idx();
380         } else if (slice.text()) {
381                 entry.type = text_entry;
382                 entry.text.id = slice.paragraph().id();
383                 entry.text.pos = slice.pos();
384         } else
385                 LASSERT(false, return row_none);
386         return entry;
387 }
388
389
390 //static
391 bool TexRow::sameParOrInsetMath(RowEntry entry1, RowEntry entry2)
392 {
393         if (entry1.type != entry2.type)
394                 return false;
395         switch (entry1.type) {
396         case TexRow::text_entry:
397                 return entry1.text.id == entry2.text.id;
398         case TexRow::math_entry:
399                 return entry1.math.id == entry2.math.id;
400         case TexRow::begin_document:
401                 return true;
402         default:
403                 return false;
404         }
405 }
406
407
408 //static
409 int TexRow::comparePos(RowEntry entry1, RowEntry entry2)
410 {
411         // assume it is sameParOrInsetMath
412         switch (entry1.type /* equal to entry2.type */) {
413         case TexRow::text_entry:
414                 return entry2.text.pos - entry1.text.pos;
415         case TexRow::math_entry:
416                 return entry2.math.cell - entry1.math.cell;
417         case TexRow::begin_document:
418                 return 0;
419         default:
420                 return 0;
421         }
422 }
423
424
425 // An iterator on RowList that goes top-down, left-right
426 //
427 // We assume that the end of RowList does not change, which makes things simpler
428 //
429 // Records a pair of iterators on the RowEntryList (row_it_, row_end_) and a
430 // pair of iterators on the current row (it_, it_end_).
431 //
432 // it_ always points to a valid position unless row_it_ == row_end_.
433 //
434 // We could turn this into a proper bidirectional iterator, but we don't need as
435 // much.
436 //
437 class TexRow::RowListIterator
438 {
439 public:
440         RowListIterator(RowList::const_iterator r,
441                         RowList::const_iterator r_end)
442                 : row_it_(r), row_end_(r_end),
443                   it_(r == r_end ? RowEntryList::const_iterator() : r->begin()),
444                   it_end_(r == r_end ? RowEntryList::const_iterator() : r->end())
445         {
446                 normalize();
447         }
448
449
450         RowListIterator() :
451                 row_it_(RowList::const_iterator()),
452                 row_end_(RowList::const_iterator()),
453                 it_(RowEntryList::const_iterator()),
454                 it_end_(RowEntryList::const_iterator()) { }
455
456
457         RowEntry const & operator*()
458         {
459                 return *it_;
460         }
461
462
463         RowListIterator & operator++()
464         {
465                 ++it_;
466                 normalize();
467                 return *this;
468         }
469
470
471         bool atEnd() const
472         {
473                 return row_it_ == row_end_;
474         }
475
476
477         bool operator==(RowListIterator const & a) const
478         {
479                 return row_it_ == a.row_it_ && ((atEnd() && a.atEnd()) || it_ == a.it_);
480         }
481
482
483         bool operator!=(RowListIterator const & a) const { return !operator==(a); }
484
485
486         // Current row.
487         RowList::const_iterator const & row() const
488         {
489                 return row_it_;
490         }
491 private:
492         // ensures that it_ points to a valid value unless row_it_ == row_end_
493         void normalize()
494         {
495                 if (row_it_ == row_end_)
496                         return;
497                 while (it_ == it_end_) {
498                         ++row_it_;
499                         if (row_it_ != row_end_) {
500                                 it_ = row_it_->begin();
501                                 it_end_ = row_it_->end();
502                         } else
503                                 return;
504                 }
505         }
506         //
507         RowList::const_iterator row_it_;
508         //
509         RowList::const_iterator row_end_;
510         //
511         RowEntryList::const_iterator it_;
512         //
513         RowEntryList::const_iterator it_end_;
514 };
515
516
517 TexRow::RowListIterator TexRow::begin() const
518 {
519         return RowListIterator(rowlist_.begin(), rowlist_.end());
520 }
521
522
523 TexRow::RowListIterator TexRow::end() const
524 {
525         return RowListIterator(rowlist_.end(), rowlist_.end());
526 }
527
528
529 pair<int,int> TexRow::rowFromDocIterator(DocIterator const & dit) const
530 {
531         // Do not change anything in this algorithm if unsure.
532         bool beg_found = false;
533         bool end_is_next = true;
534         int end_offset = 1;
535         size_t best_slice = 0;
536         RowEntry best_entry = row_none;
537         size_t const n = dit.depth();
538         // this loop finds a pair (best_beg_row,best_end_row) where best_beg_row is
539         // the first row of the topmost possible CursorSlice, and best_end_row is
540         // the one just before the first row matching the next CursorSlice.
541         RowListIterator const begin = this->begin();//necessary disambiguation
542         RowListIterator const end = this->end();
543         RowListIterator best_beg_entry;
544         //best last entry with same pos as the beg_entry, or first entry with pos
545         //immediately following the beg_entry
546         RowListIterator best_end_entry;
547         RowListIterator it = begin;
548         for (; it != end; ++it) {
549                 // Compute the best end row.
550                 if (beg_found
551                         && (!sameParOrInsetMath(*it, *best_end_entry)
552                                 || comparePos(*it, *best_end_entry) <= 0)
553                         && sameParOrInsetMath(*it, best_entry)) {
554                     switch (comparePos(*it, best_entry)) {
555                         case 0:
556                                 // Either it is the last one that matches pos...
557                                 best_end_entry = it;
558                                 end_is_next = false;
559                                 end_offset = 1;
560                                 break;
561                         case -1: {
562                                 // ...or it is the row preceding the first that matches pos+1
563                                 if (!end_is_next) {
564                                         end_is_next = true;
565                                         if (it.row() != best_end_entry.row())
566                                                 end_offset = 0;
567                                         best_end_entry = it;
568                                 }
569                                 break;
570                         }
571                         }
572                 }
573                 // Compute the best begin row. It is better than the previous one if it
574                 // matches either at a deeper level, or at the same level but not
575                 // before.
576                 for (size_t i = best_slice; i < n; ++i) {
577                         RowEntry entry_i = rowEntryFromCursorSlice(dit[i]);
578                         if (sameParOrInsetMath(*it, entry_i)) {
579                                 if (comparePos(*it, entry_i) >= 0
580                                         && (i > best_slice
581                                                 || !beg_found
582                                                 || !sameParOrInsetMath(*it, *best_beg_entry)
583                                                 || (comparePos(*it, *best_beg_entry) <= 0
584                                                         && comparePos(entry_i, *best_beg_entry) != 0)
585                                                 )
586                                         ) {
587                                         beg_found = true;
588                                         end_is_next = false;
589                                         end_offset = 1;
590                                         best_slice = i;
591                                         best_entry = entry_i;
592                                         best_beg_entry = best_end_entry = it;
593                                 }
594                                 //found CursorSlice
595                                 break;
596                         }
597                 }
598         }
599         if (!beg_found)
600                 return make_pair(-1,-1);
601         int const best_beg_row = distance(rowlist_.begin(),
602                                                                           best_beg_entry.row()) + 1;
603         int const best_end_row = distance(rowlist_.begin(),
604                                                                           best_end_entry.row()) + end_offset;
605         return make_pair(best_beg_row, best_end_row);
606 }
607
608
609 pair<int,int> TexRow::rowFromCursor(Cursor const & cur) const
610 {
611         DocIterator beg = cur.selectionBegin();
612         pair<int,int> beg_rows = rowFromDocIterator(beg);
613         if (cur.selection()) {
614                 DocIterator end = cur.selectionEnd();
615                 if (!cur.selIsMultiCell() && !end.top().at_cell_begin())
616                         end.top().backwardPos();
617                 pair<int,int> end_rows = rowFromDocIterator(end);
618                 return make_pair(min(beg_rows.first, end_rows.first),
619                                  max(beg_rows.second, end_rows.second));
620         } else
621                 return make_pair(beg_rows.first, beg_rows.second);
622 }
623
624
625 size_t TexRow::rows() const
626 {
627         return rowlist_.size();
628 }
629
630
631 void TexRow::setRows(size_t r)
632 {
633         rowlist_.resize(r, RowEntryList());
634 }
635
636
637 // debugging functions
638
639 ///
640 docstring TexRow::asString(RowEntry entry)
641 {
642         odocstringstream os;
643         switch (entry.type) {
644         case TexRow::text_entry:
645                 os << "(par " << entry.text.id << "," << entry.text.pos << ")";
646                 break;
647         case TexRow::math_entry:
648                 os << "(" << entry.math.id << "," << entry.math.cell << ")";
649                 break;
650         case TexRow::begin_document:
651                 os << "(begin_document)";
652                 break;
653         default:
654                 break;
655         }
656         return os.str();
657 }
658
659
660 ///prepends the texrow to the source given by tex, for debugging purpose
661 void TexRow::prepend(docstring_list & tex) const
662 {
663         size_type const prefix_length = 25;
664         if (tex.size() < rowlist_.size())
665                 tex.resize(rowlist_.size());
666         auto it = rowlist_.cbegin();
667         auto const beg = rowlist_.cbegin();
668         auto const end = rowlist_.cend();
669         for (; it < end; ++it) {
670                 docstring entry;
671                 for (RowEntry const & e : *it)
672                         entry += asString(e);
673                 if (entry.length() < prefix_length)
674                         entry = entry + docstring(prefix_length - entry.length(), ' ');
675                 ptrdiff_t i = it - beg;
676                 tex[i] = entry + "  " + tex[i];
677         }
678 }
679
680
681 } // namespace lyx