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