X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FParagraph.cpp;h=3ed382a23ea96912bb6cf6c04f604a39efc7be70;hb=86994bf75320524ec656fd70950633c83d445a6c;hp=859a01835da0eb16b80570dbdc1e0df7bbf7591f;hpb=ab7a5bc382d4fb47809c5e4a1f254384b69d4d09;p=lyx.git diff --git a/src/Paragraph.cpp b/src/Paragraph.cpp index 859a01835d..3ed382a23e 100644 --- a/src/Paragraph.cpp +++ b/src/Paragraph.cpp @@ -43,7 +43,6 @@ #include "TextClass.h" #include "TexRow.h" #include "Text.h" -#include "VSpace.h" #include "WordLangTuple.h" #include "WordList.h" @@ -70,9 +69,11 @@ using namespace lyx::support; namespace lyx { namespace { + /// Inset identifier (above 0x10ffff, for ucs-4) char_type const META_INSET = 0x200001; -}; + +} ///////////////////////////////////////////////////////////////////// @@ -112,7 +113,7 @@ public: if (range_.first > pos) { range_.first += offset; range_.last += offset; - } else if (range_.last > pos) { + } else if (range_.last >= pos) { range_.last += offset; } } @@ -130,14 +131,38 @@ private: class SpellCheckerState { public: - SpellCheckerState() { + SpellCheckerState() + { needs_refresh_ = true; current_change_number_ = 0; } - void setRange(FontSpan const fp, SpellChecker::Result state) + void setRange(FontSpan const & fp, SpellChecker::Result state) { - eraseCoveredRanges(fp); + Ranges result; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + if (!it->covered(fp)) + result.push_back(SpellResultRange(it->range(), it->result())); + else if (state == SpellChecker::WORD_OK) { + // trim or split the current misspelled range + // store misspelled ranges only + FontSpan range = it->range(); + if (fp.first > range.first) { + // misspelled area in front of WORD_OK + range.last = fp.first - 1; + result.push_back(SpellResultRange(range, it->result())); + range = it->range(); + } + if (fp.last < range.last) { + // misspelled area after WORD_OK range + range.first = fp.last + 1; + result.push_back(SpellResultRange(range, it->result())); + } + } + } + ranges_ = result; if (state != SpellChecker::WORD_OK) ranges_.push_back(SpellResultRange(fp, state)); } @@ -173,39 +198,59 @@ public: return result; } - bool needsRefresh() const { + FontSpan const & getRange(pos_type pos) const + { + /// empty span to indicate mismatch + static FontSpan empty_; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + if(it->inside(pos)) { + return it->range(); + } + } + return empty_; + } + + bool needsRefresh() const + { return needs_refresh_; } - SpellChecker::ChangeNumber currentChangeNumber() const { + SpellChecker::ChangeNumber currentChangeNumber() const + { return current_change_number_; } - void refreshRange(pos_type & first, pos_type & last) const { + void refreshRange(pos_type & first, pos_type & last) const + { first = refresh_.first; last = refresh_.last; } - void needsRefresh(pos_type pos) { + void needsRefresh(pos_type pos) + { if (needs_refresh_ && pos != -1) { if (pos < refresh_.first) refresh_.first = pos; if (pos > refresh_.last) refresh_.last = pos; } else if (pos != -1) { - refresh_.first = pos; - refresh_.last = pos; + // init request check for neighbour positions too + refresh_.first = pos > 0 ? pos - 1 : 0; + // no need for special end of paragraph check + refresh_.last = pos + 1; } needs_refresh_ = pos != -1; } - void needsCompleteRefresh(SpellChecker::ChangeNumber change_number) { + void needsCompleteRefresh(SpellChecker::ChangeNumber change_number) + { needs_refresh_ = true; refresh_.first = 0; refresh_.last = -1; current_change_number_ = change_number; } - private: typedef vector Ranges; typedef Ranges::const_iterator RangesIterator; @@ -217,18 +262,6 @@ private: SpellChecker::ChangeNumber current_change_number_; - void eraseCoveredRanges(FontSpan const fp) - { - Ranges result; - RangesIterator et = ranges_.end(); - RangesIterator it = ranges_.begin(); - for (; it != et; ++it) { - if (!it->covered(fp)) - result.push_back(SpellResultRange(it->range(), it->result())); - } - ranges_ = result; - } - void correctRangesAfterPos(pos_type pos, int offset) { RangesIterator et = ranges_.end(); @@ -261,14 +294,14 @@ public: /// Output the surrogate pair formed by \p c and \p next to \p os. /// \return the number of characters written. - int latexSurrogatePair(odocstream & os, char_type c, char_type next, + int latexSurrogatePair(otexstream & os, char_type c, char_type next, OutputParams const &); /// Output a space in appropriate formatting (or a surrogate pair /// if the next character is a combining character). /// \return whether a surrogate pair was output. bool simpleTeXBlanks(OutputParams const &, - odocstream &, TexRow & texrow, + otexstream &, pos_type i, unsigned int & column, Font const & font, @@ -277,21 +310,21 @@ public: /// Output consecutive unicode chars, belonging to the same script as /// specified by the latex macro \p ltx, to \p os starting from \p i. /// \return the number of characters written. - int writeScriptChars(odocstream & os, docstring const & ltx, + int writeScriptChars(otexstream & os, docstring const & ltx, Change const &, Encoding const &, pos_type & i); /// This could go to ParagraphParameters if we want to. - int startTeXParParams(BufferParams const &, odocstream &, TexRow &, + int startTeXParParams(BufferParams const &, otexstream &, OutputParams const &) const; /// This could go to ParagraphParameters if we want to. - int endTeXParParams(BufferParams const &, odocstream &, TexRow &, - OutputParams const &) const; + bool endTeXParParams(BufferParams const &, otexstream &, + OutputParams const &) const; /// void latexInset(BufferParams const &, - odocstream &, - TexRow & texrow, OutputParams &, + otexstream &, + OutputParams &, Font & running_font, Font & basefont, Font const & outerfont, @@ -303,30 +336,39 @@ public: /// void latexSpecialChar( - odocstream & os, + otexstream & os, + BufferParams const & bparams, OutputParams const & runparams, Font const & running_font, Change const & running_change, Layout const & style, pos_type & i, + pos_type end_pos, unsigned int & column); /// bool latexSpecialT1( char_type const c, - odocstream & os, + otexstream & os, + pos_type i, + unsigned int & column); + /// + bool latexSpecialT3( + char_type const c, + otexstream & os, pos_type i, unsigned int & column); /// bool latexSpecialTypewriter( char_type const c, - odocstream & os, + otexstream & os, pos_type i, unsigned int & column); /// bool latexSpecialPhrase( - odocstream & os, + otexstream & os, pos_type & i, + pos_type end_pos, unsigned int & column, OutputParams const & runparams); @@ -349,33 +391,38 @@ public: Language * getSpellLanguage(pos_type const from) const; Language * locateSpellRange(pos_type & from, pos_type & to, - SkipPositions & skips) const; + SkipPositions & skips) const; - bool hasSpellerChange() const { + bool hasSpellerChange() const + { SpellChecker::ChangeNumber speller_change_number = 0; if (theSpellChecker()) speller_change_number = theSpellChecker()->changeNumber(); return speller_change_number > speller_state_.currentChangeNumber(); } + bool ignoreWord(docstring const & word) const ; + void setMisspelled(pos_type from, pos_type to, SpellChecker::Result state) { pos_type textsize = owner_->size(); // check for sane arguments - if (to < from || from >= textsize) + if (to <= from || from >= textsize) return; - FontSpan fp = FontSpan(from, to); - // don't mark end of paragraph - if (fp.last >= textsize) - fp.last = textsize - 1; + FontSpan fp = FontSpan(from, to - 1); speller_state_.setRange(fp, state); } - void requestSpellCheck(pos_type pos) { - speller_state_.needsRefresh(pos); + void requestSpellCheck(pos_type pos) + { + if (pos == -1) + speller_state_.needsCompleteRefresh(speller_state_.currentChangeNumber()); + else + speller_state_.needsRefresh(pos); } - void readySpellCheck() { + void readySpellCheck() + { speller_state_.needsRefresh(-1); } @@ -538,9 +585,9 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner, void Paragraph::addChangesToToc(DocIterator const & cdit, - Buffer const & buf) const + Buffer const & buf, bool output_active) const { - d->changes_.addToToc(cdit, buf); + d->changes_.addToToc(cdit, buf, output_active); } @@ -811,7 +858,7 @@ int Paragraph::eraseChars(pos_type start, pos_type end, bool trackChanges) } -int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, +int Paragraph::Private::latexSurrogatePair(otexstream & os, char_type c, char_type next, OutputParams const & runparams) { // Writing next here may circumvent a possible font change between @@ -822,8 +869,14 @@ int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, // FIXME: change tracking // Is this correct WRT change tracking? Encoding const & encoding = *(runparams.encoding); - docstring const latex1 = encoding.latexChar(next); - docstring const latex2 = encoding.latexChar(c); + docstring latex1 = encoding.latexChar(next).first; + if (runparams.inIPA) { + string const tipashortcut = Encodings::TIPAShortcut(next); + if (!tipashortcut.empty()) { + latex1 = from_ascii(tipashortcut); + } + } + docstring const latex2 = encoding.latexChar(c).first; if (docstring(1, next) == latex1) { // the encoding supports the combination os << latex2 << latex1; @@ -840,7 +893,7 @@ int Paragraph::Private::latexSurrogatePair(odocstream & os, char_type c, bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, - odocstream & os, TexRow & texrow, + otexstream & os, pos_type i, unsigned int & column, Font const & font, @@ -873,8 +926,7 @@ bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, || text_[i - 1] == ':' || text_[i - 1] == '!'))) { os << '\n'; - texrow.newline(); - texrow.start(owner_->id(), i + 1); + os.texrow().start(owner_->id(), i + 1); column = 0; } else if (style.free_spacing) { os << '~'; @@ -885,7 +937,7 @@ bool Paragraph::Private::simpleTeXBlanks(OutputParams const & runparams, } -int Paragraph::Private::writeScriptChars(odocstream & os, +int Paragraph::Private::writeScriptChars(otexstream & os, docstring const & ltx, Change const & runningChange, Encoding const & encoding, @@ -940,7 +992,7 @@ int Paragraph::Private::writeScriptChars(odocstream & os, // Stop here if there is a font attribute or encoding change. if (found && cit != end && prev_font != cit->font()) break; - docstring const latex = encoding.latexChar(next); + docstring const latex = encoding.latexChar(next).first; docstring::size_type const b1 = latex.find_first_of(from_ascii("{")); docstring::size_type const b2 = @@ -979,8 +1031,7 @@ bool Paragraph::Private::isTextAt(string const & str, pos_type pos) const void Paragraph::Private::latexInset(BufferParams const & bparams, - odocstream & os, - TexRow & texrow, + otexstream & os, OutputParams & runparams, Font & running_font, Font & basefont, @@ -995,7 +1046,9 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, LASSERT(inset, /**/); if (style.pass_thru) { - inset->plaintext(os, runparams); + odocstringstream ods; + inset->plaintext(ods, runparams); + os << ods.str(); return; } @@ -1023,8 +1076,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, os << "\\protect "; } - texrow.newline(); - texrow.start(owner_->id(), i + 1); + os.texrow().start(owner_->id(), i + 1); column = 0; } @@ -1040,9 +1092,10 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, } bool close = false; - odocstream::pos_type const len = os.tellp(); + odocstream::pos_type const len = os.os().tellp(); if (inset->forceLTR() + && !runparams.use_polyglossia && running_font.isRightToLeft() // ERT is an exception, it should be output with no // decorations at all @@ -1066,7 +1119,7 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, // ArabTeX, though, cannot handle this special behavior, it seems. bool arabtex = basefont.language()->lang() == "arabic_arabtex" || running_font.language()->lang() == "arabic_arabtex"; - if (open_font && inset->noFontChange()) { + if (open_font && !inset->inheritFont()) { bool closeLanguage = arabtex || basefont.isRightToLeft() == running_font.isRightToLeft(); unsigned int count = running_font.latexWriteEndChanges(os, @@ -1089,10 +1142,12 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, } } - int tmp; + int prev_rows = os.texrow().rows(); try { - tmp = inset->latex(os, runparams); + runparams.lastid = id_; + runparams.lastpos = i; + inset->latex(os, runparams); } catch (EncodingException & e) { // add location information and throw again. e.par_id = id_; @@ -1107,12 +1162,11 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, os << '}'; } - if (tmp) { - texrow.newlines(tmp); - texrow.start(owner_->id(), i + 1); + if (os.texrow().rows() > prev_rows) { + os.texrow().start(owner_->id(), i + 1); column = 0; } else { - column += (unsigned int)(os.tellp() - len); + column += (unsigned int)(os.os().tellp() - len); } if (owner_->isDeleted(i)) @@ -1120,36 +1174,46 @@ void Paragraph::Private::latexInset(BufferParams const & bparams, } -void Paragraph::Private::latexSpecialChar( - odocstream & os, - OutputParams const & runparams, - Font const & running_font, - Change const & running_change, - Layout const & style, - pos_type & i, - unsigned int & column) +void Paragraph::Private::latexSpecialChar(otexstream & os, + BufferParams const & bparams, + OutputParams const & runparams, + Font const & running_font, + Change const & running_change, + Layout const & style, + pos_type & i, + pos_type end_pos, + unsigned int & column) { - char_type const c = text_[i]; + // With polyglossia, brackets and stuff need not be reversed + // in RTL scripts (see bug #8251) + char_type const c = (runparams.use_polyglossia) ? + owner_->getUChar(bparams, i) : text_[i]; if (style.pass_thru || runparams.pass_thru) { - if (c != '\0') - // FIXME UNICODE: This can fail if c cannot - // be encoded in the current encoding. + if (c != '\0') { + Encoding const * const enc = runparams.encoding; + if (enc && !enc->encodable(c)) + throw EncodingException(c); os.put(c); + } return; } + // TIPA uses its own T3 encoding + if (runparams.inIPA && latexSpecialT3(c, os, i, column)) + return; // If T1 font encoding is used, use the special // characters it provides. // NOTE: some languages reset the font encoding // internally - if (!running_font.language()->internalFontEncoding() + if (!runparams.inIPA && !running_font.language()->internalFontEncoding() && lyxrc.fontenc == "T1" && latexSpecialT1(c, os, i, column)) return; // \tt font needs special treatment - if (running_font.fontInfo().family() == TYPEWRITER_FAMILY - && latexSpecialTypewriter(c, os, i, column)) + if (!runparams.inIPA + && running_font.fontInfo().family() == TYPEWRITER_FAMILY + && latexSpecialTypewriter(c, os, i, column)) return; // Otherwise, we use what LaTeX provides us. @@ -1216,15 +1280,16 @@ void Paragraph::Private::latexSpecialChar( default: // LyX, LaTeX etc. - if (latexSpecialPhrase(os, i, column, runparams)) + if (latexSpecialPhrase(os, i, end_pos, column, runparams)) return; if (c == '\0') return; Encoding const & encoding = *(runparams.encoding); + char_type next = '\0'; if (i + 1 < int(text_.size())) { - char_type next = text_[i + 1]; + next = text_[i + 1]; if (Encodings::isCombiningChar(next)) { column += latexSurrogatePair(os, c, next, runparams) - 1; ++i; @@ -1232,27 +1297,57 @@ void Paragraph::Private::latexSpecialChar( } } string script; - docstring const latex = encoding.latexChar(c); + pair latex = encoding.latexChar(c); + docstring nextlatex; + bool nexttipas = false; + string nexttipashortcut; + if (next != '\0' && next != META_INSET) { + nextlatex = encoding.latexChar(next).first; + if (runparams.inIPA) { + nexttipashortcut = Encodings::TIPAShortcut(next); + nexttipas = !nexttipashortcut.empty(); + } + } + bool tipas = false; + if (runparams.inIPA) { + string const tipashortcut = Encodings::TIPAShortcut(c); + if (!tipashortcut.empty()) { + latex.first = from_ascii(tipashortcut); + latex.second = false; + tipas = true; + } + } if (Encodings::isKnownScriptChar(c, script) - && prefixIs(latex, from_ascii("\\" + script))) - column += writeScriptChars(os, latex, + && prefixIs(latex.first, from_ascii("\\" + script))) + column += writeScriptChars(os, latex.first, running_change, encoding, i) - 1; - else if (latex.length() > 1 && latex[latex.length() - 1] != '}') { + else if (latex.second + && ((!prefixIs(nextlatex, '\\') + && !prefixIs(nextlatex, '{') + && !prefixIs(nextlatex, '}')) + || (nexttipas + && !prefixIs(from_ascii(nexttipashortcut), '\\'))) + && !tipas) { // Prevent eating of a following // space or command corruption by // following characters - column += latex.length() + 1; - os << latex << "{}"; + if (next == ' ' || next == '\0') { + column += latex.first.length() + 1; + os << latex.first << "{}"; + } else { + column += latex.first.length(); + os << latex.first << " "; + } } else { - column += latex.length() - 1; - os << latex; + column += latex.first.length() - 1; + os << latex.first; } break; } } -bool Paragraph::Private::latexSpecialT1(char_type const c, odocstream & os, +bool Paragraph::Private::latexSpecialT1(char_type const c, otexstream & os, pos_type i, unsigned int & column) { switch (c) { @@ -1280,7 +1375,27 @@ bool Paragraph::Private::latexSpecialT1(char_type const c, odocstream & os, } -bool Paragraph::Private::latexSpecialTypewriter(char_type const c, odocstream & os, +bool Paragraph::Private::latexSpecialT3(char_type const c, otexstream & os, + pos_type /*i*/, unsigned int & column) +{ + switch (c) { + case '*': + case '[': + case ']': + case '\"': + os.put(c); + return true; + case '|': + os << "\\textvertline{}"; + column += 14; + return true; + default: + return false; + } +} + + +bool Paragraph::Private::latexSpecialTypewriter(char_type const c, otexstream & os, pos_type i, unsigned int & column) { switch (c) { @@ -1302,7 +1417,10 @@ bool Paragraph::Private::latexSpecialTypewriter(char_type const c, odocstream & } -bool Paragraph::Private::latexSpecialPhrase(odocstream & os, pos_type & i, +/// \param end_pos +/// If [start_pos, end_pos) does not include entirely the special phrase, then +/// do not apply the macro transformation. +bool Paragraph::Private::latexSpecialPhrase(otexstream & os, pos_type & i, pos_type end_pos, unsigned int & column, OutputParams const & runparams) { // FIXME: if we have "LaTeX" with a font @@ -1312,7 +1430,8 @@ bool Paragraph::Private::latexSpecialPhrase(odocstream & os, pos_type & i, // "words" for some definition of word for (size_t pnr = 0; pnr < phrases_nr; ++pnr) { - if (!isTextAt(special_phrases[pnr].phrase, i)) + if (!isTextAt(special_phrases[pnr].phrase, i) + || (end_pos != -1 && i + int(special_phrases[pnr].phrase.size()) > end_pos)) continue; if (runparams.moving_arg) os << "\\protect"; @@ -1328,26 +1447,49 @@ bool Paragraph::Private::latexSpecialPhrase(odocstream & os, pos_type & i, void Paragraph::Private::validate(LaTeXFeatures & features) const { if (layout_->inpreamble && inset_owner_) { + bool const is_command = layout_->latextype == LATEX_COMMAND; Buffer const & buf = inset_owner_->buffer(); BufferParams const & bp = buf.params(); Font f; - TexRow tr; + TexRow texrow; + // Using a string stream here circumvents the encoding + // switching machinery of odocstream. Therefore the + // output is wrong if this paragraph contains content + // that needs to switch encoding. odocstringstream ods; - // we have to provide all the optional arguments here, even though - // the last one is the only one we care about. - owner_->latex(bp, f, ods, tr, features.runparams(), 0, -1, true); - docstring const d = ods.str(); - if (!d.empty()) { - // this will have "{" at the beginning, but not at the end - string const content = to_utf8(d); - string const cmd = layout_->latexname(); - features.addPreambleSnippet("\\" + cmd + content + "}"); + otexstream os(ods, texrow); + if (is_command) { + os << '\\' << from_ascii(layout_->latexname()); + // we have to provide all the optional arguments here, even though + // the last one is the only one we care about. + // Separate handling of optional argument inset. + if (!layout_->latexargs().empty()) { + OutputParams rp = features.runparams(); + rp.local_font = &owner_->getFirstFontSettings(bp); + latexArgInsets(*owner_, os, rp, layout_->latexargs()); + } + os << from_ascii(layout_->latexparam()); + } + docstring::size_type const length = ods.str().length(); + // this will output "{" at the beginning, but not at the end + owner_->latex(bp, f, os, features.runparams(), 0, -1, true); + if (ods.str().length() > length) { + if (is_command) { + ods << '}'; + if (!layout_->postcommandargs().empty()) { + OutputParams rp = features.runparams(); + rp.local_font = &owner_->getFirstFontSettings(bp); + latexArgInsets(*owner_, os, rp, layout_->postcommandargs(), "post:"); + } + } + string const snippet = to_utf8(ods.str()); + features.addPreambleSnippet(snippet); } } if (features.runparams().flavor == OutputParams::HTML && layout_->htmltitle()) { - features.setHTMLTitle(owner_->asString(AS_STR_INSETS)); + features.setHTMLTitle(owner_->asString(AS_STR_INSETS | AS_STR_SKIPDELETE)); } // check the params. @@ -1587,6 +1729,7 @@ void Paragraph::appendChar(char_type c, Font const & font, // when appending characters, no need to update tables d->text_.push_back(c); setFont(d->text_.size() - 1, font); + d->requestSpellCheck(d->text_.size() - 1); } @@ -1607,6 +1750,7 @@ void Paragraph::appendString(docstring const & s, Font const & font, for (size_t i = oldsize; i != newsize; ++i) { // track change d->changes_.insert(change, i); + d->requestSpellCheck(i); } d->fontlist_.set(oldsize, font); d->fontlist_.set(newsize - 1, font); @@ -1786,16 +1930,23 @@ FontSize Paragraph::highestFontInRange char_type Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const { char_type c = d->text_[pos]; - if (!lyxrc.rtl_support) + if (!lyxrc.rtl_support || !getFontSettings(bparams, pos).isRightToLeft()) return c; + // FIXME: The arabic special casing is due to the difference of arabic + // round brackets input introduced in r18599. Check if this should be + // unified with Hebrew or at least if all bracket types should be + // handled the same (file format change in either case). + string const & lang = getFontSettings(bparams, pos).language()->lang(); + bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" + || lang == "farsi"; char_type uc = c; switch (c) { case '(': - uc = ')'; + uc = arabic ? c : ')'; break; case ')': - uc = '('; + uc = arabic ? c : '('; break; case '[': uc = ']'; @@ -1816,9 +1967,8 @@ char_type Paragraph::getUChar(BufferParams const & bparams, pos_type pos) const uc = '<'; break; } - if (uc != c && getFontSettings(bparams, pos).isRightToLeft()) - return uc; - return c; + + return uc; } @@ -1936,8 +2086,7 @@ docstring Paragraph::expandParagraphLabel(Layout const & layout, bool const in_appendix = process_appendix && d->params_.appendix(); docstring fmt = translateIfPossible(layout.labelstring(in_appendix), lang); - if (fmt.empty() && layout.labeltype == LABEL_COUNTER - && !layout.counter.empty()) + if (fmt.empty() && !layout.counter.empty()) return tclass.counters().theCounter(layout.counter, lang); // handle 'inherited level parts' in 'fmt', @@ -2026,6 +2175,11 @@ bool Paragraph::usePlainLayout() const } +bool Paragraph::isPassThru() const +{ + return inInset().isPassThru() || d->layout_->pass_thru; +} + namespace { // paragraphs inside floats need different alignment tags to avoid @@ -2073,13 +2227,12 @@ string const corrected_env(string const & suffix, string const & env, } -void adjust_row_column(string const & str, TexRow & texrow, int & column) +void adjust_column(string const & str, int & column) { if (!contains(str, "\n")) column += str.size(); else { string tmp; - texrow.newline(); column = rsplit(str, tmp, '\n').size(); } } @@ -2088,12 +2241,11 @@ void adjust_row_column(string const & str, TexRow & texrow, int & column) int Paragraph::Private::startTeXParParams(BufferParams const & bparams, - odocstream & os, TexRow & texrow, - OutputParams const & runparams) const + otexstream & os, OutputParams const & runparams) const { int column = 0; - if (params_.noindent()) { + if (params_.noindent() && !layout_->pass_thru) { os << "\\noindent "; column += 10; } @@ -2138,7 +2290,7 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, else output = corrected_env(begin_tag, "flushright", code, lastpar); os << from_ascii(output); - adjust_row_column(output, texrow, column); + adjust_column(output, column); break; } case LYX_ALIGN_RIGHT: { string output; @@ -2147,13 +2299,13 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, else output = corrected_env(begin_tag, "flushleft", code, lastpar); os << from_ascii(output); - adjust_row_column(output, texrow, column); + adjust_column(output, column); break; } case LYX_ALIGN_CENTER: { string output; output = corrected_env(begin_tag, "center", code, lastpar); os << from_ascii(output); - adjust_row_column(output, texrow, column); + adjust_column(output, column); break; } } @@ -2162,16 +2314,13 @@ int Paragraph::Private::startTeXParParams(BufferParams const & bparams, } -int Paragraph::Private::endTeXParParams(BufferParams const & bparams, - odocstream & os, TexRow & texrow, - OutputParams const & runparams) const +bool Paragraph::Private::endTeXParParams(BufferParams const & bparams, + otexstream & os, OutputParams const & runparams) const { - int column = 0; - LyXAlignment const curAlign = params_.align(); if (curAlign == layout_->align) - return column; + return false; switch (curAlign) { case LYX_ALIGN_NONE: @@ -2183,13 +2332,12 @@ int Paragraph::Private::endTeXParParams(BufferParams const & bparams, case LYX_ALIGN_LEFT: case LYX_ALIGN_RIGHT: case LYX_ALIGN_CENTER: - if (runparams.moving_arg) { + if (runparams.moving_arg) os << "\\protect"; - column = 8; - } break; } + string output; string const end_tag = "\n\\par\\end"; InsetCode code = ownerCode(); bool const lastpar = runparams.isLastPar; @@ -2202,40 +2350,34 @@ int Paragraph::Private::endTeXParParams(BufferParams const & bparams, case LYX_ALIGN_DECIMAL: break; case LYX_ALIGN_LEFT: { - string output; if (owner_->getParLanguage(bparams)->babel() != "hebrew") output = corrected_env(end_tag, "flushleft", code, lastpar); else output = corrected_env(end_tag, "flushright", code, lastpar); os << from_ascii(output); - adjust_row_column(output, texrow, column); break; } case LYX_ALIGN_RIGHT: { - string output; if (owner_->getParLanguage(bparams)->babel() != "hebrew") output = corrected_env(end_tag, "flushright", code, lastpar); else output = corrected_env(end_tag, "flushleft", code, lastpar); os << from_ascii(output); - adjust_row_column(output, texrow, column); break; } case LYX_ALIGN_CENTER: { - string output; output = corrected_env(end_tag, "center", code, lastpar); os << from_ascii(output); - adjust_row_column(output, texrow, column); break; } } - return column; + return !output.empty() || lastpar; } // This one spits out the text of the paragraph void Paragraph::latex(BufferParams const & bparams, Font const & outerfont, - odocstream & os, TexRow & texrow, + otexstream & os, OutputParams const & runparams, int start_pos, int end_pos, bool force) const { @@ -2266,6 +2408,10 @@ void Paragraph::latex(BufferParams const & bparams, if (body_pos > 0) { // the optional argument is kept in curly brackets in // case it contains a ']' + // This is not strictly needed, but if this is changed it + // would be a file format change, and tex2lyx would need + // to be adjusted, since it unconditionally removes the + // braces when it parses \item. os << "[{"; column += 2; basefont = getLabelFont(bparams, outerfont); @@ -2282,7 +2428,7 @@ void Paragraph::latex(BufferParams const & bparams, Encoding const * const prev_encoding = runparams.encoding; - texrow.start(id(), 0); + os.texrow().start(id(), 0); // if the paragraph is empty, the loop will not be entered at all if (empty()) { @@ -2290,9 +2436,12 @@ void Paragraph::latex(BufferParams const & bparams, os << '{'; ++column; } + if (!style.leftdelim().empty()) { + os << style.leftdelim(); + column += style.leftdelim().size(); + } if (allowcust) - column += d->startTeXParParams(bparams, os, texrow, - runparams); + column += d->startTeXParParams(bparams, os, runparams); } for (pos_type i = 0; i < size(); ++i) { @@ -2321,14 +2470,18 @@ void Paragraph::latex(BufferParams const & bparams, ++column; } + if (!style.leftdelim().empty()) { + os << style.leftdelim(); + column += style.leftdelim().size(); + } + if (allowcust) column += d->startTeXParParams(bparams, os, - texrow, runparams); } - Change const & change = runparams.inDeletedInset ? runparams.changeOfDeletedInset - : lookupChange(i); + Change const & change = runparams.inDeletedInset + ? runparams.changeOfDeletedInset : lookupChange(i); if (bparams.outputChanges && runningChange != change) { if (open_font) { @@ -2387,8 +2540,9 @@ void Paragraph::latex(BufferParams const & bparams, if (!runparams.pass_thru && !style.pass_thru && runparams.encoding->package() != Encoding::none && font.language()->encoding()->package() != Encoding::none) { - pair const enc_switch = switchEncoding(os, bparams, - runparams, *(font.language()->encoding())); + pair const enc_switch = + switchEncoding(os.os(), bparams, runparams, + *(font.language()->encoding())); if (enc_switch.first) { column += enc_switch.second; runparams.encoding = font.language()->encoding(); @@ -2431,8 +2585,7 @@ void Paragraph::latex(BufferParams const & bparams, // latexSpecialChar ignores spaces if // style.pass_thru is false. if (i != body_pos - 1) { - if (d->simpleTeXBlanks( - runparams, os, texrow, + if (d->simpleTeXBlanks(runparams, os, i, column, font, style)) { // A surrogate pair was output. We // must not call latexSpecialChar @@ -2454,16 +2607,15 @@ void Paragraph::latex(BufferParams const & bparams, // and then split to handle the two modes separately. if (c == META_INSET) { if (i >= start_pos && (end_pos == -1 || i < end_pos)) { - d->latexInset(bparams, os, - texrow, rp, running_font, + d->latexInset(bparams, os, rp, running_font, basefont, outerfont, open_font, runningChange, style, i, column); } } else { if (i >= start_pos && (end_pos == -1 || i < end_pos)) { try { - d->latexSpecialChar(os, rp, running_font, runningChange, - style, i, column); + d->latexSpecialChar(os, bparams, rp, running_font, runningChange, + style, i, end_pos, column); } catch (EncodingException & e) { if (runparams.dryrun) { os << "<" << _("LyX Warning: ") @@ -2513,7 +2665,12 @@ void Paragraph::latex(BufferParams const & bparams, os << "}]~"; } - if (allowcust && d->endTeXParParams(bparams, os, texrow, runparams) + if (!style.rightdelim().empty()) { + os << style.rightdelim(); + column += style.rightdelim().size(); + } + + if (allowcust && d->endTeXParParams(bparams, os, runparams) && runparams.encoding != prev_encoding) { runparams.encoding = prev_encoding; if (!runparams.isFullUnicode()) @@ -2684,10 +2841,11 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, bool emph_flag = false; bool bold_flag = false; - string closing_tag; Layout const & style = *d->layout_; + xs.startParagraph(allowEmpty()); + if (!runparams.for_toc && runparams.html_make_pars) { // generate a magic label for this paragraph string const attr = "id='" + magicLabel() + "'"; @@ -2737,9 +2895,9 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, retval += inset->xhtml(xs, np); } } else { - char_type c = d->text_[i]; + char_type c = getUChar(buf.params(), i); - if (style.pass_thru) + if (style.pass_thru || runparams.pass_thru) xs << c; else if (c == '-') { docstring str; @@ -2767,6 +2925,7 @@ docstring Paragraph::simpleLyXHTMLOnePar(Buffer const & buf, } xs.closeFontTags(); + xs.endParagraph(); return retval; } @@ -2798,13 +2957,47 @@ bool Paragraph::isLineSeparator(pos_type pos) const bool Paragraph::isWordSeparator(pos_type pos) const { + if (pos == size()) + return true; if (Inset const * inset = getInset(pos)) return !inset->isLetter(); + // if we have a hard hyphen (no en- or emdash) or apostrophe + // we pass this to the spell checker + // FIXME: this method is subject to change, visit + // https://bugzilla.mozilla.org/show_bug.cgi?id=355178 + // to get an impression how complex this is. + if (isHardHyphenOrApostrophe(pos)) + return false; char_type const c = d->text_[pos]; - // We want to pass the ' and escape chars to the spellchecker - static docstring const quote = from_utf8(lyxrc.spellchecker_esc_chars + '\''); - return (!isLetterChar(c) && !isDigit(c) && !contains(quote, c)) - || pos == size(); + // We want to pass the escape chars to the spellchecker + docstring const escape_chars = from_utf8(lyxrc.spellchecker_esc_chars); + return !isLetterChar(c) && !isDigitASCII(c) && !contains(escape_chars, c); +} + + +bool Paragraph::isHardHyphenOrApostrophe(pos_type pos) const +{ + pos_type const psize = size(); + if (pos >= psize) + return false; + char_type const c = d->text_[pos]; + if (c != '-' && c != '\'') + return false; + int nextpos = pos + 1; + int prevpos = pos > 0 ? pos - 1 : 0; + if ((nextpos == psize || isSpace(nextpos)) + && (pos == 0 || isSpace(prevpos))) + return false; + return c == '\'' + || ((nextpos == psize || d->text_[nextpos] != '-') + && (pos == 0 || d->text_[prevpos] != '-')); +} + + +bool Paragraph::isSameSpellRange(pos_type pos1, pos_type pos2) const +{ + return pos1 == pos2 + || d->speller_state_.getRange(pos1) == d->speller_state_.getRange(pos2); } @@ -2813,7 +3006,7 @@ bool Paragraph::isChar(pos_type pos) const if (Inset const * inset = getInset(pos)) return inset->isChar(); char_type const c = d->text_[pos]; - return !isLetterChar(c) && !isDigit(c) && !lyx::isSpace(c); + return !isLetterChar(c) && !isDigitASCII(c) && !lyx::isSpace(c); } @@ -2853,9 +3046,9 @@ void Paragraph::changeLanguage(BufferParams const & bparams, if (font.language() == from) { font.setLanguage(to); setFont(i, font); + d->requestSpellCheck(i); } } - d->requestSpellCheck(size()); } @@ -2911,7 +3104,7 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options) const || (c == '\n' && (options & AS_STR_NEWLINES))) os.put(c); else if (c == META_INSET && (options & AS_STR_INSETS)) { - getInset(i)->tocString(os); + getInset(i)->toString(os); if (getInset(i)->asInsetMath()) os << " "; } @@ -2921,6 +3114,24 @@ docstring Paragraph::asString(pos_type beg, pos_type end, int options) const } +void Paragraph::forToc(docstring & os, size_t maxlen) const +{ + if (!d->params_.labelString().empty()) + os += d->params_.labelString() + ' '; + for (pos_type i = 0; i < size() && os.length() < maxlen; ++i) { + if (isDeleted(i)) + continue; + char_type const c = d->text_[i]; + if (isPrintable(c)) + os += c; + else if (c == '\t' || c == '\n') + os += ' '; + else if (c == META_INSET) + getInset(i)->forToc(os, maxlen); + } +} + + docstring Paragraph::stringify(pos_type beg, pos_type end, int options, OutputParams & runparams) const { odocstringstream os; @@ -3071,70 +3282,61 @@ char_type Paragraph::transformChar(char_type c, pos_type pos) const } -int Paragraph::checkBiblio(Buffer const & buffer) +bool Paragraph::brokenBiblio() const { - // FIXME From JS: - // This is getting more and more a mess. ...We really should clean - // up this bibitem issue for 1.6. + // there is a problem if there is no bibitem at position 0 or + // if there is another bibitem in the paragraph. + return d->layout_->labeltype == LABEL_BIBLIO + && (d->insetlist_.find(BIBITEM_CODE) != 0 + || d->insetlist_.find(BIBITEM_CODE, 1) > 0); +} + + +int Paragraph::fixBiblio(Buffer const & buffer) +{ + // FIXME: What about the case where paragraph is not BIBLIO + // but there is an InsetBibitem? + // FIXME: when there was already an inset at 0, the return value is 1, + // which does not tell whether another inset has been remove; the + // cursor cannot be correctly updated. - // Add bibitem insets if necessary if (d->layout_->labeltype != LABEL_BIBLIO) return 0; - bool hasbibitem = !d->insetlist_.empty() - // Insist on it being in pos 0 - && d->text_[0] == META_INSET - && d->insetlist_.begin()->inset->lyxCode() == BIBITEM_CODE; - - bool track_changes = buffer.params().trackChanges; - - docstring oldkey; - docstring oldlabel; - - // remove a bibitem in pos != 0 - // restore it later in pos 0 if necessary - // (e.g. if a user inserts contents _before_ the item) - // we're assuming there's only one of these, which there - // should be. - int erasedInsetPosition = -1; - InsetList::iterator it = d->insetlist_.begin(); - InsetList::iterator end = d->insetlist_.end(); - for (; it != end; ++it) - if (it->inset->lyxCode() == BIBITEM_CODE - && it->pos > 0) { - InsetCommand * olditem = it->inset->asInsetCommand(); - oldkey = olditem->getParam("key"); - oldlabel = olditem->getParam("label"); - erasedInsetPosition = it->pos; - eraseChar(erasedInsetPosition, track_changes); - break; - } + bool const track_changes = buffer.params().trackChanges; + int bibitem_pos = d->insetlist_.find(BIBITEM_CODE); + bool const hasbibitem0 = bibitem_pos == 0; - // There was an InsetBibitem at the beginning, and we didn't - // have to erase one. - if (hasbibitem && erasedInsetPosition < 0) + if (hasbibitem0) { + bibitem_pos = d->insetlist_.find(BIBITEM_CODE, 1); + // There was an InsetBibitem at pos 0, and no other one => OK + if (bibitem_pos == -1) return 0; + // there is a bibitem at the 0 position, but since + // there is a second one, we copy the second on the + // first. We're assuming there are at most two of + // these, which there should be. + // FIXME: why does it make sense to do that rather + // than keep the first? (JMarc) + Inset * inset = d->insetlist_.release(bibitem_pos); + eraseChar(bibitem_pos, track_changes); + d->insetlist_.begin()->inset = inset; + return -bibitem_pos; + } + + // We need to create an inset at the beginning + Inset * inset = 0; + if (bibitem_pos > 0) { + // there was one somewhere in the paragraph, let's move it + inset = d->insetlist_.release(bibitem_pos); + eraseChar(bibitem_pos, track_changes); + } else + // make a fresh one + inset = new InsetBibitem(const_cast(&buffer), + InsetCommandParams(BIBITEM_CODE)); - // There was an InsetBibitem at the beginning and we did have to - // erase one. So we give its properties to the beginning inset. - if (hasbibitem) { - InsetCommand * inset = d->insetlist_.begin()->inset->asInsetCommand(); - if (!oldkey.empty()) - inset->setParam("key", oldkey); - inset->setParam("label", oldlabel); - return -erasedInsetPosition; - } - - // There was no inset at the beginning, so we need to create one with - // the key and label of the one we erased. - InsetBibitem * inset = - new InsetBibitem(const_cast(&buffer), InsetCommandParams(BIBITEM_CODE)); - // restore values of previously deleted item in this par. - if (!oldkey.empty()) - inset->setParam("key", oldkey); - inset->setParam("label", oldlabel); - insertInset(0, inset, - Change(track_changes ? Change::INSERTED : Change::UNCHANGED)); + insertInset(0, inset, Change(track_changes ? Change::INSERTED + : Change::UNCHANGED)); return 1; } @@ -3271,12 +3473,12 @@ int Paragraph::find(docstring const & str, bool cs, bool mw, int i = 0; pos_type const parsize = d->text_.size(); for (i = 0; i < strsize && pos < parsize; ++i, ++pos) { - // Ignore ligature break and hyphenation chars while searching + // Ignore "invisible" letters such as ligature breaks + // and hyphenation chars while searching while (pos < parsize - 1 && isInset(pos)) { - const InsetSpecialChar *isc = dynamic_cast(getInset(pos)); - if (isc == 0 - || (isc->kind() != InsetSpecialChar::HYPHENATION - && isc->kind() != InsetSpecialChar::LIGATURE_BREAK)) + odocstringstream os; + getInset(pos)->toString(os); + if (!getInset(pos)->isLetter() || !os.str().empty()) break; pos++; } @@ -3389,16 +3591,13 @@ void Paragraph::locateWord(pos_type & from, pos_type & to, void Paragraph::collectWords() { - // This is the value that needs to be exposed in the preferences - // to resolve bug #6760. - static int minlength = 6; pos_type n = size(); for (pos_type pos = 0; pos < n; ++pos) { if (isWordSeparator(pos)) continue; pos_type from = pos; locateWord(from, pos, WHOLE_WORD); - if (pos - from >= minlength) { + if ((pos - from) >= (int)lyxrc.completion_minlength) { docstring word = asString(from, pos, AS_STR_NONE); FontList::const_iterator cit = d->fontlist_.fontIterator(pos); if (cit == d->fontlist_.end()) @@ -3514,7 +3713,7 @@ Language * Paragraph::Private::getSpellLanguage(pos_type const from) const void Paragraph::requestSpellCheck(pos_type pos) { - d->requestSpellCheck(pos == -1 ? size() : pos); + d->requestSpellCheck(pos); } @@ -3530,6 +3729,21 @@ bool Paragraph::needsSpellCheck() const } +bool Paragraph::Private::ignoreWord(docstring const & word) const +{ + // Ignore words with digits + // FIXME: make this customizable + // (note that some checkers ignore words with digits by default) + docstring::const_iterator cit = word.begin(); + docstring::const_iterator const end = word.end(); + for (; cit != end; ++cit) { + if (isNumber((*cit))) + return true; + } + return false; +} + + SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, docstring_list & suggestions, bool do_suggestion, bool check_learned) const @@ -3546,19 +3760,17 @@ SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, if (from == to || from >= size()) return result; - docstring word = asString(from, to, AS_STR_INSETS + AS_STR_SKIPDELETE); + docstring word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE); Language * lang = d->getSpellLanguage(from); wl = WordLangTuple(word, lang); - if (!word.size()) + if (word.empty()) return result; if (needsSpellCheck() || check_learned) { - // Ignore words with digits - // FIXME: make this customizable - // (note that some checkers ignore words with digits by default) - if (!hasDigit(word)) { + pos_type end = to; + if (!d->ignoreWord(word)) { bool const trailing_dot = to < size() && d->text_[to] == '.'; result = speller->check(wl); if (SpellChecker::misspelled(result) && trailing_dot) { @@ -3568,24 +3780,34 @@ SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, LYXERR(Debug::GUI, "misspelled word is correct with dot: \"" << word << "\" [" << from << ".." << to << "]"); + } else { + // spell check with dot appended failed too + // restore original word/lang value + word = asString(from, to, AS_STR_INSETS | AS_STR_SKIPDELETE); + wl = WordLangTuple(word, lang); } } } - d->setMisspelled(from, to, result); + if (!SpellChecker::misspelled(result)) { + // area up to the begin of the next word is not misspelled + while (end < size() && isWordSeparator(end)) + ++end; + } + d->setMisspelled(from, end, result); } else { result = d->speller_state_.getState(from); } - bool const misspelled_ = SpellChecker::misspelled(result) ; - if (misspelled_ && do_suggestion) - speller->suggest(wl, suggestions); - else if (misspelled_) + if (do_suggestion) + suggestions.clear(); + + if (SpellChecker::misspelled(result)) { LYXERR(Debug::GUI, "misspelled word: \"" << word << "\" [" << from << ".." << to << "]"); - else - suggestions.clear(); - + if (do_suggestion) + speller->suggest(wl, suggestions); + } return result; } @@ -3640,7 +3862,7 @@ void Paragraph::Private::markMisspelledWords( void Paragraph::spellCheck() const { SpellChecker * speller = theSpellChecker(); - if (!speller || !size() ||!needsSpellCheck()) + if (!speller || empty() ||!needsSpellCheck()) return; pos_type start; pos_type endpos; @@ -3674,9 +3896,14 @@ void Paragraph::spellCheck() const } -bool Paragraph::isMisspelled(pos_type pos) const +bool Paragraph::isMisspelled(pos_type pos, bool check_boundary) const { - return SpellChecker::misspelled(d->speller_state_.getState(pos)); + bool result = SpellChecker::misspelled(d->speller_state_.getState(pos)); + if (result || pos <= 0 || pos > size()) + return result; + if (check_boundary && (pos == size() || isWordSeparator(pos))) + result = SpellChecker::misspelled(d->speller_state_.getState(pos - 1)); + return result; }