Am 04.08.2010 um 00:51 schrieb Jean-Marc Lasgouttes: >> The last release I built against 10.5 SDK and used the 10.4 minimum version >> switch. >> So the features of 10.5 are available but the check with >> instancesRespondToSelector cares for 10.4 runtime environment. >> The defines are to care for 10.4 build environments aka 10.4 SDK. This I got >> from this and the next pages: >> http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/cross_development/Configuring/configuring.html >> Since I don't have Tiger myself I couldn't test it and asked on the users >> list - no answer. Maybe we can drop Tiger support completely... > > But if you use the 10.5 sdk, you cannot use 10.6 features, right? Are you > using 10.5 yourself? > > The spellchecker finally decided to work for a reason I do not understand :) > I can indeed check that it becomes > very slow and Shqrk.qpp tells me that all the time is spent in > checkSpellingOfString ;( > > What could we do from there? > > The first solution is to make Paragraph send strings directly to the > spellchecker. This would > require to rework the spellcheck interface to allow to pass full strings. I > do not think > that aspell or enchant allow this, although I could not find a documentation > for enchant :(
That was my only idea too, until now. > > The problem with this approach is that I am not even sure that it will be > enough. I am not sure > whether the time is spent in the setup of the function or in the word > checking itself. I guess its slow because its a complicated client-server beast. > If checking is slow (bad apple!), then we would have to rethink the > on-the-fly checking strategy. > > Abdel, in case you are here: what were the other strategies, and which ones > were doable and would > incur less calls to the spellchecker? > > Anyway, the fact is that I did the checks by pressing page down repeatedly on > a big manual, which > means that the number of words that got checked cannot be made smaller than > they are now :( > > While typing this, I notice that Mail.app only does on the fly checking at > cursor position. I could add a > "isSlow()" method to the spell checking backends and revert to this strategy > for slow backends... JMarc, I sent a patch to support remove of a learned word. Bennett tested it already. Since I got no comments I didn't commit it. I'll send it again for review and it would be good to apply before you start to change things... Stephan
Index: src/EnchantChecker.cpp =================================================================== --- src/EnchantChecker.cpp (Revision 35045) +++ src/EnchantChecker.cpp (Arbeitskopie) @@ -113,12 +113,12 @@ enchant::Dict * m = d->speller(word.lang()->code()); if (!m) - return OK; + return WORD_OK; string utf8word = to_utf8(word.word()); if (m->check(utf8word)) - return OK; + return WORD_OK; return UNKNOWN_WORD; } Index: src/LyXAction.cpp =================================================================== --- src/LyXAction.cpp (Revision 35045) +++ src/LyXAction.cpp (Arbeitskopie) @@ -1016,6 +1016,17 @@ */ { LFUN_SPELLING_IGNORE, "spelling-ignore", ReadOnly, Edit }, /*! + * \var lyx::FuncCode lyx::LFUN_SPELLING_REMOVE + * \li Action: Remove the word under the cursor from the respective + * spell checker dictionary. + * \li Syntax: spelling-remove [<STRING>] [<LANG>] + * \li Params: <WORD>: word to remove + <LANG>: language name (see file languages) + * \li Origin: SWitt, 28 July 2010 + * \endvar + */ + { LFUN_SPELLING_REMOVE, "spelling-remove", ReadOnly, Edit }, +/*! * \var lyx::FuncCode lyx::LFUN_THESAURUS_ENTRY * \li Action: Look up thesaurus entries with respect to the word under the cursor. * \li Syntax: thesaurus-entry [<STRING>] [lang=<LANG>] Index: src/HunspellChecker.cpp =================================================================== --- src/HunspellChecker.cpp (Revision 35045) +++ src/HunspellChecker.cpp (Arbeitskopie) @@ -207,18 +207,18 @@ SpellChecker::Result HunspellChecker::check(WordLangTuple const & wl) { if (d->isIgnored(wl)) - return OK; + return WORD_OK; Hunspell * h = d->speller(wl.lang()->code()); 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 WORD_OK; if (info & SPELL_COMPOUND) { // FIXME: What to do with that? Index: src/AppleSpellChecker.cpp =================================================================== --- src/AppleSpellChecker.cpp (Revision 35045) +++ src/AppleSpellChecker.cpp (Arbeitskopie) @@ -29,6 +29,9 @@ ~Private(); + SpellChecker::Result toResult(SpellCheckResult status); + string toString(SpellCheckResult status); + /// the speller AppleSpeller speller; }; @@ -58,11 +61,26 @@ } +SpellChecker::Result AppleSpellChecker::Private::toResult(SpellCheckResult status) +{ + return status == SPELL_CHECK_FAILED ? UNKNOWN_WORD : + status == SPELL_CHECK_LEARNED ? LEARNED_WORD : WORD_OK ; +} + + +string AppleSpellChecker::Private::toString(SpellCheckResult status) +{ + return status == SPELL_CHECK_FAILED ? "FAILED" : + status == SPELL_CHECK_LEARNED ? "LEARNED" : "OK"; +} + + SpellChecker::Result AppleSpellChecker::check(WordLangTuple const & word) { string const word_str = to_utf8(word.word()); - int const word_ok = checkAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); - return (word_ok) ? OK : UNKNOWN_WORD; + SpellCheckResult result = checkAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); + LYXERR(Debug::GUI, "spellCheck: \"" << word.word() << "\" = " << d->toString(result)) ; + return d->toResult(result); } @@ -70,10 +88,20 @@ void AppleSpellChecker::insert(WordLangTuple const & word) { string const word_str = to_utf8(word.word()); - learnAppleSpeller(d->speller, word_str.c_str(), word.lang()->code().c_str()); + learnAppleSpeller(d->speller, word_str.c_str()); + LYXERR(Debug::GUI, "learn word: \"" << word.word() << "\"") ; } +// remove from personal dictionary +void AppleSpellChecker::remove(WordLangTuple const & word) +{ + string const word_str = to_utf8(word.word()); + unlearnAppleSpeller(d->speller, word_str.c_str()); + LYXERR(Debug::GUI, "unlearn word: \"" << word.word() << "\"") ; +} + + // ignore for session void AppleSpellChecker::accept(WordLangTuple const & word) { Index: src/AppleSpellChecker.h =================================================================== --- src/AppleSpellChecker.h (Revision 35045) +++ src/AppleSpellChecker.h (Arbeitskopie) @@ -27,6 +27,7 @@ enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &); void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); Index: src/EnchantChecker.h =================================================================== --- src/EnchantChecker.h (Revision 35045) +++ src/EnchantChecker.h (Arbeitskopie) @@ -34,6 +34,7 @@ enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &) {}; void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); Index: src/frontends/qt4/Menus.cpp =================================================================== --- src/frontends/qt4/Menus.cpp (Revision 35045) +++ src/frontends/qt4/Menus.cpp (Arbeitskopie) @@ -46,6 +46,7 @@ #include "Paragraph.h" #include "ParIterator.h" #include "Session.h" +#include "SpellChecker.h" #include "TextClass.h" #include "TocBackend.h" #include "Toolbars.h" @@ -730,39 +731,55 @@ void MenuDefinition::expandSpellingSuggestions(BufferView const * bv) { - if (!bv || !lyxrc.spellcheck_continuously) + if (!bv) return; WordLangTuple wl; docstring_list suggestions; pos_type from = bv->cursor().pos(); pos_type to = from; Paragraph const & par = bv->cursor().paragraph(); - if (!par.spellCheck(from, to, wl, suggestions)) - return; - LYXERR(Debug::GUI, "Misspelled Word! Suggested Words = "); - size_t i = 0; - MenuItem item(MenuItem::Submenu, qt_("More Spelling Suggestions")); - item.setSubmenu(MenuDefinition(qt_("More Spelling Suggestions"))); - for (; i != suggestions.size(); ++i) { - docstring const & suggestion = suggestions[i]; - LYXERR(Debug::GUI, suggestion); - MenuItem w(MenuItem::Command, toqstr(suggestion), - FuncRequest(LFUN_WORD_REPLACE, suggestion)); - if (i < 10) - add(w); - else - item.submenu().add(w); + SpellChecker::Result res = par.spellCheck(from, to, wl, suggestions); + switch (res) { + case SpellChecker::UNKNOWN_WORD: + if (lyxrc.spellcheck_continuously) { + LYXERR(Debug::GUI, "Misspelled Word! Suggested Words = "); + size_t i = 0; + MenuItem item(MenuItem::Submenu, qt_("More Spelling Suggestions")); + item.setSubmenu(MenuDefinition(qt_("More Spelling Suggestions"))); + for (; i != suggestions.size(); ++i) { + docstring const & suggestion = suggestions[i]; + LYXERR(Debug::GUI, suggestion); + MenuItem w(MenuItem::Command, toqstr(suggestion), + FuncRequest(LFUN_WORD_REPLACE, suggestion)); + if (i < 10) + add(w); + else + item.submenu().add(w); + } + if (i >= 10) + add(item); + 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"), + FuncRequest(LFUN_SPELLING_ADD, arg))); + add(MenuItem(MenuItem::Command, qt_("Ignore all|I"), + FuncRequest(LFUN_SPELLING_IGNORE, arg))); + } + break; + case SpellChecker::LEARNED_WORD: { + LYXERR(Debug::GUI, "Learned Word."); + docstring const arg = wl.word() + " " + from_ascii(wl.lang()->lang()); + add(MenuItem(MenuItem::Command, qt_("Remove from personal dictionary|r"), + FuncRequest(LFUN_SPELLING_REMOVE, arg))); + } + break; + case SpellChecker::WORD_OK: + case SpellChecker::COMPOUND_WORD: + case SpellChecker::ROOT_FOUND: + case SpellChecker::IGNORED_WORD: + break; } - if (i >= 10) - add(item); - 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"), - FuncRequest(LFUN_SPELLING_ADD, arg))); - add(MenuItem(MenuItem::Command, qt_("Ignore all|I"), - FuncRequest(LFUN_SPELLING_IGNORE, arg))); - } struct sortLanguageByName { Index: src/support/AppleSpeller.m =================================================================== --- src/support/AppleSpeller.m (Revision 35045) +++ src/support/AppleSpeller.m (Arbeitskopie) @@ -68,16 +68,16 @@ } -int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +SpellCheckResult checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang) { if (!speller->checker || !lang || !word) - return 0; + return SPELL_CHECK_FAILED; NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * word_ = toString(speller, word); NSString * lang_ = toString(speller, lang); - NSRange result = [speller->checker + NSRange match = [speller->checker checkSpellingOfString:word_ startingAt:0 language:lang_ @@ -85,11 +85,17 @@ inSpellDocumentWithTag:speller->doctag wordCount:NULL]; + SpellCheckResult result = match.length == 0 ? SPELL_CHECK_OK : SPELL_CHECK_FAILED; + if (result == SPELL_CHECK_OK && [NSSpellChecker instancesRespondToSelector:@selector(hasLearnedWord:)]) { + if ([speller->checker hasLearnedWord:word_]) + result = SPELL_CHECK_LEARNED; + } + [word_ release]; [lang_ release]; [pool release]; - return (result.length ? 0 : 1); + return result; } @@ -166,13 +172,13 @@ } -void learnAppleSpeller(AppleSpeller speller, const char * word, const char * lang) +void learnAppleSpeller(AppleSpeller speller, const char * word) { #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * word_ = toString(speller, word); - if ([NSSpellChecker instancesRespondToSelector:@selector(learnWord)]) + if ([NSSpellChecker instancesRespondToSelector:@selector(learnWord:)]) [speller->checker learnWord:word_]; [word_ release]; @@ -181,13 +187,29 @@ } + +void unlearnAppleSpeller(AppleSpeller speller, const char * word) +{ +#if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) + NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; + NSString * word_ = toString(speller, word); + + if ([NSSpellChecker instancesRespondToSelector:@selector(unlearnWord:)]) + [speller->checker unlearnWord:word_]; + + [word_ release]; + [pool release]; +#endif +} + + int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang) { BOOL result = NO; #if defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && (__MAC_OS_X_VERSION_MAX_ALLOWED >= 1050) NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; NSString * lang_ = toString(speller, lang); - if ([NSSpellChecker instancesRespondToSelector:@selector(availableLanguages)]) { + if ([NSSpellChecker instancesRespondToSelector:@selector(availableLanguages:)]) { NSArray * languages = [speller->checker availableLanguages]; for (NSString *element in languages) { Index: src/support/AppleSpeller.h =================================================================== --- src/support/AppleSpeller.h (Revision 35045) +++ src/support/AppleSpeller.h (Arbeitskopie) @@ -16,16 +16,24 @@ extern "C" { #endif +typedef enum SpellCheckResult { + SPELL_CHECK_FAILED, + SPELL_CHECK_OK, + SPELL_CHECK_IGNORED, + SPELL_CHECK_LEARNED +} SpellCheckResult ; + typedef struct AppleSpellerRec * AppleSpeller ; AppleSpeller newAppleSpeller(void); void freeAppleSpeller(AppleSpeller speller); -int checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +SpellCheckResult checkAppleSpeller(AppleSpeller speller, const char * word, const char * lang); void ignoreAppleSpeller(AppleSpeller speller, const char * word, const char * lang); size_t makeSuggestionAppleSpeller(AppleSpeller speller, const char * word, const char * lang); const char * getSuggestionAppleSpeller(AppleSpeller speller, size_t pos); -void learnAppleSpeller(AppleSpeller speller, const char * word, const char * lang); +void learnAppleSpeller(AppleSpeller speller, const char * word); +void unlearnAppleSpeller(AppleSpeller speller, const char * word); int hasLanguageAppleSpeller(AppleSpeller speller, const char * lang); #ifdef __cplusplus Index: src/Paragraph.cpp =================================================================== --- src/Paragraph.cpp (Revision 35045) +++ src/Paragraph.cpp (Arbeitskopie) @@ -3169,19 +3169,19 @@ } -bool Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, +SpellChecker::Result Paragraph::spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, docstring_list & suggestions, bool do_suggestion) const { SpellChecker * speller = theSpellChecker(); if (!speller) - return false; + return SpellChecker::WORD_OK; if (!d->layout_->spellcheck || !inInset().allowSpellCheck()) - return false; + return SpellChecker::WORD_OK; locateWord(from, to, WHOLE_WORD); if (from == to || from >= pos_type(d->text_.size())) - return false; + return SpellChecker::WORD_OK; docstring word = asString(from, to, AS_STR_INSETS); // Ignore words with digits @@ -3200,29 +3200,19 @@ } wl = WordLangTuple(word, lang); SpellChecker::Result res = ignored ? - SpellChecker::OK : speller->check(wl); -#if 0 -// FIXME: the code below makes aspell abort if a word in an unknown -// language is checked. - // Just ignore any error that the spellchecker reports. - // FIXME: we should through out an exception and catch it in the GUI to - // display the error. - if (!speller->error().empty()) - return false; -#endif + SpellChecker::WORD_OK : speller->check(wl); - bool const misspelled = res != SpellChecker::OK - && res != SpellChecker::IGNORED_WORD; + bool const misspelled_ = SpellChecker::misspelled(res) ; if (lyxrc.spellcheck_continuously) - d->fontlist_.setMisspelled(from, to, misspelled); + d->fontlist_.setMisspelled(from, to, misspelled_); - if (misspelled && do_suggestion) + if (misspelled_ && do_suggestion) speller->suggest(wl, suggestions); else suggestions.clear(); - return misspelled; + return res; } @@ -3232,7 +3222,8 @@ pos_type to = pos; WordLangTuple wl; docstring_list suggestions; - return spellCheck(from, to, wl, suggestions, false); + SpellChecker::Result res = spellCheck(from, to, wl, suggestions, false); + return SpellChecker::misspelled(res) ; } Index: src/AspellChecker.cpp =================================================================== --- src/AspellChecker.cpp (Revision 35045) +++ src/AspellChecker.cpp (Arbeitskopie) @@ -266,17 +266,17 @@ d->speller(word.lang()->code(), word.lang()->variety()); if (!m) - return OK; + return WORD_OK; if (word.word().empty()) // MSVC compiled Aspell doesn't like it. - return OK; + return WORD_OK; string const word_str = to_utf8(word.word()); int const word_ok = aspell_speller_check(m, word_str.c_str(), -1); LASSERT(word_ok != -1, /**/); - return (word_ok) ? OK : UNKNOWN_WORD; + return (word_ok) ? WORD_OK : UNKNOWN_WORD; } Index: src/Text3.cpp =================================================================== --- src/Text3.cpp (Revision 35045) +++ src/Text3.cpp (Arbeitskopie) @@ -2044,6 +2044,25 @@ break; } + case LFUN_SPELLING_REMOVE: { + docstring word = from_utf8(cmd.getArg(0)); + Language * lang; + if (word.empty()) { + word = cur.selectionAsString(false); + // FIXME + if (word.size() > 100 || word.empty()) { + // Get word or selection + selectWordWhenUnderCursor(cur, WHOLE_WORD); + word = cur.selectionAsString(false); + } + lang = const_cast<Language *>(cur.getFont().language()); + } else + lang = const_cast<Language *>(languages.getLanguage(cmd.getArg(1))); + WordLangTuple wl(word, lang); + theSpellChecker()->remove(wl); + break; + } + case LFUN_PARAGRAPH_PARAMS_APPLY: { // Given data, an encoding of the ParagraphParameters // generated in the Paragraph dialog, this function sets @@ -2591,6 +2610,7 @@ case LFUN_SPELLING_ADD: case LFUN_SPELLING_IGNORE: + case LFUN_SPELLING_REMOVE: enable = theSpellChecker(); break; Index: src/FuncCode.h =================================================================== --- src/FuncCode.h (Revision 35045) +++ src/FuncCode.h (Arbeitskopie) @@ -444,6 +444,7 @@ LFUN_SPELLING_ADD, // spitz 20100118 LFUN_SPELLING_IGNORE, // spitz 20100118 // 345 + LFUN_SPELLING_REMOVE, // switt 20100728 LFUN_PREVIEW_INSERT, // vfr, 20100328 LFUN_FORWARD_SEARCH, LFUN_INSET_COPY_AS, // vfr, 20100419 Index: src/SpellChecker.h =================================================================== --- src/SpellChecker.h (Revision 35045) +++ src/SpellChecker.h (Arbeitskopie) @@ -32,19 +32,27 @@ /// the result from checking a single word enum Result { /// word is correct - OK = 1, + WORD_OK = 1, /// root of given word was found - ROOT, + ROOT_FOUND, /// word found through compound formation COMPOUND_WORD, /// word not found UNKNOWN_WORD, /// number of other ignored "word" - IGNORED_WORD + IGNORED_WORD, + /// number of personal dictionary "word" + LEARNED_WORD }; virtual ~SpellChecker() {} + /// does the spell check failed + static bool misspelled(Result res) { + return res != SpellChecker::WORD_OK + && res != SpellChecker::IGNORED_WORD + && res != SpellChecker::LEARNED_WORD; } + /// check the given word of the given lang code and return the result virtual enum Result check(WordLangTuple const &) = 0; @@ -54,6 +62,9 @@ /// insert the given word into the personal dictionary virtual void insert(WordLangTuple const &) = 0; + /// remove the given word from the personal dictionary + virtual void remove(WordLangTuple const &) = 0; + /// accept the given word temporarily virtual void accept(WordLangTuple const &) = 0; Index: src/Buffer.cpp =================================================================== --- src/Buffer.cpp (Revision 35045) +++ src/Buffer.cpp (Arbeitskopie) @@ -3995,7 +3995,8 @@ if (from == end) break; to = from; - if (from.paragraph().spellCheck(from.pos(), to.pos(), wl, suggestions)) { + SpellChecker::Result res = from.paragraph().spellCheck(from.pos(), to.pos(), wl, suggestions); + if (SpellChecker::misspelled(res)) { word_lang = wl; break; } Index: src/AspellChecker.h =================================================================== --- src/AspellChecker.h (Revision 35045) +++ src/AspellChecker.h (Arbeitskopie) @@ -28,6 +28,7 @@ enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &) {}; void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); Index: src/Paragraph.h =================================================================== --- src/Paragraph.h (Revision 35045) +++ src/Paragraph.h (Arbeitskopie) @@ -17,6 +17,7 @@ #define PARAGRAPH_H #include "FontEnums.h" +#include "SpellChecker.h" #include "insets/InsetCode.h" @@ -422,8 +423,8 @@ /// Spellcheck word at position \p from and fill in found misspelled word /// and \p suggestions if \p do_suggestion is true. - /// \return true if pointed word is misspelled. - bool spellCheck(pos_type & from, pos_type & to, WordLangTuple & wl, + /// \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; /// Spellcheck word at position \p pos. Index: src/HunspellChecker.h =================================================================== --- src/HunspellChecker.h (Revision 35045) +++ src/HunspellChecker.h (Arbeitskopie) @@ -28,6 +28,7 @@ enum Result check(WordLangTuple const &); void suggest(WordLangTuple const &, docstring_list &); void insert(WordLangTuple const &); + void remove(WordLangTuple const &) {}; void accept(WordLangTuple const &); bool hasDictionary(Language const * lang) const; docstring const error(); Index: RELEASE-NOTES =================================================================== --- RELEASE-NOTES (Revision 35045) +++ RELEASE-NOTES (Arbeitskopie) @@ -84,6 +84,8 @@ - LFUN_SPELLING_IGNORE ("spelling-ignore"). +- LFUN_SPELLING_REMOVE ("spelling-remove"). + - LFUN_PREVIEW_INSERT ("preview-insert"). - LFUN_FORWARD_SEARCH ("forward-search").