diff --git a/src/ChangeLog b/src/ChangeLog index 4f4b46f01b..174a383e18 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,38 @@ +2006-03-18 Martin Vermeer + + * text.C (backspace): Fix changebar non-update + +2006-03-15 Martin Vermeer + + * CutAndPaste.C (pasteSelectionHelper): comments + * paragraph_funcs.C (mergeParagraph): fix Juergen's cut&paste bug + * changes.h: comments + * paragraph.C (stripLeadingSpaces): remove unnecessary setChange + * text.C (backspace): allow deletion of inserted para break + Change tracking -related bug fixes (reported by Juergen) and + some documentation work + +2006-03-13 Martin Vermeer + + * rowpainter.C (paintChangeBar): fix painting of change bar with + only paragraph break changed + +2006-03-11 Martin Vermeer + + * paragraph.[Ch] (write, lookupChange, lookupChangeFull; + added setChangeFull): + * paragraph_pimpl.[Ch] (trackChanges, cleanChanges, acceptChange, + rejectChange, erase; added setChangeFull): + * CutAndPaste.C (eraseSelectionHelper): + * lyxtext.h: + * text.C (readParToken, readParagraph, breakParagraph, + acceptChange, rejectChange, backspace, currentState; + added backspacePos0): + * paragraph_funcs.C (breakParagraphConservative, mergeParagraph): + * lyxfind.C (findChange, findNextChange): fix bug 880: Change + tracked paragraphs should still allow a paragraph split (and related + things, i.e., multi-paragraph change tracking) + 2006-02-20 Jean-Marc Lasgouttes * cursor.C (bruteFind): only iterate over the paragraphs that are diff --git a/src/CutAndPaste.C b/src/CutAndPaste.C index ce12c42960..9c78a841ed 100644 --- a/src/CutAndPaste.C +++ b/src/CutAndPaste.C @@ -234,7 +234,8 @@ pasteSelectionHelper(Buffer const & buffer, pit = last_paste; pos = pars[last_paste].size(); - // Maybe some pasting. + // Join (conditionally) last pasted paragraph with next one, i.e., + // the tail of the spliced document paragraph if (!empty && last_paste + 1 != pit_type(pars.size())) { if (pars[last_paste + 1].hasSameLayout(pars[last_paste])) { mergeParagraph(buffer.params(), pars, last_paste); @@ -271,64 +272,35 @@ PitPosPair eraseSelectionHelper(BufferParams const & params, return PitPosPair(endpit, endpos); } - bool all_erased = true; - - // Clear fragments of the first par in selection - pars[startpit].erase(startpos, pars[startpit].size()); - if (pars[startpit].size() != startpos) - all_erased = false; - - // Clear fragments of the last par in selection - endpos -= pars[endpit].erase(0, endpos); - if (endpos != 0) - all_erased = false; - - // Erase all the "middle" paragraphs. - if (params.tracking_changes) { - // Look through the deleted pars if any, erasing as needed - for (pit_type pit = startpit + 1; pit != endpit;) { - // "erase" the contents of the par - pars[pit].erase(0, pars[pit].size()); - if (pars[pit].empty()) { - // remove the par if it's now empty - pars.erase(pars.begin() + pit); - --endpit; - } else { - ++pit; - all_erased = false; - } - } - } else { - pars.erase(pars.begin() + startpit + 1, pars.begin() + endpit); - endpit = startpit + 1; - } - -#if 0 // FIXME: why for cut but not copy ? - // the cut selection should begin with standard layout - if (realcut) { - buf->params().clear(); - buf->bibkey = 0; - buf->layout(textclasslist[buffer->params.textclass].defaultLayoutName()); - } -#endif - - if (startpit + 1 == pit_type(pars.size())) - return PitPosPair(endpit, endpos); - - if (doclear) { - pars[startpit + 1].stripLeadingSpaces(); - } - - // Merge first and last paragraph, if possible - if (all_erased && - (pars[startpit].hasSameLayout(pars[startpit + 1]) || - pars[startpit + 1].empty())) { - mergeParagraph(params, pars, startpit); - // This because endpar gets deleted here! - endpit = startpit; - endpos = startpos; + // A paragraph break has to be physically removed by merging, but + // only if either (1) change tracking is off, or (2) the para break + // is "blue" + for (pit_type pit = startpit; pit != endpit + 1;) { + bool const merge = !params.tracking_changes || + pars[pit].lookupChange(pars[pit].size()) == + Change::INSERTED; + pos_type const left = ( pit == startpit ? startpos : 0 ); + pos_type const right = ( pit == endpit ? endpos : + pars[pit].size() + 1 ); + // Logical erase only: + pars[pit].erase(left, right); + // Separate handling of para break: + if (merge && pit != endpit && + pars[pit].hasSameLayout(pars[pit + 1])) { + pos_type const thissize = pars[pit].size(); + if (doclear) + pars[pit + 1].stripLeadingSpaces(); + mergeParagraph(params, pars, pit); + --endpit; + if (pit == endpit) + endpos += thissize; + } else + ++pit; } + // Ensure legal cursor pos: + endpit = startpit; + endpos = startpos; return PitPosPair(endpit, endpos); } diff --git a/src/changes.h b/src/changes.h index 1103bd2012..1777c8f09a 100644 --- a/src/changes.h +++ b/src/changes.h @@ -84,7 +84,8 @@ public: /// return true if there is a deleted or unchanged range contained bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; - /// remove the given entry + /// remove the given entry. This implies that a character was + /// deleted at pos, and will adjust all range bounds past it void erase(lyx::pos_type pos); /// output latex to mark a transition between two changetypes @@ -134,22 +135,23 @@ private: typedef std::vector ChangeTable; - /// our table of changes + /// our table of changes, every row a range and change descriptor ChangeTable table_; /// change type for an empty paragraph Change::Type empty_type_; - /// handle a delete + /// handle a delete, either logical or physical (see erase) void del(Change change, ChangeTable::size_type pos); - /// handle an add + /// handle an add, adjusting range bounds past it void add(Change change, ChangeTable::size_type pos); - /// merge neighbouring ranges + /// merge neighbouring ranges, assuming that they are abutting + /// (as done by set()) void merge(); - /// consistency check + /// consistency check, needed before merge() void check() const; }; diff --git a/src/lyxfind.C b/src/lyxfind.C index 2f16fd3d6c..a88ef7e836 100644 --- a/src/lyxfind.C +++ b/src/lyxfind.C @@ -127,9 +127,8 @@ bool findBackwards(DocIterator & cur, MatchString const & match) bool findChange(DocIterator & cur) { - for (; cur; cur.forwardChar()) - if (cur.inTexted() && cur.pos() != cur.paragraph().size() && - cur.paragraph().lookupChange(cur.pos()) + for (; cur; cur.forwardPos()) + if (cur.inTexted() && cur.paragraph().lookupChange(cur.pos()) != Change::UNCHANGED) return true; return false; @@ -344,25 +343,21 @@ bool findNextChange(BufferView * bv) if (!findChange(cur)) return false; - Paragraph const & par = cur.paragraph(); - const pos_type pos = cur.pos(); + bv->cursor().setCursor(cur); + bv->cursor().resetAnchor(); + + Change orig_change = cur.paragraph().lookupChangeFull(cur.pos()); - Change orig_change = par.lookupChangeFull(pos); - const pos_type parsize = par.size(); - pos_type end = pos; - - for (; end != parsize; ++end) { - Change change = par.lookupChangeFull(end); + DocIterator et = doc_iterator_end(cur.inset()); + for (; cur != et; cur.forwardPosNoDescend()) { + Change change = cur.paragraph().lookupChangeFull(cur.pos()); if (change != orig_change) { - // slight UI optimisation: for replacements, we get - // text like : _old_new. Consider that as one change. - if (!(orig_change.type == Change::DELETED && - change.type == Change::INSERTED)) - break; + break; } } - pos_type length = end - pos; - bv->putSelectionAt(cur, length, false); + // Now put cursor to end of selection: + bv->cursor().setCursor(cur); + bv->cursor().setSelection(); // if we used a lfun like in find/replace, dispatch would do // that for us bv->update(); diff --git a/src/lyxtext.h b/src/lyxtext.h index 28507d39f1..e5cc72d738 100644 --- a/src/lyxtext.h +++ b/src/lyxtext.h @@ -220,9 +220,14 @@ public: bool cursorTop(LCursor & cur); /// bool cursorBottom(LCursor & cur); - /// + /// Delete character at cursor. Honour change tracking bool Delete(LCursor & cur); - /// + /** At cursor position 0, merge paragraph with the one before it. + * Ignore CT (this is used in \c acceptChange, \c rejectChange for + * physical deletion of paragraph break) + */ + bool backspacePos0(LCursor & cur); + /// Delete character before cursor. Honour CT bool backspace(LCursor & cur); /// bool selectWordWhenUnderCursor(LCursor & cur, lyx::word_location); diff --git a/src/paragraph.C b/src/paragraph.C index 8f136352fe..4bfe0db3fe 100644 --- a/src/paragraph.C +++ b/src/paragraph.C @@ -159,12 +159,15 @@ void Paragraph::write(Buffer const & buf, ostream & os, lyx::time_type const curtime(lyx::current_time()); int column = 0; - for (pos_type i = 0; i < size(); ++i) { + for (pos_type i = 0; i <= size(); ++i) { Change change = pimpl_->lookupChangeFull(i); Changes::lyxMarkChange(os, column, curtime, running_change, change); running_change = change; + if (i == size()) + break; + // Write font changes LyXFont font2 = getFontSettings(bparams, i); if (font2 != font1) { @@ -223,15 +226,6 @@ void Paragraph::write(Buffer const & buf, ostream & os, } } - // to make reading work properly - if (!size()) { - running_change = pimpl_->lookupChange(0); - Changes::lyxMarkChange(os, column, curtime, - Change(Change::UNCHANGED), running_change); - } - Changes::lyxMarkChange(os, column, curtime, - running_change, Change(Change::UNCHANGED)); - os << "\n\\end_layout\n"; } @@ -569,9 +563,8 @@ int Paragraph::stripLeadingSpaces() return 0; int i = 0; - while (!empty() && (isNewline(0) || isLineSeparator(0))) { - // Set Change::Type to Change::INSERTED to quietly remove it - setChange(0, Change::INSERTED); + while (!empty() && (isNewline(0) || isLineSeparator(0)) + && (lookupChange(0) != Change::DELETED)) { erase(0); ++i; } @@ -1639,14 +1632,14 @@ void Paragraph::cleanChanges() Change::Type Paragraph::lookupChange(lyx::pos_type pos) const { - BOOST_ASSERT(empty() || pos < size()); + BOOST_ASSERT(pos <= size()); return pimpl_->lookupChange(pos); } Change const Paragraph::lookupChangeFull(lyx::pos_type pos) const { - BOOST_ASSERT(empty() || pos < size()); + BOOST_ASSERT(pos <= size()); return pimpl_->lookupChangeFull(pos); } @@ -1669,6 +1662,12 @@ void Paragraph::setChange(lyx::pos_type pos, Change::Type type) } +void Paragraph::setChangeFull(lyx::pos_type pos, Change change) +{ + pimpl_->setChangeFull(pos, change); +} + + void Paragraph::markErased(bool erased) { pimpl_->markErased(erased); diff --git a/src/paragraph.h b/src/paragraph.h index 3cf48bea42..27038e0ff1 100644 --- a/src/paragraph.h +++ b/src/paragraph.h @@ -224,6 +224,9 @@ public: /// set change at pos void setChange(lyx::pos_type pos, Change::Type type); + + /// set full change at pos + void setChangeFull(lyx::pos_type pos, Change change); /// accept change void acceptChange(lyx::pos_type start, lyx::pos_type end); diff --git a/src/paragraph_funcs.C b/src/paragraph_funcs.C index 36bed60fe6..b5b43d87d7 100644 --- a/src/paragraph_funcs.C +++ b/src/paragraph_funcs.C @@ -213,6 +213,9 @@ void breakParagraphConservative(BufferParams const & bparams, if (moveItem(par, tmp, bparams, i, j - pos, change)) ++j; } + // Move over end-of-par change attr + tmp.setChange(tmp.size(), par.lookupChange(par.size())); + // If tracking changes, set all the text that is to be // erased to Type::INSERTED. for (pos_type k = pos_end; k >= pos; --k) { @@ -233,12 +236,28 @@ void mergeParagraph(BufferParams const & bparams, pos_type pos_end = next.size() - 1; pos_type pos_insert = par.size(); + // What happens is the following. Later on, moveItem() will copy + // over characters from the next paragraph to be inserted into this + // position. Now, if the first char to be so copied is "red" (i.e., + // marked deleted) and the paragraph break is marked "blue", + // insertChar will trigger (eventually, through record(), and see + // del() and erase() in changes.C) a "hard" character deletion. + // Which doesn't make sense of course at this pos, but the effect is + // to shorten the change range to which this para break belongs, by + // one. It will (should) remain "orphaned", having no CT info to it, + // and check() in changes.C will assert. Setting the para break + // forcibly to "black" prevents this scenario. -- MV 13.3.2006 + par.setChange(par.size(), Change::UNCHANGED); + + Change::Type cr = next.lookupChange(next.size()); // ok, now copy the paragraph for (pos_type i = 0, j = 0; i <= pos_end; ++i) { Change::Type change = next.lookupChange(i); if (moveItem(next, par, bparams, i, pos_insert + j, change)) ++j; } + // Move the change status of "carriage return" over + par.setChange(par.size(), cr); pars.erase(pars.begin() + par_offset + 1); } diff --git a/src/paragraph_pimpl.C b/src/paragraph_pimpl.C index 9547fc9838..76065063c5 100644 --- a/src/paragraph_pimpl.C +++ b/src/paragraph_pimpl.C @@ -99,7 +99,7 @@ void Paragraph::Pimpl::trackChanges(Change::Type type) lyxerr[Debug::CHANGES] << "track changes for par " << id_ << " type " << type << endl; changes_.reset(new Changes(type)); - changes_->set(type, 0, size()); + changes_->set(type, 0, size() + 1); } @@ -116,7 +116,7 @@ void Paragraph::Pimpl::cleanChanges() return; changes_.reset(new Changes(Change::INSERTED)); - changes_->set(Change::INSERTED, 0, size()); + changes_->set(Change::INSERTED, 0, size() + 1); } @@ -147,6 +147,14 @@ void Paragraph::Pimpl::setChange(pos_type pos, Change::Type type) } +void Paragraph::Pimpl::setChangeFull(pos_type pos, Change change) +{ + if (!tracking()) + return; + + changes_->set(change, pos); +} + Change::Type Paragraph::Pimpl::lookupChange(pos_type pos) const { if (!tracking()) @@ -204,10 +212,14 @@ void Paragraph::Pimpl::acceptChange(pos_type start, pos_type end) break; case Change::DELETED: - eraseIntern(i); - changes_->erase(i); - --end; - --i; + // Suppress access to nonexistent + // "end-of-paragraph char": + if (i < size()) { + eraseIntern(i); + changes_->erase(i); + --end; + --i; + } break; } } @@ -235,15 +247,18 @@ void Paragraph::Pimpl::rejectChange(pos_type start, pos_type end) break; case Change::INSERTED: - eraseIntern(i); - changes_->erase(i); - --end; - --i; + if (i < size()) { + eraseIntern(i); + changes_->erase(i); + --end; + --i; + } break; case Change::DELETED: changes_->set(Change::UNCHANGED, i); - if (owner_->isInset(i)) + // No real char at position size(): + if (i < size() && owner_->isInset(i)) owner_->getInset(i)->markErased(false); break; } @@ -351,7 +366,7 @@ void Paragraph::Pimpl::eraseIntern(pos_type pos) bool Paragraph::Pimpl::erase(pos_type pos) { - BOOST_ASSERT(pos < size()); + BOOST_ASSERT(pos <= size()); if (tracking()) { Change::Type changetype(changes_->lookup(pos)); @@ -359,14 +374,19 @@ bool Paragraph::Pimpl::erase(pos_type pos) // only allow the actual removal if it was /new/ text if (changetype != Change::INSERTED) { - if (owner_->isInset(pos)) + if (pos < size() && owner_->isInset(pos)) owner_->getInset(pos)->markErased(true); return false; } } - eraseIntern(pos); - return true; + // Don't physically access nonexistent end-of-paragraph char + if (pos < size()) { + eraseIntern(pos); + return true; + } + + return false; } diff --git a/src/paragraph_pimpl.h b/src/paragraph_pimpl.h index ed2e809270..488e7e4ded 100644 --- a/src/paragraph_pimpl.h +++ b/src/paragraph_pimpl.h @@ -54,6 +54,8 @@ public: bool isChangeEdited(lyx::pos_type start, lyx::pos_type end) const; /// set change at pos void setChange(lyx::pos_type pos, Change::Type type); + /// set full change at pos + void setChangeFull(lyx::pos_type pos, Change change); /// mark as erased void markErased(bool); /// accept change diff --git a/src/rowpainter.C b/src/rowpainter.C index 2b8193f5ea..55ab558241 100644 --- a/src/rowpainter.C +++ b/src/rowpainter.C @@ -347,7 +347,7 @@ void RowPainter::paintChangeBar() pos_type const start = row_.pos(); pos_type const end = row_.endpos(); - if (start == end || !par_.isChanged(start, end - 1)) + if (start == end || !par_.isChanged(start, end)) return; int const height = text_.isLastRow(pit_, row_) diff --git a/src/text.C b/src/text.C index e447cca6db..f8cc42b480 100644 --- a/src/text.C +++ b/src/text.C @@ -315,8 +315,10 @@ void readParToken(Buffer const & buf, Paragraph & par, LyXLex & lex, } else if (token == "\\change_unchanged") { // Hack ! Needed for empty paragraphs :/ // FIXME: is it still ?? + /* if (!par.size()) par.cleanChanges(); + */ change = Change(Change::UNCHANGED); } else if (token == "\\change_inserted") { lex.eatLine(); @@ -375,6 +377,9 @@ void readParagraph(Buffer const & buf, Paragraph & par, LyXLex & lex) break; } } + // Final change goes to paragraph break: + par.setChangeFull(par.size(), change); + // Initialize begin_of_body_ on load; redoParagraph maintains par.setBeginOfBody(); } @@ -1026,14 +1031,10 @@ namespace { void LyXText::breakParagraph(LCursor & cur, bool keep_layout) { BOOST_ASSERT(this == cur.text()); - // allow only if at start or end, or all previous is new text + Paragraph & cpar = cur.paragraph(); pit_type cpit = cur.pit(); - if (cur.pos() != 0 && cur.pos() != cur.lastpos() - && cpar.isChangeEdited(0, cur.pos())) - return; - LyXTextClass const & tclass = cur.buffer().params().getLyXTextClass(); LyXLayout_ptr const & layout = cpar.layout(); @@ -1088,6 +1089,12 @@ void LyXText::breakParagraph(LCursor & cur, bool keep_layout) updateCounters(cur.buffer()); + // Mark "carriage return" as inserted if change tracking: + if (cur.buffer().params().tracking_changes) { + cur.paragraph().setChange(cur.paragraph().size(), + Change::INSERTED); + } + // This check is necessary. Otherwise the new empty paragraph will // be deleted automatically. And it is more friendly for the user! if (cur.pos() != 0 || isempty) @@ -1392,18 +1399,34 @@ void LyXText::acceptChange(LCursor & cur) if (!cur.selection() && cur.lastpos() != 0) return; - CursorSlice const & startc = cur.selBegin(); - CursorSlice const & endc = cur.selEnd(); - if (startc.pit() == endc.pit()) { - recordUndoSelection(cur, Undo::INSERT); - pars_[startc.pit()].acceptChange(startc.pos(), endc.pos()); - finishUndo(); - cur.clearSelection(); - setCursorIntern(cur, startc.pit(), 0); + recordUndoSelection(cur, Undo::INSERT); + + DocIterator it = cur.selectionBegin(); + DocIterator et = cur.selectionEnd(); + pit_type pit = it.pit(); + Change::Type const type = pars_[pit].lookupChange(it.pos()); + for (; pit <= et.pit(); ++pit) { + pos_type left = ( pit == it.pit() ? it.pos() : 0 ); + pos_type right = + ( pit == et.pit() ? et.pos() : pars_[pit].size() + 1 ); + pars_[pit].acceptChange(left, right); } -#ifdef WITH_WARNINGS -#warning handle multi par selection -#endif + if (type == Change::DELETED) { + ParagraphList & plist = paragraphs(); + if (it.pit() + 1 < et.pit()) + pars_.erase(plist.begin() + it.pit() + 1, + plist.begin() + et.pit()); + + // Paragraph merge if appropriate: + if (pars_[it.pit()].lookupChange(pars_[it.pit()].size()) + == Change::DELETED) { + setCursorIntern(cur, it.pit() + 1, 0); + backspacePos0(cur); + } + } + finishUndo(); + cur.clearSelection(); + setCursorIntern(cur, it.pit(), 0); } @@ -1413,18 +1436,33 @@ void LyXText::rejectChange(LCursor & cur) if (!cur.selection() && cur.lastpos() != 0) return; - CursorSlice const & startc = cur.selBegin(); - CursorSlice const & endc = cur.selEnd(); - if (startc.pit() == endc.pit()) { - recordUndoSelection(cur, Undo::INSERT); - pars_[startc.pit()].rejectChange(startc.pos(), endc.pos()); - finishUndo(); - cur.clearSelection(); - setCursorIntern(cur, startc.pit(), 0); + recordUndoSelection(cur, Undo::INSERT); + + DocIterator it = cur.selectionBegin(); + DocIterator et = cur.selectionEnd(); + pit_type pit = it.pit(); + Change::Type const type = pars_[pit].lookupChange(it.pos()); + for (; pit <= et.pit(); ++pit) { + pos_type left = ( pit == it.pit() ? it.pos() : 0 ); + pos_type right = + ( pit == et.pit() ? et.pos() : pars_[pit].size() + 1 ); + pars_[pit].rejectChange(left, right); } -#ifdef WITH_WARNINGS -#warning handle multi par selection -#endif + if (type == Change::INSERTED) { + ParagraphList & plist = paragraphs(); + if (it.pit() + 1 < et.pit()) + pars_.erase(plist.begin() + it.pit() + 1, + plist.begin() + et.pit()); + // Paragraph merge if appropriate: + if (pars_[it.pit()].lookupChange(pars_[it.pit()].size()) + == Change::INSERTED) { + setCursorIntern(cur, it.pit() + 1, 0); + backspacePos0(cur); + } + } + finishUndo(); + cur.clearSelection(); + setCursorIntern(cur, it.pit(), 0); } @@ -1560,6 +1598,80 @@ bool LyXText::Delete(LCursor & cur) } +bool LyXText::backspacePos0(LCursor & cur) +{ + BOOST_ASSERT(this == cur.text()); + bool needsUpdate = false; + + Paragraph & par = cur.paragraph(); + // is it an empty paragraph? + pos_type lastpos = cur.lastpos(); + if (lastpos == 0 || (lastpos == 1 && par.isSeparator(0))) { + // This is an empty paragraph and we delete it just + // by moving the cursor one step + // left and let the DeleteEmptyParagraphMechanism + // handle the actual deletion of the paragraph. + + if (cur.pit() != 0) { + // For KeepEmpty layouts we need to get + // rid of the keepEmpty setting first. + // And the only way to do this is to + // reset the layout to something + // else: f.ex. the default layout. + if (par.allowEmpty()) { + Buffer & buf = cur.buffer(); + BufferParams const & bparams = buf.params(); + par.layout(bparams.getLyXTextClass().defaultLayout()); + } + + cursorLeft(cur); + return true; + } + } + + if (cur.pit() != 0) + recordUndo(cur, Undo::DELETE, cur.pit() - 1); + + pit_type tmppit = cur.pit(); + // We used to do cursorLeftIntern() here, but it is + // not a good idea since it triggers the auto-delete + // mechanism. So we do a cursorLeftIntern()-lite, + // without the dreaded mechanism. (JMarc) + if (cur.pit() != 0) { + // steps into the above paragraph. + setCursorIntern(cur, cur.pit() - 1, + pars_[cur.pit() - 1].size(), + false); + } + + // Pasting is not allowed, if the paragraphs have different + // layout. I think it is a real bug of all other + // word processors to allow it. It confuses the user. + // Correction: Pasting is always allowed with standard-layout + // Correction (Jug 20050717): Remove check about alignment! + Buffer & buf = cur.buffer(); + BufferParams const & bufparams = buf.params(); + LyXTextClass const & tclass = bufparams.getLyXTextClass(); + pit_type const cpit = cur.pit(); + + if (cpit != tmppit + && (pars_[cpit].layout() == pars_[tmppit].layout() + || pars_[tmppit].layout() == tclass.defaultLayout())) + { + mergeParagraph(bufparams, pars_, cpit); + needsUpdate = true; + + if (cur.pos() != 0 && pars_[cpit].isSeparator(cur.pos() - 1)) + --cur.pos(); + + // the counters may have changed + updateCounters(cur.buffer()); + setCursor(cur, cur.pit(), cur.pos(), false); + } + return needsUpdate; +} + + bool LyXText::backspace(LCursor & cur) { BOOST_ASSERT(this == cur.text()); @@ -1569,77 +1681,20 @@ bool LyXText::backspace(LCursor & cur) // the the backspace will collapse two paragraphs into // one. - // but it's not allowed unless it's new - Paragraph & par = cur.paragraph(); - if (par.isChangeEdited(0, par.size())) - return false; - - // we may paste some paragraphs - - // is it an empty paragraph? - pos_type lastpos = cur.lastpos(); - if (lastpos == 0 || (lastpos == 1 && par.isSeparator(0))) { - // This is an empty paragraph and we delete it just - // by moving the cursor one step - // left and let the DeleteEmptyParagraphMechanism - // handle the actual deletion of the paragraph. - - if (cur.pit() != 0) { - // For KeepEmpty layouts we need to get - // rid of the keepEmpty setting first. - // And the only way to do this is to - // reset the layout to something - // else: f.ex. the default layout. - if (par.allowEmpty()) { - Buffer & buf = cur.buffer(); - BufferParams const & bparams = buf.params(); - par.layout(bparams.getLyXTextClass().defaultLayout()); - } - - cursorLeft(cur); + if (cur.buffer().params().tracking_changes) { + // Previous paragraph, mark "carriage return" as + // deleted: + Paragraph & par = pars_[cur.pit() - 1]; + // Take care of a just inserted para break: + if (par.lookupChange(par.size()) != Change::INSERTED) { + par.setChange(par.size(), Change::DELETED); + setCursorIntern(cur, cur.pit() - 1, par.size()); return true; } } - if (cur.pit() != 0) - recordUndo(cur, Undo::DELETE, cur.pit() - 1); + needsUpdate = backspacePos0(cur); - pit_type tmppit = cur.pit(); - // We used to do cursorLeftIntern() here, but it is - // not a good idea since it triggers the auto-delete - // mechanism. So we do a cursorLeftIntern()-lite, - // without the dreaded mechanism. (JMarc) - if (cur.pit() != 0) { - // steps into the above paragraph. - setCursorIntern(cur, cur.pit() - 1, - pars_[cur.pit() - 1].size(), - false); - } - - // Pasting is not allowed, if the paragraphs have different - // layout. I think it is a real bug of all other - // word processors to allow it. It confuses the user. - // Correction: Pasting is always allowed with standard-layout - // Correction (Jug 20050717): Remove check about alignment! - Buffer & buf = cur.buffer(); - BufferParams const & bufparams = buf.params(); - LyXTextClass const & tclass = bufparams.getLyXTextClass(); - pit_type const cpit = cur.pit(); - - if (cpit != tmppit - && (pars_[cpit].layout() == pars_[tmppit].layout() - || pars_[tmppit].layout() == tclass.defaultLayout())) - { - mergeParagraph(bufparams, pars_, cpit); - needsUpdate = true; - - if (cur.pos() != 0 && pars_[cpit].isSeparator(cur.pos() - 1)) - --cur.pos(); - - // the counters may have changed - updateCounters(cur.buffer()); - setCursor(cur, cur.pit(), cur.pos(), false); - } } else { // this is the code for a normal backspace, not pasting // any paragraphs @@ -2186,9 +2241,11 @@ string LyXText::currentState(LCursor & cur) std::ostringstream os; bool const show_change = buf.params().tracking_changes - && cur.pos() != cur.lastpos() && par.lookupChange(cur.pos()) != Change::UNCHANGED; + if (buf.params().tracking_changes) + os << "[C] "; + if (show_change) { Change change = par.lookupChangeFull(cur.pos()); Author const & a = buf.params().authors().get(change.author); diff --git a/status.14x b/status.14x index 034f4ba9eb..552b6d0200 100644 --- a/status.14x +++ b/status.14x @@ -47,6 +47,9 @@ What's new * User Interface: +- Enable breaking and merging of paragraphs in change tracking mode + (bug 880). + - Convert line endings for external copy/paste on OS X (bug 1955). - Disable saving when document is unchanged (bug 2313)