Fix handling of empty spellcheck language string (#1098)
This commit is contained in:
@@ -25,7 +25,7 @@ jobs:
|
||||
- name: Install Packages (apt)
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install libenchant-dev qttools5-dev-tools
|
||||
sudo apt install libenchant-dev qttools5-dev-tools aspell-en
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Install Dependencies (pip)
|
||||
|
||||
@@ -360,7 +360,7 @@ class Config:
|
||||
# Check the availability of optional packages
|
||||
self._checkOptionalPackages()
|
||||
|
||||
if self.spellLanguage is None:
|
||||
if not self.spellLanguage:
|
||||
self.spellLanguage = "en"
|
||||
|
||||
# Look for a PDF version of the manual
|
||||
|
||||
@@ -26,6 +26,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import os
|
||||
import logging
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from novelwriter.error import logException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -46,36 +48,47 @@ class NWSpellEnchant():
|
||||
return
|
||||
|
||||
##
|
||||
# Getters and Setters
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def spellLanguage(self):
|
||||
return self._spellLanguage
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
|
||||
def setLanguage(self, theLang, projectDict=None):
|
||||
"""Load a dictionary for the language specified in the config.
|
||||
If that fails, we load a mock dictionary so that lookups don't
|
||||
crash.
|
||||
crash. Note that enchant will allow loading an empty string as
|
||||
a tag, but this will fail later on. See issue #1096.
|
||||
"""
|
||||
self._theBroker = None
|
||||
self._theDict = None
|
||||
self._spellLanguage = None
|
||||
|
||||
try:
|
||||
import enchant
|
||||
if self._theBroker is not None:
|
||||
logger.debug("Deleting old pyenchant broker")
|
||||
del self._theBroker
|
||||
|
||||
self._theBroker = enchant.Broker()
|
||||
self._theDict = self._theBroker.request_dict(theLang)
|
||||
self._spellLanguage = theLang
|
||||
logger.debug("Enchant spell checking for language '%s' loaded", theLang)
|
||||
if theLang and enchant.dict_exists(theLang):
|
||||
self._theBroker = enchant.Broker()
|
||||
self._theDict = self._theBroker.request_dict(theLang)
|
||||
self._spellLanguage = theLang
|
||||
logger.debug("Enchant spell checking for language '%s' loaded", theLang)
|
||||
else:
|
||||
logger.warning("Enchant found no dictionary for language '%s'", theLang)
|
||||
|
||||
except Exception:
|
||||
logger.error("Failed to load enchant spell checking for language '%s'", theLang)
|
||||
self._theDict = FakeEnchant()
|
||||
self._spellLanguage = None
|
||||
|
||||
self._readProjectDictionary(projectDict)
|
||||
for pWord in self._projDict:
|
||||
self._theDict.add_to_session(pWord)
|
||||
if self._theDict is None:
|
||||
self._theDict = FakeEnchant()
|
||||
else:
|
||||
self._readProjectDictionary(projectDict)
|
||||
for pWord in self._projDict:
|
||||
self._theDict.add_to_session(pWord)
|
||||
|
||||
return
|
||||
|
||||
@@ -189,6 +202,9 @@ class FakeEnchant:
|
||||
"""Fallback for when Enchant is selected, but not installed.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.tag = ""
|
||||
self.provider = namedtuple("provider", "name")
|
||||
self.provider.name = ""
|
||||
return
|
||||
|
||||
def check(self, theWord):
|
||||
|
||||
@@ -718,7 +718,7 @@ class GuiDocEditor(QTextEdit):
|
||||
), nwAlert.INFO)
|
||||
theMode = False
|
||||
|
||||
if self.spEnchant.spellLanguage() is None:
|
||||
if self.spEnchant.spellLanguage is None:
|
||||
theMode = False
|
||||
|
||||
self._spellCheck = theMode
|
||||
|
||||
@@ -26,29 +26,57 @@ import pytest
|
||||
from mock import causeOSError
|
||||
from tools import readFile, writeFile
|
||||
|
||||
from novelwriter.core.spellcheck import NWSpellEnchant
|
||||
from novelwriter.core.spellcheck import FakeEnchant, NWSpellEnchant
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreSpell_Enchant(monkeypatch, tmpDir):
|
||||
"""Test the pyenchant spell checker
|
||||
def testCoreSpell_FakeEnchant(monkeypatch):
|
||||
"""Test the FakeEnchant spell checker fallback.
|
||||
"""
|
||||
wList = os.path.join(tmpDir, "wordlist.txt")
|
||||
writeFile(wList, "a_word\nb_word\nc_word\n")
|
||||
|
||||
# Block the enchant package (and trigger the default class)
|
||||
# Make package import fail
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setitem(sys.modules, "enchant", None)
|
||||
spChk = NWSpellEnchant()
|
||||
spChk.setLanguage("en_US", "")
|
||||
assert isinstance(spChk._theDict, FakeEnchant)
|
||||
|
||||
spChk.setLanguage("en", wList)
|
||||
assert spChk.setLanguage("", "") is None
|
||||
assert spChk.checkWord("") is True
|
||||
assert spChk.suggestWords("") == []
|
||||
# Request a non-existent dictionary
|
||||
spChk = NWSpellEnchant()
|
||||
spChk.setLanguage("whatchamajig", "")
|
||||
assert isinstance(spChk._theDict, FakeEnchant)
|
||||
|
||||
# Request an emety language string
|
||||
# See issue https://github.com/vkbo/novelWriter/issues/1096
|
||||
spChk = NWSpellEnchant()
|
||||
spChk.setLanguage("", "")
|
||||
assert isinstance(spChk._theDict, FakeEnchant)
|
||||
|
||||
# FakeEnchant should handle requests
|
||||
fkChk = FakeEnchant()
|
||||
assert fkChk.tag == ""
|
||||
assert fkChk.provider.name == ""
|
||||
assert fkChk.check("whatchamajig") is True
|
||||
assert fkChk.suggest("whatchamajig") == []
|
||||
assert fkChk.add_to_session("whatchamajig") is None
|
||||
|
||||
# END Test testCoreSpell_FakeEnchant
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreSpell_Enchant(monkeypatch, fncDir):
|
||||
"""Test the pyenchant spell checker.
|
||||
"""
|
||||
wList = os.path.join(fncDir, "wordlist.txt")
|
||||
writeFile(wList, "a_word\nb_word\nc_word\n")
|
||||
|
||||
# Break the enchant package, and check error handling
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setitem(sys.modules, "enchant", None)
|
||||
spChk = NWSpellEnchant()
|
||||
assert spChk.listDictionaries() == []
|
||||
assert spChk.describeDict() == ("", "")
|
||||
|
||||
# Break the enchant package, and check error handling
|
||||
# Set the dict to None, and check dictionary call error handling
|
||||
spChk = NWSpellEnchant()
|
||||
spChk.theDict = None
|
||||
assert spChk.checkWord("word") is True
|
||||
@@ -57,8 +85,9 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir):
|
||||
|
||||
# Load the proper enchant package (twice)
|
||||
spChk = NWSpellEnchant()
|
||||
spChk.setLanguage("en", wList)
|
||||
spChk.setLanguage("en", wList)
|
||||
spChk.setLanguage("en_US", wList)
|
||||
spChk.setLanguage("en_US", wList)
|
||||
assert spChk.spellLanguage == "en_US"
|
||||
|
||||
# Add a word to the user's dictionary
|
||||
assert spChk._readProjectDictionary("stuff") is False
|
||||
@@ -98,7 +127,39 @@ def testCoreSpell_Enchant(monkeypatch, tmpDir):
|
||||
assert len(dList) > 0
|
||||
|
||||
aTag, aName = spChk.describeDict()
|
||||
assert aTag == "en"
|
||||
assert aTag == "en_US"
|
||||
assert aName != ""
|
||||
|
||||
# END Test testCoreSpell_Enchant
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreSpell_SessionWords(fncDir):
|
||||
"""Test the handling of the custom word list in the spell checker.
|
||||
New project sessions should not inherit the project word list from
|
||||
other sessions, so this test checks that they don't bleed through.
|
||||
"""
|
||||
wList1 = os.path.join(fncDir, "wordlist1.txt")
|
||||
wList2 = os.path.join(fncDir, "wordlist2.txt")
|
||||
writeFile(wList1, "a_word\nb_word\nc_word\n")
|
||||
writeFile(wList2, "d_word\ne_word\nf_word\n")
|
||||
|
||||
spChk = NWSpellEnchant()
|
||||
|
||||
spChk.setLanguage("en_US", wList1)
|
||||
assert spChk.checkWord("a_word") is True
|
||||
assert spChk.checkWord("b_word") is True
|
||||
assert spChk.checkWord("c_word") is True
|
||||
assert spChk.checkWord("d_word") is False
|
||||
assert spChk.checkWord("e_word") is False
|
||||
assert spChk.checkWord("f_word") is False
|
||||
|
||||
spChk.setLanguage("en_US", wList2)
|
||||
assert spChk.checkWord("a_word") is False
|
||||
assert spChk.checkWord("b_word") is False
|
||||
assert spChk.checkWord("c_word") is False
|
||||
assert spChk.checkWord("d_word") is True
|
||||
assert spChk.checkWord("e_word") is True
|
||||
assert spChk.checkWord("f_word") is True
|
||||
|
||||
# END Test testCoreSpell_SessionWords
|
||||
|
||||
@@ -439,7 +439,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir):
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_nwProject.nwx")
|
||||
compFile = os.path.join(refDir, "guiEditor_Main_Final_nwProject.nwx")
|
||||
copyfile(projFile, testFile)
|
||||
assert cmpFiles(testFile, compFile, [2, 6, 7, 8])
|
||||
assert cmpFiles(testFile, compFile, [2, 6, 7, 8, 13])
|
||||
|
||||
projFile = os.path.join(fncProj, "content", "031b4af5197ec.nwd")
|
||||
testFile = os.path.join(outDir, "guiEditor_Main_Final_031b4af5197ec.nwd")
|
||||
|
||||
Reference in New Issue
Block a user