diff --git a/Directory.Build.props b/Directory.Build.props
index c2c63465..d9a2027f 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -16,7 +16,7 @@
false
$(MSBuildThisFileDirectory)/artifacts
true
- NU1605;CS8002
+ NU1605
$(MSBuildThisFileDirectory)/liblcm.snk
true
snupkg
diff --git a/src/SIL.LCModel.Core/SIL.LCModel.Core.csproj b/src/SIL.LCModel.Core/SIL.LCModel.Core.csproj
index c448c981..619c7dda 100644
--- a/src/SIL.LCModel.Core/SIL.LCModel.Core.csproj
+++ b/src/SIL.LCModel.Core/SIL.LCModel.Core.csproj
@@ -22,6 +22,7 @@ SIL.LCModel.Core provides a base library with core functionality.
+
diff --git a/src/SIL.LCModel.Core/SpellChecking/SpellEngine.cs b/src/SIL.LCModel.Core/SpellChecking/SpellEngine.cs
index 81bffaf9..98cbae46 100644
--- a/src/SIL.LCModel.Core/SpellChecking/SpellEngine.cs
+++ b/src/SIL.LCModel.Core/SpellChecking/SpellEngine.cs
@@ -26,10 +26,12 @@ internal static SpellEngine Create(string affixPath, string dictPath, string exc
SpellEngine spellEngine = null;
try
{
- if (Platform.IsWindows)
- spellEngine = CreateSpellEngineWindows(affixPath, dictPath, exceptionPath);
- else
- spellEngine = CreateSpellEngineLinux(affixPath, dictPath, exceptionPath);
+ if (SpellingHelper.UseWeCantSpell)
+ {
+ spellEngine = new SpellEngineWeCantSpell(affixPath, dictPath, exceptionPath);
+ } else {
+ spellEngine = Platform.IsWindows ? CreateSpellEngineWindows(affixPath, dictPath, exceptionPath) : CreateSpellEngineLinux(affixPath, dictPath, exceptionPath);
+ }
spellEngine.Initialize();
}
@@ -87,9 +89,20 @@ private void Initialize()
///
public abstract bool Check(string word);
+ private bool _isVernacular;
+ private bool _gotIsVernacular;
+ public bool IsVernacular
+ {
+ get
+ {
+ if (_gotIsVernacular)
+ return _isVernacular;
- ///
- public abstract bool IsVernacular { get; }
+ _isVernacular = Check(SpellingHelper.PrototypeWord);
+ _gotIsVernacular = true;
+ return _isVernacular;
+ }
+ }
///
public abstract ICollection Suggest(string badWord);
diff --git a/src/SIL.LCModel.Core/SpellChecking/SpellEngineLinux.cs b/src/SIL.LCModel.Core/SpellChecking/SpellEngineLinux.cs
index 65f09892..811970da 100644
--- a/src/SIL.LCModel.Core/SpellChecking/SpellEngineLinux.cs
+++ b/src/SIL.LCModel.Core/SpellChecking/SpellEngineLinux.cs
@@ -13,8 +13,6 @@ namespace SIL.LCModel.Core.SpellChecking
internal sealed class SpellEngineLinux: SpellEngine
{
private IntPtr _hunspellHandle;
- private bool _isVernacular;
- private bool _gotIsVernacular;
internal SpellEngineLinux(string affixPath, string dictPath, string exceptionPath)
: base(exceptionPath)
@@ -218,20 +216,6 @@ public override ICollection Suggest(string badWord)
Hunspell_free_list(_hunspellHandle, ref pointerToAddressStringArray, resultCount);
return results;
}
-
- ///
- public override bool IsVernacular
- {
- get
- {
- if (_gotIsVernacular)
- return _isVernacular;
-
- _isVernacular = Check(SpellingHelper.PrototypeWord);
- _gotIsVernacular = true;
- return _isVernacular;
- }
- }
}
///
diff --git a/src/SIL.LCModel.Core/SpellChecking/SpellEngineWeCantSpell.cs b/src/SIL.LCModel.Core/SpellChecking/SpellEngineWeCantSpell.cs
new file mode 100644
index 00000000..6c0c4493
--- /dev/null
+++ b/src/SIL.LCModel.Core/SpellChecking/SpellEngineWeCantSpell.cs
@@ -0,0 +1,54 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using WeCantSpell.Hunspell;
+
+namespace SIL.LCModel.Core.SpellChecking
+{
+ internal class SpellEngineWeCantSpell: SpellEngine
+ {
+ private readonly WordList _wordList;
+ private readonly WordList.Builder _customWordsBuilder;
+ private WordList _customWordList;
+ private readonly HashSet _badWords = new HashSet();
+
+ public SpellEngineWeCantSpell(string affixPath, string dictPath, string exceptionPath) : base(exceptionPath)
+ {
+ _wordList = WordList.CreateFromFiles(dictPath, affixPath);
+ _customWordsBuilder = new WordList.Builder(_wordList.Affix);
+ _customWordList = _customWordsBuilder.ToImmutable();
+ }
+
+ public override bool Check(string word)
+ {
+ if (_badWords.Contains(word)) return false;
+ if (_customWordList.Check(word)) return true;
+ return _wordList.Check(word);
+ }
+
+ public override ICollection Suggest(string badWord)
+ {
+ var suggestions = _wordList.Suggest(badWord).Union(_customWordList.Suggest(badWord));
+ return suggestions.Where(suggestion => !_badWords.Contains(suggestion)).ToArray();
+ }
+
+ protected override void SetStatusInternal(string word1, bool isCorrect)
+ {
+ // WeCantSpell does not support modifying the word list, so we have to use 2 and merge them.
+ if (isCorrect)
+ {
+ var detail = IsVernacular
+ ? new WordEntryDetail(FlagSet.Empty,
+ MorphSet.Create(new []{SpellingHelper.PrototypeWord}),
+ WordEntryOptions.None)
+ : WordEntryDetail.Default;
+ _customWordsBuilder.Add(word1, detail);
+ _customWordList = _customWordsBuilder.ToImmutable();
+ }
+ else
+ {
+ _badWords.Add(word1);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/SIL.LCModel.Core/SpellChecking/SpellEngineWindows.cs b/src/SIL.LCModel.Core/SpellChecking/SpellEngineWindows.cs
index 7c04ba03..67c91d49 100644
--- a/src/SIL.LCModel.Core/SpellChecking/SpellEngineWindows.cs
+++ b/src/SIL.LCModel.Core/SpellChecking/SpellEngineWindows.cs
@@ -22,8 +22,6 @@ internal class NoLinuxRepack : System.Attribute
internal sealed class SpellEngineWindows: SpellEngine
{
private readonly Hunspell _hunspellHandle;
- private bool _isVernacular;
- private bool _gotIsVernacular;
internal SpellEngineWindows(string affixPath, string dictPath, string exceptionPath)
: base(exceptionPath)
@@ -87,20 +85,6 @@ public override ICollection Suggest(string badWord)
return _hunspellHandle.Suggest(MarshallAsUtf8Bytes(badWord));
}
- ///
- public override bool IsVernacular
- {
- get
- {
- if (_gotIsVernacular)
- return _isVernacular;
-
- _isVernacular = Check(MarshallAsUtf8Bytes(SpellingHelper.PrototypeWord));
- _gotIsVernacular = true;
- return _isVernacular;
- }
- }
-
///
/// We can't declare these arguments (char * in C++) as [MarshalAs(UnmanagedType.LPStr)] string, because that
/// unconditionally coverts the string to bytes using the current system code page, which is never what we want.
diff --git a/src/SIL.LCModel.Core/SpellChecking/SpellingHelper.cs b/src/SIL.LCModel.Core/SpellChecking/SpellingHelper.cs
index 77cd3209..d82b7214 100644
--- a/src/SIL.LCModel.Core/SpellChecking/SpellingHelper.cs
+++ b/src/SIL.LCModel.Core/SpellChecking/SpellingHelper.cs
@@ -22,6 +22,17 @@ namespace SIL.LCModel.Core.SpellChecking
///
public static class SpellingHelper
{
+ ///
+ /// FieldWorks uses NHunspell for spell checking, but NHunspell is not available on Linux.
+ /// Use this flag to switch between NHunspell and WeCantSpell as needed. On dotnet framework we use NHunspell by default.
+ /// On dotnet core we use WeCantSpell by default.
+ ///
+ public static bool UseWeCantSpell { get; set; } =
+ #if NETFRAMEWORK
+ false;
+ #else
+ true;
+ #endif
// A helper object used to ensure that the spelling engines are properly disposed of
private sealed class SingletonToDispose : IDisposable
{