]> git.lyx.org Git - lyx.git/blob - src/DocIterator.cpp
Update tex2lyx tests.
[lyx.git] / src / DocIterator.cpp
1 /**
2  * \file DocIterator.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author André Pönitz
7  * \author Alfredo Braunstein
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12
13 #include <config.h>
14
15 #include "DocIterator.h"
16
17 #include "Buffer.h"
18 #include "InsetList.h"
19 #include "Paragraph.h"
20 #include "LyXRC.h"
21 #include "Text.h"
22
23 #include "mathed/MathData.h"
24 #include "mathed/InsetMath.h"
25 #include "mathed/InsetMathHull.h"
26
27 #include "insets/InsetTabular.h"
28
29 #include "support/convert.h"
30 #include "support/debug.h"
31 #include "support/ExceptionMessage.h"
32 #include "support/gettext.h"
33 #include "support/lassert.h"
34 #include "support/lstrings.h"
35
36 #include <ostream>
37
38 using namespace std;
39 using namespace lyx::support;
40
41 namespace lyx {
42
43
44 DocIterator::DocIterator()
45         : boundary_(false), inset_(0), buffer_(0)
46 {}
47
48
49 // We could be able to get rid of this if only every BufferView were
50 // associated to a buffer on construction.
51 DocIterator::DocIterator(Buffer * buf)
52         : boundary_(false), inset_(0), buffer_(buf)
53 {}
54
55
56 DocIterator::DocIterator(Buffer * buf, Inset * inset)
57         : boundary_(false), inset_(inset), buffer_(buf)
58 {}
59
60
61 DocIterator doc_iterator_begin(const Buffer * buf0, const Inset * inset0)
62 {
63         Buffer * buf = const_cast<Buffer *>(buf0);      
64         Inset * inset = const_cast<Inset *>(inset0);
65         DocIterator dit(buf, inset ? inset : &buf->inset());
66         dit.forwardPos();
67         return dit;
68 }
69
70
71 DocIterator doc_iterator_end(const Buffer * buf0, const Inset * inset0)
72 {
73         Buffer * buf = const_cast<Buffer *>(buf0);      
74         Inset * inset = const_cast<Inset *>(inset0);
75         return DocIterator(buf, inset ? inset : &buf->inset());
76 }
77
78
79 DocIterator DocIterator::clone(Buffer * buffer) const
80 {
81         LASSERT(buffer->isClone(), return DocIterator());
82         Inset * inset = &buffer->inset();
83         DocIterator dit(buffer);
84         size_t const n = slices_.size();
85         for (size_t i = 0 ; i != n; ++i) {
86                 LBUFERR(inset);
87                 dit.push_back(slices_[i]);
88                 dit.top().inset_ = inset;
89                 if (i + 1 != n)
90                         inset = dit.nextInset();
91         }
92         return dit;
93 }
94
95
96 bool DocIterator::inRegexped() const
97 {
98         InsetMath * im = inset().asInsetMath();
99         if (!im) 
100                 return false;
101         InsetMathHull * hull = im->asHullInset();
102         return hull && hull->getType() == hullRegexp;
103 }
104
105
106 LyXErr & operator<<(LyXErr & os, DocIterator const & it)
107 {
108         os.stream() << it;
109         return os;
110 }
111
112
113 Inset * DocIterator::nextInset() const
114 {
115         LASSERT(!empty(), return 0);
116         if (pos() == lastpos())
117                 return 0;
118         if (pos() > lastpos()) {
119                 LYXERR0("Should not happen, but it does: pos() = "
120                         << pos() << ", lastpos() = " << lastpos());
121                 return 0;
122         }
123         if (inMathed())
124                 return nextAtom().nucleus();
125         return paragraph().getInset(pos());
126 }
127
128
129 Inset * DocIterator::prevInset() const
130 {
131         LASSERT(!empty(), return 0);
132         if (pos() == 0)
133                 return 0;
134         if (inMathed()) {
135                 if (cell().empty())
136                         // FIXME: this should not happen but it does.
137                         // See bug 3189
138                         // http://www.lyx.org/trac/ticket/3189
139                         return 0;
140                 else
141                         return prevAtom().nucleus();
142         }
143         return paragraph().getInset(pos() - 1);
144 }
145
146
147 Inset * DocIterator::realInset() const
148 {
149         LASSERT(inTexted(), return 0);
150         // if we are in a tabular, we need the cell
151         if (inset().lyxCode() == TABULAR_CODE) {
152                 InsetTabular * tabular = inset().asInsetTabular();
153                 return tabular->cell(idx()).get();
154         }
155         return &inset();
156 }
157
158
159 MathAtom & DocIterator::prevAtom() const
160 {
161         LASSERT(!empty(), /**/);
162         LASSERT(pos() > 0, /**/);
163         return cell()[pos() - 1];
164 }
165
166
167 MathAtom & DocIterator::nextAtom() const
168 {
169         LASSERT(!empty(), /**/);
170         //lyxerr << "lastpos: " << lastpos() << " next atom:\n" << *this << endl;
171         LASSERT(pos() < lastpos(), /**/);
172         return cell()[pos()];
173 }
174
175
176 Text * DocIterator::text() const
177 {
178         LASSERT(!empty(), return 0);
179         return top().text();
180 }
181
182
183 Paragraph & DocIterator::paragraph() const
184 {
185         if (!inTexted()) {
186                 LYXERR0(*this);
187                 LBUFERR(false);
188         }
189         return top().paragraph();
190 }
191
192
193 Paragraph & DocIterator::innerParagraph() const
194 {
195         LBUFERR(!empty());
196         return innerTextSlice().paragraph();
197 }
198
199
200 FontSpan DocIterator::locateWord(word_location const loc) const
201 {
202         FontSpan f = FontSpan();
203
204         if (!top().text()->empty()) {
205                 f.first = pos();
206                 top().paragraph().locateWord(f.first, f.last, loc);
207         }
208         return f;
209 }
210
211
212 CursorSlice const & DocIterator::innerTextSlice() const
213 {
214         LBUFERR(!empty());
215         // go up until first non-0 text is hit
216         // (innermost text is 0 in mathed)
217         for (int i = depth() - 1; i >= 0; --i)
218                 if (slices_[i].text())
219                         return slices_[i];
220
221         // This case is in principle not possible. We _must_
222         // be inside a Text.
223         LBUFERR(false);
224         // Squash warning
225         static const CursorSlice c;
226         return c;
227 }
228
229
230 docstring DocIterator::paragraphGotoArgument() const
231 {
232         CursorSlice const & s = innerTextSlice();
233         return convert<docstring>(s.paragraph().id()) + ' ' +
234                 convert<docstring>(s.pos());
235 }
236
237
238 DocIterator DocIterator::getInnerText() const
239 {
240         DocIterator texted = *this;
241         while (!texted.inTexted())
242                 texted.pop_back();
243         return texted;
244 }
245
246
247 pit_type DocIterator::lastpit() const
248 {
249         return inMathed() ? 0 : text()->paragraphs().size() - 1;
250 }
251
252
253 pos_type DocIterator::lastpos() const
254 {
255         return inMathed() ? cell().size() : paragraph().size();
256 }
257
258
259 DocIterator::idx_type DocIterator::lastidx() const
260 {
261         return top().lastidx();
262 }
263
264
265 size_t DocIterator::nargs() const
266 {
267         // assume 1x1 grid for main text
268         return top().nargs();
269 }
270
271
272 size_t DocIterator::ncols() const
273 {
274         // assume 1x1 grid for main text
275         return top().ncols();
276 }
277
278
279 size_t DocIterator::nrows() const
280 {
281         // assume 1x1 grid for main text
282         return top().nrows();
283 }
284
285
286 DocIterator::row_type DocIterator::row() const
287 {
288         return top().row();
289 }
290
291
292 DocIterator::col_type DocIterator::col() const
293 {
294         return top().col();
295 }
296
297
298 MathData & DocIterator::cell() const
299 {
300 //      LASSERT(inMathed(), /**/);
301         return top().cell();
302 }
303
304
305 Text * DocIterator::innerText() const
306 {
307         LASSERT(!empty(), return 0);
308         return innerTextSlice().text();
309 }
310
311
312 Inset * DocIterator::innerInsetOfType(int code) const
313 {
314         for (int i = depth() - 1; i >= 0; --i)
315                 if (slices_[i].inset_->lyxCode() == code)
316                         return slices_[i].inset_;
317         return 0;
318 }
319
320
321 // This duplicates code above, but is in the critical path.
322 // So please think twice before adding stuff
323 void DocIterator::forwardPos()
324 {
325         // this dog bites his tail
326         if (empty()) {
327                 push_back(CursorSlice(*inset_));
328                 return;
329         }
330
331         CursorSlice & tip = top();
332         //lyxerr << "XXX\n" << *this << endl;
333
334         // not at cell/paragraph end?
335         if (tip.pos() != tip.lastpos()) {
336                 // move into an inset to the right if possible
337                 Inset * n = 0;
338                 if (inMathed())
339                         n = (tip.cell().begin() + tip.pos())->nucleus();
340                 else
341                         n = paragraph().getInset(tip.pos());
342                 if (n && n->isActive()) {
343                         //lyxerr << "... descend" << endl;
344                         push_back(CursorSlice(*n));
345                         return;
346                 }
347         }
348
349         // jump to the next cell/paragraph if possible
350         if (!tip.at_end()) {
351                 tip.forwardPos();
352                 return;
353         }
354
355         // otherwise leave inset and jump over inset as a whole
356         pop_back();
357         // 'tip' is invalid now...
358         if (!empty())
359                 ++top().pos();
360 }
361
362
363 void DocIterator::forwardPosIgnoreCollapsed()
364 {
365         Inset * const nextinset = nextInset();
366         // FIXME: the check for asInsetMath() shouldn't be necessary
367         // but math insets do not return a sensible editable() state yet.
368         if (nextinset && !nextinset->asInsetMath()
369             && !nextinset->editable()) {
370                 ++top().pos();
371                 return;
372         }
373         forwardPos();
374 }
375
376
377 void DocIterator::forwardPar()
378 {
379         forwardPos();
380
381         while (!empty() && (!inTexted() || pos() != 0)) {
382                 if (inTexted()) {
383                         pos_type const lastp = lastpos();
384                         Paragraph const & par = paragraph();
385                         pos_type & pos = top().pos();
386                         if (par.insetList().empty())
387                                 pos = lastp;
388                         else
389                                 while (pos < lastp && !par.isInset(pos))
390                                         ++pos;
391                 }
392                 forwardPos();
393         }
394 }
395
396
397 void DocIterator::forwardChar()
398 {
399         forwardPos();
400         while (!empty() && pos() == lastpos())
401                 forwardPos();
402 }
403
404
405 void DocIterator::forwardInset()
406 {
407         forwardPos();
408
409         while (!empty() && !nextInset()) {
410                 if (inTexted()) {
411                         pos_type const lastp = lastpos();
412                         Paragraph const & par = paragraph();
413                         pos_type & pos = top().pos();
414                         while (pos < lastp && !par.isInset(pos))
415                                 ++pos;
416                         if (pos < lastp)
417                                 break;
418                 }
419                 forwardPos();
420         }
421 }
422
423
424 void DocIterator::backwardChar()
425 {
426         backwardPos();
427         while (!empty() && pos() == lastpos())
428                 backwardPos();
429 }
430
431
432 void DocIterator::backwardPos()
433 {
434         //this dog bites his tail
435         if (empty()) {
436                 push_back(CursorSlice(*inset_));
437                 top().idx() = lastidx();
438                 top().pit() = lastpit();
439                 top().pos() = lastpos();
440                 return;
441         }
442
443         // at inset beginning?
444         if (top().at_begin()) {
445                 pop_back();
446                 return;
447         }
448
449         top().backwardPos();
450
451         // entered another cell/paragraph from the right?
452         if (top().pos() == top().lastpos())
453                 return;
454
455         // move into an inset to the left if possible
456         Inset * n = 0;
457         if (inMathed())
458                 n = (top().cell().begin() + top().pos())->nucleus();
459         else
460                 n = paragraph().getInset(top().pos());
461         if (n && n->isActive()) {
462                 push_back(CursorSlice(*n));
463                 top().idx() = lastidx();
464                 top().pit() = lastpit();
465                 top().pos() = lastpos();
466         }
467 }
468
469
470 #if 0
471 // works, but currently not needed
472 void DocIterator::backwardInset()
473 {
474         backwardPos();
475
476         while (!empty() && !nextInset()) {
477                 if (inTexted()) {
478                         pos_type const lastp = lastpos();
479                         Paragraph const & par = paragraph();
480                         pos_type & pos = top().pos();
481                         while (pos > 0 && (pos == lastp || !par.isInset(pos)))
482                                 --pos;
483                         if (pos > 0)
484                                 break;
485                 }
486                 backwardPos();
487         }
488 }
489 #endif
490
491
492 bool DocIterator::hasPart(DocIterator const & it) const
493 {
494         // it can't be a part if it is larger
495         if (it.depth() > depth())
496                 return false;
497
498         // as inset adresses are the 'last' level
499         return &it.top().inset() == &slices_[it.depth() - 1].inset();
500 }
501
502
503 bool DocIterator::allowSpellCheck() const
504 {
505         /// spell check is disabled if the iterator position
506         /// is inside of an inset which disables the spell checker
507         size_t const n = depth();
508         for (size_t i = 0; i < n; ++i) {
509                 if (!slices_[i].inset_->allowSpellCheck())
510                         return false;
511         }
512         return true;
513 }
514
515
516 void DocIterator::updateInsets(Inset * inset)
517 {
518         // this function re-creates the cache of inset pointers.
519         //lyxerr << "converting:\n" << *this << endl;
520         DocIterator dit = *this;
521         size_t const n = slices_.size();
522         slices_.resize(0);
523         for (size_t i = 0 ; i < n; ++i) {
524                 LBUFERR(inset);
525                 push_back(dit[i]);
526                 top().inset_ = inset;
527                 if (i + 1 != n)
528                         inset = nextInset();
529         }
530         //lyxerr << "converted:\n" << *this << endl;
531 }
532
533
534 bool DocIterator::fixIfBroken()
535 {
536         if (empty())
537                 return false;
538
539         // Go through the slice stack from the bottom. 
540         // Check that all coordinates (idx, pit, pos) are correct and
541         // that the inset is the one which is claimed to be there
542         Inset * inset = &slices_[0].inset();
543         size_t i = 0;
544         size_t n = slices_.size();
545         for (; i != n; ++i) {
546                 CursorSlice & cs = slices_[i];
547                 if (&cs.inset() != inset) {
548                         // the whole slice is wrong, chop off this as well
549                         --i;
550                         LYXERR(Debug::DEBUG, "fixIfBroken(): inset changed");
551                         break;
552                 } else if (cs.idx() > cs.lastidx()) {
553                         cs.idx() = cs.lastidx();
554                         cs.pit() = cs.lastpit();
555                         cs.pos() = cs.lastpos();
556                         LYXERR(Debug::DEBUG, "fixIfBroken(): idx fixed");
557                         break;
558                 } else if (cs.pit() > cs.lastpit()) {
559                         cs.pit() = cs.lastpit();
560                         cs.pos() = cs.lastpos();
561                         LYXERR(Debug::DEBUG, "fixIfBroken(): pit fixed");
562                         break;
563                 } else if (cs.pos() > cs.lastpos()) {
564                         cs.pos() = cs.lastpos();
565                         LYXERR(Debug::DEBUG, "fixIfBroken(): pos fixed");
566                         break;
567                 } else if (i != n - 1 && cs.pos() != cs.lastpos()) {
568                         // get inset which is supposed to be in the next slice
569                         if (cs.inset().inMathed())
570                                 inset = (cs.cell().begin() + cs.pos())->nucleus();
571                         else if (Inset * csInset = cs.paragraph().getInset(cs.pos()))
572                                 inset = csInset;
573                         else {
574                                 // there are slices left, so there must be another inset
575                                 break;
576                         }
577                 }
578         }
579
580         // Did we make it through the whole slice stack? Otherwise there
581         // was a problem at slice i, and we have to chop off above
582         if (i < n) {
583                 LYXERR(Debug::DEBUG, "fixIfBroken(): cursor chopped at " << i);
584                 resize(i + 1);
585                 return true;
586         } else
587                 return false;
588 }
589
590
591 void DocIterator::sanitize()
592 {
593         // keep a copy of the slices
594         vector<CursorSlice> const sl = slices_;
595         slices_.clear();
596         if (buffer_)
597                 inset_ = &buffer_->inset();
598         Inset * inset = inset_;
599         // re-add the slices one by one, and adjust the inset pointer.
600         for (size_t i = 0, n = sl.size(); i != n; ++i) {
601                 if (inset == 0) {
602                         // FIXME
603                         LYXERR0(" Should not happen, but does e.g. after "
604                                 "C-n C-l C-z S-C-z\n"
605                                 << " or when a Buffer has been concurrently edited by two views"
606                                 << '\n' << "dit: " << *this << '\n'
607                                 << " lastpos: " << slices_[i].lastpos());
608                         fixIfBroken();
609                         break;
610                 }
611                 if (!inset->isActive()) {
612                         LYXERR0("Inset found on cursor stack is not active.");
613                         fixIfBroken();
614                         break;
615                 }
616                 push_back(sl[i]);
617                 top().inset_ = inset;
618                 if (fixIfBroken())
619                         break;
620                 if (i + 1 != n)
621                         inset = nextInset();
622         }
623 }
624
625
626 int DocIterator::find(MathData const & cell) const
627 {
628         for (size_t l = 0; l != slices_.size(); ++l) {
629                 if (slices_[l].asInsetMath() && &slices_[l].cell() == &cell)
630                         return l;
631         }
632         return -1;
633 }
634
635
636 int DocIterator::find(Inset const * inset) const 
637 {
638         for (size_t l = 0; l != slices_.size(); ++l) {
639                 if (&slices_[l].inset() == inset)
640                         return l;
641         }
642         return -1;
643 }
644
645
646 void DocIterator::cutOff(int above, vector<CursorSlice> & cut)
647 {
648         cut = vector<CursorSlice>(slices_.begin() + above + 1, slices_.end());
649         slices_.resize(above + 1);
650 }
651
652
653 void DocIterator::cutOff(int above)
654 {
655         slices_.resize(above + 1);
656 }
657
658
659 void DocIterator::append(vector<CursorSlice> const & x) 
660 {
661         slices_.insert(slices_.end(), x.begin(), x.end());
662 }
663
664
665 void DocIterator::append(DocIterator::idx_type idx, pos_type pos) 
666 {
667         slices_.push_back(CursorSlice());
668         top().idx() = idx;
669         top().pos() = pos;
670 }
671
672
673 ostream & operator<<(ostream & os, DocIterator const & dit)
674 {
675         for (size_t i = 0, n = dit.depth(); i != n; ++i)
676                 os << " " << dit[i] << "\n";
677         return os;
678 }
679
680
681 ///////////////////////////////////////////////////////
682
683 StableDocIterator::StableDocIterator(DocIterator const & dit) :
684         data_(dit.internalData())
685 {
686         for (size_t i = 0, n = data_.size(); i != n; ++i)
687                 data_[i].inset_ = 0;
688 }
689
690
691 DocIterator StableDocIterator::asDocIterator(Buffer * buf) const
692 {
693         DocIterator dit(buf);
694         dit.slices_ = data_;
695         dit.sanitize();
696         return dit;
697 }
698
699
700 ostream & operator<<(ostream & os, StableDocIterator const & dit)
701 {
702         for (size_t i = 0, n = dit.data_.size(); i != n; ++i)
703                 os << " " << dit.data_[i] << "\n";
704         return os;
705 }
706
707
708 bool operator==(StableDocIterator const & dit1, StableDocIterator const & dit2)
709 {
710         return dit1.data_ == dit2.data_;
711 }
712
713
714 } // namespace lyx