]> git.lyx.org Git - lyx.git/blob - src/DocIterator.cpp
* gcc does not like missing characters in keywords
[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 "debug.h"
18 #include "InsetList.h"
19 #include "Paragraph.h"
20 #include "Text.h"
21
22 #include "mathed/MathData.h"
23 #include "mathed/InsetMath.h"
24
25 #include "insets/InsetTabular.h"
26
27 #include <boost/assert.hpp>
28 #include <boost/current_function.hpp>
29
30 using std::endl;
31
32
33 namespace lyx {
34
35
36 // We could be able to get rid of this if only every BufferView were
37 // associated to a buffer on construction.
38 DocIterator::DocIterator()
39         : boundary_(false), inset_(0)
40 {}
41
42
43 DocIterator::DocIterator(Inset & inset)
44         : boundary_(false), inset_(&inset)
45 {}
46
47
48 DocIterator doc_iterator_begin(Inset & inset)
49 {
50         DocIterator dit(inset);
51         dit.forwardPos();
52         return dit;
53 }
54
55
56 DocIterator doc_iterator_end(Inset & inset)
57 {
58         return DocIterator(inset);
59 }
60
61
62 Inset * DocIterator::nextInset() const
63 {
64         BOOST_ASSERT(!empty());
65         if (pos() == lastpos())
66                 return 0;
67         if (pos() > lastpos()) {
68                 lyxerr << "Should not happen, but it does. " << endl;
69                 return 0;
70         }
71         if (inMathed())
72                 return nextAtom().nucleus();
73         return paragraph().isInset(pos()) ? paragraph().getInset(pos()) : 0;
74 }
75
76
77 Inset * DocIterator::prevInset() const
78 {
79         BOOST_ASSERT(!empty());
80         if (pos() == 0)
81                 return 0;
82         if (inMathed()) {
83                 if (cell().empty())
84                         // FIXME: this should not happen but it does.
85                         // See bug 3189
86                         // http://bugzilla.lyx.org/show_bug.cgi?id=3189
87                         return 0;
88                 else
89                         return prevAtom().nucleus();
90         }
91         return paragraph().isInset(pos() - 1) ? paragraph().getInset(pos() - 1) : 0;
92 }
93
94
95 Inset * DocIterator::realInset() const
96 {
97         BOOST_ASSERT(inTexted());
98         // if we are in a tabular, we need the cell
99         if (inset().lyxCode() == TABULAR_CODE) {
100                 InsetTabular & tabular = static_cast<InsetTabular&>(inset());
101                 return tabular.cell(idx()).get();
102         }
103         return &inset();
104 }
105
106
107 MathAtom & DocIterator::prevAtom() const
108 {
109         BOOST_ASSERT(!empty());
110         BOOST_ASSERT(pos() > 0);
111         return cell()[pos() - 1];
112 }
113
114
115 MathAtom & DocIterator::nextAtom() const
116 {
117         BOOST_ASSERT(!empty());
118         //lyxerr << "lastpos: " << lastpos() << " next atom:\n" << *this << endl;
119         BOOST_ASSERT(pos() < lastpos());
120         return cell()[pos()];
121 }
122
123
124 Text * DocIterator::text() const
125 {
126         BOOST_ASSERT(!empty());
127         return top().text();
128 }
129
130
131 Paragraph & DocIterator::paragraph() const
132 {
133         if (!inTexted())
134                 lyxerr << *this << endl;
135         BOOST_ASSERT(inTexted());
136         return top().paragraph();
137 }
138
139
140 Paragraph & DocIterator::innerParagraph() const
141 {
142         BOOST_ASSERT(!empty());
143         return innerTextSlice().paragraph();
144 }
145
146
147 CursorSlice const & DocIterator::innerTextSlice() const
148 {
149         BOOST_ASSERT(!empty());
150         // go up until first non-0 text is hit
151         // (innermost text is 0 in mathed)
152         for (int i = depth() - 1; i >= 0; --i)
153                 if (slices_[i].text())
154                         return slices_[i];
155
156         // This case is in principe not possible. We _must_
157         // be inside a Text.
158         BOOST_ASSERT(false);
159         static CursorSlice dummy;
160         return dummy;
161 }
162
163
164 pit_type DocIterator::lastpit() const
165 {
166         return inMathed() ? 0 : text()->paragraphs().size() - 1;
167 }
168
169
170 pos_type DocIterator::lastpos() const
171 {
172         return inMathed() ? cell().size() : paragraph().size();
173 }
174
175
176 DocIterator::idx_type DocIterator::lastidx() const
177 {
178         return top().lastidx();
179 }
180
181
182 size_t DocIterator::nargs() const
183 {
184         // assume 1x1 grid for main text
185         return top().nargs();
186 }
187
188
189 size_t DocIterator::ncols() const
190 {
191         // assume 1x1 grid for main text
192         return top().ncols();
193 }
194
195
196 size_t DocIterator::nrows() const
197 {
198         // assume 1x1 grid for main text
199         return top().nrows();
200 }
201
202
203 DocIterator::row_type DocIterator::row() const
204 {
205         return top().row();
206 }
207
208
209 DocIterator::col_type DocIterator::col() const
210 {
211         return top().col();
212 }
213
214
215 MathData & DocIterator::cell() const
216 {
217 //      BOOST_ASSERT(inMathed());
218         return top().cell();
219 }
220
221
222 Text * DocIterator::innerText() const
223 {
224         BOOST_ASSERT(!empty());
225         // go up until first non-0 text is hit
226         // (innermost text is 0 in mathed)
227         for (int i = depth() - 1; i >= 0; --i)
228                 if (slices_[i].text())
229                         return slices_[i].text();
230         return 0;
231 }
232
233
234 Inset * DocIterator::innerInsetOfType(int code) const
235 {
236         for (int i = depth() - 1; i >= 0; --i)
237                 if (slices_[i].inset_->lyxCode() == code)
238                         return slices_[i].inset_;
239         return 0;
240 }
241
242
243 void DocIterator::forwardPos(bool ignorecollapsed)
244 {
245         //this dog bites his tail
246         if (empty()) {
247                 push_back(CursorSlice(*inset_));
248                 return;
249         }
250
251         Inset * const nextinset = nextInset();
252         // jump over collapsables if they are collapsed
253         // FIXME: the check for asInsetMath() shouldn't be necessary
254         // but math insets do not return a sensible editable() state yet.
255         if (ignorecollapsed && nextinset && (!nextinset->asInsetMath()
256             && nextinset->editable() != Inset::HIGHLY_EDITABLE)) {
257                 ++top().pos();
258                 return;
259         }
260
261         CursorSlice & tip = top();
262         //lyxerr << "XXX\n" << *this << endl;
263
264         // this is used twice and shows up in the profiler!
265         pos_type const lastp = lastpos();
266
267         // move into an inset to the right if possible
268         Inset * n = 0;
269
270         if (tip.pos() != lastp) {
271                 // this is impossible for pos() == size()
272                 if (inMathed()) {
273                         n = (tip.cell().begin() + tip.pos())->nucleus();
274                 } else {
275                         if (paragraph().isInset(tip.pos()))
276                                 n = paragraph().getInset(tip.pos());
277                 }
278         }
279
280         if (n && n->isActive()) {
281                 //lyxerr << "... descend" << endl;
282                 push_back(CursorSlice(*n));
283                 return;
284         }
285
286         if (!tip.at_end()) {
287                 tip.forwardPos();
288                 return;
289         }
290         // otherwise leave inset and jump over inset as a whole
291         pop_back();
292         // 'tip' is invalid now...
293         if (!empty())
294                 ++top().pos();
295 }
296
297
298 void DocIterator::forwardPar()
299 {
300         forwardPos();
301
302         while (!empty() && (!inTexted() || pos() != 0)) {
303                 if (inTexted()) {
304                         pos_type const lastp = lastpos();
305                         Paragraph const & par = paragraph();
306                         pos_type & pos = top().pos();
307                         if (par.insetList().empty())
308                                 pos = lastp;
309                         else
310                                 while (pos < lastp && !par.isInset(pos))
311                                         ++pos;
312                 }
313                 forwardPos();
314         }
315 }
316
317
318 void DocIterator::forwardChar()
319 {
320         forwardPos();
321         while (!empty() && pos() == lastpos())
322                 forwardPos();
323 }
324
325
326 void DocIterator::forwardInset()
327 {
328         forwardPos();
329
330         while (!empty() && !nextInset()) {
331                 if (inTexted()) {
332                         pos_type const lastp = lastpos();
333                         Paragraph const & par = paragraph();
334                         pos_type & pos = top().pos();
335                         while (pos < lastp && !par.isInset(pos))
336                                 ++pos;
337                         if (pos < lastp)
338                                 break;
339                 }
340                 forwardPos();
341         }
342 }
343
344
345 void DocIterator::backwardChar()
346 {
347         backwardPos();
348         while (!empty() && pos() == lastpos())
349                 backwardPos();
350 }
351
352
353 void DocIterator::backwardPos()
354 {
355         //this dog bites his tail
356         if (empty()) {
357                 push_back(CursorSlice(*inset_));
358                 top().idx() = lastidx();
359                 top().pit() = lastpit();
360                 top().pos() = lastpos();
361                 return;
362         }
363
364         if (top().at_begin()) {
365                 pop_back();
366                 return;
367         }
368
369         top().backwardPos();
370
371         // move into an inset to the left if possible
372         Inset * n = 0;
373
374         if (inMathed()) {
375                 n = (top().cell().begin() + top().pos())->nucleus();
376         } else {
377                 if (paragraph().isInset(top().pos()))
378                         n = paragraph().getInset(top().pos());
379         }
380
381         if (n && n->isActive()) {
382                 push_back(CursorSlice(*n));
383                 top().idx() = lastidx();
384                 top().pit() = lastpit();
385                 top().pos() = lastpos();
386         }
387 }
388
389
390 bool DocIterator::hasPart(DocIterator const & it) const
391 {
392         // it can't be a part if it is larger
393         if (it.depth() > depth())
394                 return false;
395
396         // as inset adresses are the 'last' level
397         return &it.top().inset() == &slices_[it.depth() - 1].inset();
398 }
399
400
401 void DocIterator::updateInsets(Inset * inset)
402 {
403         // this function re-creates the cache of inset pointers.
404         // code taken in part from StableDocIterator::asDocIterator.
405         //lyxerr << "converting:\n" << *this << endl;
406         DocIterator dit = DocIterator(*inset);
407         size_t const n = slices_.size();
408         for (size_t i = 0 ; i < n; ++i) {
409                 BOOST_ASSERT(inset);
410                 dit.push_back(slices_[i]);
411                 dit.top().inset_ = inset;
412                 if (i + 1 != n)
413                         inset = dit.nextInset();
414         }
415         //lyxerr << "converted:\n" << *this << endl;
416         operator=(dit);
417 }
418
419
420 bool DocIterator::fixIfBroken()
421 {
422         // Go through the slice stack from the bottom. 
423         // Check that all coordinates (idx, pit, pos) are correct and
424         // that the inset is the one which is claimed to be there
425         Inset * inset = &slices_[0].inset();
426         size_t i = 0;
427         size_t n = slices_.size();
428         for (; i != n; ++i) {
429                 CursorSlice & cs = slices_[i];
430                 if (&cs.inset() != inset) {
431                         // the whole slice is wrong, chop off this as well
432                         --i;
433                         LYXERR(Debug::DEBUG) << "fixIfBroken(): inset changed" << endl;
434                         break;
435                 } else if (cs.idx() > cs.lastidx()) {
436                         cs.idx() = cs.lastidx();
437                         cs.pit() = cs.lastpit();
438                         cs.pos() = cs.lastpos();
439                         LYXERR(Debug::DEBUG) << "fixIfBroken(): idx fixed" << endl;
440                         break;
441                 } else if (cs.pit() > cs.lastpit()) {
442                         cs.pit() = cs.lastpit();
443                         cs.pos() = cs.lastpos();
444                         LYXERR(Debug::DEBUG) << "fixIfBroken(): pit fixed" << endl;
445                         break;
446                 } else if (cs.pos() > cs.lastpos()) {
447                         cs.pos() = cs.lastpos();
448                         LYXERR(Debug::DEBUG) << "fixIfBroken(): pos fixed" << endl;
449                         break;
450                 } else if (i != n - 1 && cs.pos() != cs.lastpos()) {
451                         // get inset which is supposed to be in the next slice
452                         if (cs.inset().inMathed())
453                                 inset = (cs.cell().begin() + cs.pos())->nucleus();
454                         else if (cs.paragraph().isInset(cs.pos()))
455                                 inset = cs.paragraph().getInset(cs.pos());
456                         else {
457                                 // there are slices left, so there must be another inset
458                                 break;
459                         }
460                 }
461         }
462
463         // Did we make it through the whole slice stack? Otherwise there
464         // was a problem at slice i, and we have to chop off above
465         if (i < n) {
466                 LYXERR(Debug::DEBUG) << "fixIfBroken(): cursor chopped at " << i << endl;
467                 resize(i + 1);
468                 return true;
469         } else
470                 return false;
471 }
472
473
474 std::ostream & operator<<(std::ostream & os, DocIterator const & dit)
475 {
476         for (size_t i = 0, n = dit.depth(); i != n; ++i)
477                 os << " " << dit[i] << "\n";
478         return os;
479 }
480
481
482 bool operator<(DocIterator const & p, DocIterator const & q)
483 {
484         size_t depth = std::min(p.depth(), q.depth());
485         for (size_t i = 0 ; i < depth ; ++i) {
486                 if (p[i] != q[i])
487                         return p[i] < q[i];
488         }
489         return p.depth() < q.depth();
490 }
491
492
493 bool operator>(DocIterator const & p, DocIterator const & q)
494 {
495         return q < p;
496 }
497
498
499 bool operator<=(DocIterator const & p, DocIterator const & q)
500 {
501         return !(q < p);
502 }
503
504
505 ///////////////////////////////////////////////////////
506
507 StableDocIterator::StableDocIterator(DocIterator const & dit)
508 {
509         data_ = dit.internalData();
510         for (size_t i = 0, n = data_.size(); i != n; ++i)
511                 data_[i].inset_ = 0;
512 }
513
514
515 DocIterator StableDocIterator::asDocIterator(Inset * inset) const
516 {
517         // this function re-creates the cache of inset pointers
518         //lyxerr << "converting:\n" << *this << endl;
519         DocIterator dit = DocIterator(*inset);
520         for (size_t i = 0, n = data_.size(); i != n; ++i) {
521                 if (inset == 0) {
522                         // FIXME
523                         lyxerr << BOOST_CURRENT_FUNCTION
524                                << " Should not happen, but does e.g. after C-n C-l C-z S-C-z\n"
525                                    << " or when a Buffer has been concurently edited by two views"
526                                 << '\n' << "dit: " << dit << '\n'
527                                 << " lastpos: " << dit.lastpos() << endl;
528                         dit.fixIfBroken();
529                         break;
530                 }
531                 dit.push_back(data_[i]);
532                 dit.top().inset_ = inset;
533                 if (dit.fixIfBroken())
534                         break;
535                 if (i + 1 != n)
536                         inset = dit.nextInset();
537         }
538         //lyxerr << "convert:\n" << *this << " to:\n" << dit << endl;
539         return dit;
540 }
541
542
543 std::ostream & operator<<(std::ostream & os, StableDocIterator const & dit)
544 {
545         for (size_t i = 0, n = dit.data_.size(); i != n; ++i)
546                 os << " " << dit.data_[i] << "\n";
547         return os;
548 }
549
550
551 bool operator==(StableDocIterator const & dit1, StableDocIterator const & dit2)
552 {
553         return dit1.data_ == dit2.data_;
554 }
555
556
557 } // namespace lyx