]> git.lyx.org Git - lyx.git/blob - src/TexRow.cpp
Disable CheckTeX while buffer is processed
[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 "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
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
46 TexString::TexString(docstring s, TexRow t)
47         : str(move(s)), texrow(move(t))
48 {
49         validate();
50 }
51
52
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
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
84 void TexRow::RowEntryList::forceAddEntry(RowEntry entry)
85 {
86         if (v_.empty() || !(v_.back() == entry))
87                 v_.push_back(entry);
88 }
89
90
91 TexRow::TextEntry TexRow::RowEntryList::getTextEntry() const
92 {
93         if (!isNone(text_entry_))
94                 return text_entry_;
95         return TexRow::text_none;
96 }
97
98
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
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
118 bool TexRow::isNone(TextEntry t)
119 {
120         return t.id < 0;
121 }
122
123
124 //static
125 bool TexRow::isNone(RowEntry r)
126 {
127         return r.type == text_entry && isNone(r.text);
128 }
129
130
131 void TexRow::reset()
132 {
133         rowlist_.clear();
134         newline();
135 }
136
137
138 TexRow::RowEntryList & TexRow::currentRow()
139 {
140         return rowlist_.back();
141 }
142
143
144 //static
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
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
167 TexRow::RowEntry TexRow::beginDocument()
168 {
169         RowEntry entry;
170         entry.type = begin_document;
171         entry.begindocument = {};
172         return entry;
173 }
174
175
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
195 bool TexRow::start(RowEntry entry)
196 {
197         return currentRow().addEntry(entry);
198 }
199
200
201 bool TexRow::start(int id, pos_type pos)
202 {
203         return start(textEntry(id,pos));
204 }
205
206
207 void TexRow::forceStart(int id, pos_type pos)
208 {
209         return currentRow().forceAddEntry(textEntry(id,pos));
210 }
211
212
213 void TexRow::startMath(uid_type id, idx_type cell)
214 {
215         start(mathEntry(id,cell));
216 }
217
218
219 void TexRow::newline()
220 {
221         rowlist_.push_back(RowEntryList());
222 }
223
224
225 void TexRow::newlines(size_t num_lines)
226 {
227         while (num_lines--)
228                 newline();
229 }
230
231
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>
243 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
306 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
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
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
358 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
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
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
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:
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
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
456         RowEntry const & operator*()
457         {
458                 return *it_;
459         }
460
461
462         RowListIterator & operator++()
463         {
464                 ++it_;
465                 normalize();
466                 return *this;
467         }
468
469
470         bool atEnd() const
471         {
472                 return row_it_ == row_end_;
473         }
474
475
476         bool operator==(RowListIterator const & a) const
477         {
478                 return row_it_ == a.row_it_ && ((atEnd() && a.atEnd()) || it_ == a.it_);
479         }
480
481
482         bool operator!=(RowListIterator const & a) const { return !operator==(a); }
483
484
485         // Current row.
486         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_
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
516 TexRow::RowListIterator TexRow::begin() const
517 {
518         return RowListIterator(rowlist_.begin(), rowlist_.end());
519 }
520
521
522 TexRow::RowListIterator TexRow::end() const
523 {
524         return RowListIterator(rowlist_.end(), rowlist_.end());
525 }
526
527
528 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
608 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
624 size_t TexRow::rows() const
625 {
626         return rowlist_.size();
627 }
628
629
630 void TexRow::setRows(size_t r)
631 {
632         rowlist_.resize(r, RowEntryList());
633 }
634
635
636 // debugging functions
637
638 ///
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
660 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