Fix spellchecker dictionary logic.

This fixes #6.
This commit is contained in:
wolfbeast 2017-08-01 16:06:43 +02:00 • committed by Roy Tam
commit 3d983be76c
2 changed files with 118 additions and 185 deletions

View file

@ -594,45 +594,24 @@ nsEditorSpellCheck::SetCurrentDictionary(const nsAString& aDictionary)
// Ignore pending dictionary fetchers by increasing this number. // Ignore pending dictionary fetchers by increasing this number.
mDictionaryFetcherGroup++; mDictionaryFetcherGroup++;
uint32_t flags = 0; if (mPreferredLang.IsEmpty() ||
mEditor->GetFlags(&flags); !mPreferredLang.Equals(aDictionary, nsCaseInsensitiveStringComparator())) {
if (!(flags & nsIPlaintextEditor::eEditorMailMask)) { // When user sets dictionary manually, we store this value associated
if (!aDictionary.IsEmpty() && (mPreferredLang.IsEmpty() || // with editor url, if it doesn't match the document language exactly.
!mPreferredLang.Equals(aDictionary, // For example on "en" sites, we need to store "en-GB", otherwise
nsCaseInsensitiveStringComparator()))) { // the language might jump back to en-US although the user explicitly
// When user sets dictionary manually, we store this value associated // chose otherwise.
// with editor url, if it doesn't match the document language exactly. StoreCurrentDictionary(mEditor, aDictionary);
// For example on "en" sites, we need to store "en-GB", otherwise } else {
// the language might jump back to en-US although the user explicitly // If user sets a dictionary matching the language defined by
// chose otherwise. // document, we consider content pref has been canceled, and we clear it.
StoreCurrentDictionary(mEditor, aDictionary); ClearCurrentDictionary(mEditor);
#ifdef DEBUG_DICT
printf("***** Writing content preferences for |%s|\n",
NS_ConvertUTF16toUTF8(aDictionary).get());
#endif
} else {
// If user sets a dictionary matching the language defined by
// document, we consider content pref has been canceled, and we clear it.
ClearCurrentDictionary(mEditor);
#ifdef DEBUG_DICT
printf("***** Clearing content preferences for |%s|\n",
NS_ConvertUTF16toUTF8(aDictionary).get());
#endif
}
// Also store it in as a preference, so we can use it as a fallback.
// We don't want this for mail composer because it uses
// "spellchecker.dictionary" as a preference.
Preferences::SetString("spellchecker.dictionary", aDictionary);
#ifdef DEBUG_DICT
printf("***** Storing spellchecker.dictionary |%s|\n",
NS_ConvertUTF16toUTF8(aDictionary).get());
#endif
} }
} }
return mSpellChecker->SetCurrentDictionary(aDictionary); return mSpellChecker->SetCurrentDictionary(aDictionary);
} }
NS_IMETHODIMP NS_IMETHODIMP
nsEditorSpellCheck::UninitSpellChecker() nsEditorSpellCheck::UninitSpellChecker()
{ {
@ -771,23 +750,16 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
/* /*
* We try to derive the dictionary to use based on the following priorities: * We try to derive the dictionary to use based on the following priorities:
* 1) Content preference, so the language the user set for the site before. * 1) Content preference: the language the user set for the site before.
* (Introduced in bug 678842 and corrected in bug 717433.) * 2) The value of "spellchecker.dictionary.override" which reflects a
* 2) Language set by the website, or any other dictionary that partly * global choice of language explicitly set by the user.
* matches that. (Introduced in bug 338427.) * 3) Language set by the website, or any other dictionary that partly matches that.
* Eg. if the website is "en-GB", a user who only has "en-US" will get * Eg. if the website is "en-GB", a user who only has "en-US" will get that.
* that. If the website is generic "en", the user will get one of the * If the website is generic "en", the user will get one of the "en-*" installed,
* "en-*" installed, (almost) at random. * pretty much at random.
* However, we prefer what is stored in "spellchecker.dictionary", * 4) The user's locale
* so if the user chose "en-AU" before, they will get "en-AU" on a plain * 5) Leave the current dictionary set.
* "en" site. (Introduced in bug 682564.) * 6) The content of the "LANG" environment variable (if set)
* 3) The value of "spellchecker.dictionary" which reflects a previous
* language choice of the user (on another site).
* (This was the original behaviour before the aforementioned bugs
* landed).
* 4) The user's locale.
* 5) Use the current dictionary that is currently set.
* 6) The content of the "LANG" environment variable (if set).
* 7) The first spell check dictionary installed. * 7) The first spell check dictionary installed.
*/ */
@ -795,131 +767,100 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
// https://html.spec.whatwg.org/#attr-lang // https://html.spec.whatwg.org/#attr-lang
// This is used in SetCurrentDictionary. // This is used in SetCurrentDictionary.
mPreferredLang.Assign(aFetcher->mRootContentLang); mPreferredLang.Assign(aFetcher->mRootContentLang);
#ifdef DEBUG_DICT
printf("***** mPreferredLang (element) |%s|\n",
NS_ConvertUTF16toUTF8(mPreferredLang).get());
#endif
// If no luck, try the "Content-Language" header. // If no luck, try the "Content-Language" header.
if (mPreferredLang.IsEmpty()) { if (mPreferredLang.IsEmpty()) {
mPreferredLang.Assign(aFetcher->mRootDocContentLang); mPreferredLang.Assign(aFetcher->mRootDocContentLang);
#ifdef DEBUG_DICT }
printf("***** mPreferredLang (content-language) |%s|\n",
NS_ConvertUTF16toUTF8(mPreferredLang).get()); // Priority 1:
#endif // Get language from content prefs, if set.
// If we successfully fetched a dictionary from content prefs, do not go
// further. Use this exact dictionary.
nsAutoString dictName;
dictName.Assign(aFetcher->mDictionary);
if (!dictName.IsEmpty()) {
if (NS_SUCCEEDED(SetCurrentDictionary(dictName))) {
// We take an early exit here, so clear the suggested word list.
DeleteSuggestedWordList();
return NS_OK;
}
// Maybe the dictionary was uninstalled ?
// Clear the content preference and continue.
ClearCurrentDictionary(mEditor);
}
// Priority 2:
// Get global preferred language from preferences, if set.
nsAutoString preferredDict;
preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary.override");
if (!preferredDict.IsEmpty()) {
dictName.Assign(preferredDict);
} }
// Auxiliary status. // Priority 3:
// Get language from element/doc, if set.
if (dictName.IsEmpty() && !mPreferredLang.IsEmpty()) {
dictName.Assign(mPreferredLang);
}
// Auxiliary status value
nsresult rv2; nsresult rv2;
// We obtain a list of available dictionaries. // Obtain a list of available dictionaries to check against.
nsTArray<nsString> dictList; nsTArray<nsString> dictList;
rv2 = mSpellChecker->GetDictionaryList(&dictList); rv2 = mSpellChecker->GetDictionaryList(&dictList);
NS_ENSURE_SUCCESS(rv2, rv2); NS_ENSURE_SUCCESS(rv2, rv2);
int32_t i, dictCount = dictList.Length();
// Priority 1:
// If we successfully fetched a dictionary from content prefs, do not go // The following will be driven by this status. Once we are able to set a
// further. Use this exact dictionary.
// Don't use content preferences for editor with eEditorMailMask flag.
nsAutoString dictName;
uint32_t flags;
mEditor->GetFlags(&flags);
if (!(flags & nsIPlaintextEditor::eEditorMailMask)) {
dictName.Assign(aFetcher->mDictionary);
if (!dictName.IsEmpty()) {
if (NS_SUCCEEDED(TryDictionary(dictName, dictList, DICT_NORMAL_COMPARE))) {
#ifdef DEBUG_DICT
printf("***** Assigned from content preferences |%s|\n",
NS_ConvertUTF16toUTF8(dictName).get());
#endif
// We take an early exit here, so let's not forget to clear the word
// list.
DeleteSuggestedWordList();
return NS_OK;
}
// May be dictionary was uninstalled ?
// Clear the content preference and continue.
ClearCurrentDictionary(mEditor);
}
}
// Priority 2:
// After checking the content preferences, we use the language of the element
// or document.
dictName.Assign(mPreferredLang);
#ifdef DEBUG_DICT
printf("***** Assigned from element/doc |%s|\n",
NS_ConvertUTF16toUTF8(dictName).get());
#endif
// Get the preference value.
nsAutoString preferredDict;
preferredDict = Preferences::GetLocalizedString("spellchecker.dictionary");
// The following will be driven by this status. Once we were able to set a
// dictionary successfully, we're done. So we start with a "failed" status. // dictionary successfully, we're done. So we start with a "failed" status.
nsresult rv = NS_ERROR_NOT_AVAILABLE; nsresult rv = NS_ERROR_FAILURE;
if (!dictName.IsEmpty()) { if (!dictName.IsEmpty()) {
// RFC 5646 explicitly states that matches should be case-insensitive. for (i = 0; i < dictCount; i++) {
rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE); nsAutoString dictStr(dictList.ElementAt(i));
if (dictName.Equals(dictStr, nsCaseInsensitiveStringComparator())) {
if (NS_FAILED(rv)) { // First let's correct any problems due to non-matching case.
#ifdef DEBUG_DICT // This applies to both a user-set override and document language.
printf("***** Setting of |%s| failed (or it wasn't available)\n", // RFC 5646 explicitly states that matches should be case-insensitive.
NS_ConvertUTF16toUTF8(dictName).get()); dictName.Assign(dictStr);
#endif // Try to set it. Doing this inside this loop avoids trying to set a
// dictionary that isn't available.
rv = SetCurrentDictionary(dictName);
break;
}
}
if (NS_FAILED(rv)) {
// Required dictionary was not available. Try to get a dictionary // Required dictionary was not available. Try to get a dictionary
// matching at least language part of dictName. // matching at least the language part of dictName:
nsAutoString langCode; nsAutoString langCode;
int32_t dashIdx = dictName.FindChar('-'); int32_t dashIdx = dictName.FindChar('-');
if (dashIdx != -1) { if (dashIdx != -1) {
langCode.Assign(Substring(dictName, 0, dashIdx)); langCode.Assign(Substring(dictName, 0, dashIdx));
} else { } else {
langCode.Assign(dictName); langCode.Assign(dictName);
} }
// Try dictionary.spellchecker preference, if it starts with langCode, nsDefaultStringComparator comparator;
// so we don't just get any random dictionary matching the language.
if (!preferredDict.IsEmpty() &&
nsStyleUtil::DashMatchCompare(preferredDict, langCode, nsDefaultStringComparator())) {
#ifdef DEBUG_DICT
printf("***** Trying preference value |%s| since it matches language code\n",
NS_ConvertUTF16toUTF8(preferredDict).get());
#endif
rv = TryDictionary (preferredDict, dictList,
DICT_COMPARE_CASE_INSENSITIVE);
}
if (NS_FAILED(rv)) { // Loop over available dictionaries; if we find one with the required
// Use any dictionary with the required language. // language, use it.
#ifdef DEBUG_DICT for (i = 0; i < dictCount; i++) {
printf("***** Trying to find match for language code |%s|\n", nsAutoString dictStr(dictList.ElementAt(i));
NS_ConvertUTF16toUTF8(langCode).get()); if (nsStyleUtil::DashMatchCompare(dictStr, langCode, comparator) &&
#endif NS_SUCCEEDED(rv = SetCurrentDictionary(dictStr))) {
rv = TryDictionary (langCode, dictList, DICT_COMPARE_DASHMATCH); break;
}
} }
} }
} }
// Priority 3:
// If the document didn't supply a dictionary or the setting failed,
// try the user preference next.
if (NS_FAILED(rv)) {
if (!preferredDict.IsEmpty()) {
#ifdef DEBUG_DICT
printf("***** Trying preference value |%s|\n",
NS_ConvertUTF16toUTF8(preferredDict).get());
#endif
rv = TryDictionary (preferredDict, dictList, DICT_NORMAL_COMPARE);
}
}
// Priority 4: // Priority 4:
// As next fallback, try the current locale. // Content prefs, override and document didn't give us a valid dictionary
if (NS_FAILED(rv)) { // name, so we just get the current locale and try to use that.
if (NS_FAILED (rv)) {
nsCOMPtr<nsIXULChromeRegistry> packageRegistry = nsCOMPtr<nsIXULChromeRegistry> packageRegistry =
mozilla::services::GetXULChromeRegistryService(); mozilla::services::GetXULChromeRegistryService();
@ -929,69 +870,57 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
false, utf8DictName); false, utf8DictName);
dictName.Assign(EmptyString()); dictName.Assign(EmptyString());
AppendUTF8toUTF16(utf8DictName, dictName); AppendUTF8toUTF16(utf8DictName, dictName);
#ifdef DEBUG_DICT // Try to set it, if it's in the list.
printf("***** Trying locale |%s|\n", for (i = 0; i < dictCount; i++) {
NS_ConvertUTF16toUTF8(dictName).get()); nsAutoString dictStr(dictList.ElementAt(i));
#endif if (dictStr.Equals(dictName)) {
rv = TryDictionary (dictName, dictList, DICT_COMPARE_CASE_INSENSITIVE); rv = SetCurrentDictionary(dictName);
break;
}
}
} }
} }
if (NS_FAILED(rv)) { if (NS_FAILED(rv)) {
// Still no success. // Still no success. Further fallback attempts required.
// Priority 5: // Priority 5:
// If we have a current dictionary, don't try anything else. // If we have a current dictionary, don't try anything else.
nsAutoString currentDictionary; nsAutoString currentDictionary;
rv2 = GetCurrentDictionary(currentDictionary); rv2 = GetCurrentDictionary(currentDictionary);
#ifdef DEBUG_DICT
if (NS_SUCCEEDED(rv2)) {
printf("***** Retrieved current dict |%s|\n",
NS_ConvertUTF16toUTF8(currentDictionary).get());
}
#endif
if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) { if (NS_FAILED(rv2) || currentDictionary.IsEmpty()) {
// We don't have a current dictionary.
// Priority 6: // Priority 6:
// Try to get current dictionary from environment variable LANG. // Try to get current dictionary from environment variable LANG.
// LANG = language[_territory][.charset] // LANG = language[_territory][.codeset]
char* env_lang = getenv("LANG"); char* env_lang = getenv("LANG");
if (env_lang) { if (env_lang != nullptr) {
nsString lang = NS_ConvertUTF8toUTF16(env_lang); nsString lang = NS_ConvertUTF8toUTF16(env_lang);
// Strip trailing charset, if there is any. // Strip trailing charset, if there is any.
int32_t dot_pos = lang.FindChar('.'); int32_t dot_pos = lang.FindChar('.');
if (dot_pos != -1) { if (dot_pos != -1) {
lang = Substring(lang, 0, dot_pos); lang = Substring(lang, 0, dot_pos);
} }
// Convert underscore to dash.
int32_t underScore = lang.FindChar('_'); int32_t underScore = lang.FindChar('_');
if (underScore != -1) { if (underScore != -1) {
lang.Replace(underScore, 1, '-'); lang.Replace(underScore, 1, '-');
#ifdef DEBUG_DICT // Only attempt to set if a _territory is present.
printf("***** Trying LANG from environment |%s|\n", rv = SetCurrentDictionary(lang);
NS_ConvertUTF16toUTF8(lang).get());
#endif
nsAutoString lang2;
lang2.Assign(lang);
rv = TryDictionary(lang2, dictList, DICT_COMPARE_CASE_INSENSITIVE);
} }
} }
// Priority 7: // Priority 7:
// If it does not work, pick the first one. // If LANG does not work either, pick the first one.
if (NS_FAILED(rv) && !dictList.IsEmpty()) { if (NS_FAILED(rv)) {
nsAutoString firstInList; if (dictList.Length() > 0) {
firstInList.Assign(dictList[0]); rv = SetCurrentDictionary(dictList[0]);
rv = TryDictionary(firstInList, dictList, DICT_NORMAL_COMPARE);
#ifdef DEBUG_DICT
printf("***** Trying first of list |%s|\n",
NS_ConvertUTF16toUTF8(dictList[0]).get());
if (NS_SUCCEEDED(rv)) {
printf ("***** Setting worked.\n");
} }
#endif
} }
} }
} }
// If an error was thrown while setting the dictionary, just // If an error was thrown while setting the dictionary, just
@ -999,6 +928,7 @@ nsEditorSpellCheck::DictionaryFetched(DictionaryFetcher* aFetcher)
// up. The user can manually reset the language to their choice on // up. The user can manually reset the language to their choice on
// the dialog if it is wrong. // the dialog if it is wrong.
// Dictionary has changed, so delete the suggested word list.
DeleteSuggestedWordList(); DeleteSuggestedWordList();
return NS_OK; return NS_OK;

View file

@ -1117,6 +1117,9 @@ pref("print.print_via_parent", false);
// in a document. // in a document.
pref("extensions.spellcheck.inline.max-misspellings", 500); pref("extensions.spellcheck.inline.max-misspellings", 500);
// Predefined convenience pref for overriding the dictionary
pref("spellchecker.dictionary.override", "");
// Prefs used by libeditor. Prefs specific to seamonkey composer // Prefs used by libeditor. Prefs specific to seamonkey composer
// belong in comm-central/editor/ui/composer.js // belong in comm-central/editor/ui/composer.js