add cache of spell checker results to speed up native speller engine on macosx

git-svn-id: svn://svn.lyx.org/lyx/lyx-devel/trunk@35362 a592a061-630c-0410-9148-cb99ea01b6c8
This commit is contained in:
Stephan Witt 2010-09-14 05:24:04 +00:00
parent 4e6bae46c2
commit dec437fbac
23 changed files with 542 additions and 113 deletions

View File

@ -88,12 +88,19 @@ SpellChecker::Result AppleSpellChecker::check(WordLangTuple const & word)
}
void AppleSpellChecker::advanceChangeNumber()
{
nextChangeNumber();
}
// add to personal dictionary
void AppleSpellChecker::insert(WordLangTuple const & word)
{
string const word_str = to_utf8(word.word());
AppleSpeller_learn(d->speller, word_str.c_str());
LYXERR(Debug::GUI, "learn word: \"" << word.word() << "\"") ;
advanceChangeNumber();
}
@ -103,6 +110,7 @@ void AppleSpellChecker::remove(WordLangTuple const & word)
string const word_str = to_utf8(word.word());
AppleSpeller_unlearn(d->speller, word_str.c_str());
LYXERR(Debug::GUI, "unlearn word: \"" << word.word() << "\"") ;
advanceChangeNumber();
}
@ -111,6 +119,7 @@ void AppleSpellChecker::accept(WordLangTuple const & word)
{
string const word_str = to_utf8(word.word());
AppleSpeller_ignore(d->speller, word_str.c_str());
advanceChangeNumber();
}

View File

@ -34,6 +34,7 @@ public:
int numMisspelledWords() const;
void misspelledWord(int index, int & start, int & length) const;
docstring const error();
void advanceChangeNumber();
//@}
private:

View File

@ -280,6 +280,12 @@ SpellChecker::Result AspellChecker::check(WordLangTuple const & word)
}
void AspellChecker::advanceChangeNumber()
{
nextChangeNumber();
}
void AspellChecker::insert(WordLangTuple const & word)
{
Spellers::iterator it = d->spellers_.find(
@ -287,6 +293,7 @@ void AspellChecker::insert(WordLangTuple const & word)
if (it != d->spellers_.end()) {
AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
aspell_speller_add_to_personal(speller, to_utf8(word.word()).c_str(), -1);
advanceChangeNumber();
}
}
@ -298,6 +305,7 @@ void AspellChecker::accept(WordLangTuple const & word)
if (it != d->spellers_.end()) {
AspellSpeller * speller = to_aspell_speller(it->second.e_speller);
aspell_speller_add_to_session(speller, to_utf8(word.word()).c_str(), -1);
advanceChangeNumber();
}
}

View File

@ -32,6 +32,7 @@ public:
void accept(WordLangTuple const &);
bool hasDictionary(Language const * lang) const;
docstring const error();
void advanceChangeNumber();
//@}
private:

View File

@ -4033,6 +4033,7 @@ int Buffer::spellCheck(DocIterator & from, DocIterator & to,
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;

View File

@ -2265,9 +2265,7 @@ void Cursor::setCurrentFont()
// 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()

View File

@ -124,19 +124,29 @@ SpellChecker::Result EnchantChecker::check(WordLangTuple const & word)
}
void EnchantChecker::advanceChangeNumber()
{
nextChangeNumber();
}
void EnchantChecker::insert(WordLangTuple const & word)
{
Spellers::iterator it = d->spellers_.find(word.lang()->code());
if (it != d->spellers_.end())
if (it != d->spellers_.end()) {
it->second.speller->add(to_utf8(word.word()));
advanceChangeNumber();
}
}
void EnchantChecker::accept(WordLangTuple const & word)
{
Spellers::iterator it = d->spellers_.find(word.lang()->code());
if (it != d->spellers_.end())
if (it != d->spellers_.end()) {
it->second.speller->add_to_session(to_utf8(word.word()));
advanceChangeNumber();
}
}

View File

@ -38,6 +38,7 @@ public:
void accept(WordLangTuple const &);
bool hasDictionary(Language const * lang) const;
docstring const error();
void advanceChangeNumber();
///@}
private:

View File

@ -95,7 +95,7 @@ char const * LaTeXSizeNames[14] =
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;

View File

@ -42,10 +42,6 @@ public:
///
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 @@ private:
FontInfo bits_;
///
Language const * lang_;
///
bool misspelled_;
/// Did latexWriteStartChanges open an encoding environment?
mutable bool open_encoding_;
};
@ -128,8 +121,7 @@ private:
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_;
}
///

