]> git.lyx.org Git - lyx.git/blob - src/Text2.cpp
Create mechanism to handle packages for which we only know after parsing
[lyx.git] / src / Text2.cpp
1 /**
2  * \file text2.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Asger Alstrup
7  * \author Lars Gullik Bjønnes
8  * \author Alfredo Braunstein
9  * \author Jean-Marc Lasgouttes
10  * \author Angus Leeming
11  * \author John Levon
12  * \author André Pönitz
13  * \author Allan Rae
14  * \author Stefan Schimanski
15  * \author Dekel Tsur
16  * \author Jürgen Vigna
17  *
18  * Full author contact details are available in file CREDITS.
19  */
20
21 #include <config.h>
22
23 #include "Text.h"
24
25 #include "Bidi.h"
26 #include "Buffer.h"
27 #include "buffer_funcs.h"
28 #include "BufferList.h"
29 #include "BufferParams.h"
30 #include "BufferView.h"
31 #include "Changes.h"
32 #include "Cursor.h"
33 #include "CutAndPaste.h"
34 #include "DispatchResult.h"
35 #include "ErrorList.h"
36 #include "Language.h"
37 #include "Layout.h"
38 #include "Lexer.h"
39 #include "LyX.h"
40 #include "LyXRC.h"
41 #include "Paragraph.h"
42 #include "ParagraphParameters.h"
43 #include "TextClass.h"
44 #include "TextMetrics.h"
45
46 #include "insets/InsetCollapsable.h"
47
48 #include "mathed/InsetMathHull.h"
49
50 #include "support/lassert.h"
51 #include "support/debug.h"
52 #include "support/gettext.h"
53 #include "support/textutils.h"
54
55 #include <boost/next_prior.hpp>
56
57 #include <sstream>
58
59 using namespace std;
60
61 namespace lyx {
62
63 bool Text::isMainText() const
64 {
65         return &owner_->buffer().text() == this;
66 }
67
68
69 // Note that this is supposed to return a fully realized font.
70 FontInfo Text::layoutFont(pit_type const pit) const
71 {
72         Layout const & layout = pars_[pit].layout();
73
74         if (!pars_[pit].getDepth())  {
75                 FontInfo lf = layout.resfont;
76                 // In case the default family has been customized
77                 if (layout.font.family() == INHERIT_FAMILY)
78                         lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
79                 // FIXME
80                 // It ought to be possible here just to use Inset::getLayout() and skip
81                 // the asInsetCollapsable() bit. Unfortunatley, that doesn't work right
82                 // now, because Inset::getLayout() will return a default-constructed
83                 // InsetLayout, and that e.g. sets the foreground color to red. So we
84                 // need to do some work to make that possible.
85                 InsetCollapsable const * icp = owner_->asInsetCollapsable();
86                 if (!icp)
87                         return lf;
88                 FontInfo icf = icp->getLayout().font();
89                 icf.realize(lf);
90                 return icf;
91         }
92
93         FontInfo font = layout.font;
94         // Realize with the fonts of lesser depth.
95         //font.realize(outerFont(pit));
96         font.realize(owner_->buffer().params().getFont().fontInfo());
97
98         return font;
99 }
100
101
102 // Note that this is supposed to return a fully realized font.
103 FontInfo Text::labelFont(Paragraph const & par) const
104 {
105         Buffer const & buffer = owner_->buffer();
106         Layout const & layout = par.layout();
107
108         if (!par.getDepth()) {
109                 FontInfo lf = layout.reslabelfont;
110                 // In case the default family has been customized
111                 if (layout.labelfont.family() == INHERIT_FAMILY)
112                         lf.setFamily(buffer.params().getFont().fontInfo().family());
113                 return lf;
114         }
115
116         FontInfo font = layout.labelfont;
117         // Realize with the fonts of lesser depth.
118         font.realize(buffer.params().getFont().fontInfo());
119
120         return font;
121 }
122
123
124 void Text::setCharFont(pit_type pit,
125                 pos_type pos, Font const & fnt, Font const & display_font)
126 {
127         Buffer const & buffer = owner_->buffer();
128         Font font = fnt;
129         Layout const & layout = pars_[pit].layout();
130
131         // Get concrete layout font to reduce against
132         FontInfo layoutfont;
133
134         if (pos < pars_[pit].beginOfBody())
135                 layoutfont = layout.labelfont;
136         else
137                 layoutfont = layout.font;
138
139         // Realize against environment font information
140         if (pars_[pit].getDepth()) {
141                 pit_type tp = pit;
142                 while (!layoutfont.resolved() &&
143                        tp != pit_type(paragraphs().size()) &&
144                        pars_[tp].getDepth()) {
145                         tp = outerHook(tp);
146                         if (tp != pit_type(paragraphs().size()))
147                                 layoutfont.realize(pars_[tp].layout().font);
148                 }
149         }
150
151         // Inside inset, apply the inset's font attributes if any
152         // (charstyle!)
153         if (!isMainText())
154                 layoutfont.realize(display_font.fontInfo());
155
156         layoutfont.realize(buffer.params().getFont().fontInfo());
157
158         // Now, reduce font against full layout font
159         font.fontInfo().reduce(layoutfont);
160
161         pars_[pit].setFont(pos, font);
162 }
163
164
165 void Text::setInsetFont(BufferView const & bv, pit_type pit,
166                 pos_type pos, Font const & font, bool toggleall)
167 {
168         Inset * const inset = pars_[pit].getInset(pos);
169         LASSERT(inset && inset->resetFontEdit(), /**/);
170
171         CursorSlice::idx_type endidx = inset->nargs();
172         for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
173                 Text * text = cs.text();
174                 if (text) {
175                         // last position of the cell
176                         CursorSlice cellend = cs;
177                         cellend.pit() = cellend.lastpit();
178                         cellend.pos() = cellend.lastpos();
179                         text->setFont(bv, cs, cellend, font, toggleall);
180                 }
181         }
182 }
183
184
185 // return past-the-last paragraph influenced by a layout change on pit
186 pit_type Text::undoSpan(pit_type pit)
187 {
188         pit_type const end = paragraphs().size();
189         pit_type nextpit = pit + 1;
190         if (nextpit == end)
191                 return nextpit;
192         //because of parindents
193         if (!pars_[pit].getDepth())
194                 return boost::next(nextpit);
195         //because of depth constrains
196         for (; nextpit != end; ++pit, ++nextpit) {
197                 if (!pars_[pit].getDepth())
198                         break;
199         }
200         return nextpit;
201 }
202
203
204 void Text::setLayout(pit_type start, pit_type end,
205                      docstring const & layout)
206 {
207         LASSERT(start != end, /**/);
208
209         Buffer const & buffer = owner_->buffer();
210         BufferParams const & bp = buffer.params();
211         Layout const & lyxlayout = bp.documentClass()[layout];
212
213         for (pit_type pit = start; pit != end; ++pit) {
214                 Paragraph & par = pars_[pit];
215                 par.applyLayout(lyxlayout);
216                 if (lyxlayout.margintype == MARGIN_MANUAL)
217                         par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
218         }
219 }
220
221
222 // set layout over selection and make a total rebreak of those paragraphs
223 void Text::setLayout(Cursor & cur, docstring const & layout)
224 {
225         LASSERT(this == cur.text(), /**/);
226
227         pit_type start = cur.selBegin().pit();
228         pit_type end = cur.selEnd().pit() + 1;
229         pit_type undopit = undoSpan(end - 1);
230         recUndo(cur, start, undopit - 1);
231         setLayout(start, end, layout);
232         cur.forceBufferUpdate();
233 }
234
235
236 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
237                         Paragraph const & par, int max_depth)
238 {
239         if (par.layout().labeltype == LABEL_BIBLIO)
240                 return false;
241         int const depth = par.params().depth();
242         if (type == Text::INC_DEPTH && depth < max_depth)
243                 return true;
244         if (type == Text::DEC_DEPTH && depth > 0)
245                 return true;
246         return false;
247 }
248
249
250 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
251 {
252         LASSERT(this == cur.text(), /**/);
253         // this happens when selecting several cells in tabular (bug 2630)
254         if (cur.selBegin().idx() != cur.selEnd().idx())
255                 return false;
256
257         pit_type const beg = cur.selBegin().pit();
258         pit_type const end = cur.selEnd().pit() + 1;
259         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
260
261         for (pit_type pit = beg; pit != end; ++pit) {
262                 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
263                         return true;
264                 max_depth = pars_[pit].getMaxDepthAfter();
265         }
266         return false;
267 }
268
269
270 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
271 {
272         LASSERT(this == cur.text(), /**/);
273         pit_type const beg = cur.selBegin().pit();
274         pit_type const end = cur.selEnd().pit() + 1;
275         cur.recordUndoSelection();
276         int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
277
278         for (pit_type pit = beg; pit != end; ++pit) {
279                 Paragraph & par = pars_[pit];
280                 if (lyx::changeDepthAllowed(type, par, max_depth)) {
281                         int const depth = par.params().depth();
282                         if (type == INC_DEPTH)
283                                 par.params().depth(depth + 1);
284                         else
285                                 par.params().depth(depth - 1);
286                 }
287                 max_depth = par.getMaxDepthAfter();
288         }
289         // this handles the counter labels, and also fixes up
290         // depth values for follow-on (child) paragraphs
291         cur.forceBufferUpdate();
292 }
293
294
295 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
296 {
297         LASSERT(this == cur.text(), /**/);
298         // Set the current_font
299         // Determine basis font
300         FontInfo layoutfont;
301         pit_type pit = cur.pit();
302         if (cur.pos() < pars_[pit].beginOfBody())
303                 layoutfont = labelFont(pars_[pit]);
304         else
305                 layoutfont = layoutFont(pit);
306
307         // Update current font
308         cur.real_current_font.update(font,
309                                         cur.buffer()->params().language,
310                                         toggleall);
311
312         // Reduce to implicit settings
313         cur.current_font = cur.real_current_font;
314         cur.current_font.fontInfo().reduce(layoutfont);
315         // And resolve it completely
316         cur.real_current_font.fontInfo().realize(layoutfont);
317
318         // if there is no selection that's all we need to do
319         if (!cur.selection())
320                 return;
321
322         // Ok, we have a selection.
323         cur.recordUndoSelection();
324
325         setFont(cur.bv(), cur.selectionBegin().top(), 
326                 cur.selectionEnd().top(), font, toggleall);
327 }
328
329
330 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
331                 CursorSlice const & end, Font const & font,
332                 bool toggleall)
333 {
334         Buffer const & buffer = bv.buffer();
335
336         // Don't use forwardChar here as ditend might have
337         // pos() == lastpos() and forwardChar would miss it.
338         // Can't use forwardPos either as this descends into
339         // nested insets.
340         Language const * language = buffer.params().language;
341         for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
342                 if (dit.pos() == dit.lastpos())
343                         continue;
344                 pit_type const pit = dit.pit();
345                 pos_type const pos = dit.pos();
346                 Inset * inset = pars_[pit].getInset(pos);
347                 if (inset && inset->resetFontEdit()) {
348                         // We need to propagate the font change to all
349                         // text cells of the inset (bugs 1973, 6919).
350                         setInsetFont(bv, pit, pos, font, toggleall);
351                 }
352                 TextMetrics const & tm = bv.textMetrics(this);
353                 Font f = tm.displayFont(pit, pos);
354                 f.update(font, language, toggleall);
355                 setCharFont(pit, pos, f, tm.font_);
356                 // font change may change language... 
357                 // spell checker has to know that
358                 pars_[pit].requestSpellCheck(pos);
359         }
360 }
361
362
363 bool Text::cursorTop(Cursor & cur)
364 {
365         LASSERT(this == cur.text(), /**/);
366         return setCursor(cur, 0, 0);
367 }
368
369
370 bool Text::cursorBottom(Cursor & cur)
371 {
372         LASSERT(this == cur.text(), /**/);
373         return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
374 }
375
376
377 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
378 {
379         LASSERT(this == cur.text(), /**/);
380         // If the mask is completely neutral, tell user
381         if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
382                 // Could only happen with user style
383                 cur.message(_("No font change defined."));
384                 return;
385         }
386
387         // Try implicit word selection
388         // If there is a change in the language the implicit word selection
389         // is disabled.
390         CursorSlice const resetCursor = cur.top();
391         bool const implicitSelection =
392                 font.language() == ignore_language
393                 && font.fontInfo().number() == FONT_IGNORE
394                 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
395
396         // Set font
397         setFont(cur, font, toggleall);
398
399         // Implicit selections are cleared afterwards
400         // and cursor is set to the original position.
401         if (implicitSelection) {
402                 cur.clearSelection();
403                 cur.top() = resetCursor;
404                 cur.resetAnchor();
405         }
406 }
407
408
409 docstring Text::getStringToIndex(Cursor const & cur)
410 {
411         LASSERT(this == cur.text(), /**/);
412
413         if (cur.selection())
414                 return cur.selectionAsString(false);
415
416         // Try implicit word selection. If there is a change
417         // in the language the implicit word selection is
418         // disabled.
419         Cursor tmpcur = cur;
420         selectWord(tmpcur, PREVIOUS_WORD);
421
422         if (!tmpcur.selection())
423                 cur.message(_("Nothing to index!"));
424         else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
425                 cur.message(_("Cannot index more than one paragraph!"));
426         else
427                 return tmpcur.selectionAsString(false);
428         
429         return docstring();
430 }
431
432
433 void Text::setLabelWidthStringToSequence(pit_type const par_offset,
434                 docstring const & s)
435 {
436         pit_type offset = par_offset;
437         // Find first of same layout in sequence
438         while (!isFirstInSequence(offset)) {
439                 offset = depthHook(offset, pars_[offset].getDepth());
440         }
441
442         // now apply label width string to every par
443         // in sequence
444         pit_type const end = pars_.size();
445         depth_type const depth = pars_[offset].getDepth();
446         Layout const & layout = pars_[offset].layout();
447         for (pit_type pit = offset; pit != end; ++pit) {
448                 while (pars_[pit].getDepth() > depth)
449                         ++pit;
450                 if (pars_[pit].getDepth() < depth)
451                         return;
452                 if (pars_[pit].layout() != layout)
453                         return;
454                 pars_[pit].setLabelWidthString(s);
455         }
456 }
457
458
459 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge) 
460 {
461         LASSERT(cur.text(), /**/);
462         // make sure that the depth behind the selection are restored, too
463         pit_type undopit = undoSpan(cur.selEnd().pit());
464         recUndo(cur, cur.selBegin().pit(), undopit - 1);
465
466         //FIXME UNICODE
467         string const argument = to_utf8(arg);
468         depth_type priordepth = -1;
469         Layout priorlayout;
470         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
471              pit <= end; ++pit) {
472                 Paragraph & par = pars_[pit];
473                 ParagraphParameters params = par.params();
474                 params.read(argument, merge);
475                 // Changes to label width string apply to all paragraphs
476                 // with same layout in a sequence.
477                 // Do this only once for a selected range of paragraphs
478                 // of the same layout and depth.
479                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
480                         setLabelWidthStringToSequence(pit, params.labelWidthString());
481                 par.params().apply(params, par.layout());
482                 priordepth = par.getDepth();
483                 priorlayout = par.layout();
484         }
485 }
486
487
488 //FIXME This is a little redundant now, but it's probably worth keeping,
489 //especially if we're going to go away from using serialization internally
490 //quite so much.
491 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p) 
492 {
493         LASSERT(cur.text(), /**/);
494         // make sure that the depth behind the selection are restored, too
495         pit_type undopit = undoSpan(cur.selEnd().pit());
496         recUndo(cur, cur.selBegin().pit(), undopit - 1);
497
498         depth_type priordepth = -1;
499         Layout priorlayout;
500         for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
501              pit <= end; ++pit) {
502                 Paragraph & par = pars_[pit];
503                 // Changes to label width string apply to all paragraphs
504                 // with same layout in a sequence.
505                 // Do this only once for a selected range of paragraphs
506                 // of the same layout and depth.
507                 if (par.getDepth() != priordepth || par.layout() != priorlayout)
508                         setLabelWidthStringToSequence(pit,
509                                 par.params().labelWidthString());
510                 par.params().apply(p, par.layout());
511                 priordepth = par.getDepth();
512                 priorlayout = par.layout();
513         }
514 }
515
516
517 // this really should just insert the inset and not move the cursor.
518 void Text::insertInset(Cursor & cur, Inset * inset)
519 {
520         LASSERT(this == cur.text(), /**/);
521         LASSERT(inset, /**/);
522         cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
523                 Change(cur.buffer()->params().trackChanges
524                 ? Change::INSERTED : Change::UNCHANGED));
525 }
526
527
528 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
529                         bool setfont, bool boundary)
530 {
531         TextMetrics const & tm = cur.bv().textMetrics(this);
532         bool const update_needed = !tm.contains(par);
533         Cursor old = cur;
534         setCursorIntern(cur, par, pos, setfont, boundary);
535         return cur.bv().checkDepm(cur, old) || update_needed;
536 }
537
538
539 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
540 {
541         LASSERT(par != int(paragraphs().size()), /**/);
542         cur.pit() = par;
543         cur.pos() = pos;
544
545         // now some strict checking
546         Paragraph & para = getPar(par);
547
548         // None of these should happen, but we're scaredy-cats
549         if (pos < 0) {
550                 lyxerr << "don't like -1" << endl;
551                 LASSERT(false, /**/);
552         }
553
554         if (pos > para.size()) {
555                 lyxerr << "don't like 1, pos: " << pos
556                        << " size: " << para.size()
557                        << " par: " << par << endl;
558                 LASSERT(false, /**/);
559         }
560 }
561
562
563 void Text::setCursorIntern(Cursor & cur,
564                               pit_type par, pos_type pos, bool setfont, bool boundary)
565 {
566         LASSERT(this == cur.text(), /**/);
567         cur.boundary(boundary);
568         setCursor(cur.top(), par, pos);
569         if (setfont)
570                 cur.setCurrentFont();
571 }
572
573
574 bool Text::checkAndActivateInset(Cursor & cur, bool front)
575 {
576         if (cur.selection())
577                 return false;
578         if (front && cur.pos() == cur.lastpos())
579                 return false;
580         if (!front && cur.pos() == 0)
581                 return false;
582         Inset * inset = front ? cur.nextInset() : cur.prevInset();
583         if (!inset || !inset->editable())
584                 return false;
585         /*
586          * Apparently, when entering an inset we are expected to be positioned
587          * *before* it in the containing paragraph, regardless of the direction
588          * from which we are entering. Otherwise, cursor placement goes awry,
589          * and when we exit from the beginning, we'll be placed *after* the
590          * inset.
591          */
592         if (!front)
593                 --cur.pos();
594         inset->edit(cur, front);
595         return true;
596 }
597
598
599 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
600 {
601         if (cur.selection())
602                 return false;
603         if (cur.pos() == -1)
604                 return false;
605         if (cur.pos() == cur.lastpos())
606                 return false;
607         Paragraph & par = cur.paragraph();
608         Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
609         if (!inset || !inset->editable())
610                 return false;
611         inset->edit(cur, movingForward, 
612                 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
613         return true;
614 }
615
616
617 bool Text::cursorBackward(Cursor & cur)
618 {
619         // Tell BufferView to test for FitCursor in any case!
620         cur.screenUpdateFlags(Update::FitCursor);
621
622         // not at paragraph start?
623         if (cur.pos() > 0) {
624                 // if on right side of boundary (i.e. not at paragraph end, but line end)
625                 // -> skip it, i.e. set boundary to true, i.e. go only logically left
626                 // there are some exceptions to ignore this: lineseps, newlines, spaces
627 #if 0
628                 // some effectless debug code to see the values in the debugger
629                 bool bound = cur.boundary();
630                 int rowpos = cur.textRow().pos();
631                 int pos = cur.pos();
632                 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
633                 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
634                 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
635 #endif
636                 if (!cur.boundary() &&
637                                 cur.textRow().pos() == cur.pos() &&
638                                 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
639                                 !cur.paragraph().isNewline(cur.pos() - 1) &&
640                                 !cur.paragraph().isSeparator(cur.pos() - 1)) {
641                         return setCursor(cur, cur.pit(), cur.pos(), true, true);
642                 }
643                 
644                 // go left and try to enter inset
645                 if (checkAndActivateInset(cur, false))
646                         return false;
647                 
648                 // normal character left
649                 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
650         }
651
652         // move to the previous paragraph or do nothing
653         if (cur.pit() > 0)
654                 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
655         return false;
656 }
657
658
659 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
660 {
661         Cursor temp_cur = cur;
662         temp_cur.posVisLeft(skip_inset);
663         if (temp_cur.depth() > cur.depth()) {
664                 cur = temp_cur;
665                 return false;
666         }
667         return setCursor(cur, temp_cur.pit(), temp_cur.pos(), 
668                 true, temp_cur.boundary());
669 }
670
671
672 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
673 {
674         Cursor temp_cur = cur;
675         temp_cur.posVisRight(skip_inset);
676         if (temp_cur.depth() > cur.depth()) {
677                 cur = temp_cur;
678                 return false;
679         }
680         return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
681                 true, temp_cur.boundary());
682 }
683
684
685 bool Text::cursorForward(Cursor & cur)
686 {
687         // Tell BufferView to test for FitCursor in any case!
688         cur.screenUpdateFlags(Update::FitCursor);
689
690         // not at paragraph end?
691         if (cur.pos() != cur.lastpos()) {
692                 // in front of editable inset, i.e. jump into it?
693                 if (checkAndActivateInset(cur, true))
694                         return false;
695
696                 TextMetrics const & tm = cur.bv().textMetrics(this);
697                 // if left of boundary -> just jump to right side
698                 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
699                 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
700                         return setCursor(cur, cur.pit(), cur.pos(), true, false);
701
702                 // next position is left of boundary, 
703                 // but go to next line for special cases like space, newline, linesep
704 #if 0
705                 // some effectless debug code to see the values in the debugger
706                 int endpos = cur.textRow().endpos();
707                 int lastpos = cur.lastpos();
708                 int pos = cur.pos();
709                 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
710                 bool newline = cur.paragraph().isNewline(cur.pos());
711                 bool sep = cur.paragraph().isSeparator(cur.pos());
712                 if (cur.pos() != cur.lastpos()) {
713                         bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
714                         bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
715                         bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
716                 }
717 #endif
718                 if (cur.textRow().endpos() == cur.pos() + 1 &&
719                     cur.textRow().endpos() != cur.lastpos() &&
720                                 !cur.paragraph().isNewline(cur.pos()) &&
721                                 !cur.paragraph().isLineSeparator(cur.pos()) &&
722                                 !cur.paragraph().isSeparator(cur.pos())) {
723                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
724                 }
725                 
726                 // in front of RTL boundary? Stay on this side of the boundary because:
727                 //   ab|cDDEEFFghi -> abc|DDEEFFghi
728                 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
729                         return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
730                 
731                 // move right
732                 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
733         }
734
735         // move to next paragraph
736         if (cur.pit() != cur.lastpit())
737                 return setCursor(cur, cur.pit() + 1, 0, true, false);
738         return false;
739 }
740
741
742 bool Text::cursorUpParagraph(Cursor & cur)
743 {
744         bool updated = false;
745         if (cur.pos() > 0)
746                 updated = setCursor(cur, cur.pit(), 0);
747         else if (cur.pit() != 0)
748                 updated = setCursor(cur, cur.pit() - 1, 0);
749         return updated;
750 }
751
752
753 bool Text::cursorDownParagraph(Cursor & cur)
754 {
755         bool updated = false;
756         if (cur.pit() != cur.lastpit())
757                 updated = setCursor(cur, cur.pit() + 1, 0);
758         else
759                 updated = setCursor(cur, cur.pit(), cur.lastpos());
760         return updated;
761 }
762
763
764 // fix the cursor `cur' after a characters has been deleted at `where'
765 // position. Called by deleteEmptyParagraphMechanism
766 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
767 {
768         // Do nothing if cursor is not in the paragraph where the
769         // deletion occured,
770         if (cur.pit() != where.pit())
771                 return;
772
773         // If cursor position is after the deletion place update it
774         if (cur.pos() > where.pos())
775                 --cur.pos();
776
777         // Check also if we don't want to set the cursor on a spot behind the
778         // pagragraph because we erased the last character.
779         if (cur.pos() > cur.lastpos())
780                 cur.pos() = cur.lastpos();
781 }
782
783
784 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
785                 Cursor & old, bool & need_anchor_change)
786 {
787         //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
788
789         Paragraph & oldpar = old.paragraph();
790
791         // We allow all kinds of "mumbo-jumbo" when freespacing.
792         if (oldpar.isFreeSpacing())
793                 return false;
794
795         /* Ok I'll put some comments here about what is missing.
796            There are still some small problems that can lead to
797            double spaces stored in the document file or space at
798            the beginning of paragraphs(). This happens if you have
799            the cursor between to spaces and then save. Or if you
800            cut and paste and the selection have a space at the
801            beginning and then save right after the paste. (Lgb)
802         */
803
804         // If old.pos() == 0 and old.pos()(1) == LineSeparator
805         // delete the LineSeparator.
806         // MISSING
807
808         // If old.pos() == 1 and old.pos()(0) == LineSeparator
809         // delete the LineSeparator.
810         // MISSING
811
812         // Find a common inset and the corresponding depth.
813         size_t depth = 0;
814         for (; depth < cur.depth(); ++depth)
815                 if (&old.inset() == &cur[depth].inset())
816                         break;
817
818         // Whether a common inset is found and whether the cursor is still in 
819         // the same paragraph (possibly nested).
820         bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
821         bool const same_par_pos = depth == cur.depth() - 1 && same_par 
822                 && old.pos() == cur[depth].pos();
823         
824         // If the chars around the old cursor were spaces, delete one of them.
825         if (!same_par_pos) {
826                 // Only if the cursor has really moved.
827                 if (old.pos() > 0
828                     && old.pos() < oldpar.size()
829                     && oldpar.isLineSeparator(old.pos())
830                     && oldpar.isLineSeparator(old.pos() - 1)
831                     && !oldpar.isDeleted(old.pos() - 1)
832                     && !oldpar.isDeleted(old.pos())) {
833                         oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().trackChanges);
834 // FIXME: This will not work anymore when we have multiple views of the same buffer
835 // In this case, we will have to correct also the cursors held by
836 // other bufferviews. It will probably be easier to do that in a more
837 // automated way in CursorSlice code. (JMarc 26/09/2001)
838                         // correct all cursor parts
839                         if (same_par) {
840                                 fixCursorAfterDelete(cur[depth], old.top());
841                                 need_anchor_change = true;
842                         }
843                         return true;
844                 }
845         }
846
847         // only do our magic if we changed paragraph
848         if (same_par)
849                 return false;
850
851         // don't delete anything if this is the ONLY paragraph!
852         if (old.lastpit() == 0)
853                 return false;
854
855         // Do not delete empty paragraphs with keepempty set.
856         if (oldpar.allowEmpty())
857                 return false;
858
859         if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
860                 // Delete old par.
861                 old.recordUndo(ATOMIC_UNDO,
862                            max(old.pit() - 1, pit_type(0)),
863                            min(old.pit() + 1, old.lastpit()));
864                 ParagraphList & plist = old.text()->paragraphs();
865                 bool const soa = oldpar.params().startOfAppendix();
866                 plist.erase(boost::next(plist.begin(), old.pit()));
867                 // do not lose start of appendix marker (bug 4212)
868                 if (soa && old.pit() < pit_type(plist.size()))
869                         plist[old.pit()].params().startOfAppendix(true);
870
871                 // see #warning (FIXME?) above 
872                 if (cur.depth() >= old.depth()) {
873                         CursorSlice & curslice = cur[old.depth() - 1];
874                         if (&curslice.inset() == &old.inset()
875                             && curslice.pit() > old.pit()) {
876                                 --curslice.pit();
877                                 // since a paragraph has been deleted, all the
878                                 // insets after `old' have been copied and
879                                 // their address has changed. Therefore we
880                                 // need to `regenerate' cur. (JMarc)
881                                 cur.updateInsets(&(cur.bottom().inset()));
882                                 need_anchor_change = true;
883                         }
884                 }
885                 return true;
886         }
887
888         if (oldpar.stripLeadingSpaces(cur.buffer()->params().trackChanges)) {
889                 need_anchor_change = true;
890                 // We return true here because the Paragraph contents changed and
891                 // we need a redraw before further action is processed.
892                 return true;
893         }
894
895         return false;
896 }
897
898
899 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
900 {
901         LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), /**/);
902
903         for (pit_type pit = first; pit <= last; ++pit) {
904                 Paragraph & par = pars_[pit];
905
906                 // We allow all kinds of "mumbo-jumbo" when freespacing.
907                 if (par.isFreeSpacing())
908                         continue;
909
910                 for (pos_type pos = 1; pos < par.size(); ++pos) {
911                         if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
912                             && !par.isDeleted(pos - 1)) {
913                                 if (par.eraseChar(pos - 1, trackChanges)) {
914                                         --pos;
915                                 }
916                         }
917                 }
918
919                 // don't delete anything if this is the only remaining paragraph
920                 // within the given range. Note: Text::acceptOrRejectChanges()
921                 // sets the cursor to 'first' after calling DEPM
922                 if (first == last)
923                         continue;
924
925                 // don't delete empty paragraphs with keepempty set
926                 if (par.allowEmpty())
927                         continue;
928
929                 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
930                         pars_.erase(boost::next(pars_.begin(), pit));
931                         --pit;
932                         --last;
933                         continue;
934                 }
935
936                 par.stripLeadingSpaces(trackChanges);
937         }
938 }
939
940
941 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
942 {
943         cur.recordUndo(ATOMIC_UNDO, first, last);
944 }
945
946
947 void Text::recUndo(Cursor & cur, pit_type par) const
948 {
949         cur.recordUndo(ATOMIC_UNDO, par, par);
950 }
951
952 } // namespace lyx