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;
        ///

Reply via email to