View File

@ -181,20 +181,6 @@ void FontList::set(pos_type pos, Font const & font)
}
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
{

View File

@ -105,12 +105,6 @@ public:
///
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,

View File

@ -233,6 +233,12 @@ SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl)
}
void HunspellChecker::advanceChangeNumber()
{
nextChangeNumber();
}
void HunspellChecker::insert(WordLangTuple const & wl)
{
string const word_to_check = to_utf8(wl.word());
@ -240,12 +246,14 @@ void HunspellChecker::insert(WordLangTuple const & wl)
if (!h)
return;
h->add(word_to_check.c_str());
advanceChangeNumber();
}
void HunspellChecker::accept(WordLangTuple const & wl)
{
d->ignored_.push_back(wl);
advanceChangeNumber();
}

View File

@ -32,6 +32,7 @@ public:
void accept(WordLangTuple const &);
bool hasDictionary(Language const * lang) const;
docstring const error();
void advanceChangeNumber();
///@}
private:

View File

@ -1354,39 +1354,48 @@ SpellChecker * theSpellChecker()
void setSpellChecker()
{
#if defined(USE_MACOSX_PACKAGING)
SpellChecker::ChangeNumber speller_change_number =singleton_->pimpl_->spell_checker_ ?
singleton_->pimpl_->spell_checker_->changeNumber() : 0;
if (lyxrc.spellchecker == "native") {
#if defined(USE_MACOSX_PACKAGING)
if (!singleton_->pimpl_->apple_spell_checker_)
singleton_->pimpl_->apple_spell_checker_ = new AppleSpellChecker();
singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->apple_spell_checker_;
return;
}
#else
singleton_->pimpl_->spell_checker_ = 0;
#endif
} else if (lyxrc.spellchecker == "aspell") {
#if defined(USE_ASPELL)
if (lyxrc.spellchecker == "aspell") {
if (!singleton_->pimpl_->aspell_checker_)
singleton_->pimpl_->aspell_checker_ = new AspellChecker();
singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->aspell_checker_;
return;
}
#else
singleton_->pimpl_->spell_checker_ = 0;
#endif
} else if (lyxrc.spellchecker == "enchant") {
#if defined(USE_ENCHANT)
if (lyxrc.spellchecker == "enchant") {
if (!singleton_->pimpl_->enchant_checker_)
singleton_->pimpl_->enchant_checker_ = new EnchantChecker();
singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->enchant_checker_;
return;
}
#else
singleton_->pimpl_->spell_checker_ = 0;
#endif
} else if (lyxrc.spellchecker == "hunspell") {
#if defined(USE_HUNSPELL)
if (lyxrc.spellchecker == "hunspell") {
if (!singleton_->pimpl_->hunspell_checker_)
singleton_->pimpl_->hunspell_checker_ = new HunspellChecker();
singleton_->pimpl_->spell_checker_ = singleton_->pimpl_->hunspell_checker_;
return;
}
#else
singleton_->pimpl_->spell_checker_ = 0;
#endif
singleton_->pimpl_->spell_checker_ = 0;
} else {
singleton_->pimpl_->spell_checker_ = 0;
}
if (singleton_->pimpl_->spell_checker_) {
singleton_->pimpl_->spell_checker_->changeNumber(speller_change_number);
singleton_->pimpl_->spell_checker_->advanceChangeNumber();
}
}
} // namespace lyx

View File

