]> git.lyx.org Git - lyx.git/blob - src/TexRow.cpp
Fix #10778 (issue with CJK and language nesting)
[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 "Cursor.h"
17 #include "Paragraph.h"
18 #include "TexRow.h"
19
20 #include "mathed/InsetMath.h"
21
22 #include "support/debug.h"
23 #include "support/docstring_list.h"
24
25 #include <algorithm>
26 #include <sstream>
27
28
29 namespace lyx {
30
31
32
33 bool TexRow::RowEntryList::addEntry(RowEntry const & entry)
34 {
35         if (!entry.is_math) {
36                 if (text_entry_ < size())
37                         return false;
38                 else {
39                         text_entry_ = size();
40                         push_back(entry);
41                         return true;
42                 }
43         }
44         forceAddEntry(entry);
45         return true;
46 }
47
48
49 void TexRow::RowEntryList::forceAddEntry(RowEntry const & entry)
50 {
51         if (size() == 0 || !(operator[](size() - 1) == entry))
52                 push_back(RowEntry(entry));
53 }
54
55
56 TexRow::TextEntry TexRow::RowEntryList::getTextEntry() const
57 {
58         if (text_entry_ < size())
59                 return operator[](text_entry_).text;
60         return TexRow::text_none;
61 }
62
63
64 TexRow::RowEntry TexRow::RowEntryList::entry() const
65 {
66         if (0 < size())
67                 return operator[](0);
68         return TexRow::row_none;
69 }
70
71
72 void TexRow::RowEntryList::append(RowEntryList const & row)
73 {
74         if (text_entry_ >= size())
75                 text_entry_ = row.text_entry_ + size();
76         insert(end(), row.begin(), row.end());
77 }
78
79
80 TexRow::TextEntry const TexRow::text_none = { -1, 0 };
81 TexRow::RowEntry const TexRow::row_none = { false, { TexRow::text_none } };
82
83
84 bool TexRow::isNone(TextEntry const & t)
85 {
86         return t.id < 0;
87 }
88
89
90 bool TexRow::isNone(RowEntry const & r)
91 {
92         return !r.is_math && isNone(r.text);
93 }
94
95
96 void TexRow::reset(bool enable)
97 {
98         rowlist_.clear();
99         current_row_ = RowEntryList();
100         enabled_ = enable;
101 }
102
103
104 TexRow::RowEntry TexRow::textEntry(int id, int pos)
105 {
106         RowEntry entry;
107         entry.is_math = false;
108         entry.text.pos = pos;
109         entry.text.id = id;
110         return entry;
111 }
112
113
114 TexRow::RowEntry TexRow::mathEntry(uid_type id, idx_type cell)
115 {
116         RowEntry entry;
117         entry.is_math = true;
118         entry.math.cell = cell;
119         entry.math.id = id;
120         return entry;
121 }
122
123
124 bool operator==(TexRow::RowEntry const & entry1,
125                                 TexRow::RowEntry const & entry2)
126 {
127         return entry1.is_math == entry2.is_math
128                 && (entry1.is_math
129                         ? (entry1.math.id == entry2.math.id
130                            && entry1.math.cell == entry2.math.cell)
131                         : (entry1.text.id == entry2.text.id
132                            && entry1.text.pos == entry2.text.pos));
133 }
134
135
136 bool TexRow::start(RowEntry entry)
137 {
138         if (!enabled_)
139                 return false;
140         return current_row_.addEntry(entry);
141 }
142
143
144 bool TexRow::start(int id, int pos)
145 {
146         return start(textEntry(id,pos));
147 }
148
149
150 void TexRow::forceStart(int id, int pos)
151 {
152         if (!enabled_)
153                 return;
154         return current_row_.forceAddEntry(textEntry(id,pos));
155 }
156
157
158 void TexRow::startMath(uid_type id, idx_type cell)
159 {
160         start(mathEntry(id,cell));
161 }
162
163
164 void TexRow::newline()
165 {
166         if (!enabled_)
167                 return;
168         rowlist_.push_back(current_row_);
169         current_row_ = RowEntryList();
170 }
171
172 void TexRow::newlines(int num_lines)
173 {
174         if (!enabled_)
175                 return;
176         for (int i = 0; i < num_lines; ++i) {
177                 newline();
178         }
179 }
180
181 void TexRow::finalize()
182 {
183         if (!enabled_)
184                 return;
185         newline();
186 }
187
188
189 void TexRow::append(TexRow const & texrow)
190 {
191         if (!enabled_ || !texrow.enabled_)
192                 return;
193         RowList::const_iterator it = texrow.rowlist_.begin();
194         RowList::const_iterator const end = texrow.rowlist_.end();
195         if (it == end) {
196                 current_row_.append(texrow.current_row_);
197         } else {
198                 current_row_.append(*it++);
199                 rowlist_.push_back(current_row_);
200                 rowlist_.insert(rowlist_.end(), it, end);
201                 current_row_ = texrow.current_row_;
202         }
203 }
204
205
206
207 bool TexRow::getIdFromRow(int row, int & id, int & pos) const
208 {
209         TextEntry t = text_none;
210         if (row <= int(rowlist_.size()))
211                 while (row > 0 && isNone(t = rowlist_[row - 1].getTextEntry()))
212                         --row;
213         id = t.id;
214         pos = t.pos;
215         return !isNone(t);
216 }
217
218
219 TexRow::RowEntry TexRow::rowEntryFromCursorSlice(CursorSlice const & slice)
220 {
221         RowEntry entry;
222         InsetMath * insetMath = slice.asInsetMath();
223         if (insetMath) {
224                 entry.is_math = 1;
225                 entry.math.id = insetMath->id();
226                 entry.math.cell = slice.idx();
227         } else if (slice.text()) {
228                 entry.is_math = 0;
229                 entry.text.id = slice.paragraph().id();
230                 entry.text.pos = slice.pos();
231         } else {
232                 // should not happen
233                 entry = row_none;
234         }
235         return entry;
236 }
237
238
239 bool TexRow::sameParOrInsetMath(RowEntry const & entry1,
240                                                                 RowEntry const & entry2)
241 {
242         return entry1.is_math == entry2.is_math
243                 && (entry1.is_math
244                         ? (entry1.math.id == entry2.math.id)
245                         : (entry1.text.id == entry2.text.id));
246 }
247
248
249 // assumes it is sameParOrInsetMath
250 int TexRow::comparePos(RowEntry const & entry1,
251                                            RowEntry const & entry2)
252 {
253         if (entry1.is_math)
254                 return entry2.math.cell - entry1.math.cell;
255         else
256                 return entry2.text.pos - entry1.text.pos;
257 }
258
259
260 // An iterator on RowList that goes top-down, left-right
261 //
262 // We assume that the end of RowList does not change, which makes things simpler
263 //
264 // Records a pair of iterators on the RowEntryList (row_it_, row_end_) and a
265 // pair of iterators on the current row (it_, it_end_).
266 //
267 // it_ always points to a valid position unless row_it_ == row_end_.
268 //
269 // We could turn this into a proper bidirectional iterator, but we don't need as
270 // much.
271 //
272 class TexRow::RowListIterator
273 {
274 public:
275         RowListIterator(RowList::const_iterator r,
276                                         RowList::const_iterator r_end)
277                 : row_it_(r), row_end_(r_end),
278                   it_(r == r_end ? RowEntryList::const_iterator() : r->begin()),
279                   it_end_(r == r_end ? RowEntryList::const_iterator() : r->end())
280         {
281                 normalize();
282         }
283
284
285         RowListIterator() :
286                 row_it_(RowList::const_iterator()),
287                 row_end_(RowList::const_iterator()),
288                 it_(RowEntryList::const_iterator()),
289                 it_end_(RowEntryList::const_iterator()) { }
290
291
292         RowEntry const & operator*()
293         {
294                 return *it_;
295         }
296
297
298         RowListIterator & operator++()
299         {
300                 ++it_;
301                 normalize();
302                 return *this;
303         }
304
305
306         bool atEnd() const
307         {
308                 return row_it_ == row_end_;
309         }
310
311
312         bool operator==(RowListIterator const & a) const
313         {
314                 return row_it_ == a.row_it_ && ((atEnd() && a.atEnd()) || it_ == a.it_);
315         }
316
317
318         bool operator!=(RowListIterator const & a) const { return !operator==(a); }
319
320
321         // Current row.
322         RowList::const_iterator const & row() const
323         {
324                 return row_it_;
325         }
326 private:
327         // ensures that it_ points to a valid value unless row_it_ == row_end_
328         void normalize()
329         {
330                 if (row_it_ == row_end_)
331                         return;
332                 while (it_ == it_end_) {
333                         ++row_it_;
334                         if (row_it_ != row_end_) {
335                                 it_ = row_it_->begin();
336                                 it_end_ = row_it_->end();
337                         } else
338                                 return;
339                 }
340         }
341         //
342         RowList::const_iterator row_it_;
343         //
344         RowList::const_iterator row_end_;
345         //
346         RowEntryList::const_iterator it_;
347         //
348         RowEntryList::const_iterator it_end_;
349 };
350
351
352 TexRow::RowListIterator TexRow::begin() const
353 {
354         return RowListIterator(rowlist_.begin(), rowlist_.end());
355 }
356
357
358 TexRow::RowListIterator TexRow::end() const
359 {
360         return RowListIterator(rowlist_.end(), rowlist_.end());
361 }
362
363
364 std::pair<int,int> TexRow::rowFromDocIterator(DocIterator const & dit) const
365 {
366         bool beg_found = false;
367         bool end_is_next = true;
368         int end_offset = 1;
369         size_t best_slice = 0;
370         RowEntry best_entry = row_none;
371         size_t const n = dit.depth();
372         // this loop finds a pair (best_beg_row,best_end_row) where best_beg_row is
373         // the first row of the topmost possible CursorSlice, and best_end_row is
374         // the one just before the first row matching the next CursorSlice.
375         RowListIterator const begin = this->begin();//necessary disambiguation
376         RowListIterator const end = this->end();
377         RowListIterator best_beg_entry;
378         //best last entry with same pos as the beg_entry, or first entry with pos
379         //immediately following the beg_entry
380         RowListIterator best_end_entry;
381         RowListIterator it = begin;
382         for (; it != end; ++it) {
383                 // Compute the best end row.
384                 if (beg_found
385                         && (!sameParOrInsetMath(*it, *best_end_entry)
386                                 || comparePos(*it, *best_end_entry) <= 0)
387                         && sameParOrInsetMath(*it, best_entry)) {
388                     switch (comparePos(*it, best_entry)) {
389                         case 0:
390                                 // Either it is the last one that matches pos...
391                                 best_end_entry = it;
392                                 end_is_next = false;
393                                 end_offset = 1;
394                                 break;
395                         case -1: {
396                                 // ...or it is the row preceding the first that matches pos+1
397                                 if (!end_is_next) {
398                                         end_is_next = true;
399                                         if (it.row() != best_end_entry.row())
400                                                 end_offset = 0;
401                                         best_end_entry = it;
402                                 }
403                                 break;
404                         }
405                         }
406                 }
407                 // Compute the best begin row. It is better than the previous one if it
408                 // matches either at a deeper level, or at the same level but not
409                 // before.
410                 for (size_t i = best_slice; i < n; ++i) {
411                         TexRow::RowEntry entry_i = rowEntryFromCursorSlice(dit[i]);
412                         if (sameParOrInsetMath(*it, entry_i)) {
413                                 if (comparePos(*it, entry_i) >= 0
414                                         && (i > best_slice
415                                                 || !beg_found
416                                                 || !sameParOrInsetMath(*it, *best_beg_entry)
417                                                 || (comparePos(*it, *best_beg_entry) <= 0
418                                                         && comparePos(entry_i, *best_beg_entry) != 0)
419                                                 )
420                                         ) {
421                                         beg_found = true;
422                                         end_is_next = false;
423                                         end_offset = 1;
424                                         best_slice = i;
425                                         best_entry = entry_i;
426                                         best_beg_entry = best_end_entry = it;
427                                 }
428                                 //found CursorSlice
429                                 break;
430                         }
431                 }
432         }
433         if (!beg_found)
434                 return std::make_pair(-1,-1);
435         int const best_beg_row = distance(rowlist_.begin(),
436                                                                           best_beg_entry.row()) + 1;
437         int const best_end_row = distance(rowlist_.begin(),
438                                                                           best_end_entry.row()) + end_offset;
439         return std::make_pair(best_beg_row, best_end_row);
440 }
441
442
443 std::pair<int,int> TexRow::rowFromCursor(Cursor const & cur) const
444 {
445         DocIterator beg = cur.selectionBegin();
446         std::pair<int,int> beg_rows = rowFromDocIterator(beg);
447         if (cur.selection()) {
448                 DocIterator end = cur.selectionEnd();
449                 if (!cur.selIsMultiCell()
450                         // backwardPos asserts without the following test, IMO it's not my
451                         // duty to check this.
452                         && (end.top().pit() != 0
453                                 || end.top().idx() != 0
454                                 || end.top().pos() != 0))
455                         end.top().backwardPos();
456                 std::pair<int,int> end_rows = rowFromDocIterator(end);
457                 return std::make_pair(std::min(beg_rows.first, end_rows.first),
458                                                           std::max(beg_rows.second, end_rows.second));
459         } else
460                 return std::make_pair(beg_rows.first, beg_rows.second);
461 }
462
463
464 // debugging functions
465
466 ///
467 docstring TexRow::asString(RowEntry const & entry)
468 {
469         odocstringstream os;
470         if (entry.is_math)
471                 os << "(1," << entry.math.id << "," << entry.math.cell << ")";
472         else
473                 os << "(0," << entry.text.id << "," << entry.text.pos << ")";
474         return os.str();
475 }
476
477
478 ///prepends the texrow to the source given by tex, for debugging purpose
479 void TexRow::prepend(docstring_list & tex) const
480 {
481         size_type const prefix_length = 25;
482         if (tex.size() < rowlist_.size())
483                 tex.resize(rowlist_.size());
484         std::vector<RowEntryList>::const_iterator it = rowlist_.begin();
485         std::vector<RowEntryList>::const_iterator const beg = rowlist_.begin();
486         std::vector<RowEntryList>::const_iterator const end = rowlist_.end();
487         for (; it < end; ++it) {
488                 docstring entry;
489                 std::vector<RowEntry>::const_iterator it2 = it->begin();
490                 std::vector<RowEntry>::const_iterator const end2 = it->end();
491                 for (; it2 != end2; ++it2)
492                         entry += asString(*it2);
493                 if (entry.length() < prefix_length)
494                         entry = entry + docstring(prefix_length - entry.length(), L' ');
495                 ptrdiff_t i = it - beg;
496                 tex[i] = entry + "  " + tex[i];
497         }
498 }
499
500
501
502 LyXErr & operator<<(LyXErr & l, TexRow & texrow)
503 {
504         if (l.enabled()) {
505                 for (int i = 0; i < texrow.rows(); i++) {
506                         int id,pos;
507                         if (texrow.getIdFromRow(i+1,id,pos) && id>0)
508                                 l << i+1 << ":" << id << ":" << pos << "\n";
509                 }
510         }
511         return l;
512 }
513
514
515
516 } // namespace lyx