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"
33 #include "CutAndPaste.h"
35 #include "DispatchResult.h"
36 #include "ErrorList.h"
37 #include "FuncRequest.h"
44 #include "Paragraph.h"
45 #include "paragraph_funcs.h"
46 #include "ParagraphParameters.h"
47 #include "ParIterator.h"
49 #include "ServerSocket.h"
50 #include "TextMetrics.h"
53 #include "frontends/FontMetrics.h"
55 #include "insets/InsetEnvironment.h"
57 #include "mathed/InsetMathHull.h"
59 #include "support/textutils.h"
61 #include <boost/current_function.hpp>
62 #include <boost/next_prior.hpp>
67 using std::ostringstream;
71 using std::istringstream;
76 : autoBreakRows_(false)
80 bool Text::isMainText(Buffer const & buffer) const
82 return &buffer.text() == this;
86 Font Text::getLayoutFont(Buffer const & buffer, pit_type const pit) const
88 LayoutPtr const & layout = pars_[pit].layout();
90 if (!pars_[pit].getDepth()) {
91 Font lf = layout->resfont;
92 // In case the default family has been customized
93 if (layout->font.family() == Font::INHERIT_FAMILY)
94 lf.setFamily(buffer.params().getFont().family());
98 Font font = layout->font;
99 // Realize with the fonts of lesser depth.
100 //font.realize(outerFont(pit, paragraphs()));
101 font.realize(buffer.params().getFont());
107 Font Text::getLabelFont(Buffer const & buffer, Paragraph const & par) const
109 LayoutPtr const & layout = par.layout();
111 if (!par.getDepth()) {
112 Font lf = layout->reslabelfont;
113 // In case the default family has been customized
114 if (layout->labelfont.family() == Font::INHERIT_FAMILY)
115 lf.setFamily(buffer.params().getFont().family());
119 Font font = layout->labelfont;
120 // Realize with the fonts of lesser depth.
121 font.realize(buffer.params().getFont());
127 void Text::setCharFont(Buffer const & buffer, pit_type pit,
128 pos_type pos, Font const & fnt, Font const & display_font)
131 LayoutPtr const & layout = pars_[pit].layout();
133 // Get concrete layout font to reduce against
136 if (pos < pars_[pit].beginOfBody())
137 layoutfont = layout->labelfont;
139 layoutfont = layout->font;
141 // Realize against environment font information
142 if (pars_[pit].getDepth()) {
144 while (!layoutfont.resolved() &&
145 tp != pit_type(paragraphs().size()) &&
146 pars_[tp].getDepth()) {
147 tp = outerHook(tp, paragraphs());
148 if (tp != pit_type(paragraphs().size()))
149 layoutfont.realize(pars_[tp].layout()->font);
153 // Inside inset, apply the inset's font attributes if any
155 if (!isMainText(buffer))
156 layoutfont.realize(display_font);
158 layoutfont.realize(buffer.params().getFont());
160 // Now, reduce font against full layout font
161 font.reduce(layoutfont);
163 pars_[pit].setFont(pos, font);
167 void Text::setInsetFont(BufferView const & bv, pit_type pit,
168 pos_type pos, Font const & font, bool toggleall)
170 BOOST_ASSERT(pars_[pit].isInset(pos) &&
171 pars_[pit].getInset(pos)->noFontChange());
173 Inset * const inset = pars_[pit].getInset(pos);
174 CursorSlice::idx_type endidx = inset->nargs();
175 for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
176 Text * text = cs.text();
178 // last position of the cell
179 CursorSlice cellend = cs;
180 cellend.pit() = cellend.lastpit();
181 cellend.pos() = cellend.lastpos();
182 text->setFont(bv, cs, cellend, font, toggleall);
188 // return past-the-last paragraph influenced by a layout change on pit
189 pit_type Text::undoSpan(pit_type pit)
191 pit_type end = paragraphs().size();
192 pit_type nextpit = pit + 1;
195 //because of parindents
196 if (!pars_[pit].getDepth())
197 return boost::next(nextpit);
198 //because of depth constrains
199 for (; nextpit != end; ++pit, ++nextpit) {
200 if (!pars_[pit].getDepth())
207 void Text::setLayout(Buffer const & buffer, pit_type start, pit_type end,
208 docstring const & layout)
210 BOOST_ASSERT(start != end);
212 BufferParams const & bufparams = buffer.params();
213 LayoutPtr const & lyxlayout = bufparams.getTextClass()[layout];
215 for (pit_type pit = start; pit != end; ++pit) {
216 Paragraph & par = pars_[pit];
217 par.applyLayout(lyxlayout);
218 if (lyxlayout->margintype == MARGIN_MANUAL)
219 par.setLabelWidthString(par.translateIfPossible(
220 lyxlayout->labelstring(), buffer.params()));
225 // set layout over selection and make a total rebreak of those paragraphs
226 void Text::setLayout(Cursor & cur, docstring const & layout)
228 BOOST_ASSERT(this == cur.text());
229 // special handling of new environment insets
230 BufferView & bv = cur.bv();
231 BufferParams const & params = bv.buffer().params();
232 LayoutPtr const & lyxlayout = params.getTextClass()[layout];
233 if (lyxlayout->is_environment) {
234 // move everything in a new environment inset
235 LYXERR(Debug::DEBUG) << "setting layout " << to_utf8(layout) << endl;
236 lyx::dispatch(FuncRequest(LFUN_LINE_BEGIN));
237 lyx::dispatch(FuncRequest(LFUN_LINE_END_SELECT));
238 lyx::dispatch(FuncRequest(LFUN_CUT));
239 Inset * inset = new InsetEnvironment(params, layout);
240 insertInset(cur, inset);
241 //inset->edit(cur, true);
242 //lyx::dispatch(FuncRequest(LFUN_PASTE));
246 pit_type start = cur.selBegin().pit();
247 pit_type end = cur.selEnd().pit() + 1;
248 pit_type undopit = undoSpan(end - 1);
249 recUndo(cur, start, undopit - 1);
250 setLayout(cur.buffer(), start, end, layout);
251 updateLabels(cur.buffer());
255 static bool changeDepthAllowed(Text::DEPTH_CHANGE type,
256 Paragraph const & par, int max_depth)
258 if (par.layout()->labeltype == LABEL_BIBLIO)
260 int const depth = par.params().depth();
261 if (type == Text::INC_DEPTH && depth < max_depth)
263 if (type == Text::DEC_DEPTH && depth > 0)
269 bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
271 BOOST_ASSERT(this == cur.text());
272 // this happens when selecting several cells in tabular (bug 2630)
273 if (cur.selBegin().idx() != cur.selEnd().idx())
276 pit_type const beg = cur.selBegin().pit();
277 pit_type const end = cur.selEnd().pit() + 1;
278 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
280 for (pit_type pit = beg; pit != end; ++pit) {
281 if (lyx::changeDepthAllowed(type, pars_[pit], max_depth))
283 max_depth = pars_[pit].getMaxDepthAfter();
289 void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
291 BOOST_ASSERT(this == cur.text());
292 pit_type const beg = cur.selBegin().pit();
293 pit_type const end = cur.selEnd().pit() + 1;
294 cur.recordUndoSelection();
295 int max_depth = (beg != 0 ? pars_[beg - 1].getMaxDepthAfter() : 0);
297 for (pit_type pit = beg; pit != end; ++pit) {
298 Paragraph & par = pars_[pit];
299 if (lyx::changeDepthAllowed(type, par, max_depth)) {
300 int const depth = par.params().depth();
301 if (type == INC_DEPTH)
302 par.params().depth(depth + 1);
304 par.params().depth(depth - 1);
306 max_depth = par.getMaxDepthAfter();
308 // this handles the counter labels, and also fixes up
309 // depth values for follow-on (child) paragraphs
310 updateLabels(cur.buffer());
314 void Text::setFont(Cursor & cur, Font const & font, bool toggleall)
316 BOOST_ASSERT(this == cur.text());
317 // Set the current_font
318 // Determine basis font
320 pit_type pit = cur.pit();
321 if (cur.pos() < pars_[pit].beginOfBody())
322 layoutfont = getLabelFont(cur.buffer(), pars_[pit]);
324 layoutfont = getLayoutFont(cur.buffer(), pit);
326 // Update current font
327 cur.real_current_font.update(font,
328 cur.buffer().params().language,
331 // Reduce to implicit settings
332 cur.current_font = cur.real_current_font;
333 cur.current_font.reduce(layoutfont);
334 // And resolve it completely
335 cur.real_current_font.realize(layoutfont);
337 // if there is no selection that's all we need to do
338 if (!cur.selection())
341 // Ok, we have a selection.
342 cur.recordUndoSelection();
344 setFont(cur.bv(), cur.selectionBegin().top(),
345 cur.selectionEnd().top(), font, toggleall);
349 void Text::setFont(BufferView const & bv, CursorSlice const & begin,
350 CursorSlice const & end, Font const & font,
353 Buffer const & buffer = bv.buffer();
355 // Don't use forwardChar here as ditend might have
356 // pos() == lastpos() and forwardChar would miss it.
357 // Can't use forwardPos either as this descends into
359 Language const * language = buffer.params().language;
360 for (CursorSlice dit = begin; dit != end; dit.forwardPos()) {
361 if (dit.pos() != dit.lastpos()) {
362 pit_type const pit = dit.pit();
363 pos_type const pos = dit.pos();
364 if (pars_[pit].isInset(pos) &&
365 pars_[pit].getInset(pos)->noFontChange())
366 // We need to propagate the font change to all
367 // text cells of the inset (bug 1973).
368 // FIXME: This should change, see documentation
369 // of noFontChange in Inset.h
370 setInsetFont(bv, pit, pos, font, toggleall);
371 TextMetrics const & tm = bv.textMetrics(this);
372 Font f = tm.getDisplayFont(pit, pos);
373 f.update(font, language, toggleall);
374 setCharFont(buffer, pit, pos, f, tm.font_);
380 bool Text::cursorTop(Cursor & cur)
382 BOOST_ASSERT(this == cur.text());
383 return setCursor(cur, 0, 0);
387 bool Text::cursorBottom(Cursor & cur)
389 BOOST_ASSERT(this == cur.text());
390 return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
394 void Text::toggleFree(Cursor & cur, Font const & font, bool toggleall)
396 BOOST_ASSERT(this == cur.text());
397 // If the mask is completely neutral, tell user
398 if (font == Font(Font::ALL_IGNORE)) {
399 // Could only happen with user style
400 cur.message(_("No font change defined."));
404 // Try implicit word selection
405 // If there is a change in the language the implicit word selection
407 CursorSlice resetCursor = cur.top();
408 bool implicitSelection =
409 font.language() == ignore_language
410 && font.number() == Font::IGNORE
411 && selectWordWhenUnderCursor(cur, WHOLE_WORD_STRICT);
414 setFont(cur, font, toggleall);
416 // Implicit selections are cleared afterwards
417 // and cursor is set to the original position.
418 if (implicitSelection) {
419 cur.clearSelection();
420 cur.top() = resetCursor;
426 docstring Text::getStringToIndex(Cursor const & cur)
428 BOOST_ASSERT(this == cur.text());
432 idxstring = cur.selectionAsString(false);
434 // Try implicit word selection. If there is a change
435 // in the language the implicit word selection is
438 selectWord(tmpcur, PREVIOUS_WORD);
440 if (!tmpcur.selection())
441 cur.message(_("Nothing to index!"));
442 else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
443 cur.message(_("Cannot index more than one paragraph!"));
445 idxstring = tmpcur.selectionAsString(false);
452 void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
454 BOOST_ASSERT(cur.text());
455 // make sure that the depth behind the selection are restored, too
456 pit_type undopit = undoSpan(cur.selEnd().pit());
457 recUndo(cur, cur.selBegin().pit(), undopit - 1);
459 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
461 Paragraph & par = pars_[pit];
462 ParagraphParameters params = par.params();
463 params.read(to_utf8(arg), merge);
464 Layout const & layout = *(par.layout());
465 par.params().apply(params, layout);
470 //FIXME This is a little redundant now, but it's probably worth keeping,
471 //especially if we're going to go away from using serialization internally
473 void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
475 BOOST_ASSERT(cur.text());
476 // make sure that the depth behind the selection are restored, too
477 pit_type undopit = undoSpan(cur.selEnd().pit());
478 recUndo(cur, cur.selBegin().pit(), undopit - 1);
480 for (pit_type pit = cur.selBegin().pit(), end = cur.selEnd().pit();
482 Paragraph & par = pars_[pit];
483 Layout const & layout = *(par.layout());
484 par.params().apply(p, layout);
489 // this really should just insert the inset and not move the cursor.
490 void Text::insertInset(Cursor & cur, Inset * inset)
492 BOOST_ASSERT(this == cur.text());
494 cur.paragraph().insertInset(cur.pos(), inset, cur.current_font,
495 Change(cur.buffer().params().trackChanges ?
496 Change::INSERTED : Change::UNCHANGED));
500 // needed to insert the selection
501 void Text::insertStringAsLines(Cursor & cur, docstring const & str)
503 cur.buffer().insertStringAsLines(pars_, cur.pit(), cur.pos(),
504 cur.current_font, str, autoBreakRows_);
508 // turn double CR to single CR, others are converted into one
509 // blank. Then insertStringAsLines is called
510 void Text::insertStringAsParagraphs(Cursor & cur, docstring const & str)
512 docstring linestr = str;
513 bool newline_inserted = false;
515 for (string::size_type i = 0, siz = linestr.size(); i < siz; ++i) {
516 if (linestr[i] == '\n') {
517 if (newline_inserted) {
518 // we know that \r will be ignored by
519 // insertStringAsLines. Of course, it is a dirty
520 // trick, but it works...
521 linestr[i - 1] = '\r';
525 newline_inserted = true;
527 } else if (isPrintable(linestr[i])) {
528 newline_inserted = false;
531 insertStringAsLines(cur, linestr);
535 bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
536 bool setfont, bool boundary)
539 setCursorIntern(cur, par, pos, setfont, boundary);
540 return cur.bv().checkDepm(cur, old);
544 void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
546 BOOST_ASSERT(par != int(paragraphs().size()));
550 // now some strict checking
551 Paragraph & para = getPar(par);
553 // None of these should happen, but we're scaredy-cats
555 lyxerr << "dont like -1" << endl;
559 if (pos > para.size()) {
560 lyxerr << "dont like 1, pos: " << pos
561 << " size: " << para.size()
562 << " par: " << par << endl;
568 void Text::setCursorIntern(Cursor & cur,
569 pit_type par, pos_type pos, bool setfont, bool boundary)
571 BOOST_ASSERT(this == cur.text());
572 cur.boundary(boundary);
573 setCursor(cur.top(), par, pos);
575 cur.setCurrentFont();
579 bool Text::checkAndActivateInset(Cursor & cur, bool front)
583 if (front && cur.pos() == cur.lastpos())
585 if (!front && cur.pos() == 0)
587 Inset * inset = front ? cur.nextInset() : cur.prevInset();
588 if (!inset || inset->editable() != Inset::HIGHLY_EDITABLE)
591 * Apparently, when entering an inset we are expected to be positioned
592 * *before* it in the containing paragraph, regardless of the direction
593 * from which we are entering. Otherwise, cursor placement goes awry,
594 * and when we exit from the beginning, we'll be placed *after* the
599 inset->edit(cur, front);
604 bool Text::cursorBackward(Cursor & cur)
606 // Tell BufferView to test for FitCursor in any case!
607 cur.updateFlags(Update::FitCursor);
609 // not at paragraph start?
611 // if on right side of boundary (i.e. not at paragraph end, but line end)
612 // -> skip it, i.e. set boundary to true, i.e. go only logically left
613 // there are some exceptions to ignore this: lineseps, newlines, spaces
615 // some effectless debug code to see the values in the debugger
616 bool bound = cur.boundary();
617 int rowpos = cur.textRow().pos();
619 bool sep = cur.paragraph().isSeparator(cur.pos() - 1);
620 bool newline = cur.paragraph().isNewline(cur.pos() - 1);
621 bool linesep = cur.paragraph().isLineSeparator(cur.pos() - 1);
623 if (!cur.boundary() &&
624 cur.textRow().pos() == cur.pos() &&
625 !cur.paragraph().isLineSeparator(cur.pos() - 1) &&
626 !cur.paragraph().isNewline(cur.pos() - 1) &&
627 !cur.paragraph().isSeparator(cur.pos() - 1)) {
628 return setCursor(cur, cur.pit(), cur.pos(), true, true);
631 // go left and try to enter inset
632 if (checkAndActivateInset(cur, false))
635 // normal character left
636 return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
639 // move to the previous paragraph or do nothing
641 return setCursor(cur, cur.pit() - 1, getPar(cur.pit() - 1).size(), true, false);
646 bool Text::cursorForward(Cursor & cur)
648 // Tell BufferView to test for FitCursor in any case!
649 cur.updateFlags(Update::FitCursor);
651 // not at paragraph end?
652 if (cur.pos() != cur.lastpos()) {
653 // in front of editable inset, i.e. jump into it?
654 if (checkAndActivateInset(cur, true))
657 TextMetrics const & tm = cur.bv().textMetrics(this);
658 // if left of boundary -> just jump to right side
659 // but for RTL boundaries don't, because: abc|DDEEFFghi -> abcDDEEF|Fghi
660 if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
661 return setCursor(cur, cur.pit(), cur.pos(), true, false);
663 // next position is left of boundary,
664 // but go to next line for special cases like space, newline, linesep
666 // some effectless debug code to see the values in the debugger
667 int endpos = cur.textRow().endpos();
668 int lastpos = cur.lastpos();
670 bool linesep = cur.paragraph().isLineSeparator(cur.pos());
671 bool newline = cur.paragraph().isNewline(cur.pos());
672 bool sep = cur.paragraph().isSeparator(cur.pos());
673 if (cur.pos() != cur.lastpos()) {
674 bool linesep2 = cur.paragraph().isLineSeparator(cur.pos()+1);
675 bool newline2 = cur.paragraph().isNewline(cur.pos()+1);
676 bool sep2 = cur.paragraph().isSeparator(cur.pos()+1);
679 if (cur.textRow().endpos() == cur.pos() + 1 &&
680 cur.textRow().endpos() != cur.lastpos() &&
681 !cur.paragraph().isNewline(cur.pos()) &&
682 !cur.paragraph().isLineSeparator(cur.pos()) &&
683 !cur.paragraph().isSeparator(cur.pos())) {
684 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
687 // in front of RTL boundary? Stay on this side of the boundary because:
688 // ab|cDDEEFFghi -> abc|DDEEFFghi
689 if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
690 return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
693 return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
696 // move to next paragraph
697 if (cur.pit() != cur.lastpit())
698 return setCursor(cur, cur.pit() + 1, 0, true, false);
703 bool Text::cursorUpParagraph(Cursor & cur)
705 bool updated = false;
707 updated = setCursor(cur, cur.pit(), 0);
708 else if (cur.pit() != 0)
709 updated = setCursor(cur, cur.pit() - 1, 0);
714 bool Text::cursorDownParagraph(Cursor & cur)
716 bool updated = false;
717 if (cur.pit() != cur.lastpit())
718 updated = setCursor(cur, cur.pit() + 1, 0);
720 updated = setCursor(cur, cur.pit(), cur.lastpos());
725 // fix the cursor `cur' after a characters has been deleted at `where'
726 // position. Called by deleteEmptyParagraphMechanism
727 void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
729 // Do nothing if cursor is not in the paragraph where the
731 if (cur.pit() != where.pit())
734 // If cursor position is after the deletion place update it
735 if (cur.pos() > where.pos())
738 // Check also if we don't want to set the cursor on a spot behind the
739 // pagragraph because we erased the last character.
740 if (cur.pos() > cur.lastpos())
741 cur.pos() = cur.lastpos();
745 bool Text::deleteEmptyParagraphMechanism(Cursor & cur,
746 Cursor & old, bool & need_anchor_change)
748 //LYXERR(Debug::DEBUG) << "DEPM: cur:\n" << cur << "old:\n" << old << endl;
750 Paragraph & oldpar = old.paragraph();
752 // We allow all kinds of "mumbo-jumbo" when freespacing.
753 if (oldpar.isFreeSpacing())
756 /* Ok I'll put some comments here about what is missing.
757 There are still some small problems that can lead to
758 double spaces stored in the document file or space at
759 the beginning of paragraphs(). This happens if you have
760 the cursor between to spaces and then save. Or if you
761 cut and paste and the selection have a space at the
762 beginning and then save right after the paste. (Lgb)
765 // If old.pos() == 0 and old.pos()(1) == LineSeparator
766 // delete the LineSeparator.
769 // If old.pos() == 1 and old.pos()(0) == LineSeparator
770 // delete the LineSeparator.
773 bool const same_inset = &old.inset() == &cur.inset();
774 bool const same_par = same_inset && old.pit() == cur.pit();
775 bool const same_par_pos = same_par && old.pos() == cur.pos();
777 // If the chars around the old cursor were spaces, delete one of them.
779 // Only if the cursor has really moved.
781 && old.pos() < oldpar.size()
782 && oldpar.isLineSeparator(old.pos())
783 && oldpar.isLineSeparator(old.pos() - 1)
784 && !oldpar.isDeleted(old.pos() - 1)
785 && !oldpar.isDeleted(old.pos())) {
786 oldpar.eraseChar(old.pos() - 1, cur.buffer().params().trackChanges);
787 // FIXME: This will not work anymore when we have multiple views of the same buffer
788 // In this case, we will have to correct also the cursors held by
789 // other bufferviews. It will probably be easier to do that in a more
790 // automated way in CursorSlice code. (JMarc 26/09/2001)
791 // correct all cursor parts
793 fixCursorAfterDelete(cur.top(), old.top());
794 need_anchor_change = true;
800 // only do our magic if we changed paragraph
804 // don't delete anything if this is the ONLY paragraph!
805 if (old.lastpit() == 0)
808 // Do not delete empty paragraphs with keepempty set.
809 if (oldpar.allowEmpty())
812 if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
814 old.recordUndo(ATOMIC_UNDO,
815 max(old.pit() - 1, pit_type(0)),
816 min(old.pit() + 1, old.lastpit()));
817 ParagraphList & plist = old.text()->paragraphs();
818 bool const soa = oldpar.params().startOfAppendix();
819 plist.erase(boost::next(plist.begin(), old.pit()));
820 // do not lose start of appendix marker (bug 4212)
822 plist[old.pit()].params().startOfAppendix(true);
824 // see #warning (FIXME?) above
825 if (cur.depth() >= old.depth()) {
826 CursorSlice & curslice = cur[old.depth() - 1];
827 if (&curslice.inset() == &old.inset()
828 && curslice.pit() > old.pit()) {
830 // since a paragraph has been deleted, all the
831 // insets after `old' have been copied and
832 // their address has changed. Therefore we
833 // need to `regenerate' cur. (JMarc)
834 cur.updateInsets(&(cur.bottom().inset()));
835 need_anchor_change = true;
841 if (oldpar.stripLeadingSpaces(cur.buffer().params().trackChanges)) {
842 need_anchor_change = true;
843 // We return true here because the Paragraph contents changed and
844 // we need a redraw before further action is processed.
852 void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
854 BOOST_ASSERT(first >= 0 && first <= last && last < (int) pars_.size());
856 for (pit_type pit = first; pit <= last; ++pit) {
857 Paragraph & par = pars_[pit];
859 // We allow all kinds of "mumbo-jumbo" when freespacing.
860 if (par.isFreeSpacing())
863 for (pos_type pos = 1; pos < par.size(); ++pos) {
864 if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
865 && !par.isDeleted(pos - 1)) {
866 if (par.eraseChar(pos - 1, trackChanges)) {
872 // don't delete anything if this is the only remaining paragraph within the given range
873 // note: Text::acceptOrRejectChanges() sets the cursor to 'first' after calling DEPM
877 // don't delete empty paragraphs with keepempty set
878 if (par.allowEmpty())
881 if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
882 pars_.erase(boost::next(pars_.begin(), pit));
888 par.stripLeadingSpaces(trackChanges);
893 void Text::recUndo(Cursor & cur, pit_type first, pit_type last) const
895 cur.recordUndo(ATOMIC_UNDO, first, last);
899 void Text::recUndo(Cursor & cur, pit_type par) const
901 cur.recordUndo(ATOMIC_UNDO, par, par);