]> git.lyx.org Git - lyx.git/blob - src/DocIterator.cpp
XHTML: fix generation of many useless </section>.
[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 "BufferParams.h"
19 #include "Encoding.h"
20 #include "Font.h"
21 #include "InsetList.h"
22 #include "Language.h"
23 #include "Paragraph.h"
24 #include "Text.h"
25
26 #include "mathed/MathData.h"
27 #include "mathed/InsetMath.h"
28 #include "mathed/InsetMathHull.h"
29
30 #include "insets/InsetTabular.h"
31
32 #include "support/convert.h"
33 #include "support/debug.h"
34 #include "support/ExceptionMessage.h"
35 #include "support/gettext.h"
36 #include "support/lassert.h"
37 #include "support/lstrings.h"
38
39 #include <ostream>
40
41 using namespace std;
42 using namespace lyx::support;
43
44 namespace lyx {
45
46
47 DocIterator::DocIterator()
48         : boundary_(false), inset_(nullptr), buffer_(nullptr)
49 {}
50
51
52 // We could be able to get rid of this if only every BufferView were
53 // associated to a buffer on construction.
54 DocIterator::DocIterator(Buffer * buf)
55         : boundary_(false), inset_(nullptr), buffer_(buf)
56 {}
57
58
59 DocIterator::DocIterator(Buffer * buf, Inset * inset)
60         : boundary_(false), inset_(inset), buffer_(buf)
61 {}
62
63
64 DocIterator doc_iterator_begin(const Buffer * buf0, const Inset * inset0)
65 {
66         Buffer * buf = const_cast<Buffer *>(buf0);
67         Inset * inset = const_cast<Inset *>(inset0);
68         DocIterator dit(buf, inset ? inset : &buf->inset());
69         dit.forwardPos();
70         return dit;
71 }
72
73
74 DocIterator doc_iterator_end(const Buffer * buf0, const Inset * inset0)
75 {
76         Buffer * buf = const_cast<Buffer *>(buf0);
77         Inset * inset = const_cast<Inset *>(inset0);
78         return DocIterator(buf, inset ? inset : &buf->inset());
79 }
80
81
82 DocIterator DocIterator::clone(Buffer * buffer) const
83 {
84         LASSERT(buffer->isClone(), return DocIterator());
85         Inset * inset = &buffer->inset();
86         DocIterator dit(buffer);
87         size_t const n = slices_.size();
88         for (size_t i = 0 ; i != n; ++i) {
89                 LBUFERR(inset);
90                 dit.push_back(slices_[i]);
91                 dit.top().inset_ = inset;
92                 if (i + 1 != n)
93                         inset = dit.nextInset();
94         }
95         return dit;
96 }
97
98
99 bool DocIterator::inRegexped() const
100 {
101         InsetMath * im = inset().asInsetMath();
102         if (!im)
103                 return false;
104         InsetMathHull * hull = im->asHullInset();
105         return hull && hull->getType() == hullRegexp;
106 }
107
108
109 LyXErr & operator<<(LyXErr & os, DocIterator const & it)
110 {
111         os.stream() << it;
112         return os;
113 }
114
115
116 Inset * DocIterator::nextInset() const
117 {
118         LASSERT(!empty(), return nullptr);
119         if (pos() == lastpos())
120                 return nullptr;
121         if (pos() > lastpos()) {
122                 LYXERR0("Should not happen, but it does: pos() = "
123                         << pos() << ", lastpos() = " << lastpos());
124                 return nullptr;
125         }
126         if (inMathed())
127                 return nextAtom().nucleus();
128         return paragraph().getInset(pos());
129 }
130
131
132 Inset * DocIterator::prevInset() const
133 {
134         LASSERT(!empty(), return nullptr);
135         if (pos() == 0)
136                 return nullptr;
137         if (inMathed()) {
138                 if (cell().empty())
139                         // FIXME: this should not happen but it does.
140                         // See bug 3189
141                         // http://www.lyx.org/trac/ticket/3189
142                         return nullptr;
143                 else
144                         return prevAtom().nucleus();
145         }
146         return paragraph().getInset(pos() - 1);
147 }
148
149
150 Inset * DocIterator::realInset() const
151 {
152         LASSERT(inTexted(), return nullptr);
153         // if we are in a tabular, we need the cell
154         if (inset().lyxCode() == TABULAR_CODE) {
155                 InsetTabular * tabular = inset().asInsetTabular();
156                 return tabular->cell(idx()).get();
157         }
158         return &inset();
159 }
160
161
162 InsetMath & DocIterator::nextMath()
163 {
164         return *nextAtom().nucleus();
165 }
166
167
168 InsetMath & DocIterator::prevMath()
169 {
170         return *prevAtom().nucleus();
171 }
172
173
174 MathAtom & DocIterator::prevAtom() const
175 {
176         LASSERT(!empty(), /**/);
177         LASSERT(pos() > 0, /**/);
178         return cell()[pos() - 1];
179 }
180
181
182 MathAtom & DocIterator::nextAtom() const
183 {
184         LASSERT(!empty(), /**/);
185         //lyxerr << "lastpos: " << lastpos() << " next atom:\n" << *this << endl;
186         LASSERT(pos() < lastpos(), /**/);
187         return cell()[pos()];
188 }
189
190
191 Text * DocIterator::text() const
192 {
193         LASSERT(!empty(), return nullptr);
194         return top().text();
195 }
196
197
198 Paragraph & DocIterator::paragraph() const
199 {
200         if (!inTexted()) {
201                 LYXERR0(*this);
202                 LBUFERR(false);
203         }
204         return top().paragraph();
205 }
206
207
208 Paragraph & DocIterator::innerParagraph() const
209 {
210         LBUFERR(!empty());
211         return innerTextSlice().paragraph();
212 }
213
214
215 FontSpan DocIterator::locateWord(word_location const loc) const
216 {
217         FontSpan f = FontSpan();
218
219         if (!top().text()->empty()) {
220                 f.first = pos();
221                 top().paragraph().locateWord(f.first, f.last, loc);
222         }
223         return f;
224 }
225
226
227 CursorSlice const & DocIterator::innerTextSlice() const
228 {
229         LBUFERR(!empty());
230         // go up until first non-0 text is hit
231         // (innermost text is 0 in mathed)
232         for (int i = depth() - 1; i >= 0; --i)
233                 if (slices_[i].text())
234                         return slices_[i];
235
236         // This case is in principle not possible. We _must_
237         // be inside a Text.
238         LBUFERR(false);
239         // Squash warning
240         static const CursorSlice c;
241         return c;
242 }
243
244
245 docstring DocIterator::paragraphGotoArgument() const
246 {
247         CursorSlice const & s = innerTextSlice();
248         return convert<docstring>(s.paragraph().id()) + ' ' +
249                 convert<docstring>(s.pos());
250 }
251
252
253 DocIterator DocIterator::getInnerText() const
254 {
255         DocIterator texted = *this;
256         while (!texted.inTexted())
257                 texted.pop_back();
258         return texted;
259 }
260
261
262 pit_type DocIterator::lastpit() const
263 {
264         return inMathed() ? 0 : text()->paragraphs().size() - 1;
265 }
266
267
268 pos_type DocIterator::lastpos() const
269 {
270         return inMathed() ? cell().size() : paragraph().size();
271 }
272
273
274 DocIterator::idx_type DocIterator::lastidx() const
275 {
276         return top().lastidx();
277 }
278
279
280 size_t DocIterator::nargs() const
281 {
282         // assume 1x1 grid for main text
283         return top().nargs();
284 }
285
286
287 size_t DocIterator::ncols() const
288 {
289         // assume 1x1 grid for main text
290         return top().ncols();
291 }
292
293
294 size_t DocIterator::nrows() const
295 {
296         // assume 1x1 grid for main text
297         return top().nrows();
298 }
299
300
301 DocIterator::row_type DocIterator::row() const
302 {
303         return top().row();
304 }
305
306
307 DocIterator::col_type DocIterator::col() const
308 {
309         return top().col();
310 }
311
312
313 MathData & DocIterator::cell() const
314 {
315 //      LASSERT(inMathed(), /**/);
316         return top().cell();
317 }
318
319
320 Text * DocIterator::innerText() const
321 {
322         LASSERT(!empty(), return nullptr);
323         return innerTextSlice().text();
324 }
325
326
327 Inset * DocIterator::innerInsetOfType(int code) const
328 {
329         for (int i = depth() - 1; i >= 0; --i)
330                 if (slices_[i].inset_->lyxCode() == code)
331                         return slices_[i].inset_;
332         return nullptr;
333 }
334
335
336 bool DocIterator::posBackward()
337 {
338         if (pos() == 0)
339                 return false;
340         --pos();
341         return true;
342 }
343
344
345 bool DocIterator::posForward()
346 {
347         if (pos() == lastpos())
348                 return false;
349         ++pos();
350         return true;
351 }
352
353
354 // This duplicates code above, but is in the critical path.
355 // So please think twice before adding stuff
356 void DocIterator::forwardPos()
357 {
358         // this dog bites his tail
359         if (empty()) {
360                 push_back(CursorSlice(*inset_));
361                 return;
362         }
363
364         CursorSlice & tip = top();
365         //lyxerr << "XXX\n" << *this << endl;
366
367         // not at cell/paragraph end?
368         if (tip.pos() != tip.lastpos()) {
369                 // move into an inset to the right if possible
370                 Inset * n = nullptr;
371                 if (inMathed())
372                         n = (tip.cell().begin() + tip.pos())->nucleus();
373                 else
374                         n = paragraph().getInset(tip.pos());
375                 if (n && n->isActive()) {
376                         //lyxerr << "... descend" << endl;
377                         push_back(CursorSlice(*n));
378                         return;
379                 }
380         }
381
382         // jump to the next cell/paragraph if possible
383         if (!tip.at_end()) {
384                 tip.forwardPos();
385                 return;
386         }
387
388         // otherwise leave inset and jump over inset as a whole
389         pop_back();
390         // 'tip' is invalid now...
391         if (!empty())
392                 ++top().pos();
393 }
394
395
396 void DocIterator::forwardPosIgnoreCollapsed()
397 {
398         Inset * const nextinset = nextInset();
399         // FIXME: the check for asInsetMath() shouldn't be necessary
400         // but math insets do not return a sensible editable() state yet.
401         if (nextinset && !nextinset->asInsetMath()
402             && !nextinset->editable()) {
403                 ++top().pos();
404                 return;
405         }
406         forwardPos();
407 }
408
409
410 void DocIterator::forwardPar()
411 {
412         forwardPos();
413
414         while (!empty() && (!inTexted() || pos() != 0)) {
415                 if (inTexted()) {
416                         pos_type const lastp = lastpos();
417                         Paragraph const & par = paragraph();
418                         pos_type & pos = top().pos();
419                         if (par.insetList().empty())
420                                 pos = lastp;
421                         else
422                                 while (pos < lastp && !par.isInset(pos))
423                                         ++pos;
424                 }
425                 forwardPos();
426         }
427 }
428
429
430 void DocIterator::forwardChar()
431 {
432         forwardPos();
433         while (!empty() && pos() == lastpos())
434                 forwardPos();
435 }
436
437
438 void DocIterator::forwardInset()
439 {
440         forwardPos();
441
442         while (!empty() && !nextInset()) {
443                 if (inTexted()) {
444                         pos_type const lastp = lastpos();
445                         Paragraph const & par = paragraph();
446                         pos_type & pos = top().pos();
447                         while (pos < lastp && !par.isInset(pos))
448                                 ++pos;
449                         if (pos < lastp)
450                                 break;
451                 }
452                 forwardPos();
453         }
454 }
455
456
457 void DocIterator::backwardChar()
458 {
459         backwardPos();
460         while (!empty() && pos() == lastpos())
461                 backwardPos();
462 }
463
464
465 void DocIterator::backwardPos()
466 {
467         //this dog bites his tail
468         if (empty()) {
469                 push_back(CursorSlice(*inset_));
470                 top().idx() = lastidx();
471                 top().pit() = lastpit();
472                 top().pos() = lastpos();
473                 return;
474         }
475
476         // at inset beginning?
477         if (top().at_begin()) {
478                 pop_back();
479                 return;
480         }
481
482         top().backwardPos();
483
484         // entered another cell/paragraph from the right?
485         if (top().pos() == top().lastpos())
486                 return;
487
488         // move into an inset to the left if possible
489         Inset * n = nullptr;
490         if (inMathed())
491                 n = (top().cell().begin() + top().pos())->nucleus();
492         else
493                 n = paragraph().getInset(top().pos());
494         if (n && n->isActive()) {
495                 push_back(CursorSlice(*n));
496                 top().idx() = lastidx();
497                 top().pit() = lastpit();
498                 top().pos() = lastpos();
499         }
500 }
501
502
503 void DocIterator::backwardPosIgnoreCollapsed()
504 {
505         backwardPos();
506         if (inTexted()) {
507                 Inset const * ins = realInset();
508                 if (ins && !ins->editable()) {
509                         pop_back(); // move out of collapsed inset
510                 }
511         }
512 }
513
514
515 #if 0
516 // works, but currently not needed
517 void DocIterator::backwardInset()
518 {
519         backwardPos();
520
521         while (!empty() && !nextInset()) {
522                 if (inTexted()) {
523                         pos_type const lastp = lastpos();
524                         Paragraph const & par = paragraph();
525                         pos_type & pos = top().pos();
526                         while (pos > 0 && (pos == lastp || !par.isInset(pos)))
527                                 --pos;
528                         if (pos > 0)
529                                 break;
530                 }
531                 backwardPos();
532         }
533 }
534 #endif
535
536
537 bool DocIterator::hasPart(DocIterator const & it) const
538 {
539         // it can't be a part if it is larger
540         if (it.depth() > depth())
541                 return false;
542
543         // as inset adresses are the 'last' level
544         return &it.top().inset() == &slices_[it.depth() - 1].inset();
545 }
546
547
548 bool DocIterator::allowSpellCheck() const
549 {
550         /// spell check is disabled if the iterator position
551         /// is inside of an inset which disables the spell checker
552         size_t const n = depth();
553         for (size_t i = 0; i < n; ++i) {
554                 if (!slices_[i].inset_->allowSpellCheck())
555                         return false;
556         }
557         return true;
558 }
559
560
561 void DocIterator::updateInsets(Inset * inset)
562 {
563         // this function re-creates the cache of inset pointers.
564         //lyxerr << "converting:\n" << *this << endl;
565         DocIterator dit = *this;
566         size_t const n = slices_.size();
567         slices_.resize(0);
568         for (size_t i = 0 ; i < n; ++i) {
569                 LBUFERR(inset);
570                 push_back(dit[i]);
571                 top().inset_ = inset;
572                 if (i + 1 != n)
573                         inset = nextInset();
574         }
575         //lyxerr << "converted:\n" << *this << endl;
576 }
577
578
579 bool DocIterator::fixIfBroken()
580 {
581         if (empty())
582                 return false;
583
584         // Go through the slice stack from the bottom.
585         // Check that all coordinates (idx, pit, pos) are correct and
586         // that the inset is the one which is claimed to be there
587         Inset * inset = &slices_[0].inset();
588         size_t i = 0;
589         size_t n = slices_.size();
590         for (; i != n; ++i) {
591                 CursorSlice & cs = slices_[i];
592                 if (&cs.inset() != inset || ! cs.inset().isActive()) {
593                         // the whole slice is wrong, chop off this as well
594                         --i;
595                         LYXERR(Debug::DEBUG, "fixIfBroken(): inset changed");
596                         break;
597                 } else if (cs.idx() > cs.lastidx()) {
598                         cs.idx() = cs.lastidx();
599                         cs.pit() = cs.lastpit();
600                         cs.pos() = cs.lastpos();
601                         LYXERR(Debug::DEBUG, "fixIfBroken(): idx fixed");
602                         break;
603                 } else if (cs.pit() > cs.lastpit()) {
604                         cs.pit() = cs.lastpit();
605                         cs.pos() = cs.lastpos();
606                         LYXERR(Debug::DEBUG, "fixIfBroken(): pit fixed");
607                         break;
608                 } else if (cs.pos() > cs.lastpos()) {
609                         cs.pos() = cs.lastpos();
610                         LYXERR(Debug::DEBUG, "fixIfBroken(): pos fixed");
611                         break;
612                 } else if (i != n - 1 && cs.pos() != cs.lastpos()) {
613                         // get inset which is supposed to be in the next slice
614                         if (cs.inset().inMathed())
615                                 inset = (cs.cell().begin() + cs.pos())->nucleus();
616                         else if (Inset * csInset = cs.paragraph().getInset(cs.pos()))
617                                 inset = csInset;
618                         else {
619                                 // there are slices left, so there must be another inset
620                                 break;
621                         }
622                 }
623         }
624
625         // Did we make it through the whole slice stack? Otherwise there
626         // was a problem at slice i, and we have to chop off above
627         if (i < n) {
628                 LYXERR(Debug::DEBUG, "fixIfBroken(): cursor chopped at " << i);
629                 resize(i + 1);
630                 return true;
631         } else
632                 return false;
633 }
634
635
636 void DocIterator::sanitize()
637 {
638         // keep a copy of the slices
639         vector<CursorSlice> const sl = slices_;
640         slices_.clear();
641         if (buffer_)
642                 inset_ = &buffer_->inset();
643         Inset * inset = inset_;
644         // re-add the slices one by one, and adjust the inset pointer.
645         for (size_t i = 0, n = sl.size(); i != n; ++i) {
646                 if (inset == nullptr) {
647                         // FIXME
648                         LYXERR0("Null inset on cursor stack.");
649                         fixIfBroken();
650                         break;
651                 }
652                 if (!inset->isActive()) {
653                         LYXERR0("Inset found on cursor stack is not active.");
654                         fixIfBroken();
655                         break;
656                 }
657                 push_back(sl[i]);
658                 top().inset_ = inset;
659                 if (fixIfBroken())
660                         break;
661                 if (i + 1 != n)
662                         inset = nextInset();
663         }
664 }
665
666
667 bool DocIterator::isInside(Inset const * p) const
668 {
669         for (CursorSlice const & sl : slices_)
670                 if (&sl.inset() == p)
671                         return true;
672         return false;
673 }
674
675
676 void DocIterator::leaveInset(Inset const & inset)
677 {
678         for (size_t i = 0; i != slices_.size(); ++i) {
679                 if (&slices_[i].inset() == &inset) {
680                         resize(i);
681                         return;
682                 }
683         }
684 }
685
686
687 int DocIterator::find(MathData const & cell) const
688 {
689         for (size_t l = 0; l != slices_.size(); ++l) {
690                 if (slices_[l].asInsetMath() && &slices_[l].cell() == &cell)
691                         return l;
692         }
693         return -1;
694 }
695
696
697 int DocIterator::find(Inset const * inset) const
698 {
699         for (size_t l = 0; l != slices_.size(); ++l) {
700                 if (&slices_[l].inset() == inset)
701                         return l;
702         }
703         return -1;
704 }
705
706
707 void DocIterator::cutOff(int above, vector<CursorSlice> & cut)
708 {
709         cut = vector<CursorSlice>(slices_.begin() + above + 1, slices_.end());
710         slices_.resize(above + 1);
711 }
712
713
714 void DocIterator::cutOff(int above)
715 {
716         slices_.resize(above + 1);
717 }
718
719
720 void DocIterator::append(vector<CursorSlice> const & x)
721 {
722         slices_.insert(slices_.end(), x.begin(), x.end());
723 }
724
725
726 void DocIterator::append(DocIterator::idx_type idx, pos_type pos)
727 {
728         slices_.push_back(CursorSlice());
729         top().idx() = idx;
730         top().pos() = pos;
731 }
732
733
734 docstring DocIterator::getPossibleLabel() const
735 {
736         return inMathed() ? from_ascii("eq:") : text()->getPossibleLabel(*this);
737 }
738
739
740 Encoding const * DocIterator::getEncoding() const
741 {
742         if (empty())
743                 return nullptr;
744
745         BufferParams const & bp = buffer()->params();
746         if (bp.useNonTeXFonts)
747                 return encodings.fromLyXName("utf8-plain");
748
749         // With platex, we don't switch encodings (not even if forced).
750         if (bp.encoding().package() == Encoding::japanese)
751                 return &bp.encoding();
752
753         CursorSlice const & sl = innerTextSlice();
754         Text const & text = *sl.text();
755         Language const * lang =
756                 text.getPar(sl.pit()).getFont(bp, sl.pos(), text.outerFont(sl.pit())).language();
757         // If we have a custom encoding for the buffer, we don't switch
758         // encodings (see output_latex::switchEncoding())
759         bool const customenc = bp.inputenc != "auto-legacy" && bp.inputenc != "auto-legacy-plain";
760         Encoding const * enc = customenc ? &bp.encoding() : lang->encoding();
761
762         // Some insets force specific encodings sometimes (e.g., listings in
763         // multibyte context forces singlebyte).
764         if (inset().forcedEncoding(enc, encodings.fromLyXName("iso8859-1"))) {
765                 // Get the language outside the inset
766                 size_t const n = depth();
767                 for (size_t i = 0; i < n; ++i) {
768                         Text const & otext = *slices_[i].text();
769                         Language const * olang =
770                                         otext.getPar(slices_[i].pit()).getFont(bp, slices_[i].pos(),
771                                                                                otext.outerFont(slices_[i].pit())).language();
772                         Encoding const * oenc = olang->encoding();
773                         if (oenc->name() != "inherit")
774                                 return inset().forcedEncoding(enc, oenc);
775                 }
776                 // Fall back to buffer encoding if no outer lang was found.
777                 return inset().forcedEncoding(enc, &bp.encoding());
778         }
779
780         // Inherited encoding (latex_language) is determined by the context
781         // Look for the first outer encoding that is not itself "inherit"
782         if (lang->encoding()->name() == "inherit") {
783                 size_t const n = depth();
784                 for (size_t i = 0; i < n; ++i) {
785                         Text const & otext = *slices_[i].text();
786                         Language const * olang =
787                                         otext.getPar(slices_[i].pit()).getFont(bp, slices_[i].pos(),
788                                                                                otext.outerFont(slices_[i].pit())).language();
789                         // Again, if we have a custom encoding, this is used
790                         // instead of the language's.
791                         Encoding const * oenc = customenc ? &bp.encoding() : olang->encoding();
792                         if (olang->encoding()->name() != "inherit")
793                                 return oenc;
794                 }
795         }
796
797         return enc;
798 }
799
800
801 ostream & operator<<(ostream & os, DocIterator const & dit)
802 {
803         for (size_t i = 0, n = dit.depth(); i != n; ++i)
804                 os << " " << dit[i] << "\n";
805         return os;
806 }
807
808
809 ///////////////////////////////////////////////////////
810
811 StableDocIterator::StableDocIterator(DocIterator const & dit) :
812         data_(dit.internalData())
813 {
814         for (size_t i = 0, n = data_.size(); i != n; ++i)
815                 data_[i].inset_ = nullptr;
816 }
817
818
819 DocIterator StableDocIterator::asDocIterator(Buffer * buf) const
820 {
821         DocIterator dit(buf);
822         dit.slices_ = data_;
823         dit.sanitize();
824         return dit;
825 }
826
827
828 ostream & operator<<(ostream & os, StableDocIterator const & dit)
829 {
830         for (size_t i = 0, n = dit.data_.size(); i != n; ++i)
831                 os << " " << dit.data_[i] << "\n";
832         return os;
833 }
834
835
836 bool operator==(StableDocIterator const & dit1, StableDocIterator const & dit2)
837 {
838         return dit1.data_ == dit2.data_;
839 }
840
841
842 } // namespace lyx