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