]> git.lyx.org Git - lyx.git/blobdiff - src/Text.cpp
Fix #10778 (issue with CJK and language nesting)
[lyx.git] / src / Text.cpp
index bd4e23db0af4737c6c4bdc957c47e51feab775e6..4e593ddf23ca619f1bcf8d3a12cadd74abbca8da 100644 (file)
 #include "support/gettext.h"
 #include "support/lassert.h"
 #include "support/lstrings.h"
+#include "support/lyxalgo.h"
+#include "support/lyxtime.h"
 #include "support/textutils.h"
 
-#include <boost/next_prior.hpp>
-
-#include <limits>
 #include <sstream>
 
 
@@ -137,7 +136,7 @@ void breakParagraphConservative(BufferParams const & bparams,
        ParagraphList & pars, pit_type par_offset, pos_type pos)
 {
        // create a new paragraph
-       Paragraph & tmp = *pars.insert(boost::next(pars.begin(), par_offset + 1),
+       Paragraph & tmp = *pars.insert(lyx::next(pars.begin(), par_offset + 1),
                                       Paragraph());
        Paragraph & par = pars[par_offset];
 
@@ -193,12 +192,12 @@ void mergeParagraph(BufferParams const & bparams,
        // move the change of the end-of-paragraph character
        par.setChange(par.size(), change);
 
-       pars.erase(boost::next(pars.begin(), par_offset + 1));
+       pars.erase(lyx::next(pars.begin(), par_offset + 1));
 }
 
 
 Text::Text(InsetText * owner, bool use_default_layout)
-       : owner_(owner), autoBreakRows_(false), undo_counter_(0)
+       : owner_(owner)
 {
        pars_.push_back(Paragraph());
        Paragraph & par = pars_.back();
@@ -212,7 +211,7 @@ Text::Text(InsetText * owner, bool use_default_layout)
 
 
 Text::Text(InsetText * owner, Text const & text)
-       : owner_(owner), autoBreakRows_(text.autoBreakRows_), undo_counter_(0)
+       : owner_(owner)
 {
        pars_ = text.pars_;
        ParagraphList::iterator const end = pars_.end();
@@ -362,7 +361,7 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        string const & token, Font & font, Change & change, ErrorList & errorList)
 {
        Buffer * buf = const_cast<Buffer *>(&owner_->buffer());
-       BufferParams const & bp = buf->params();
+       BufferParams & bp = buf->params();
 
        if (token[0] != '\\') {
                docstring dstr = lex.getDocString();
@@ -486,18 +485,42 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
        } else if (token == "\\color") {
                lex.next();
                setLyXColor(lex.getString(), font.fontInfo());
-       } else if (token == "\\SpecialChar") {
+       } else if (token == "\\SpecialChar" ||
+                  (token == "\\SpecialCharNoPassThru" &&
+                   !par.layout().pass_thru && !inset().isPassThru())) {
                auto_ptr<Inset> inset;
                inset.reset(new InsetSpecialChar);
                inset->read(lex);
                inset->setBuffer(*buf);
                par.insertInset(par.size(), inset.release(), font, change);
+       } else if (token == "\\SpecialCharNoPassThru") {
+               lex.next();
+               docstring const s = ltrim(lex.getDocString(), "\\");
+               par.insert(par.size(), s, font, change);
        } else if (token == "\\IPAChar") {
                auto_ptr<Inset> inset;
                inset.reset(new InsetIPAChar);
                inset->read(lex);
                inset->setBuffer(*buf);
                par.insertInset(par.size(), inset.release(), font, change);
+       } else if (token == "\\twohyphens" || token == "\\threehyphens") {
+               // Ideally, this should be done by lyx2lyx, but lyx2lyx does not know the
+               // running font and does not know anything about layouts (and CopyStyle).
+               Layout const & layout(par.layout());
+               FontInfo info = font.fontInfo();
+               info.realize(layout.resfont);
+               if (layout.pass_thru || inset().isPassThru() ||
+                   info.family() == TYPEWRITER_FAMILY) {
+                       if (token == "\\twohyphens")
+                               par.insert(par.size(), from_ascii("--"), font, change);
+                       else
+                               par.insert(par.size(), from_ascii("---"), font, change);
+               } else {
+                       if (token == "\\twohyphens")
+                               par.insertChar(par.size(), 0x2013, font, change);
+                       else
+                               par.insertChar(par.size(), 0x2014, font, change);
+               }
        } else if (token == "\\backslash") {
                par.appendChar('\\', font, change);
        } else if (token == "\\LyXTable") {
@@ -512,18 +535,25 @@ void Text::readParToken(Paragraph & par, Lexer & lex,
                int aid;
                time_t ct;
                is >> aid >> ct;
-               BufferParams::AuthorMap const & am = bp.author_map;
+               BufferParams::AuthorMap const & am = bp.author_map_;
                if (am.find(aid) == am.end()) {
-                       errorList.push_back(ErrorItem(_("Change tracking error"),
-                                           bformat(_("Unknown author index for change: %1$d\n"), aid),
-                                           par.id(), 0, par.size()));
-                       change = Change(Change::UNCHANGED);
-               } else {
-                       if (token == "\\change_inserted")
-                               change = Change(Change::INSERTED, am.find(aid)->second, ct);
-                       else
-                               change = Change(Change::DELETED, am.find(aid)->second, ct);
+                       errorList.push_back(ErrorItem(
+                               _("Change tracking author index missing"),
+                               bformat(_("A change tracking author information for index "
+                                         "%1$d is missing. This can happen after a wrong "
+                                         "merge by a version control system. In this case, "
+                                         "either fix the merge, or have this information "
+                                         "missing until the corresponding tracked changes "
+                                         "are merged or this user edits the file again.\n"),
+                                       aid),
+                               par.id(), par.size(), par.size() + 1
+                               ));
+                       bp.addAuthor(Author(aid));
                }
+               if (token == "\\change_inserted")
+                       change = Change(Change::INSERTED, am.find(aid)->second, ct);
+               else
+                       change = Change(Change::DELETED, am.find(aid)->second, ct);
        } else {
                lex.eatLine();
                errorList.push_back(ErrorItem(_("Unknown token"),
@@ -639,7 +669,7 @@ static void breakParagraph(Text & text, pit_type par_offset, pos_type pos,
        ParagraphList & pars = text.paragraphs();
        // create a new paragraph, and insert into the list
        ParagraphList::iterator tmp =
-               pars.insert(boost::next(pars.begin(), par_offset + 1),
+               pars.insert(lyx::next(pars.begin(), par_offset + 1),
                            Paragraph());
 
        Paragraph & par = pars[par_offset];
@@ -734,12 +764,17 @@ void Text::breakParagraph(Cursor & cur, bool inverse_logic)
        Layout const & layout = cpar.layout();
 
        if (cur.lastpos() == 0 && !cpar.allowEmpty()) {
-               if (changeDepthAllowed(cur, DEC_DEPTH))
+               if (changeDepthAllowed(cur, DEC_DEPTH)) {
                        changeDepth(cur, DEC_DEPTH);
-               else {
+                       pit_type const prev = depthHook(cpit, cpar.getDepth());
+                       docstring const & lay = pars_[prev].layout().name();
+                       if (lay != layout.name())
+                               setLayout(cur, lay);
+               } else {
                        docstring const & lay = cur.paragraph().usePlainLayout()
                            ? tclass.plainLayoutName() : tclass.defaultLayoutName();
-                       setLayout(cur, lay);
+                       if (lay != layout.name())
+                               setLayout(cur, lay);
                }
                return;
        }
@@ -815,7 +850,7 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str,
            cit != str.end(); ++cit) {
                Paragraph & par = pars_[pit];
                if (*cit == '\n') {
-                       if (autoBreakRows_ && (!par.empty() || par.allowEmpty())) {
+                       if (inset().allowMultiPar() && (!par.empty() || par.allowEmpty())) {
                                lyx::breakParagraph(*this, pit, pos,
                                        par.layout().isEnvironment());
                                ++pit;
@@ -824,7 +859,7 @@ void Text::insertStringAsLines(Cursor & cur, docstring const & str,
                        } else {
                                continue;
                        }
-                       // do not insert consecutive spaces if !free_spacing
+               // do not insert consecutive spaces if !free_spacing
                } else if ((*cit == ' ' || *cit == '\t') &&
                           space_inserted && !par.isFreeSpacing()) {
                        continue;
@@ -1019,14 +1054,31 @@ void Text::insertChar(Cursor & cur, char_type c)
                }
        }
 
-       par.insertChar(cur.pos(), c, cur.current_font,
+       pos_type pos = cur.pos();
+       if (!cur.paragraph().isPassThru() && owner_->lyxCode() != IPA_CODE &&
+           cur.real_current_font.fontInfo().family() != TYPEWRITER_FAMILY &&
+           c == '-' && pos > 0) {
+               if (par.getChar(pos - 1) == '-') {
+                       // convert "--" to endash
+                       par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
+                       c = 0x2013;
+                       pos--;
+               } else if (par.getChar(pos - 1) == 0x2013) {
+                       // convert "---" to emdash
+                       par.eraseChar(pos - 1, cur.buffer()->params().track_changes);
+                       c = 0x2014;
+                       pos--;
+               }
+       }
+
+       par.insertChar(pos, c, cur.current_font,
                cur.buffer()->params().track_changes);
        cur.checkBufferStructure();
 
 //             cur.screenUpdateFlags(Update::Force);
        bool boundary = cur.boundary()
-               || tm.isRTLBoundary(cur.pit(), cur.pos() + 1);
-       setCursor(cur, cur.pit(), cur.pos() + 1, false, boundary);
+               || tm.isRTLBoundary(cur.pit(), pos + 1);
+       setCursor(cur, cur.pit(), pos + 1, false, boundary);
        charInserted(cur);
 }
 
@@ -1035,15 +1087,6 @@ void Text::charInserted(Cursor & cur)
 {
        Paragraph & par = cur.paragraph();
 
-       // Here we call finishUndo for every 20 characters inserted.
-       // This is from my experience how emacs does it. (Lgb)
-       if (undo_counter_ < 20) {
-               ++undo_counter_;
-       } else {
-               cur.finishUndo();
-               undo_counter_ = 0;
-       }
-
        // register word if a non-letter was entered
        if (cur.pos() > 1
            && !par.isWordSeparator(cur.pos() - 2)
@@ -1068,7 +1111,7 @@ bool Text::cursorForwardOneWord(Cursor & cur)
        Paragraph const & par = cur.paragraph();
 
        // Paragraph boundary is a word boundary
-       if (pos == lastpos) {
+       if (pos == lastpos || (pos + 1 == lastpos && par.isEnvSeparator(pos))) {
                if (pit != cur.lastpit())
                        return setCursor(cur, pit + 1, 0);
                else
@@ -1078,7 +1121,7 @@ bool Text::cursorForwardOneWord(Cursor & cur)
        if (lyxrc.mac_like_cursor_movement) {
                // Skip through trailing punctuation and spaces.
                while (pos != lastpos && (par.isChar(pos) || par.isSpace(pos)))
-                        ++pos;
+                       ++pos;
 
                // Skip over either a non-char inset or a full word
                if (pos != lastpos && par.isWordSeparator(pos))
@@ -1101,6 +1144,10 @@ bool Text::cursorForwardOneWord(Cursor & cur)
                             ++pos;
        }
 
+       // Don't skip a separator inset at the end of a paragraph
+       if (pos == lastpos && pos && par.isEnvSeparator(pos - 1))
+               --pos;
+
        return setCursor(cur, pit, pos);
 }
 
@@ -1114,8 +1161,14 @@ bool Text::cursorBackwardOneWord(Cursor & cur)
        Paragraph & par = cur.paragraph();
 
        // Paragraph boundary is a word boundary
-       if (pos == 0 && pit != 0)
-               return setCursor(cur, pit - 1, getPar(pit - 1).size());
+       if (pos == 0 && pit != 0) {
+               Paragraph & prevpar = getPar(pit - 1);
+               pos = prevpar.size();
+               // Don't stop after an environment separator
+               if (pos && prevpar.isEnvSeparator(pos - 1))
+                       --pos;
+               return setCursor(cur, pit - 1, pos);
+       }
 
        if (lyxrc.mac_like_cursor_movement) {
                // Skip through punctuation and spaces.
@@ -1151,7 +1204,6 @@ bool Text::cursorVisLeftOneWord(Cursor & cur)
        LBUFERR(this == cur.text());
 
        pos_type left_pos, right_pos;
-       bool left_is_letter, right_is_letter;
 
        Cursor temp_cur = cur;
 
@@ -1160,9 +1212,9 @@ bool Text::cursorVisLeftOneWord(Cursor & cur)
 
                // collect some information about current cursor position
                temp_cur.getSurroundingPos(left_pos, right_pos);
-               left_is_letter =
+               bool left_is_letter =
                        (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
-               right_is_letter =
+               bool right_is_letter =
                        (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
 
                // if we're not at a letter/non-letter boundary, continue moving
@@ -1188,7 +1240,6 @@ bool Text::cursorVisRightOneWord(Cursor & cur)
        LBUFERR(this == cur.text());
 
        pos_type left_pos, right_pos;
-       bool left_is_letter, right_is_letter;
 
        Cursor temp_cur = cur;
 
@@ -1197,9 +1248,9 @@ bool Text::cursorVisRightOneWord(Cursor & cur)
 
                // collect some information about current cursor position
                temp_cur.getSurroundingPos(left_pos, right_pos);
-               left_is_letter =
+               bool left_is_letter =
                        (left_pos > -1 ? !temp_cur.paragraph().isWordSeparator(left_pos) : false);
-               right_is_letter =
+               bool right_is_letter =
                        (right_pos > -1 ? !temp_cur.paragraph().isWordSeparator(right_pos) : false);
 
                // if we're not at a letter/non-letter boundary, continue moving
@@ -1226,7 +1277,7 @@ void Text::selectWord(Cursor & cur, word_location loc)
 {
        LBUFERR(this == cur.text());
        CursorSlice from = cur.top();
-       CursorSlice to = cur.top();
+       CursorSlice to;
        getWord(from, to, loc);
        if (cur.top() != from)
                setCursor(cur, from.pit(), from.pos());
@@ -1276,8 +1327,7 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
        LBUFERR(this == cur.text());
 
        if (!cur.selection()) {
-               bool const changed = cur.paragraph().isChanged(cur.pos());
-               if (!(changed && findNextChange(&cur.bv())))
+               if (!selectChange(cur))
                        return;
        }
 
@@ -1293,7 +1343,6 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
        bool endsBeforeEndOfPar = (endPos < pars_[endPit].size());
 
        // first, accept/reject changes within each individual paragraph (do not consider end-of-par)
-
        for (pit_type pit = begPit; pit <= endPit; ++pit) {
                pos_type parSize = pars_[pit].size();
 
@@ -1369,11 +1418,8 @@ void Text::acceptOrRejectChanges(Cursor & cur, ChangeOp op)
        }
 
        // finally, invoke the DEPM
-
        deleteEmptyParagraphMechanism(begPit, endPit, cur.buffer()->params().track_changes);
 
-       //
-
        cur.finishUndo();
        cur.clearSelection();
        setCursorIntern(cur, begPit, begPos);
@@ -1459,20 +1505,19 @@ void Text::deleteWordBackward(Cursor & cur)
 
 
 // Kill to end of line.
-void Text::changeCase(Cursor & cur, TextCase action)
+void Text::changeCase(Cursor & cur, TextCase action, bool partial)
 {
        LBUFERR(this == cur.text());
        CursorSlice from;
        CursorSlice to;
 
-       bool gotsel = false;
-       if (cur.selection()) {
+       bool const gotsel = cur.selection();
+       if (gotsel) {
                from = cur.selBegin();
                to = cur.selEnd();
-               gotsel = true;
        } else {
                from = cur.top();
-               getWord(from, to, PARTIAL_WORD);
+               getWord(from, to, partial ? PARTIAL_WORD : WHOLE_WORD);
                cursorForwardOneWord(cur);
        }
 
@@ -1526,7 +1571,7 @@ bool Text::handleBibitems(Cursor & cur)
        // if a bibitem is deleted, merge with previous paragraph
        // if this is a bibliography item as well
        if (cur.pit() > 0 && par.layout() == prevpar.layout()) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit());
+               cur.recordUndo(prevcur.pit());
                mergeParagraph(bufparams, cur.text()->paragraphs(),
                                                        prevcur.pit());
                cur.forceBufferUpdate();
@@ -1566,6 +1611,7 @@ bool Text::erase(Cursor & cur)
                        return dissolveInset(cur);
 
                if (!par.isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
+                       cur.recordUndo(DELETE_UNDO);
                        par.setChange(cur.pos(), Change(Change::DELETED));
                        cur.forwardPos();
                        needsUpdate = true;
@@ -1608,15 +1654,15 @@ bool Text::backspacePos0(Cursor & cur)
        // is it an empty paragraph?
        if (cur.lastpos() == 0
            || (cur.lastpos() == 1 && par.isSeparator(0))) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit());
-               plist.erase(boost::next(plist.begin(), cur.pit()));
+               cur.recordUndo(prevcur.pit());
+               plist.erase(lyx::next(plist.begin(), cur.pit()));
                needsUpdate = true;
        }
        // is previous par empty?
        else if (prevcur.lastpos() == 0
                 || (prevcur.lastpos() == 1 && prevpar.isSeparator(0))) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit(), cur.pit());
-               plist.erase(boost::next(plist.begin(), prevcur.pit()));
+               cur.recordUndo(prevcur.pit());
+               plist.erase(lyx::next(plist.begin(), prevcur.pit()));
                needsUpdate = true;
        }
        // Pasting is not allowed, if the paragraphs have different
@@ -1627,7 +1673,7 @@ bool Text::backspacePos0(Cursor & cur)
        else if (par.layout() == prevpar.layout()
                 || tclass.isDefaultLayout(par.layout())
                 || tclass.isPlainLayout(par.layout())) {
-               cur.recordUndo(ATOMIC_UNDO, prevcur.pit());
+               cur.recordUndo(prevcur.pit());
                mergeParagraph(bufparams, plist, prevcur.pit());
                needsUpdate = true;
        }
@@ -1653,7 +1699,7 @@ bool Text::backspace(Cursor & cur)
                --prev_cur.pit();
 
                if (!prev_cur.paragraph().isMergedOnEndOfParDeletion(cur.buffer()->params().track_changes)) {
-                       cur.recordUndo(ATOMIC_UNDO, prev_cur.pit(), prev_cur.pit());
+                       cur.recordUndo(prev_cur.pit(), prev_cur.pit());
                        prev_cur.paragraph().setChange(prev_cur.lastpos(), Change(Change::DELETED));
                        setCursorIntern(cur, prev_cur.pit(), prev_cur.lastpos());
                        return true;
@@ -1856,13 +1902,11 @@ docstring Text::currentState(Cursor const & cur) const
        Change change = par.lookupChange(cur.pos());
 
        if (change.changed()) {
-               Author const & a = buf.params().authors().get(change.author);
-               os << _("Change: ") << a.name();
-               if (!a.email().empty())
-                       os << " (" << a.email() << ")";
-               // FIXME ctime is english, we should translate that
-               os << _(" at ") << ctime(&change.changetime);
-               os << " : ";
+               docstring const author =
+                       buf.params().authors().get(change.author).nameAndEmail();
+               docstring const date = formatted_datetime(change.changetime);
+               os << bformat(_("Changed by %1$s[[author]] on %2$s[[date]]. "),
+                             author, date);
        }
 
        // I think we should only show changes from the default
@@ -2016,16 +2060,25 @@ docstring Text::asString(pit_type beg, pit_type end, int options) const
 }
 
 
-void Text::forOutliner(docstring & os, size_t maxlen, bool shorten) const
+void Text::shortenForOutliner(docstring & str, size_t const maxlen)
 {
-       if (maxlen == 0)
-               maxlen = std::numeric_limits<std::size_t>::max();
-       else
-               LASSERT(maxlen >= 8, maxlen = TOC_ENTRY_LENGTH);
-       for (size_t i = 0; i != pars_.size() && os.length() < maxlen; ++i)
-               pars_[i].forOutliner(os, maxlen);
-       if (shorten && os.length() >= maxlen)
-               os = os.substr(0, maxlen - 3) + from_ascii("...");
+       support::truncateWithEllipsis(str, maxlen);
+       docstring::iterator it = str.begin();
+       docstring::iterator end = str.end();
+       for (; it != end; ++it)
+               if ((*it) == L'\n' || (*it) == L'\t')
+                       (*it) = L' ';   
+}
+
+
+void Text::forOutliner(docstring & os, size_t const maxlen,
+                                          bool const shorten) const
+{
+       size_t tmplen = shorten ? maxlen + 1 : maxlen;
+       for (size_t i = 0; i != pars_.size() && os.length() < tmplen; ++i)
+               pars_[i].forOutliner(os, tmplen, false);
+       if (shorten)
+               shortenForOutliner(os, maxlen);
 }