Am 28.08.2010 um 19:26 schrieb Pavel Sanda: > Stephan Witt wrote: >> The performance gain with native spell checker on mac is as follows: >> Scrolling the complete users guide with spell check as you type enabled >> * without patch: 34% of the cpu time in spell check, >> * with patch: 4% of the time in spell check. >> Didn't made measurements for explicit spell check (F7) but there is an big >> difference too. > > the results looks good. i would be more happy to have this in a6, so lets > wait on the final patch for review.
I've made some changes again... shorten lines as Jürgen suggested and formatting of if/while. And I added code to handle the soft-hyphens and friends. So I attach it again and hope I can put it in later. JMarc, can you have a look, please? Stephan
Index: src/AppleSpellChecker.cpp =================================================================== --- src/AppleSpellChecker.cpp (Revision 35252) +++ src/AppleSpellChecker.cpp (Arbeitskopie) @@ -78,8 +78,12 @@ SpellChecker::Result AppleSpellChecker::check(WordLangTuple const & word) { string const word_str = to_utf8(word.word()); - SpellCheckResult result = checkAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); - LYXERR(Debug::GUI, "spellCheck: \"" << word.word() << "\" = " << d->toString(result)) ; + SpellCheckResult result = + checkAppleSpeller(d->speller, + word_str.c_str(), word.lang()->code().c_str()); + LYXERR(Debug::GUI, "spellCheck: \"" << + word.word() << "\" = " << d->toString(result) << + ", lang = " << word.lang()->code()) ; return d->toResult(result); } @@ -130,6 +134,18 @@ } +int AppleSpellChecker::numMisspelledWords() const +{ + return numMisspelledWordsAppleSpeller(d->speller); +} + + +void AppleSpellChecker::misspelledWord(int index, int & start, int & length) const +{ + misspelledWordAppleSpeller(d->speller, index, &start, &length); +} + + docstring const AppleSpellChecker::error() { return docstring(); Index: src/Text2.cpp =================================================================== --- src/Text2.cpp (Revision 35252) +++ src/Text2.cpp (Arbeitskopie) @@ -356,6 +356,9 @@ Font f = tm.displayFont(pit, pos); f.update(font, language, toggleall); setCharFont(pit, pos, f, tm.font_); + // font change may change language... + // spell checker has to know that + pars_[pit].requestSpellCheck(); } } Index: src/AppleSpellChecker.h =================================================================== --- src/AppleSpellChecker.h (Revision 35252) +++ src/AppleSpellChecker.h (Arbeitskopie) @@ -30,6 +30,9 @@ void remove(WordLangTuple const &); void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; + bool canCheckParagraph() const { return true; } + int numMisspelledWords() const; + void misspelledWord(int index, int & start, int & length) const; docstring const error(); //@} Index: src/Font.cpp =================================================================== --- src/Font.cpp (Revision 35252) +++ src/Font.cpp (Arbeitskopie) @@ -95,7 +95,7 @@ Font::Font(FontInfo bits, Language const * l) - : bits_(bits), lang_(l), misspelled_(false), open_encoding_(false) + : bits_(bits), lang_(l), open_encoding_(false) { if (!lang_) lang_ = default_language; Index: src/TextMetrics.cpp =================================================================== --- src/TextMetrics.cpp (Revision 35252) +++ src/TextMetrics.cpp (Arbeitskopie) @@ -2132,15 +2132,7 @@ // Take this opportunity to spellcheck the row contents. if (row_has_changed && lyxrc.spellcheck_continuously) { - WordLangTuple wl; - // dummy variable, not used. - static docstring_list suggestions; - pos_type from = row.pos(); - pos_type to = row.endpos(); - while (from < row.endpos()) { - text_->getPar(pit).spellCheck(from, to, wl, suggestions, false); - from = to + 1; - } + text_->getPar(pit).spellCheck(); } // Don't paint the row if a full repaint has not been requested Index: src/Font.h =================================================================== --- src/Font.h (Revision 35252) +++ src/Font.h (Arbeitskopie) @@ -42,10 +42,6 @@ /// Language const * language() const { return lang_; } /// - void setMisspelled(bool misspelled) { misspelled_ = misspelled; } - /// - bool isMisspelled() const { return misspelled_; } - /// bool isRightToLeft() const; /// bool isVisibleRightToLeft() const; @@ -116,9 +112,6 @@ FontInfo bits_; /// Language const * lang_; - /// - bool misspelled_; - /// Did latexWriteStartChanges open an encoding environment? mutable bool open_encoding_; }; @@ -128,8 +121,7 @@ inline bool operator==(Font const & font1, Font const & font2) { - return font1.bits_ == font2.bits_ && font1.lang_ == font2.lang_ - && font1.misspelled_ == font2.misspelled_; + return font1.bits_ == font2.bits_ && font1.lang_ == font2.lang_; } /// Index: src/frontends/qt4/Menus.cpp =================================================================== --- src/frontends/qt4/Menus.cpp (Revision 35252) +++ src/frontends/qt4/Menus.cpp (Arbeitskopie) @@ -738,7 +738,7 @@ pos_type from = bv->cursor().pos(); pos_type to = from; Paragraph const & par = bv->cursor().paragraph(); - SpellChecker::Result res = par.spellCheck(from, to, wl, suggestions); + SpellChecker::Result res = par.spellCheck(from, to, wl, suggestions, true, true); switch (res) { case SpellChecker::UNKNOWN_WORD: if (lyxrc.spellcheck_continuously) { @@ -761,7 +761,7 @@ if (i > 0) add(MenuItem(MenuItem::Separator)); docstring const arg = wl.word() + " " + from_ascii(wl.lang()->lang()); - add(MenuItem(MenuItem::Command, qt_("Add to personal dictionary|c"), + add(MenuItem(MenuItem::Command, qt_("Add to personal dictionary|n"), FuncRequest(LFUN_SPELLING_ADD, arg))); add(MenuItem(MenuItem::Command, qt_("Ignore all|I"), FuncRequest(LFUN_SPELLING_IGNORE, arg))); Index: src/support/AppleSpeller.m =================================================================== --- src/support/AppleSpeller.m (Revision 35252) +++ src/support/AppleSpeller.m (Arbeitskopie) @@ -230,9 +230,9 @@ } -void misspelledWordAppleSpeller(AppleSpeller speller, int const position, int * start, int * length) +void misspelledWordAppleSpeller(AppleSpeller speller, int index, int * start, int * length) { - NSRange range = [[speller->misspelled objectAtIndex:position] rangeValue]; + NSRange range = [[speller->misspelled objectAtIndex:index] rangeValue]; *start = range.location; *length = range.length; } Index: src/support/AppleSpeller.h =================================================================== --- src/support/AppleSpeller.h (Revision 35252) +++ src/support/AppleSpeller.h (Arbeitskopie) @@ -36,7 +36,7 @@ void unlearnAppleSpeller(AppleSpeller speller, const char * word); int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang); int numMisspelledWordsAppleSpeller(AppleSpeller speller); -void misspelledWordAppleSpeller(AppleSpeller speller, int const position, int * start, int * length); +void misspelledWordAppleSpeller(AppleSpeller speller, int index, int * start, int * length); #ifdef __cplusplus } // extern "C" Index: src/FontList.cpp =================================================================== --- src/FontList.cpp (Revision 35252) +++ src/FontList.cpp (Arbeitskopie) @@ -181,20 +181,6 @@ } -void FontList::setMisspelled(pos_type startpos, pos_type endpos, - bool misspelled) -{ - // FIXME: move misspelled state out of font!? - for (pos_type p = startpos; p <= endpos; ++p) { - Font f = fontIterator(p)->font(); - if (f.isMisspelled() != misspelled) { - f.setMisspelled(misspelled); - set(p, f); - } - } -} - - FontSize FontList::highestInRange(pos_type startpos, pos_type endpos, FontSize def_size) const { Index: src/Paragraph.cpp =================================================================== --- src/Paragraph.cpp (Revision 35252) +++ src/Paragraph.cpp (Arbeitskopie) @@ -73,8 +73,107 @@ char_type const META_INSET = 0x200001; }; + ///////////////////////////////////////////////////////////////////// // +// Paragraph::SpellRanges +// +///////////////////////////////////////////////////////////////////// + +class Paragraph::SpellCheckerState { +public: + SpellCheckerState() { + needs_refresh_ = true; + } + + void setRange(FontSpan const fp, SpellChecker::Result state) + { + eraseCoveredRanges(fp); + if (state != SpellChecker::WORD_OK) + ranges_[fp] = state; + } + + void increasePosAfterPos(pos_type pos) + { + correctRangesAfterPos(pos, 1); + needs_refresh_ = true; + } + + void decreasePosAfterPos(pos_type pos) + { + correctRangesAfterPos(pos, -1); + needs_refresh_ = true; + } + + SpellChecker::Result getState(pos_type pos) const + { + SpellChecker::Result result = SpellChecker::WORD_OK; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + FontSpan fc = it->first; + if(fc.first <= pos && pos <= fc.last) { + result = it->second; + break; + } + } + return result; + } + + /// + bool needs_refresh_; + +private: + /// store the ranges as map of FontSpan and spell result pairs + typedef map<FontSpan, SpellChecker::Result> Ranges; + typedef map<FontSpan, SpellChecker::Result>::const_iterator RangesIterator; + Ranges ranges_; + + void eraseCoveredRanges(FontSpan const fp) + { + Ranges result; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + FontSpan fc = it->first; + // 1. first of new range inside current range or + // 2. last of new range inside current range or + // 3. first of current range inside new range or + // 4. last of current range inside new range or + if ((fc.first <= fp.first && fp.first <= fc.last) || + (fc.first <= fp.last && fp.last <= fc.last) || + (fp.first <= fc.first && fc.first <= fp.last) || + (fp.first <= fc.last && fc.last <= fp.last)) + { + continue; + } + result[fc] = it->second; + } + ranges_ = result; + } + + void correctRangesAfterPos(pos_type pos, int offset) + { + Ranges result; + RangesIterator et = ranges_.end(); + RangesIterator it = ranges_.begin(); + for (; it != et; ++it) { + FontSpan m = it->first; + if (m.first > pos) { + m.first += offset; + m.last += offset; + } else if (m.last > pos) { + m.last += offset; + } + result[m] = it->second; + } + ranges_ = result; + } + +}; + +///////////////////////////////////////////////////////////////////// +// // Paragraph::Private // ///////////////////////////////////////////////////////////////////// @@ -173,18 +272,44 @@ /// match a string against a particular point in the paragraph bool isTextAt(string const & str, pos_type pos) const; - void setMisspelled(pos_type from, pos_type to, bool misspelled) + /// a vector of inset positions + typedef vector<pos_type> Positions; + typedef vector<pos_type>::const_iterator PositionsIterator; + + Language * getSpellLanguage(pos_type const from) const; + + Language * locateSpellRange( + pos_type & from, pos_type & to, + Positions & softbreaks) 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) return; + if (to < from || from >= textsize) + return; + FontSpan fp = FontSpan(from, to); // don't mark end of paragraph - if (to >= textsize) - to = textsize - 1; - fontlist_.setMisspelled(from, to, misspelled); + if (fp.last >= textsize) + fp.last = textsize - 1; + speller_state_.setRange(fp, state); } + + void requestSpellCheck() { speller_state_.needs_refresh_ = true; } + + void readySpellCheck() { speller_state_.needs_refresh_ = false; } - + bool needsSpellCheck() const + { + return speller_state_.needs_refresh_; + } + + void markMisspelledWords( + pos_type const & first, pos_type const & last, + SpellChecker::Result result, + docstring const & word, + Positions const & softbreaks); + InsetCode ownerCode() const { return inset_owner_ ? inset_owner_->lyxCode() : NO_CODE; @@ -224,6 +349,8 @@ LangWordsMap words_; /// Layout const * layout_; + /// + SpellCheckerState speller_state_; }; @@ -265,9 +392,10 @@ : owner_(owner), inset_owner_(p.inset_owner_), fontlist_(p.fontlist_), params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_), begin_of_body_(p.begin_of_body_), text_(p.text_), words_(p.words_), - layout_(p.layout_) + layout_(p.layout_), speller_state_(p.speller_state_) { id_ = ++paragraph_id; + requestSpellCheck(); } @@ -277,7 +405,7 @@ params_(p.params_), changes_(p.changes_), insetlist_(p.insetlist_, beg, end), begin_of_body_(p.begin_of_body_), words_(p.words_), - layout_(p.layout_) + layout_(p.layout_), speller_state_(p.speller_state_) { id_ = ++paragraph_id; if (beg >= pos_type(p.text_.size())) @@ -297,6 +425,7 @@ // Add a new entry in the fontlist_. fontlist_.set(fcit->pos() - beg, fcit->font()); } + requestSpellCheck(); } @@ -464,6 +593,8 @@ if (pos == pos_type(text_.size())) { // when appending characters, no need to update tables text_.push_back(c); + // but we want spell checking + requestSpellCheck(); return; } @@ -474,6 +605,9 @@ // Update the insets insetlist_.increasePosAfterPos(pos); + + // Update list of misspelled positions + speller_state_.increasePosAfterPos(pos); } @@ -493,6 +627,9 @@ // Add a new entry in the insetlist_. d->insetlist_.insert(inset, pos); + + // Some insets require run of spell checker + requestSpellCheck(); return true; } @@ -542,6 +679,9 @@ // Update the insetlist_ d->insetlist_.decreasePosAfterPos(pos); + // Update list of misspelled positions + d->speller_state_.decreasePosAfterPos(pos); + return true; } @@ -1249,9 +1389,8 @@ if (i == size()) break; - // Write font changes (ignore spelling markers) + // Write font changes Font font2 = getFontSettings(bparams, i); - font2.setMisspelled(false); if (font2 != font1) { flushString(os, write_buffer); font2.lyxWriteChanges(font1, os); @@ -2605,6 +2744,7 @@ setFont(i, font); } } + d->requestSpellCheck(); } @@ -3172,61 +3312,216 @@ } -SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, - docstring_list & suggestions, bool do_suggestion) const +Language * Paragraph::Private::locateSpellRange( + pos_type & from, pos_type & to, + Positions & softbreaks) const { + // skip leading white space + while (from < to && owner_->isWordSeparator(from)) + ++from; + // don't check empty range + if (from >= to) + return 0; + // get current language + Language * lang = getSpellLanguage(from); + pos_type last = from; + bool samelang = true; + bool sameinset = true; + while (last < to && samelang && sameinset) { + // hop to end of word + while (last < to && !owner_->isWordSeparator(last)) { + if (owner_->getInset(last)) { + softbreaks.insert(softbreaks.end(), last); + } + ++last; + } + // hop to next word while checking for insets + while (sameinset && last < to && owner_->isWordSeparator(last)) { + if (Inset const * inset = owner_->getInset(last)) + sameinset = inset->isChar() && inset->isLetter(); + if (sameinset) + last++; + } + if (sameinset && last < to) { + // now check for language change + samelang = lang == getSpellLanguage(last); + } + } + // if language change detected backstep is needed + if (!samelang) + --last; + to = last; + return lang; +} + + +Language * Paragraph::Private::getSpellLanguage(pos_type const from) const +{ + Language * lang = + const_cast<Language *>(owner_->getFontSettings( + inset_owner_->buffer().params(), from).language()); + if (lang == inset_owner_->buffer().params().language + && !lyxrc.spellchecker_alt_lang.empty()) { + string lang_code; + string const lang_variety = + split(lyxrc.spellchecker_alt_lang, lang_code, '-'); + lang->setCode(lang_code); + lang->setVariety(lang_variety); + } + return lang; +} + + +void Paragraph::requestSpellCheck() +{ + d->requestSpellCheck(); +} + + +bool Paragraph::needsSpellCheck() const +{ + return d->needsSpellCheck(); +} + + +SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, + WordLangTuple & wl, docstring_list & suggestions, + bool do_suggestion, bool check_learned) const +{ + SpellChecker::Result result = SpellChecker::WORD_OK; SpellChecker * speller = theSpellChecker(); if (!speller) - return SpellChecker::WORD_OK; + return result; if (!d->layout_->spellcheck || !inInset().allowSpellCheck()) - return SpellChecker::WORD_OK; + return result; locateWord(from, to, WHOLE_WORD); if (from == to || from >= pos_type(d->text_.size())) - return SpellChecker::WORD_OK; + return result; docstring word = asString(from, to, AS_STR_INSETS); - // Ignore words with digits - // FIXME: make this customizable - // (note that hunspell ignores words with digits by default) - bool const ignored = hasDigit(word); - Language * lang = const_cast<Language *>(getFontSettings( - d->inset_owner_->buffer().params(), from).language()); - if (lang == d->inset_owner_->buffer().params().language - && !lyxrc.spellchecker_alt_lang.empty()) { - string lang_code; - string const lang_variety = - split(lyxrc.spellchecker_alt_lang, lang_code, '-'); - lang->setCode(lang_code); - lang->setVariety(lang_variety); - } + Language * lang = d->getSpellLanguage(from); + wl = WordLangTuple(word, lang); - SpellChecker::Result res = ignored ? - SpellChecker::WORD_OK : speller->check(wl); + + if (!word.size()) + return result; - bool const misspelled_ = SpellChecker::misspelled(res) ; - - if (lyxrc.spellcheck_continuously) - d->setMisspelled(from, to, misspelled_); - + 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)) + result = speller->check(wl); + d->setMisspelled(from, to, 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_) + LYXERR(Debug::GUI, "misspelled word: \"" << + word << "\" [" << + from << ".." << to << "]"); else suggestions.clear(); - return res; + return result; } +void Paragraph::Private::markMisspelledWords( + pos_type const & first, pos_type const & last, + SpellChecker::Result result, + docstring const & word, + Positions const & softbreaks) +{ + pos_type snext = first; + if (SpellChecker::misspelled(result)) { + SpellChecker * speller = theSpellChecker(); + // locate and enumerate the error positions + int nerrors = speller->numMisspelledWords(); + int numbreaks = 0; + PositionsIterator it = softbreaks.begin(); + PositionsIterator et = softbreaks.end(); + for (int index = 0; index < nerrors; ++index) { + int wstart; + int wlen = 0; + speller->misspelledWord(index, wstart, wlen); + wstart += first + numbreaks; + if (wlen) { + if (snext < wstart) { + while (it != et && *it < wstart) { + ++wstart; + ++numbreaks; + ++it; + } + setMisspelled(snext, + wstart - 1, SpellChecker::WORD_OK); + } + snext = wstart + wlen; + while (it != et && *it < snext) { + ++snext; + ++numbreaks; + ++it; + } + setMisspelled(wstart, snext, result); + LYXERR(Debug::GUI, "misspelled word: \"" << + word.substr(wstart, wlen) << "\" [" << + wstart << ".." << snext << "]"); + ++snext; + } + } + } + if (snext <= last) { + setMisspelled(snext, last, SpellChecker::WORD_OK); + } +} + + +void Paragraph::spellCheck() const +{ + pos_type startpos = 0; + pos_type endpos = size(); + SpellChecker * speller = theSpellChecker(); + if (!speller || !endpos ||!needsSpellCheck()) + return; + if (speller->canCheckParagraph()) { + // loop until we leave the range argument + for (pos_type first = 0; first < endpos; ) { + pos_type last = endpos; + Private::Positions softbreaks; + Language * lang = d->locateSpellRange(first, last, softbreaks); + if (first >= endpos) + return; + // start the spell checker on the unit of meaning + docstring word = asString(first, last, AS_STR_INSETS); + WordLangTuple wl = WordLangTuple(word, lang); + SpellChecker::Result result = word.size() ? + speller->check(wl) : SpellChecker::WORD_OK; + d->markMisspelledWords(first, last, result, word, softbreaks); + first = ++last; + } + } else { + static docstring_list suggestions; + pos_type to = endpos; + startpos = 0; + while (startpos < endpos) { + WordLangTuple wl; + spellCheck(startpos, to, wl, suggestions, false); + startpos = to + 1; + } + } + d->readySpellCheck(); +} + + bool Paragraph::isMisspelled(pos_type pos) const { - pos_type from = pos; - pos_type to = pos; - WordLangTuple wl; - docstring_list suggestions; - SpellChecker::Result res = spellCheck(from, to, wl, suggestions, false); - return SpellChecker::misspelled(res) ; + return SpellChecker::misspelled(d->speller_state_.getState(pos)); } Index: src/rowpainter.cpp =================================================================== --- src/rowpainter.cpp (Revision 35252) +++ src/rowpainter.cpp (Arbeitskopie) @@ -226,6 +226,10 @@ // selected text? bool const selection = pos >= row_.sel_beg && pos < row_.sel_end; + // spelling correct? + bool const spell_state = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); + char_type prev_char = ' '; // collect as much similar chars as we can for (++vpos ; vpos < end ; ++vpos) { @@ -238,6 +242,12 @@ // Selection ends or starts here. break; + bool const new_spell_state = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); + if (new_spell_state != spell_state) + // Spell checker state changed here. + break; + Change const & change = par_.lookupChange(pos); if (!change_running.isSimilarTo(change)) // Track change type or author has changed. @@ -349,6 +359,10 @@ bool const arabic = lang == "arabic_arabtex" || lang == "arabic_arabi" || lang == "farsi"; + // spelling correct? + bool const misspelled_ = + lyxrc.spellcheck_continuously && par_.isMisspelled(pos); + // draw as many chars as we can if ((!hebrew && !arabic) || (hebrew && !Encodings::isHebrewComposeChar(c)) @@ -362,8 +376,9 @@ paintForeignMark(orig_x, orig_font.language()); - if (lyxrc.spellcheck_continuously && orig_font.isMisspelled()) + if (lyxrc.spellcheck_continuously && misspelled_) { paintMisspelledMark(orig_x, 2); + } } Index: src/FontList.h =================================================================== --- src/FontList.h (Revision 35252) +++ src/FontList.h (Arbeitskopie) @@ -105,12 +105,6 @@ /// void decreasePosAfterPos(pos_type pos); - /// - void setMisspelled( - pos_type startpos, - pos_type endpos, - bool misspelled); - /// Returns the height of the highest font in range FontSize highestInRange( pos_type startpos, Index: src/Cursor.cpp =================================================================== --- src/Cursor.cpp (Revision 35252) +++ src/Cursor.cpp (Arbeitskopie) @@ -2265,9 +2265,7 @@ // get font BufferParams const & bufparams = buffer()->params(); current_font = par.getFontSettings(bufparams, cpos); - current_font.setMisspelled(false); real_current_font = tm.displayFont(cpit, cpos); - real_current_font.setMisspelled(false); // special case for paragraph end if (cs.pos() == lastpos() Index: src/SpellChecker.h =================================================================== --- src/SpellChecker.h (Revision 35252) +++ src/SpellChecker.h (Arbeitskopie) @@ -55,7 +55,7 @@ /// check the given word of the given lang code and return the result virtual enum Result check(WordLangTuple const &) = 0; - + /// Gives suggestions. virtual void suggest(WordLangTuple const &, docstring_list & suggestions) = 0; @@ -71,6 +71,23 @@ /// check if dictionary exists virtual bool hasDictionary(Language const *) const = 0; + /// if speller can spell check whole paragraph return true + virtual bool canCheckParagraph() const { return false; } + + /// count of misspelled words + virtual int numMisspelledWords() const { return 0; } + + /// start position and length of misspelled word at index + virtual void misspelledWord( + int index, + int & start, int & length) const + { + /// index is used here to make the compiler happy + if (index == 0) + start = 0; + length = 0; + } + /// give an error message on messy exit virtual docstring const error() = 0; }; Index: src/Buffer.cpp =================================================================== --- src/Buffer.cpp (Revision 35252) +++ src/Buffer.cpp (Arbeitskopie) @@ -4033,6 +4033,7 @@ if (from == end) break; to = from; + from.paragraph().spellCheck(); SpellChecker::Result res = from.paragraph().spellCheck(from.pos(), to.pos(), wl, suggestions); if (SpellChecker::misspelled(res)) { word_lang = wl; Index: src/Paragraph.h =================================================================== --- src/Paragraph.h (Revision 35252) +++ src/Paragraph.h (Arbeitskopie) @@ -66,6 +66,18 @@ public: /// Range including first and last. pos_type first, last; + + inline bool operator<(FontSpan const & s) const + { + return first < s.first; + } + + inline bool operator==(FontSpan const & s) const + { + return first == s.first && last == s.last; + } + + }; /// @@ -147,7 +159,7 @@ /// \param force means: output even if layout.inpreamble is true. void latex(BufferParams const &, Font const & outerfont, odocstream &, TexRow & texrow, OutputParams const &, - int start_pos = 0, int end_pos = -1, bool force = false) const; + int start_pos = 0, int end_pos = -1, bool force = false) const; /// Can we drop the standard paragraph wrapper? bool emptyTag() const; @@ -425,11 +437,22 @@ /// and \p suggestions if \p do_suggestion is true. /// \return result from spell checker, SpellChecker::UNKNOWN_WORD when misspelled. SpellChecker::Result spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, - docstring_list & suggestions, bool do_suggestion = true) const; + docstring_list & suggestions, bool do_suggestion = true, + bool check_learned = false) const; - /// Spellcheck word at position \p pos. - /// \return true if pointed word is misspelled. + /// Spell checker status at position \p pos. + /// \return true if pointed position is misspelled. bool isMisspelled(pos_type pos) const; + + /// spell check of whole paragraph + /// remember results until call of requestSpellCheck() + void spellCheck() const; + + /// query state of spell checker results + bool needsSpellCheck() const; + /// invalidate state of spell checker results + void requestSpellCheck(); + /// an automatically generated identifying label for this paragraph. /// presently used only in the XHTML output routines. std::string magicLabel() const; @@ -445,6 +468,10 @@ /// void registerWords(); + /// spell checker result ranges + class SpellCheckerState; + /// + friend class Paragraph::SpellCheckerState; /// Pimpl away stuff class Private; ///