X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2FHunspellChecker.cpp;h=af3d8404c01b09ad7e8fb8ab35792249cbe13a32;hb=e2982037d9f981a6070c3cf90148bde33edbfdc9;hp=f024f2ec0268d15382a6d098714ef96044739e81;hpb=21daab357bcff2bf26d63cb59df69f6afa2acf3f;p=lyx.git diff --git a/src/HunspellChecker.cpp b/src/HunspellChecker.cpp index f024f2ec02..af3d8404c0 100644 --- a/src/HunspellChecker.cpp +++ b/src/HunspellChecker.cpp @@ -11,21 +11,28 @@ #include #include "HunspellChecker.h" +#include "PersonalWordList.h" #include "LyXRC.h" #include "WordLangTuple.h" +#include "frontends/alert.h" + #include "support/debug.h" #include "support/docstring_list.h" +#include "support/filetools.h" +#include "support/Package.h" #include "support/FileName.h" #include "support/gettext.h" #include "support/lassert.h" +#include "support/lstrings.h" #include "support/os.h" #include #include #include +#include using namespace std; using namespace lyx::support; @@ -36,72 +43,230 @@ namespace lyx { namespace { typedef map Spellers; +typedef map LangPersonalWordList; + +typedef vector IgnoreList; } // anon namespace + struct HunspellChecker::Private { - Private() {} - + Private(); ~Private(); - Hunspell * addSpeller(string const & lang); - Hunspell * speller(string const & lang); - + const string dictPath(int selector); + bool haveLanguageFiles(string const & hpath); + bool haveDictionary(string const & lang, string & hpath); + bool haveDictionary(string const & lang); + Hunspell * addSpeller(string const & lang, string & hpath); + Hunspell * addSpeller(Language const * lang); + Hunspell * speller(Language const * lang); + /// ignored words + bool isIgnored(WordLangTuple const & wl) const; + /// personal word list interface + void remove(WordLangTuple const & wl); + void insert(WordLangTuple const & wl); + bool learned(WordLangTuple const & wl); /// the spellers Spellers spellers_; + /// + IgnoreList ignored_; + /// + LangPersonalWordList personal_; + + /// the location below system/user directory + /// there the aff+dic files lookup will happen + const string dictDirectory(void) const { return "dict"; } + int maxLookupSelector(void) const { return 3; } }; +HunspellChecker::Private::Private() +{ +} + + HunspellChecker::Private::~Private() { Spellers::iterator it = spellers_.begin(); Spellers::iterator end = spellers_.end(); for (; it != end; ++it) { - delete it->second; + if ( 0 != it->second) delete it->second; + } + + LangPersonalWordList::const_iterator pdit = personal_.begin(); + LangPersonalWordList::const_iterator pdet = personal_.end(); + + for (; pdit != pdet; ++pdit) { + if ( 0 == pdit->second) + continue; + PersonalWordList * pd = pdit->second; + pd->save(); + delete pd; } } -Hunspell * HunspellChecker::Private::addSpeller(string const & lang) +bool HunspellChecker::Private::haveLanguageFiles(string const & hpath) { - string hunspell_path = external_path(lyxrc.hunspelldir_path); - LYXERR(Debug::FILES, "hunspell path: " << hunspell_path); - if (hunspell_path.empty()) + FileName const affix(hpath + ".aff"); + FileName const dict(hpath + ".dic"); + return affix.isReadableFile() && dict.isReadableFile(); +} + + +const string HunspellChecker::Private::dictPath(int selector) +{ + switch (selector) { + case 2: + return addName(package().system_support().absFileName(),dictDirectory()); + break; + case 1: + return addName(package().user_support().absFileName(),dictDirectory()); + break; + default: + return lyxrc.hunspelldir_path; + } +} + + +bool HunspellChecker::Private::haveDictionary(string const & lang, string & hpath) +{ + if (hpath.empty()) { return false; + } - hunspell_path += "/" + lang; - // replace '_' with '-' as this is the convention used by hunspell. - hunspell_path[hunspell_path.size() - 3] = '-'; - FileName const affix(hunspell_path + ".aff"); - FileName const dict(hunspell_path + ".dic"); - if (!affix.isReadableFile()) { - // FIXME: We should indicate somehow that this language is not - // supported. - LYXERR(Debug::FILES, "Hunspell affix file " << affix << " does not exist"); - return 0; + LYXERR(Debug::FILES, "check hunspell path: " << hpath << " for language " << lang); + string h_path = addName(hpath, lang); + if (!haveLanguageFiles(h_path)) { + // try with '_' replaced by '-' + h_path = addName(hpath, subst(lang, '_', '-')); + if (!haveLanguageFiles(h_path)) { + // FIXME: We should indicate somehow that this language is not + // supported, probably by popping a warning. But we'll need to + // remember which warnings we've issued. + return false; + } } - if (!dict.isReadableFile()) { - LYXERR(Debug::FILES, "Hunspell dictionary file " << dict << " does not exist"); - return 0; + hpath = h_path; + return true; +} + + +bool HunspellChecker::Private::haveDictionary(string const & lang) +{ + bool result = false; + for ( int p = 0; !result && p < maxLookupSelector(); p++ ) { + string lpath = dictPath(p); + result = haveDictionary(lang, lpath); } - Hunspell * h = new Hunspell(affix.absFilename().c_str(), dict.absFilename().c_str()); - spellers_[lang] = h; - return h; + return result; } -Hunspell * HunspellChecker::Private::speller(string const & lang) +Hunspell * HunspellChecker::Private::speller(Language const * lang) { - Spellers::iterator it = spellers_.find(lang); + Spellers::iterator it = spellers_.find(lang->code()); if (it != spellers_.end()) return it->second; - + return addSpeller(lang); } +Hunspell * HunspellChecker::Private::addSpeller(string const & lang,string & path) +{ + if (!haveDictionary(lang, path)) { + spellers_[lang] = 0; + return 0; + } + + FileName const affix(path + ".aff"); + FileName const dict(path + ".dic"); + Hunspell * h = new Hunspell(affix.absFileName().c_str(), dict.absFileName().c_str()); + LYXERR(Debug::FILES, "Hunspell speller for langage " << lang << " at " << dict << " found"); + spellers_[lang] = h; + return h; +} + + +Hunspell * HunspellChecker::Private::addSpeller(Language const * lang) +{ + Hunspell * h = 0; + for ( int p = 0; p < maxLookupSelector() && 0 == h; p++ ) { + string lpath = dictPath(p); + h = addSpeller(lang->code(), lpath); + } + if (0 != h) { + string const encoding = h->get_dic_encoding(); + PersonalWordList * pd = new PersonalWordList(lang->lang()); + pd->load(); + personal_[lang->lang()] = pd; + docstring_list::const_iterator it = pd->begin(); + docstring_list::const_iterator et = pd->end(); + for (; it != et; ++it) { + string const word_to_add = to_iconv_encoding(*it, encoding); + h->add(word_to_add.c_str()); + } + } + return h; +} + + +bool HunspellChecker::Private::isIgnored(WordLangTuple const & wl) const +{ + IgnoreList::const_iterator it = ignored_.begin(); + for (; it != ignored_.end(); ++it) { + if ((*it).lang()->code() != wl.lang()->code()) + continue; + if ((*it).word() == wl.word()) + return true; + } + return false; +} + +/// personal word list interface +void HunspellChecker::Private::remove(WordLangTuple const & wl) +{ + Hunspell * h = speller(wl.lang()); + if (!h) + return; + string const encoding = h->get_dic_encoding(); + string const word_to_check = to_iconv_encoding(wl.word(), encoding); + h->remove(word_to_check.c_str()); + PersonalWordList * pd = personal_[wl.lang()->lang()]; + if (!pd) + return; + pd->remove(wl.word()); +} + + +void HunspellChecker::Private::insert(WordLangTuple const & wl) +{ + Hunspell * h = speller(wl.lang()); + if (!h) + return; + string const encoding = h->get_dic_encoding(); + string const word_to_check = to_iconv_encoding(wl.word(), encoding); + h->add(word_to_check.c_str()); + PersonalWordList * pd = personal_[wl.lang()->lang()]; + if (!pd) + return; + pd->insert(wl.word()); +} + + +bool HunspellChecker::Private::learned(WordLangTuple const & wl) +{ + PersonalWordList * pd = personal_[wl.lang()->lang()]; + if (!pd) + return false; + return pd->exists(wl.word()); +} + + HunspellChecker::HunspellChecker(): d(new Private) { } @@ -115,20 +280,26 @@ HunspellChecker::~HunspellChecker() SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl) { - string const word_to_check = to_utf8(wl.word()); - Hunspell * h = d->speller(wl.lang_code()); + if (d->isIgnored(wl)) + return WORD_OK; + + Hunspell * h = d->speller(wl.lang()); if (!h) - return OK; + return WORD_OK; int info; + + string const encoding = h->get_dic_encoding(); + string const word_to_check = to_iconv_encoding(wl.word(), encoding); + if (h->spell(word_to_check.c_str(), &info)) - return OK; + return d->learned(wl) ? LEARNED_WORD : WORD_OK; if (info & SPELL_COMPOUND) { // FIXME: What to do with that? LYXERR(Debug::FILES, "Hunspell compound word found " << word_to_check); } if (info & SPELL_FORBIDDEN) { - // FIXME: What to do with that? + // This was removed from personal dictionary LYXERR(Debug::FILES, "Hunspell explicit forbidden word found " << word_to_check); } @@ -136,19 +307,33 @@ 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()); - Hunspell * h = d->speller(wl.lang_code()); - if (!h) - return; - h->add(word_to_check.c_str()); + d->insert(wl); + LYXERR(Debug::GUI, "learn word: \"" << wl.word() << "\"") ; + advanceChangeNumber(); +} + + +void HunspellChecker::remove(WordLangTuple const & wl) +{ + d->remove(wl); + LYXERR(Debug::GUI, "unlearn word: \"" << wl.word() << "\"") ; + advanceChangeNumber(); } -void HunspellChecker::accept(WordLangTuple const & word) +void HunspellChecker::accept(WordLangTuple const & wl) { - // FIXME: not implemented! + d->ignored_.push_back(wl); + LYXERR(Debug::GUI, "ignore word: \"" << wl.word() << "\"") ; + advanceChangeNumber(); } @@ -156,19 +341,26 @@ void HunspellChecker::suggest(WordLangTuple const & wl, docstring_list & suggestions) { suggestions.clear(); - string const word_to_check = to_utf8(wl.word()); - Hunspell * h = d->speller(wl.lang_code()); + Hunspell * h = d->speller(wl.lang()); if (!h) return; - char *** suggestion_list = 0; + string const encoding = h->get_dic_encoding(); + string const word_to_check = to_iconv_encoding(wl.word(), encoding); + char ** suggestion_list; + int const suggestion_number = h->suggest(&suggestion_list, word_to_check.c_str()); + if (suggestion_number <= 0) + return; + for (int i = 0; i != suggestion_number; ++i) + suggestions.push_back(from_iconv_encoding(suggestion_list[i], encoding)); + h->free_list(&suggestion_list, suggestion_number); +} - // FIXME: Hunspell::suggest() crashes on Win/MSVC9 - return; - int const suggestion_number = h->suggest(suggestion_list, word_to_check.c_str()); - if (suggestion_number == 0) - return; - h->free_list(suggestion_list, suggestion_number); +bool HunspellChecker::hasDictionary(Language const * lang) const +{ + if (!lang) + return false; + return (d->haveDictionary(lang->code())); }