3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
7 * \author Lars Gullik Bjønnes
8 * \author Alfredo Braunstein
9 * \author Jean-Marc Lasgouttes
10 * \author Angus Leeming
12 * \author André Pönitz
14 * \author Stefan Schimanski
16 * \author Jürgen Vigna
18 * Full author contact details are available in file CREDITS.
26 #include "buffer_funcs.h"
27 #include "BufferList.h"
28 #include "BufferParams.h"
29 #include "BufferView.h"
32 #include "CutAndPaste.h"
33 #include "DispatchResult.h"
34 #include "ErrorList.h"
40 #include "Paragraph.h"
41 #include "ParagraphParameters.h"
42 #include "TextClass.h"
43 #include "TextMetrics.h"
45 #include "insets/InsetCollapsible.h"
47 #include "mathed/InsetMathHull.h"
49 #include "support/lassert.h"
50 #include "support/debug.h"
51 #include "support/gettext.h"
52 #include "support/lyxalgo.h"
53 #include "support/textutils.h"
61 bool Text::isMainText() const
63 return &owner_->buffer().text() == this;
67 // Note that this is supposed to return a fully realized font.
68 FontInfo Text::layoutFont(pit_type const pit) const
70 Layout const & layout = pars_[pit].layout();
72 if (!pars_[pit].getDepth()) {
73 FontInfo lf = layout.resfont;
74 // In case the default family has been customized
75 if (layout.font.family() == INHERIT_FAMILY)
76 lf.setFamily(owner_->buffer().params().getFont().fontInfo().family());
77 FontInfo icf = owner_->getLayout().font();
82 FontInfo font = layout.font;
83 // Realize with the fonts of lesser depth.
84 //font.realize(outerFont(pit));
85 font.realize(owner_->buffer().params().getFont().fontInfo());
91 // Note that this is supposed to return a fully realized font.
92 FontInfo Text::labelFont(Paragraph const & par) const
94 Buffer const & buffer = owner_->buffer();
95 Layout const & layout = par.layout();
97 if (!par.getDepth()) {
98 FontInfo lf = layout.reslabelfont;
99 // In case the default family has been customized
100 if (layout.labelfont.family() == INHERIT_FAMILY)
101 lf.setFamily(buffer.params().getFont().fontInfo().family());
105 FontInfo font = layout.labelfont;
106 // Realize with the fonts of lesser depth.
107 font.realize(buffer.params().getFont().fontInfo());
113 void Text::setCharFont(pit_type pit,
114 pos_type pos, Font const & fnt, Font const & display_font)
116 Buffer const & buffer = owner_->buffer();
118 Layout const & layout = pars_[pit].layout();
120 // Get concrete layout font to reduce against
123 if (pos < pars_[pit].beginOfBody())
124 layoutfont = layout.labelfont;
126 layoutfont = layout.font;
128 // Realize against environment font information
129 if (pars_[pit].getDepth()) {
131 while (!layoutfont.resolved() &&
132 tp != pit_type(paragraphs().size()) &&
133 pars_[tp].getDepth()) {
135 if (tp != pit_type(paragraphs().size()))
136 layoutfont.realize(pars_[tp].layout().font);
140 // Inside inset, apply the inset's font attributes if any
143 layoutfont.realize(display_font.fontInfo());
145 layoutfont.realize(buffer.params().getFont().fontInfo());
147 // Now, reduce font against full layout font
148 font.fontInfo().reduce(layoutfont);
150 pars_[pit].setFont(pos, font);
154 void Text::setInsetFont(BufferView const & bv, pit_type pit,
155 pos_type pos, Font const & font)
157 Inset * const inset = pars_[pit].getInset(pos);
158 LASSERT(inset && inset->resetFontEdit(), return);
160 CursorSlice::idx_type endidx = inset->nargs();
161 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
162 Text * text = cs.text();
164 // last position of the cell
165 CursorSlice cellend = cs;
166 cellend.pit() = cellend.lastpit();
167 cellend.pos() = cellend.lastpos();
168 text->setFont(bv, cs, cellend, font);
174 void Text::setLayout(pit_type start, pit_type end,
175 docstring const & layout)
177 LASSERT(start != end, return);
179 Buffer const & buffer = owner_->buffer();
180 BufferParams const & bp = buffer.params();
181 Layout const & lyxlayout = bp.documentClass()[layout];
183 for (pit_type pit = start; pit != end; ++pit) {
184 Paragraph & par = pars_[pit];
185 // Is this a separating paragraph? If so,
186 // this needs to be standard layout
187 bool const is_separator = par.size() == 1
188 && par.isEnvSeparator(0);
189 par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
190 if (lyxlayout.margintype == MARGIN_MANUAL)
191 par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
194 deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
198 // set layout over selection and make a total rebreak of those paragraphs
199 void Text::setLayout(Cursor & cur, docstring const & layout)
201 LBUFERR(this == cur.text());
203 pit_type start = cur.selBegin().pit();
204 pit_type end = cur.selEnd().pit() + 1;
205 cur.recordUndoSelection();
206 setLayout(start, end, layout);
208 cur.setCurrentFont();
209 cur.forceBufferUpdate();
213 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
214 Paragraph const & par, int max_depth)
216 int const depth = par.params().depth();
217 if (type == Text::INC_DEPTH && depth < max_depth)
219 if (type == Text::DEC_DEPTH && depth > 0)
225 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
227 LBUFERR(this == cur.text());
228 // this happens when selecting several cells in tabular (bug 2630)
229 if (cur.selBegin().idx() != cur.selEnd().idx())
232 pit_type const beg = cur.selBegin().pit();
233 pit_type const end = cur.selEnd().pit() + 1;
234 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
236 for (pit_type pit = beg; pit != end; ++pit) {
237 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
239 max_depth = pars_[pit].getMaxDepthAfter();
245 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
247 LBUFERR(this == cur.text());
248 pit_type const beg = cur.selBegin().pit();
249 pit_type const end = cur.selEnd().pit() + 1;
250 cur.recordUndoSelection();
251 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
253 for (pit_type pit = beg; pit != end; ++pit) {
254 Paragraph & par = pars_[pit];
255 if (lyx::changeDepthAllowed(type, par, max_depth)) {
256 int const depth = par.params().depth();
257 if (type == INC_DEPTH)
258 par.params().depth(depth + 1);
260 par.params().depth(depth - 1);
262 max_depth = par.getMaxDepthAfter();
264 // this handles the counter labels, and also fixes up
265 // depth values for follow-on (child) paragraphs
266 cur.forceBufferUpdate();
270 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
272 LASSERT(this == cur.text(), return);
274 // If there is a selection, record undo before the cursor font is changed.
276 cur.recordUndoSelection();
278 // Set the current_font
279 // Determine basis font
281 pit_type pit = cur.pit();
282 if (cur.pos() < pars_[pit].beginOfBody())
283 layoutfont = labelFont(pars_[pit]);
285 layoutfont = layoutFont(pit);
287 // Update current font
288 cur.real_current_font.update(font,
289 cur.buffer()->params().language,
292 // Reduce to implicit settings
293 cur.current_font = cur.real_current_font;
294 cur.current_font.fontInfo().reduce(layoutfont);
295 // And resolve it completely
296 cur.real_current_font.fontInfo().realize(layoutfont);
298 // if there is no selection that's all we need to do
299 if (!cur.selection())
302 // Ok, we have a selection.
306 // Toggling behaves as follows: We check the first character of the
307 // selection. If it's (say) got EMPH on, then we set to off; if off,
308 // then to on. With families and the like, we set it to INHERIT, if
309 // we already have it.
310 CursorSlice const & sl = cur.selBegin();
311 Text const & text = *sl.text();
312 Paragraph const & par = text.getPar(sl.pit());
314 // get font at the position
315 Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
316 text.outerFont(sl.pit()));
317 FontInfo const & oldfi = oldfont.fontInfo();
319 FontInfo & newfi = newfont.fontInfo();
321 FontFamily newfam = newfi.family();
322 if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
323 newfam == oldfi.family())
324 newfi.setFamily(INHERIT_FAMILY);
326 FontSeries newser = newfi.series();
327 if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
328 newfi.setSeries(INHERIT_SERIES);
330 FontShape newshp = newfi.shape();
331 if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
332 newshp == oldfi.shape())
333 newfi.setShape(INHERIT_SHAPE);
335 ColorCode newcol = newfi.color();
336 if (newcol != Color_none && newcol != Color_inherit
337 && newcol != Color_ignore && newcol == oldfi.color())
338 newfi.setColor(Color_none);
341 if (newfi.emph() == FONT_TOGGLE)
342 newfi.setEmph(oldfi.emph() == FONT_OFF ? FONT_ON : FONT_OFF);
343 if (newfi.underbar() == FONT_TOGGLE)
344 newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
345 if (newfi.strikeout() == FONT_TOGGLE)
346 newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
347 if (newfi.xout() == FONT_TOGGLE)
348 newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
349 if (newfi.uuline() == FONT_TOGGLE)
350 newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
351 if (newfi.uwave() == FONT_TOGGLE)
352 newfi.setUwave(oldfi.uwave() == FONT_OFF ? FONT_ON : FONT_OFF);
353 if (newfi.noun() == FONT_TOGGLE)
354 newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
355 if (newfi.number() == FONT_TOGGLE)
356 newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
357 if (newfi.nospellcheck() == FONT_TOGGLE)
358 newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
361 setFont(cur.bv(), cur.selectionBegin().top(),
362 cur.selectionEnd().top(), newfont);
366 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
367 CursorSlice const & end, Font const & font)
369 Buffer const & buffer = bv.buffer();
371 // Don't use forwardChar here as ditend might have
372 // pos() == lastpos() and forwardChar would miss it.
373 // Can't use forwardPos either as this descends into
375 Language const * language = buffer.params().language;
376 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
377 if (dit.pos() == dit.lastpos())
379 pit_type const pit = dit.pit();
380 pos_type const pos = dit.pos();
381 Inset * inset = pars_[pit].getInset(pos);
382 if (inset && inset->resetFontEdit()) {
383 // We need to propagate the font change to all
384 // text cells of the inset (bugs 1973, 6919).
385 setInsetFont(bv, pit, pos, font);
387 TextMetrics const & tm = bv.textMetrics(this);
388 Font f = tm.displayFont(pit, pos);
389 f.update(font, language);
390 setCharFont(pit, pos, f, tm.font_);
391 // font change may change language...
392 // spell checker has to know that
393 pars_[pit].requestSpellCheck(pos);
398 bool Text::cursorTop(Cursor & cur)
400 LBUFERR(this == cur.text());
401 return setCursor(cur, 0, 0);
405 bool Text::cursorBottom(Cursor & cur)
407 LBUFERR(this == cur.text());
408 return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
412 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
414 LBUFERR(this == cur.text());
415 // If the mask is completely neutral, tell user
416 if (font.fontInfo() == ignore_font && font.language() == ignore_language) {
417 // Could only happen with user style
418 cur.message(_("No font change defined."));
422 // Try implicit word selection
423 // If there is a change in the language the implicit word selection
425 CursorSlice const resetCursor = cur.top();
426 bool const implicitSelection =
427 font.language() == ignore_language
428 && font.fontInfo().number() == FONT_IGNORE
429 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
432 setFont(cur, font, toggleall);
434 // Implicit selections are cleared afterwards
435 // and cursor is set to the original position.
436 if (implicitSelection) {
437 cur.clearSelection();
438 cur.top() = resetCursor;
444 docstring Text::getStringForDialog(Cursor & cur)
446 LBUFERR(this == cur.text());
449 return cur.selectionAsString(false);
451 // Try implicit word selection. If there is a change
452 // in the language the implicit word selection is
454 selectWordWhenUnderCursor(cur, WHOLE_WORD);
455 docstring const & retval = cur.selectionAsString(false);
456 cur.clearSelection();
461 void Text::setLabelWidthStringToSequence(Cursor const & cur,
465 // Find first of same layout in sequence
466 while (!isFirstInSequence(c.pit())) {
467 c.pit() = depthHook(c.pit(), c.paragraph().getDepth());
470 // now apply label width string to every par
472 depth_type const depth = c.paragraph().getDepth();
473 Layout const & layout = c.paragraph().layout();
474 for ( ; c.pit() <= c.lastpit() ; ++c.pit()) {
475 while (c.paragraph().getDepth() > depth) {
477 if (c.pit() > c.lastpit())
480 if (c.paragraph().getDepth() < depth)
482 if (c.paragraph().layout() != layout)
485 c.paragraph().setLabelWidthString(s);
490 void Text::setParagraphs(Cursor & cur, docstring const & arg, bool merge)
495 string const argument = to_utf8(arg);
496 depth_type priordepth = -1;
499 c.setCursor(cur.selectionBegin());
500 for ( ; c <= cur.selectionEnd() ; ++c.pit()) {
501 Paragraph & par = c.paragraph();
502 ParagraphParameters params = par.params();
503 params.read(argument, merge);
504 // Changes to label width string apply to all paragraphs
505 // with same layout in a sequence.
506 // Do this only once for a selected range of paragraphs
507 // of the same layout and depth.
509 par.params().apply(params, par.layout());
510 if (par.getDepth() != priordepth || par.layout() != priorlayout)
511 setLabelWidthStringToSequence(c, params.labelWidthString());
512 priordepth = par.getDepth();
513 priorlayout = par.layout();
518 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
522 depth_type priordepth = -1;
525 c.setCursor(cur.selectionBegin());
526 for ( ; c < cur.selectionEnd() ; ++c.pit()) {
527 Paragraph & par = c.paragraph();
528 // Changes to label width string apply to all paragraphs
529 // with same layout in a sequence.
530 // Do this only once for a selected range of paragraphs
531 // of the same layout and depth.
533 par.params().apply(p, par.layout());
534 if (par.getDepth() != priordepth || par.layout() != priorlayout)
535 setLabelWidthStringToSequence(c,
536 par.params().labelWidthString());
537 priordepth = par.getDepth();
538 priorlayout = par.layout();
543 // this really should just insert the inset and not move the cursor.
544 void Text::insertInset(Cursor & cur, Inset * inset)
546 LBUFERR(this == cur.text());
548 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
549 Change(cur.buffer()->params().track_changes
550 ? Change::INSERTED : Change::UNCHANGED));
554 bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
555 bool setfont, bool boundary)
557 TextMetrics const & tm = cur.bv().textMetrics(this);
558 bool const update_needed = !tm.contains(pit);
560 setCursorIntern(cur, pit, pos, setfont, boundary);
561 return cur.bv().checkDepm(cur, old) || update_needed;
565 void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
566 bool setfont, bool boundary)
568 LBUFERR(this == cur.text());
569 cur.boundary(boundary);
570 cur.top().setPitPos(pit, pos);
572 cur.setCurrentFont();
576 bool Text::checkAndActivateInset(Cursor & cur, bool front)
578 if (front && cur.pos() == cur.lastpos())
580 if (!front && cur.pos() == 0)
582 Inset * inset = front ? cur.nextInset() : cur.prevInset();
583 if (!inset || !inset->editable())
585 if (cur.selection() && cur.realAnchor().find(inset) == -1)
588 * Apparently, when entering an inset we are expected to be positioned
589 * *before* it in the containing paragraph, regardless of the direction
590 * from which we are entering. Otherwise, cursor placement goes awry,
591 * and when we exit from the beginning, we'll be placed *after* the
596 inset->edit(cur, front);
597 cur.setCurrentFont();
603 bool Text::checkAndActivateInsetVisual(Cursor & cur, bool movingForward, bool movingLeft)
607 if (cur.pos() == cur.lastpos())
609 Paragraph & par = cur.paragraph();
610 Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
611 if (!inset || !inset->editable())
613 if (cur.selection() && cur.realAnchor().find(inset) == -1)
615 inset->edit(cur, movingForward,
616 movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
617 cur.setCurrentFont();
623 bool Text::cursorBackward(Cursor & cur)
625 // Tell BufferView to test for FitCursor in any case!
626 cur.screenUpdateFlags(Update::FitCursor);
628 // not at paragraph start?
630 // if on right side of boundary (i.e. not at paragraph end, but line end)
631 // -> skip it, i.e. set boundary to true, i.e. go only logically left
632 // there are some exceptions to ignore this: lineseps, newlines, spaces
634 // some effectless debug code to see the values in the debugger
635 bool bound = cur.boundary();
636 int rowpos = cur.textRow().pos();
638 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
639 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
640 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
642 if (!cur.boundary() &&
643 cur.textRow().pos() == cur.pos() &&
644 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
645 !cur.paragraph().isNewline(cur.pos() - 1) &&
646 !cur.paragraph().isEnvSeparator(cur.pos() - 1) &&
647 !cur.paragraph().isSeparator(cur.pos() - 1)) {
648 return setCursor(cur, cur.pit(), cur.pos(), true, true);
651 // go left and try to enter inset
652 if (checkAndActivateInset(cur, false))
655 // normal character left
656 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
659 // move to the previous paragraph or do nothing
661 Paragraph & par = getPar(cur.pit() - 1);
662 pos_type lastpos = par.size();
663 if (lastpos > 0 && par.isEnvSeparator(lastpos - 1))
664 return setCursor(cur, cur.pit() - 1, lastpos - 1, true, false);
666 return setCursor(cur, cur.pit() - 1, lastpos, true, false);
672 bool Text::cursorVisLeft(Cursor & cur, bool skip_inset)
674 Cursor temp_cur = cur;
675 temp_cur.posVisLeft(skip_inset);
676 if (temp_cur.depth() > cur.depth()) {
680 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
681 true, temp_cur.boundary());
685 bool Text::cursorVisRight(Cursor & cur, bool skip_inset)
687 Cursor temp_cur = cur;
688 temp_cur.posVisRight(skip_inset);
689 if (temp_cur.depth() > cur.depth()) {
693 return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
694 true, temp_cur.boundary());
698 bool Text::cursorForward(Cursor & cur)
700 // Tell BufferView to test for FitCursor in any case!
701 cur.screenUpdateFlags(Update::FitCursor);
703 // not at paragraph end?
704 if (cur.pos() != cur.lastpos()) {
705 // in front of editable inset, i.e. jump into it?
706 if (checkAndActivateInset(cur, true))
709 TextMetrics const & tm = cur.bv().textMetrics(this);
710 // if left of boundary -> just jump to right side
711 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
712 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
713 return setCursor(cur, cur.pit(), cur.pos(), true, false);
715 // next position is left of boundary,
716 // but go to next line for special cases like space, newline, linesep
718 // some effectless debug code to see the values in the debugger
719 int endpos = cur.textRow().endpos();
720 int lastpos = cur.lastpos();
722 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
723 bool newline = cur.paragraph().isNewline(cur.pos());
724 bool sep = cur.paragraph().isSeparator(cur.pos());
725 if (cur.pos() != cur.lastpos()) {
726 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
727 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
728 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
731 if (cur.textRow().endpos() == cur.pos() + 1) {
732 if (cur.paragraph().isEnvSeparator(cur.pos()) &&
733 cur.pos() + 1 == cur.lastpos() &&
734 cur.pit() != cur.lastpit()) {
735 // move to next paragraph
736 return setCursor(cur, cur.pit() + 1, 0, true, false);
737 } else if (cur.textRow().endpos() != cur.lastpos() &&
738 !cur.paragraph().isNewline(cur.pos()) &&
739 !cur.paragraph().isEnvSeparator(cur.pos()) &&
740 !cur.paragraph().isLineSeparator(cur.pos()) &&
741 !cur.paragraph().isSeparator(cur.pos())) {
742 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
746 // in front of RTL boundary? Stay on this side of the boundary because:
747 // ab|cDDEEFFghi -> abc|DDEEFFghi
748 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
749 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
752 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
755 // move to next paragraph
756 if (cur.pit() != cur.lastpit())
757 return setCursor(cur, cur.pit() + 1, 0, true, false);
762 bool Text::cursorUpParagraph(Cursor & cur)
764 bool updated = false;
766 updated = setCursor(cur, cur.pit(), 0);
767 else if (cur.pit() != 0)
768 updated = setCursor(cur, cur.pit() - 1, 0);
773 bool Text::cursorDownParagraph(Cursor & cur)
775 bool updated = false;
776 if (cur.pit() != cur.lastpit())
777 if (lyxrc.mac_like_cursor_movement)
778 if (cur.pos() == cur.lastpos())
779 updated = setCursor(cur, cur.pit() + 1, getPar(cur.pit() + 1).size());
781 updated = setCursor(cur, cur.pit(), cur.lastpos());
783 updated = setCursor(cur, cur.pit() + 1, 0);
785 updated = setCursor(cur, cur.pit(), cur.lastpos());
791 /** delete num_spaces characters between from and to. Return the
792 * number of spaces that got physically deleted (not marked as
794 int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
795 int num_spaces, bool const trackChanges)
800 // First, delete spaces marked as inserted
802 while (pos < to && num_spaces > 0) {
803 Change const & change = par.lookupChange(pos);
804 if (change.inserted() && change.currentAuthor()) {
805 par.eraseChar(pos, trackChanges);
812 // Then remove remaining spaces
813 int const psize = par.size();
814 par.eraseChars(from, from + num_spaces, trackChanges);
815 return psize - par.size();
821 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
822 Cursor & old, bool & need_anchor_change)
824 //LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
826 Paragraph & oldpar = old.paragraph();
827 bool const trackChanges = cur.buffer()->params().track_changes;
830 // We do nothing if cursor did not move
831 if (cur.top() == old.top())
834 // We do not do anything on read-only documents
835 if (cur.buffer()->isReadonly())
838 // We allow all kinds of "mumbo-jumbo" when freespacing.
839 if (oldpar.isFreeSpacing())
842 // Whether a common inset is found and whether the cursor is still in
843 // the same paragraph (possibly nested).
844 int const depth = cur.find(&old.inset());
845 bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
846 && old.pit() == cur[depth].pit();
849 * (1) If the chars around the old cursor were spaces, delete some of
850 * them, but only if the cursor has really moved.
853 /* There are still some small problems that can lead to
854 double spaces stored in the document file or space at
855 the beginning of paragraphs(). This happens if you have
856 the cursor between two spaces and then save. Or if you
857 cut and paste and the selection has a space at the
858 beginning and then save right after the paste. (Lgb)
861 // find range of spaces around cursors
862 pos_type from = old.pos();
864 && oldpar.isLineSeparator(from - 1)
865 && !oldpar.isDeleted(from - 1))
867 pos_type to = old.pos();
868 while (to < old.lastpos()
869 && oldpar.isLineSeparator(to)
870 && !oldpar.isDeleted(to))
873 int num_spaces = to - from;
874 // If we are not at the start of the paragraph, keep one space
875 if (from != to && from > 0)
878 // If cursor is inside range, keep one additional space
879 if (same_par && cur.pos() > from && cur.pos() < to)
882 // Remove spaces and adapt cursor.
883 if (num_spaces > 0) {
886 deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
887 // correct cur position
888 // FIXME: there can be other cursors pointing there, we should update them
890 if (cur[depth].pos() >= to)
891 cur[depth].pos() -= deleted;
892 else if (cur[depth].pos() > from)
893 cur[depth].pos() = min(from + 1, old.lastpos());
894 need_anchor_change = true;
900 * (2) If the paragraph where the cursor was is empty, delete it
903 // only do our other magic if we changed paragraph
907 // only do our magic if the paragraph is empty
911 // don't delete anything if this is the ONLY paragraph!
912 if (old.lastpit() == 0)
915 // Do not delete empty paragraphs with keepempty set.
916 if (oldpar.allowEmpty())
920 old.recordUndo(max(old.pit() - 1, pit_type(0)),
921 min(old.pit() + 1, old.lastpit()));
922 ParagraphList & plist = old.text()->paragraphs();
923 bool const soa = oldpar.params().startOfAppendix();
924 plist.erase(plist.iterator_at(old.pit()));
925 // do not lose start of appendix marker (bug 4212)
926 if (soa && old.pit() < pit_type(plist.size()))
927 plist[old.pit()].params().startOfAppendix(true);
929 // see #warning (FIXME?) above
930 if (cur.depth() >= old.depth()) {
931 CursorSlice & curslice = cur[old.depth() - 1];
932 if (&curslice.inset() == &old.inset()
933 && curslice.pit() > old.pit()) {
935 // since a paragraph has been deleted, all the
936 // insets after `old' have been copied and
937 // their address has changed. Therefore we
938 // need to `regenerate' cur. (JMarc)
939 cur.updateInsets(&(cur.bottom().inset()));
940 need_anchor_change = true;
948 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
950 LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
952 for (pit_type pit = first; pit <= last; ++pit) {
953 Paragraph & par = pars_[pit];
955 // We allow all kinds of "mumbo-jumbo" when freespacing.
956 if (par.isFreeSpacing())
960 while (from < par.size()) {
962 while (from < par.size()
963 && (!par.isLineSeparator(from) || par.isDeleted(from)))
965 // find string of spaces
967 while (to < par.size()
968 && par.isLineSeparator(to) && !par.isDeleted(to))
970 // empty? We are done
974 int num_spaces = to - from;
976 // If we are not at the extremity of the paragraph, keep one space
977 if (from != to && from > 0 && to < par.size())
980 // Remove spaces if needed
981 int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
985 // don't delete anything if this is the only remaining paragraph
986 // within the given range. Note: Text::acceptOrRejectChanges()
987 // sets the cursor to 'first' after calling DEPM
991 // don't delete empty paragraphs with keepempty set
992 if (par.allowEmpty())
995 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
996 pars_.erase(pars_.iterator_at(pit));