diff --git a/Directory.Build.props b/Directory.Build.props index 6d8c5c73..f7f9fdca 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -15,7 +15,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 df9b1c1a..0b7a90a5 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 {