@ -73,6 +73,140 @@ namespace {
char_type const META_INSET = 0x200001;
};
/////////////////////////////////////////////////////////////////////
//
// SpellCheckerState
//
/////////////////////////////////////////////////////////////////////
class SpellCheckerState {
public:
SpellCheckerState() {
needs_refresh_ = true;
current_change_number_ = 0;
}
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);
needsRefresh(pos);
}
void decreasePosAfterPos(pos_type pos)
{
correctRangesAfterPos(pos, -1);
needsRefresh(pos);
}
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 needsRefresh() const {
return needs_refresh_;
}
SpellChecker::ChangeNumber currentChangeNumber() const {
return current_change_number_;
}
void refreshRange(pos_type & first, pos_type & last) const {
first = refresh_.first;
last = refresh_.last;
}
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;
}
needs_refresh_ = pos != -1;
}
void needsCompleteRefresh(SpellChecker::ChangeNumber change_number) {
needs_refresh_ = true;
refresh_.first = 0;
refresh_.last = -1;
current_change_number_ = change_number;
}
private:
/// store the ranges as map of FontSpan and spell result pairs
typedef map<FontSpan, SpellChecker::Result> Ranges;
typedef Ranges::const_iterator RangesIterator;
Ranges ranges_;
///
FontSpan refresh_;
bool needs_refresh_;
SpellChecker::ChangeNumber current_change_number_;
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 +307,69 @@ public:
/// 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 Positions::const_iterator PositionsIterator;
Language * getSpellLanguage(pos_type const from) const;
Language * locateSpellRange(pos_type & from, pos_type & to,
Positions & softbreaks) const;
bool hasSpellerChange() const {
SpellChecker::ChangeNumber speller_change_number = 0;
if (theSpellChecker())
speller_change_number = theSpellChecker()->changeNumber();
return speller_change_number > speller_state_.currentChangeNumber();
}
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(pos_type pos) {
speller_state_.needsRefresh(pos);
}
void readySpellCheck() {
speller_state_.needsRefresh(-1);
}
bool needsSpellCheck() const
{
return speller_state_.needsRefresh();
}
void rangeOfSpellCheck(pos_type & first, pos_type & last) const
{
speller_state_.refreshRange(first, last);
if (last == -1) {
last = owner_->size();
return;
}
pos_type endpos = last;
owner_->locateWord(first, endpos, WHOLE_WORD);
if (endpos < last) {
endpos = last;
owner_->locateWord(last, endpos, WHOLE_WORD);
}
last = endpos;
}
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 +409,8 @@ public:
LangWordsMap words_;
///
Layout const * layout_;
///
SpellCheckerState speller_state_;
};
@ -265,9 +452,10 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner)
: 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(p.text_.size());
}
@ -277,7 +465,7 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner,
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 +485,7 @@ Paragraph::Private::Private(Private const & p, Paragraph * owner,
// Add a new entry in the fontlist_.
fontlist_.set(fcit->pos() - beg, fcit->font());
}
requestSpellCheck(p.text_.size());
}
@ -464,6 +653,8 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c,
if (pos == pos_type(text_.size())) {
// when appending characters, no need to update tables
text_.push_back(c);
// but we want spell checking
requestSpellCheck(pos);
return;
}
@ -474,6 +665,9 @@ void Paragraph::Private::insertChar(pos_type pos, char_type c,
// Update the insets
insetlist_.increasePosAfterPos(pos);
// Update list of misspelled positions
speller_state_.increasePosAfterPos(pos);
}
@ -493,6 +687,9 @@ bool Paragraph::insertInset(pos_type pos, Inset * inset,
// Add a new entry in the insetlist_.
d->insetlist_.insert(inset, pos);
// Some insets require run of spell checker
requestSpellCheck(pos);
return true;
}
@ -542,6 +739,9 @@ bool Paragraph::eraseChar(pos_type pos, bool trackChanges)
// Update the insetlist_
d->insetlist_.decreasePosAfterPos(pos);
// Update list of misspelled positions
d->speller_state_.decreasePosAfterPos(pos);
return true;
}
@ -1249,9 +1449,8 @@ void Paragraph::write(ostream & os, BufferParams const & bparams,
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 +2804,7 @@ void Paragraph::changeLanguage(BufferParams const & bparams,
setFont(i, font);
}
}
d->requestSpellCheck(size());
}
@ -3172,61 +3372,223 @@ void Paragraph::updateWords()
}
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
{
SpellChecker * speller = theSpellChecker();
if (!speller)
return SpellChecker::WORD_OK;
// 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;
}
if (!d->layout_->spellcheck || !inInset().allowSpellCheck())
return SpellChecker::WORD_OK;
locateWord(from, to, WHOLE_WORD);
if (from == to || from >= pos_type(d->text_.size()))
return SpellChecker::WORD_OK;
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()) {
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);
}
wl = WordLangTuple(word, lang);
SpellChecker::Result res = ignored ?
SpellChecker::WORD_OK : speller->check(wl);
bool const misspelled_ = SpellChecker::misspelled(res) ;
if (lyxrc.spellcheck_continuously)
d->setMisspelled(from, to, misspelled_);
if (misspelled_ && do_suggestion)
speller->suggest(wl, suggestions);
else
suggestions.clear();
return res;
return lang;
}
void Paragraph::requestSpellCheck(pos_type pos)
{
d->requestSpellCheck(pos == -1 ? size() : pos);
}
bool Paragraph::needsSpellCheck() const
{
SpellChecker::ChangeNumber speller_change_number = 0;
if (theSpellChecker())
speller_change_number = theSpellChecker()->changeNumber();
if (speller_change_number > d->speller_state_.currentChangeNumber()) {
d->speller_state_.needsCompleteRefresh(speller_change_number);
}
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 result;
if (!d->layout_->spellcheck || !inInset().allowSpellCheck())
return result;
locateWord(from, to, WHOLE_WORD);
if (from == to || from >= pos_type(d->text_.size()))
return result;
docstring word = asString(from, to, AS_STR_INSETS);
Language * lang = d->getSpellLanguage(from);
wl = WordLangTuple(word, lang);
if (!word.size())
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))
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 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);
if (wlen) {
docstring const misspelled = word.substr(wstart, wlen);
wstart += first + numbreaks;
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: \"" <<
misspelled << "\" [" <<
wstart << ".." << (snext-1) << "]");
++snext;
}
}
}
if (snext <= last) {
setMisspelled(snext, last, SpellChecker::WORD_OK);
}
}
void Paragraph::spellCheck() const
{
SpellChecker * speller = theSpellChecker();
if (!speller || !size() ||!needsSpellCheck())
return;
pos_type start;
pos_type endpos;
d->rangeOfSpellCheck(start, endpos);
if (speller->canCheckParagraph()) {
// loop until we leave the range argument
for (pos_type first = start; first < endpos; ) {
pos_type last = endpos;
Private::Positions softbreaks;
Language * lang = d->locateSpellRange(first, last, softbreaks);
if (first >= endpos)
break;
// 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;
while (start < endpos) {
WordLangTuple wl;
spellCheck(start, to, wl, suggestions, false);
start = 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));
}

View File

@ -66,6 +66,18 @@ public:
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 @@ public:
/// \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,23 @@ public:
/// 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;
/// mark position of text manipulation to inform the spell checker
/// default value -1 marks the whole paragraph to be checked (again)
void requestSpellCheck(pos_type pos = -1);
/// an automatically generated identifying label for this paragraph.
/// presently used only in the XHTML output routines.
std::string magicLabel() const;

View File

@ -14,6 +14,7 @@
#define SPELL_BASE_H
#include "support/strfwd.h"
#include "support/lyxtime.h"
namespace lyx {
@ -89,6 +90,16 @@ public:
/// give an error message on messy exit
virtual docstring const error() = 0;
/// spell checker state versioning support
typedef unsigned long int ChangeNumber ;
ChangeNumber changeNumber() const { return change_number_; }
void changeNumber(ChangeNumber value) { change_number_ = value; }
void nextChangeNumber() { ++change_number_; }
virtual void advanceChangeNumber() = 0;
private:
ChangeNumber change_number_;
};
/// Access to the singleton SpellChecker.

View File

@ -519,6 +519,9 @@ void Text::readParagraph(Paragraph & par, Lexer & lex,
// Initialize begin_of_body_ on load; redoParagraph maintains
par.setBeginOfBody();
// mark paragraph for spell checking on load
// par.requestSpellCheck();
}

View File

@ -356,6 +356,9 @@ void Text::setFont(BufferView const & bv, CursorSlice const & begin,
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(pos);
}
}

View File

@ -2132,15 +2132,7 @@ void TextMetrics::drawParagraph(PainterInfo & pi, pit_type pit, int x, int y) co
// 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

View File

@ -738,7 +738,7 @@ void MenuDefinition::expandSpellingSuggestions(BufferView const * bv)
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) {

View File

@ -234,6 +234,10 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font,
bool const selection = (pos >= row_.sel_beg && pos < row_.sel_end)
|| pi_.selected;
// 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) {
@ -246,6 +250,12 @@ void RowPainter::paintChars(pos_type & vpos, FontInfo const & font,
// 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.
@ -357,6 +367,10 @@ void RowPainter::paintFromPos(pos_type & vpos)
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))
@ -370,8 +384,9 @@ void RowPainter::paintFromPos(pos_type & vpos)
paintForeignMark(orig_x, orig_font.language());
if (lyxrc.spellcheck_continuously && orig_font.isMisspelled())
if (lyxrc.spellcheck_continuously && misspelled_) {
paintMisspelledMark(orig_x, 2);
}
}