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