#include "Text.h"
-#include "Bidi.h"
#include "Buffer.h"
-#include "buffer_funcs.h"
-#include "BufferList.h"
#include "BufferParams.h"
#include "BufferView.h"
#include "Changes.h"
#include "Cursor.h"
-#include "CutAndPaste.h"
-#include "DispatchResult.h"
-#include "ErrorList.h"
#include "Language.h"
#include "Layout.h"
-#include "Lexer.h"
-#include "LyX.h"
#include "LyXRC.h"
#include "Paragraph.h"
#include "ParagraphParameters.h"
#include "TextClass.h"
#include "TextMetrics.h"
-#include "insets/InsetCollapsable.h"
-
-#include "mathed/InsetMathHull.h"
+#include "insets/InsetText.h"
#include "support/lassert.h"
-#include "support/debug.h"
#include "support/gettext.h"
-#include "support/textutils.h"
-
-#include <boost/next_prior.hpp>
#include <sstream>
Inset * const inset = pars_[pit].getInset(pos);
LASSERT(inset && inset->resetFontEdit(), return);
- CursorSlice::idx_type endidx = inset->nargs();
+ idx_type endidx = inset->nargs();
for (CursorSlice cs(*inset); cs.idx() != endidx; ++cs.idx()) {
Text * text = cs.text();
if (text) {
for (pit_type pit = start; pit != end; ++pit) {
Paragraph & par = pars_[pit];
- par.applyLayout(lyxlayout);
+ // Is this a separating paragraph? If so,
+ // this needs to be standard layout
+ bool const is_separator = par.size() == 1
+ && par.isEnvSeparator(0);
+ par.applyLayout(is_separator ? bp.documentClass().defaultLayout() : lyxlayout);
if (lyxlayout.margintype == MARGIN_MANUAL)
par.setLabelWidthString(par.expandLabel(lyxlayout, bp));
}
+
+ deleteEmptyParagraphMechanism(start, end - 1, bp.track_changes);
}
pit_type end = cur.selEnd().pit() + 1;
cur.recordUndoSelection();
setLayout(start, end, layout);
+ cur.fixIfBroken();
+ cur.setCurrentFont();
cur.forceBufferUpdate();
}
}
-bool Text::changeDepthAllowed(Cursor & cur, DEPTH_CHANGE type) const
+bool Text::changeDepthAllowed(Cursor const & cur, DEPTH_CHANGE type) const
{
LBUFERR(this == cur.text());
// this happens when selecting several cells in tabular (bug 2630)
}
-void Text::changeDepth(Cursor & cur, DEPTH_CHANGE type)
+void Text::changeDepth(Cursor const & cur, DEPTH_CHANGE type)
{
LBUFERR(this == cur.text());
pit_type const beg = cur.selBegin().pit();
// Ok, we have a selection.
Font newfont = font;
- if (toggleall) {
+ if (toggleall) {
// Toggling behaves as follows: We check the first character of the
// selection. If it's (say) got EMPH on, then we set to off; if off,
// then to on. With families and the like, we set it to INHERIT, if
CursorSlice const & sl = cur.selBegin();
Text const & text = *sl.text();
Paragraph const & par = text.getPar(sl.pit());
-
+
// get font at the position
Font oldfont = par.getFont(cur.bv().buffer().params(), sl.pos(),
text.outerFont(sl.pit()));
FontInfo const & oldfi = oldfont.fontInfo();
-
+
FontInfo & newfi = newfont.fontInfo();
-
+
FontFamily newfam = newfi.family();
if (newfam != INHERIT_FAMILY && newfam != IGNORE_FAMILY &&
newfam == oldfi.family())
newfi.setFamily(INHERIT_FAMILY);
-
+
FontSeries newser = newfi.series();
if (newser == BOLD_SERIES && oldfi.series() == BOLD_SERIES)
newfi.setSeries(INHERIT_SERIES);
-
+
FontShape newshp = newfi.shape();
if (newshp != INHERIT_SHAPE && newshp != IGNORE_SHAPE &&
newshp == oldfi.shape())
newfi.setShape(INHERIT_SHAPE);
ColorCode newcol = newfi.color();
- if (newcol != Color_none && newcol != Color_inherit
+ if (newcol != Color_none && newcol != Color_inherit
&& newcol != Color_ignore && newcol == oldfi.color())
newfi.setColor(Color_none);
newfi.setUnderbar(oldfi.underbar() == FONT_OFF ? FONT_ON : FONT_OFF);
if (newfi.strikeout() == FONT_TOGGLE)
newfi.setStrikeout(oldfi.strikeout() == FONT_OFF ? FONT_ON : FONT_OFF);
+ if (newfi.xout() == FONT_TOGGLE)
+ newfi.setXout(oldfi.xout() == FONT_OFF ? FONT_ON : FONT_OFF);
if (newfi.uuline() == FONT_TOGGLE)
newfi.setUuline(oldfi.uuline() == FONT_OFF ? FONT_ON : FONT_OFF);
if (newfi.uwave() == FONT_TOGGLE)
newfi.setNoun(oldfi.noun() == FONT_OFF ? FONT_ON : FONT_OFF);
if (newfi.number() == FONT_TOGGLE)
newfi.setNumber(oldfi.number() == FONT_OFF ? FONT_ON : FONT_OFF);
+ if (newfi.nospellcheck() == FONT_TOGGLE)
+ newfi.setNoSpellcheck(oldfi.nospellcheck() == FONT_OFF ? FONT_ON : FONT_OFF);
}
- setFont(cur.bv(), cur.selectionBegin().top(),
+ setFont(cur.bv(), cur.selectionBegin().top(),
cur.selectionEnd().top(), newfont);
}
Font f = tm.displayFont(pit, pos);
f.update(font, language);
setCharFont(pit, pos, f, tm.font_);
- // font change may change language...
+ // font change may change language...
// spell checker has to know that
pars_[pit].requestSpellCheck(pos);
}
bool Text::cursorBottom(Cursor & cur)
{
LBUFERR(this == cur.text());
- return setCursor(cur, cur.lastpit(), boost::prior(paragraphs().end())->size());
+ return setCursor(cur, cur.lastpit(), prev(paragraphs().end(), 1)->size());
}
cur.clearSelection();
cur.top() = resetCursor;
cur.resetAnchor();
+ cur.setCurrentFont();
}
}
-docstring Text::getStringToIndex(Cursor const & cur)
+docstring Text::getStringForDialog(Cursor & cur)
{
LBUFERR(this == cur.text());
// Try implicit word selection. If there is a change
// in the language the implicit word selection is
// disabled.
- Cursor tmpcur = cur;
- selectWord(tmpcur, PREVIOUS_WORD);
-
- if (!tmpcur.selection())
- cur.message(_("Nothing to index!"));
- else if (tmpcur.selBegin().pit() != tmpcur.selEnd().pit())
- cur.message(_("Cannot index more than one paragraph!"));
- else
- return tmpcur.selectionAsString(false);
-
- return docstring();
+ selectWordWhenUnderCursor(cur, WHOLE_WORD);
+ docstring const & retval = cur.selectionAsString(false);
+ cur.clearSelection();
+ return retval;
}
}
-void Text::setParagraphs(Cursor & cur, docstring arg, bool merge)
+void Text::setParagraphs(Cursor const & cur, docstring const & arg, bool merge)
{
LBUFERR(cur.text());
}
-void Text::setParagraphs(Cursor & cur, ParagraphParameters const & p)
+void Text::setParagraphs(Cursor const & cur, ParagraphParameters const & p)
{
LBUFERR(cur.text());
}
-bool Text::setCursor(Cursor & cur, pit_type par, pos_type pos,
+bool Text::setCursor(Cursor & cur, pit_type pit, pos_type pos,
bool setfont, bool boundary)
{
TextMetrics const & tm = cur.bv().textMetrics(this);
- bool const update_needed = !tm.contains(par);
+ bool const update_needed = !tm.contains(pit);
Cursor old = cur;
- setCursorIntern(cur, par, pos, setfont, boundary);
+ setCursorIntern(cur, pit, pos, setfont, boundary);
return cur.bv().checkDepm(cur, old) || update_needed;
}
-void Text::setCursor(CursorSlice & cur, pit_type par, pos_type pos)
-{
- LASSERT(par != int(paragraphs().size()), return);
- cur.pit() = par;
- cur.pos() = pos;
-
- // now some strict checking
- Paragraph & para = getPar(par);
-
- // None of these should happen, but we're scaredy-cats
- if (pos < 0) {
- LYXERR0("Don't like -1!");
- LATTEST(false);
- }
-
- if (pos > para.size()) {
- LYXERR0("Don't like 1, pos: " << pos
- << " size: " << para.size()
- << " par: " << par);
- LATTEST(false);
- }
-}
-
-
-void Text::setCursorIntern(Cursor & cur,
- pit_type par, pos_type pos, bool setfont, bool boundary)
+void Text::setCursorIntern(Cursor & cur, pit_type pit, pos_type pos,
+ bool setfont, bool boundary)
{
LBUFERR(this == cur.text());
cur.boundary(boundary);
- setCursor(cur.top(), par, pos);
+ cur.top().setPitPos(pit, pos);
if (setfont)
cur.setCurrentFont();
}
if (!front)
--cur.pos();
inset->edit(cur, front);
+ cur.setCurrentFont();
+ cur.boundary(false);
return true;
}
if (cur.pos() == cur.lastpos())
return false;
Paragraph & par = cur.paragraph();
- Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : 0;
+ Inset * inset = par.isInset(cur.pos()) ? par.getInset(cur.pos()) : nullptr;
if (!inset || !inset->editable())
return false;
if (cur.selection() && cur.realAnchor().find(inset) == -1)
return false;
- inset->edit(cur, movingForward,
+ inset->edit(cur, movingForward,
movingLeft ? Inset::ENTRY_DIRECTION_RIGHT : Inset::ENTRY_DIRECTION_LEFT);
+ cur.setCurrentFont();
+ cur.boundary(false);
return true;
}
!cur.paragraph().isSeparator(cur.pos() - 1)) {
return setCursor(cur, cur.pit(), cur.pos(), true, true);
}
-
+
// go left and try to enter inset
if (checkAndActivateInset(cur, false))
return false;
-
+
// normal character left
return setCursor(cur, cur.pit(), cur.pos() - 1, true, false);
}
cur = temp_cur;
return false;
}
- return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
+ return setCursor(cur, temp_cur.pit(), temp_cur.pos(),
true, temp_cur.boundary());
}
if (cur.boundary() && !tm.isRTLBoundary(cur.pit(), cur.pos()))
return setCursor(cur, cur.pit(), cur.pos(), true, false);
- // next position is left of boundary,
+ // next position is left of boundary,
// but go to next line for special cases like space, newline, linesep
#if 0
// some effectless debug code to see the values in the debugger
return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
}
}
-
+
// in front of RTL boundary? Stay on this side of the boundary because:
// ab|cDDEEFFghi -> abc|DDEEFFghi
if (tm.isRTLBoundary(cur.pit(), cur.pos() + 1))
return setCursor(cur, cur.pit(), cur.pos() + 1, true, true);
-
+
// move right
return setCursor(cur, cur.pit(), cur.pos() + 1, true, false);
}
return updated;
}
+namespace {
-// fix the cursor `cur' after a characters has been deleted at `where'
-// position. Called by deleteEmptyParagraphMechanism
-void Text::fixCursorAfterDelete(CursorSlice & cur, CursorSlice const & where)
+/** delete num_spaces characters between from and to. Return the
+ * number of spaces that got physically deleted (not marked as
+ * deleted) */
+int deleteSpaces(Paragraph & par, pos_type const from, pos_type to,
+ int num_spaces, bool const trackChanges)
{
- // Do nothing if cursor is not in the paragraph where the
- // deletion occurred,
- if (cur.pit() != where.pit())
- return;
+ if (num_spaces <= 0)
+ return 0;
- // If cursor position is after the deletion place update it
- if (cur.pos() > where.pos())
- --cur.pos();
+ // First, delete spaces marked as inserted
+ int pos = from;
+ while (pos < to && num_spaces > 0) {
+ Change const & change = par.lookupChange(pos);
+ if (change.inserted() && change.currentAuthor()) {
+ par.eraseChar(pos, trackChanges);
+ --num_spaces;
+ --to;
+ } else
+ ++pos;
+ }
+
+ // Then remove remaining spaces
+ int const psize = par.size();
+ par.eraseChars(from, from + num_spaces, trackChanges);
+ return psize - par.size();
+}
- // Check also if we don't want to set the cursor on a spot behind the
- // pagragraph because we erased the last character.
- if (cur.pos() > cur.lastpos())
- cur.pos() = cur.lastpos();
}
//LYXERR(Debug::DEBUG, "DEPM: cur:\n" << cur << "old:\n" << old);
Paragraph & oldpar = old.paragraph();
+ bool const trackChanges = cur.buffer()->params().track_changes;
+ bool result = false;
- // We allow all kinds of "mumbo-jumbo" when freespacing.
- if (oldpar.isFreeSpacing())
+ // We do nothing if cursor did not move
+ if (cur.top() == old.top())
return false;
- /* Ok I'll put some comments here about what is missing.
- There are still some small problems that can lead to
+ // We do not do anything on read-only documents
+ if (cur.buffer()->isReadonly())
+ return false;
+
+ // Whether a common inset is found and whether the cursor is still in
+ // the same paragraph (possibly nested).
+ int const depth = cur.find(&old.inset());
+ bool const same_par = depth != -1 && old.idx() == cur[depth].idx()
+ && old.pit() == cur[depth].pit();
+
+ /*
+ * (1) If the chars around the old cursor were spaces and the
+ * paragraph is not in free spacing mode, delete some of them, but
+ * only if the cursor has really moved.
+ */
+
+ /* There are still some small problems that can lead to
double spaces stored in the document file or space at
the beginning of paragraphs(). This happens if you have
- the cursor between to spaces and then save. Or if you
- cut and paste and the selection have a space at the
+ the cursor between two spaces and then save. Or if you
+ cut and paste and the selection has a space at the
beginning and then save right after the paste. (Lgb)
*/
-
- // If old.pos() == 0 and old.pos()(1) == LineSeparator
- // delete the LineSeparator.
- // MISSING
-
- // If old.pos() == 1 and old.pos()(0) == LineSeparator
- // delete the LineSeparator.
- // MISSING
-
- // Find a common inset and the corresponding depth.
- size_t depth = 0;
- for (; depth < cur.depth(); ++depth)
- if (&old.inset() == &cur[depth].inset())
- break;
-
- // Whether a common inset is found and whether the cursor is still in
- // the same paragraph (possibly nested).
- bool const same_par = depth < cur.depth() && old.pit() == cur[depth].pit();
- bool const same_par_pos = depth == cur.depth() - 1 && same_par
- && old.pos() == cur[depth].pos();
-
- // If the chars around the old cursor were spaces, delete one of them.
- if (!same_par_pos) {
- // Only if the cursor has really moved.
- if (old.pos() > 0
- && old.pos() < oldpar.size()
- && oldpar.isLineSeparator(old.pos())
- && oldpar.isLineSeparator(old.pos() - 1)
- && !oldpar.isDeleted(old.pos() - 1)
- && !oldpar.isDeleted(old.pos())) {
- oldpar.eraseChar(old.pos() - 1, cur.buffer()->params().track_changes);
-// FIXME: This will not work anymore when we have multiple views of the same buffer
-// In this case, we will have to correct also the cursors held by
-// other bufferviews. It will probably be easier to do that in a more
-// automated way in CursorSlice code. (JMarc 26/09/2001)
- // correct all cursor parts
+ if (!oldpar.isFreeSpacing()) {
+ // find range of spaces around cursors
+ pos_type from = old.pos();
+ while (from > 0
+ && oldpar.isLineSeparator(from - 1)
+ && !oldpar.isDeleted(from - 1))
+ --from;
+ pos_type to = old.pos();
+ while (to < old.lastpos()
+ && oldpar.isLineSeparator(to)
+ && !oldpar.isDeleted(to))
+ ++to;
+
+ int num_spaces = to - from;
+ // If we are not at the start of the paragraph, keep one space
+ if (from != to && from > 0)
+ --num_spaces;
+
+ // If cursor is inside range, keep one additional space
+ if (same_par && cur.pos() > from && cur.pos() < to)
+ --num_spaces;
+
+ // Remove spaces and adapt cursor.
+ if (num_spaces > 0) {
+ old.recordUndo();
+ int const deleted =
+ deleteSpaces(oldpar, from, to, num_spaces, trackChanges);
+ // correct cur position
+ // FIXME: there can be other cursors pointing there, we should update them
if (same_par) {
- fixCursorAfterDelete(cur[depth], old.top());
+ if (cur[depth].pos() >= to)
+ cur[depth].pos() -= deleted;
+ else if (cur[depth].pos() > from)
+ cur[depth].pos() = min(from + 1, old.lastpos());
need_anchor_change = true;
}
- return true;
+ result = true;
}
}
- // only do our magic if we changed paragraph
+ /*
+ * (2) If the paragraph where the cursor was is empty, delete it
+ */
+
+ // only do our other magic if we changed paragraph
if (same_par)
- return false;
+ return result;
+
+ // only do our magic if the paragraph is empty
+ if (!oldpar.empty())
+ return result;
// don't delete anything if this is the ONLY paragraph!
if (old.lastpit() == 0)
- return false;
+ return result;
// Do not delete empty paragraphs with keepempty set.
if (oldpar.allowEmpty())
- return false;
-
- if (oldpar.empty() || (oldpar.size() == 1 && oldpar.isLineSeparator(0))) {
- // Delete old par.
- old.recordUndo(max(old.pit() - 1, pit_type(0)),
- min(old.pit() + 1, old.lastpit()));
- ParagraphList & plist = old.text()->paragraphs();
- bool const soa = oldpar.params().startOfAppendix();
- plist.erase(boost::next(plist.begin(), old.pit()));
- // do not lose start of appendix marker (bug 4212)
- if (soa && old.pit() < pit_type(plist.size()))
- plist[old.pit()].params().startOfAppendix(true);
-
- // see #warning (FIXME?) above
- if (cur.depth() >= old.depth()) {
- CursorSlice & curslice = cur[old.depth() - 1];
- if (&curslice.inset() == &old.inset()
- && curslice.pit() > old.pit()) {
- --curslice.pit();
- // since a paragraph has been deleted, all the
- // insets after `old' have been copied and
- // their address has changed. Therefore we
- // need to `regenerate' cur. (JMarc)
- cur.updateInsets(&(cur.bottom().inset()));
- need_anchor_change = true;
- }
+ return result;
+
+ // Delete old par.
+ old.recordUndo(max(old.pit() - 1, pit_type(0)),
+ min(old.pit() + 1, old.lastpit()));
+ ParagraphList & plist = old.text()->paragraphs();
+ bool const soa = oldpar.params().startOfAppendix();
+ plist.erase(plist.iterator_at(old.pit()));
+ // do not lose start of appendix marker (bug 4212)
+ if (soa && old.pit() < pit_type(plist.size()))
+ plist[old.pit()].params().startOfAppendix(true);
+
+ // see #warning (FIXME?) above
+ if (cur.depth() >= old.depth()) {
+ CursorSlice & curslice = cur[old.depth() - 1];
+ if (&curslice.inset() == &old.inset()
+ && curslice.idx() == old.idx()
+ && curslice.pit() > old.pit()) {
+ --curslice.pit();
+ // since a paragraph has been deleted, all the
+ // insets after `old' have been copied and
+ // their address has changed. Therefore we
+ // need to `regenerate' cur. (JMarc)
+ cur.updateInsets(&(cur.bottom().inset()));
+ need_anchor_change = true;
}
- return true;
- }
-
- if (oldpar.stripLeadingSpaces(cur.buffer()->params().track_changes)) {
- need_anchor_change = true;
- // We return true here because the Paragraph contents changed and
- // we need a redraw before further action is processed.
- return true;
}
- return false;
+ return true;
}
void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last, bool trackChanges)
+{
+ pos_type last_pos = pars_[last].size() - 1;
+ deleteEmptyParagraphMechanism(first, last, 0, last_pos, trackChanges);
+}
+
+
+void Text::deleteEmptyParagraphMechanism(pit_type first, pit_type last,
+ pos_type first_pos, pos_type last_pos,
+ bool trackChanges)
{
LASSERT(first >= 0 && first <= last && last < (int) pars_.size(), return);
for (pit_type pit = first; pit <= last; ++pit) {
Paragraph & par = pars_[pit];
- // We allow all kinds of "mumbo-jumbo" when freespacing.
- if (par.isFreeSpacing())
- continue;
-
- for (pos_type pos = 1; pos < par.size(); ++pos) {
- if (par.isLineSeparator(pos) && par.isLineSeparator(pos - 1)
- && !par.isDeleted(pos - 1)) {
- if (par.eraseChar(pos - 1, trackChanges)) {
- --pos;
- }
+ /*
+ * (1) Delete consecutive spaces
+ */
+ if (!par.isFreeSpacing()) {
+ pos_type from = (pit == first) ? first_pos : 0;
+ pos_type to_pos = (pit == last) ? last_pos + 1 : par.size();
+ while (from < to_pos) {
+ // skip non-spaces
+ while (from < par.size()
+ && (!par.isLineSeparator(from) || par.isDeleted(from)))
+ ++from;
+ // find string of spaces
+ pos_type to = from;
+ while (to < par.size()
+ && par.isLineSeparator(to) && !par.isDeleted(to))
+ ++to;
+ // empty? We are done
+ if (from == to)
+ break;
+
+ int num_spaces = to - from;
+
+ // If we are not at the extremity of the paragraph, keep one space
+ if (from != to && from > 0 && to < par.size())
+ --num_spaces;
+
+ // Remove spaces if needed
+ int const deleted = deleteSpaces(par, from , to, num_spaces, trackChanges);
+ from = to - deleted;
}
}
+ /*
+ * (2) Delete empty pragraphs
+ */
+
// don't delete anything if this is the only remaining paragraph
// within the given range. Note: Text::acceptOrRejectChanges()
// sets the cursor to 'first' after calling DEPM
continue;
if (par.empty() || (par.size() == 1 && par.isLineSeparator(0))) {
- pars_.erase(boost::next(pars_.begin(), pit));
+ pars_.erase(pars_.iterator_at(pit));
--pit;
--last;
continue;
}
-
- par.stripLeadingSpaces(trackChanges);
}
}