diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/SpellChecker/SpellChecker.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/SpellChecker/SpellChecker.cs
index eb3a37b5a16..98d597617d8 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/SpellChecker/SpellChecker.cs
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/SpellChecker/SpellChecker.cs
@@ -28,16 +28,16 @@ namespace MsSpellCheckLib
/// a resilient (to out-of-proc COM server failures) interface to callers.
///
///
- /// The ISpellCheckerFactory and IUserDictionareisRegistrar methods are implemented using the following pattern.
- /// For a method Foo(), we see the following entries:
- ///
+ /// The ISpellCheckerFactory and IUserDictionareisRegistrar methods are implemented using the following pattern.
+ /// For a method Foo(), we see the following entries:
+ ///
/// 1. The most basic implementation of the method.
/// private FooImpl();
- ///
+ ///
/// 2. Some resilience added to the basic implementation. This calls into FooImpl repeatedly.
/// private FooImplWithRetries(bool shouldSuppressCOMExceptions);
- ///
- /// 3. Finally, the version that is exposed to callers.
+ ///
+ /// 3. Finally, the version that is exposed to callers.
/// public Foo(bool shouldSuppressCOMExceptions = true);
///
internal partial class SpellChecker : IDisposable
@@ -71,7 +71,7 @@ private bool Init(bool shouldSuppressCOMExceptions = true)
#region GetLanguageTage
///
- /// We really don't need to call into COM to get this
+ /// We really don't need to call into COM to get this
/// value since we cache it.
///
public string GetLanguageTag()
@@ -88,23 +88,23 @@ public List SuggestImpl(string word)
{
IEnumString suggestions = _speller.Value.Suggest(word);
- return
- suggestions != null ?
- suggestions.ToList(shouldSuppressCOMExceptions:false, shouldReleaseCOMObject:true) :
- null;
+ return
+ suggestions != null ?
+ suggestions.ToList(shouldSuppressCOMExceptions:false, shouldReleaseCOMObject:true) :
+ null;
}
-
+
public List SuggestImplWithRetries(string word, bool shouldSuppressCOMExceptions = true)
{
List result = null;
bool callSucceeded =
RetryHelper.TryExecuteFunction(
- func: () => { return SuggestImpl(word); },
+ func: () => { return SuggestImpl(word); },
result: out result,
preamble: () => Init(shouldSuppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[shouldSuppressCOMExceptions]);
- return callSucceeded ? result : null;
+ return callSucceeded ? result : null;
}
public List Suggest(string word, bool shouldSuppressCOMExceptions = true)
@@ -114,7 +114,7 @@ public List Suggest(string word, bool shouldSuppressCOMExceptions = true
#endregion // Suggest
- #region Add
+ #region Add
private void AddImpl(string word)
{
@@ -123,8 +123,8 @@ private void AddImpl(string word)
private void AddImplWithRetries(string word, bool shouldSuppressCOMExceptions = true)
{
- // AddImpl and Init are SecuritySafeCritical, so it is okay to
- // create an anon. lambdas that calls into them, and pass
+ // AddImpl and Init are SecuritySafeCritical, so it is okay to
+ // create an anon. lambdas that calls into them, and pass
// those lambdas below.
RetryHelper.TryCallAction(
action: () => AddImpl(word),
@@ -141,7 +141,7 @@ public void Add(string word, bool shouldSuppressCOMExceptions = true)
#endregion // Add
- #region Ignore
+ #region Ignore
private void IgnoreImpl(string word)
{
@@ -150,8 +150,8 @@ private void IgnoreImpl(string word)
public void IgnoreImplWithRetries(string word, bool shouldSuppressCOMExceptions = true)
{
- // IgnoreImpl and Init are SecuritySafeCritical, so it is okay to
- // create anon. lambdas that calls into them, and pass
+ // IgnoreImpl and Init are SecuritySafeCritical, so it is okay to
+ // create anon. lambdas that calls into them, and pass
// those lambdas below.
RetryHelper.TryCallAction(
action: () => IgnoreImpl(word),
@@ -189,7 +189,7 @@ public void AutoCorrect(string from, string to, bool suppressCOMExceptions = tru
#endregion
- #region GetOptionValue
+ #region GetOptionValue
private byte GetOptionValueImpl(string optionId)
{
@@ -214,16 +214,16 @@ public byte GetOptionValue(string optionId, bool suppressCOMExceptions = true)
return GetOptionValueImplWithRetries(optionId, suppressCOMExceptions);
}
- #endregion // GetOptionValue
+ #endregion // GetOptionValue
#region GetOptionIds
private List GetOptionIdsImpl()
{
IEnumString optionIds = _speller.Value.OptionIds;
- return (optionIds != null) ? optionIds.ToList(false, true) : null;
+ return (optionIds != null) ? optionIds.ToList(false, true) : null;
}
-
+
private List GetOptionIdsImplWithRetries(bool suppressCOMExceptions)
{
List optionIds = null;
@@ -234,7 +234,7 @@ private List GetOptionIdsImplWithRetries(bool suppressCOMExceptions)
preamble: () => Init(suppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[suppressCOMExceptions]);
- return callSucceeded ? optionIds : null;
+ return callSucceeded ? optionIds : null;
}
public List GetOptionIds(bool suppressCOMExceptions = true)
@@ -261,7 +261,7 @@ private string GetIdImplWithRetries(bool suppressCOMExceptions)
preamble: () => Init(suppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[suppressCOMExceptions]);
- return callSucceeded ? id : null;
+ return callSucceeded ? id : null;
}
string GetId(bool suppressCOMExceptions = true)
@@ -288,7 +288,7 @@ private string GetLocalizedNameImplWithRetries(bool suppressCOMExceptions)
preamble: () => Init(suppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[suppressCOMExceptions]);
- return callSucceeded ? localizedName : null;
+ return callSucceeded ? localizedName : null;
}
public string GetLocalizedName(bool suppressCOMExceptions = true)
@@ -303,7 +303,7 @@ public string GetLocalizedName(bool suppressCOMExceptions = true)
private OptionDescription GetOptionDescriptionImpl(string optionId)
{
IOptionDescription iod = _speller.Value.GetOptionDescription(optionId);
- return (iod != null) ? OptionDescription.Create(iod, false, true) : null;
+ return (iod != null) ? OptionDescription.Create(iod, false, true) : null;
}
private OptionDescription GetOptionDescriptionImplWithRetries(string optionId, bool suppressCOMExceptions)
@@ -316,7 +316,7 @@ private OptionDescription GetOptionDescriptionImplWithRetries(string optionId, b
preamble: () => Init(suppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[suppressCOMExceptions]);
- return callSucceeded ? optionDescription : null;
+ return callSucceeded ? optionDescription : null;
}
public OptionDescription GetOptionDescription(string optionId, bool suppressCOMExceptions = true)
@@ -326,12 +326,12 @@ public OptionDescription GetOptionDescription(string optionId, bool suppressCOME
#endregion // GetOptionDescription
- #region Check
+ #region Check
private List CheckImpl(string text)
{
IEnumSpellingError errors = _speller.Value.Check(text);
- return (errors != null) ? errors.ToList(this, text, false, true) : null;
+ return (errors != null) ? errors.ToList(this, text, false, true) : null;
}
private List CheckImplWithRetries(string text, bool suppressCOMExceptions)
@@ -344,7 +344,7 @@ private List CheckImplWithRetries(string text, bool suppressCOMEx
preamble: () => Init(suppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[suppressCOMExceptions]);
- return callSucceeded ? errors : null;
+ return callSucceeded ? errors : null;
}
public List Check(string text, bool suppressCOMExceptions = true)
@@ -359,7 +359,7 @@ public List Check(string text, bool suppressCOMExceptions = true)
public List ComprehensiveCheckImpl(string text)
{
IEnumSpellingError errors = _speller.Value.ComprehensiveCheck(text);
- return (errors != null) ? errors.ToList(this, text, false, true) : null;
+ return (errors != null) ? errors.ToList(this, text, false, true) : null;
}
public List ComprehensiveCheckImplWithRetries(string text, bool shouldSuppressCOMExceptions = true)
@@ -372,7 +372,7 @@ public List ComprehensiveCheckImplWithRetries(string text, bool s
preamble: () => Init(shouldSuppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[shouldSuppressCOMExceptions]);
- return callSucceeded ? errors : null;
+ return callSucceeded ? errors : null;
}
public List ComprehensiveCheck(string text, bool shouldSuppressCOMExceptions = true)
@@ -382,6 +382,108 @@ public List ComprehensiveCheck(string text, bool shouldSuppressCO
#endregion // ComprehensiveCheck
+ #region HasErrors
+
+ // This returns true if the given text has any spelling errors.
+ // It is a shortcut for
+ // ComprehensiveCheck(text)?.Count != 0
+ // that avoids the (expensive) creation of the managed list of errors and
+ // their suggested corrections.
+ public bool HasErrorsImpl(string text)
+ {
+ IEnumSpellingError errors = _speller.Value.ComprehensiveCheck(text);
+ return (errors != null) ? errors.HasErrors(false, true) : false;
+ }
+
+ public bool HasErrorsImplWithRetries(string text, bool shouldSuppressCOMExceptions = true)
+ {
+ bool hasErrors = false;
+ bool callSucceeded =
+ RetryHelper.TryExecuteFunction(
+ func: () => HasErrorsImpl(text),
+ result: out hasErrors,
+ preamble: () => Init(shouldSuppressCOMExceptions),
+ ignoredExceptions: SuppressedExceptions[shouldSuppressCOMExceptions]);
+
+ return callSucceeded ? hasErrors : false;
+ }
+
+ public bool HasErrors(string text, bool shouldSuppressCOMExceptions = true)
+ {
+ if (_disposed || String.IsNullOrWhiteSpace(text))
+ return false;
+
+ // In practice, this method is called many times on the same few
+ // words in the vicinity of the insertion caret. The calls to the
+ // native spell-checker can be expensive (more so for misspelled
+ // that have many nearby corrections), enough to cause lags in
+ // response time. To mitigate this, we keep a cache of the most
+ // recent queries and answer from the cache when possible, avoiding
+ // the expensive native calls about 80% of the time.
+
+ // The _hasErrorsCache member can be set to null by another thread
+ // when the native spell-checker changes. To avoid NREs, use a local
+ // reference here. If the cache is nulled out while we're in
+ // this method, the worst that happens is that a new entry we add
+ // to the old cache won't be visible to the next query, causing one
+ // extra "avoidable" native query. It's not worth the effort and
+ // synchronization overhead to "solve" this very infrequent case.
+ List hasErrorsCache = _hasErrorsCache;
+
+ // search the MRU cache for the text
+ int cacheSize = (hasErrorsCache != null) ? hasErrorsCache.Count : 0;
+ int index;
+ for (index = 0; index < cacheSize; ++index)
+ {
+ if (text == hasErrorsCache[index].Text)
+ break;
+ }
+
+ HasErrorsResult result;
+ if (index < cacheSize)
+ {
+ // if found, use the cached result
+ result = hasErrorsCache[index];
+ }
+ else
+ {
+ // otherwise, get the result from the native spell checker
+ result = new HasErrorsResult(text, HasErrorsImplWithRetries(text, shouldSuppressCOMExceptions));
+
+ // add it to the cache, initializing as needed
+ if (hasErrorsCache == null)
+ {
+ hasErrorsCache = new List(HasErrorsCacheCapacity);
+ _hasErrorsCache = hasErrorsCache;
+ }
+
+ if (cacheSize < HasErrorsCacheCapacity)
+ {
+ // add an entry at index cacheSize. It will get overwritten
+ // in the first iteration of the move-to-front loop,
+ // but we have to add something so that the reference
+ // to cache[index] doesn't hit an out-of-range exception.
+ hasErrorsCache.Add(result);
+ }
+ else
+ {
+ index = HasErrorsCacheCapacity - 1;
+ }
+ }
+
+ // move the entry to the front of the cache (to preserve MRU),
+ // and return the result
+ for (; index > 0; --index)
+ {
+ hasErrorsCache[index] = hasErrorsCache[index-1];
+ }
+ hasErrorsCache[0] = result;
+
+ return result.HasErrors;
+ }
+
+ #endregion HasErrors
+
#region Add/Remove SpellCheckerChanged support
private uint? add_SpellCheckerChangedImpl(ISpellCheckerChangedEventHandler handler)
@@ -399,7 +501,7 @@ public List ComprehensiveCheck(string text, bool shouldSuppressCO
preamble: () => Init(suppressCOMExceptions),
ignoredExceptions: SuppressedExceptions[suppressCOMExceptions]);
- return callSucceeded ? eventCookie : null;
+ return callSucceeded ? eventCookie : null;
}
private uint? add_SpellCheckerChanged(ISpellCheckerChangedEventHandler handler, bool suppressCOMExceptions = true)
@@ -422,21 +524,23 @@ private void remove_SpellCheckerChangedImplWithRetries(uint eventCookie, bool su
private void remove_SpellCheckerChanged(uint eventCookie, bool suppressCOMExceptions = true)
{
- if (_disposed) return;
+ if (_disposed) return;
remove_SpellCheckerChangedImplWithRetries(eventCookie, suppressCOMExceptions);
}
///
- /// This is called when the ISpellChecker instnace stored in .Value
- /// changes (likely due to a COM failure and reinitialization). When this happens,
+ /// This is called when the ISpellChecker instance stored in .Value
+ /// changes (likely due to a COM failure and reinitialization). When this happens,
/// we will re-register with add_SpellCheckerChanged if appropriate and update
- /// the eventCookie. Thsi will in-turn permit users of the SpellChecker type
- /// to listen to SpellChecker.Changed event when the underlying ISpellChecker
- /// instance indicates a change.
+ /// the eventCookie. Thsi will in-turn permit users of the SpellChecker type
+ /// to listen to SpellChecker.Changed event when the underlying ISpellChecker
+ /// instance indicates a change.
///
private void SpellerInstanceChanged(object sender, PropertyChangedEventArgs args)
{
- // Re-register callbacks with ISpellChecker
+ _hasErrorsCache = null; // cached HasErrors results are no longer valid
+
+ // Re-register callbacks with ISpellChecker
if (_changed != null)
{
lock (_changed)
@@ -450,18 +554,20 @@ private void SpellerInstanceChanged(object sender, PropertyChangedEventArgs args
}
///
- /// Called when ISpellChecker instnace calls into _spellCheckerChangedEventHandler.Invoke
- /// to indicate a change. Invoke in turn calls OnChanged.
+ /// Called when ISpellChecker instance calls into _spellCheckerChangedEventHandler.Invoke
+ /// to indicate a change. Invoke in turn calls OnChanged.
///
internal virtual void OnChanged(SpellCheckerChangedEventArgs e)
{
+ _hasErrorsCache = null; // cached HasErrors results are no longer valid
+
_changed?.Invoke(this, e);
}
#region Events
///
- /// Event used to receive notifications when the underlying ISpellChecker
+ /// Event used to receive notifications when the underlying ISpellChecker
/// instance indicates a change.
///
public event EventHandler Changed
@@ -547,13 +653,23 @@ public void Dispose()
private string _languageTag;
// Change notification related fields
- SpellCheckerChangedEventHandler _spellCheckerChangedEventHandler;
+ SpellCheckerChangedEventHandler _spellCheckerChangedEventHandler;
private uint? _eventCookie = null;
- private event EventHandler _changed;
+ private event EventHandler _changed;
+
+ // caching HasErrors results
+ private class HasErrorsResult : Tuple
+ {
+ public HasErrorsResult(string text, bool hasErrors) : base(text, hasErrors) {}
+ public string Text { get { return Item1; } }
+ public bool HasErrors { get { return Item2; } }
+ }
+ private List _hasErrorsCache;
+ const int HasErrorsCacheCapacity = 10; // cache the most recent 10 results
private bool _disposed = false;
- #endregion // Fields
+ #endregion // Fields
}
}
}
diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/Utils/Extensions.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/Utils/Extensions.cs
index d643b1f153b..2751836b12c 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/Utils/Extensions.cs
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/MsSpellCheckLib/Utils/Extensions.cs
@@ -30,8 +30,8 @@ internal static class Extensions
/// Extracts a list of strings from an RCW.IEnumString instance.
///
internal static List ToList(
- this IEnumString enumString,
- bool shouldSuppressCOMExceptions = true,
+ this IEnumString enumString,
+ bool shouldSuppressCOMExceptions = true,
bool shouldReleaseCOMObject = true)
{
var result = new List();
@@ -77,10 +77,10 @@ internal static List ToList(
/// Extracts a list of SpellingError's from an RCW.IEnumSpellingError instance.
///
internal static List ToList(
- this IEnumSpellingError spellingErrors,
- SpellChecker spellChecker,
- string text,
- bool shouldSuppressCOMExceptions = true,
+ this IEnumSpellingError spellingErrors,
+ SpellChecker spellChecker,
+ string text,
+ bool shouldSuppressCOMExceptions = true,
bool shouldReleaseCOMObject = true)
{
if (spellingErrors == null)
@@ -99,7 +99,7 @@ internal static List ToList(
if (iSpellingError == null)
{
// no more ISpellingError objects left in the enum
- break;
+ break;
}
var error = new SpellingError(iSpellingError, spellChecker, text, shouldSuppressCOMExceptions, true);
@@ -108,8 +108,8 @@ internal static List ToList(
}
catch (COMException) when (shouldSuppressCOMExceptions)
{
- // do nothing here
- // the exception filter does it all.
+ // do nothing here
+ // the exception filter does it all.
}
finally
{
@@ -125,7 +125,7 @@ internal static List ToList(
///
/// Determines whether a collection of SpellingError instances
/// has any actual errors, or whether they represent a 'clean'
- /// result.
+ /// result.
///
internal static bool IsClean(this List errors)
{
@@ -134,7 +134,7 @@ internal static bool IsClean(this List errors)
throw new ArgumentNullException(nameof(errors));
}
- bool isClean = true;
+ bool isClean = true;
foreach (var error in errors)
{
if (error.CorrectiveAction != CorrectiveAction.None)
@@ -146,5 +146,56 @@ internal static bool IsClean(this List errors)
return isClean;
}
+
+ ///
+ /// Determines whether an RCW.IEnumSpellingError instance has any errors,
+ /// without asking for expensive details.
+ ///
+ internal static bool HasErrors(
+ this IEnumSpellingError spellingErrors,
+ bool shouldSuppressCOMExceptions = true,
+ bool shouldReleaseCOMObject = true)
+ {
+ if (spellingErrors == null)
+ {
+ throw new ArgumentNullException(nameof(spellingErrors));
+ }
+
+ bool result = false;
+
+ try
+ {
+ while (!result)
+ {
+ ISpellingError iSpellingError = spellingErrors.Next();
+
+ if (iSpellingError == null)
+ {
+ // no more ISpellingError objects left in the enum
+ break;
+ }
+
+ if ((CorrectiveAction)iSpellingError.CorrectiveAction != CorrectiveAction.None)
+ {
+ result = true;
+ }
+ Marshal.ReleaseComObject(iSpellingError);
+ }
+ }
+ catch (COMException) when (shouldSuppressCOMExceptions)
+ {
+ // do nothing here
+ // the exception filter does it all.
+ }
+ finally
+ {
+ if (shouldReleaseCOMObject)
+ {
+ Marshal.ReleaseComObject(spellingErrors);
+ }
+ }
+
+ return result;
+ }
}
}
diff --git a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/WinRTSpellerInteropExtensions.cs b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/WinRTSpellerInteropExtensions.cs
index 37ea34c0d86..75f4eab8339 100644
--- a/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/WinRTSpellerInteropExtensions.cs
+++ b/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/WinRTSpellerInteropExtensions.cs
@@ -15,7 +15,7 @@ internal static class WinRTSpellerInteropExtensions
{
///
/// Tokenizes using , and then identifies fixes-up
- /// the tokens to account for any missed text "in-between" those tokens.
+ /// the tokens to account for any missed text "in-between" those tokens.
///
/// Word-breaker instance
/// The text being tokenized
@@ -23,35 +23,35 @@ internal static class WinRTSpellerInteropExtensions
/// Calling instance
///
///
- /// Windows.Data.Text.WordsSegmenter tends to drop punctuation characters like period ('.')
+ /// Windows.Data.Text.WordsSegmenter tends to drop punctuation characters like period ('.')
/// when tokenizing text. Though this behavior is compatible with a vast majority of text-processing
- /// scenarios (like word-counting), it is not ideal for spell-checking.
- ///
- /// In this method, the following augmented heuristic is applied to update the token-list generated by
- /// .
- ///
+ /// scenarios (like word-counting), it is not ideal for spell-checking.
+ ///
+ /// In this method, the following augmented heuristic is applied to update the token-list generated by
+ /// .
+ ///
/// - Identify if any text 'missingFragment' has been dropped by the
- /// - If the token immediately preceding 'missingFragment', previousToken, has a spelling error, then attempt to
+ /// - If the token immediately preceding 'missingFragment', previousToken, has a spelling error, then attempt to
/// create new candiate tokens in the following order:
- ///
+ ///
/// previousToken + missingFragment[0..0]
/// previousToken + missingFragment[0..1]
/// previousToken + missingFragment[0..2]
/// ...
/// ...
/// previousToken + missingFragment[0..LEN-1], where LEN = LEN(missingFragment)
- ///
- /// - Select the first candidate token that is free of spelling errors, and replace 'previousToken' with it.
+ ///
+ /// - Select the first candidate token that is free of spelling errors, and replace 'previousToken' with it.
/// - For performance reasons, we choose a constant MAXLEN = 4 such that when LEN > MAXLEN, only MAXLEN
- /// tokens are considered.
- /// - MAXLEN = 4 is a somewhat arbitrary choice, though it seems more than sufficient to address common
- /// problems this heuristic is intended to help with.
- ///
+ /// tokens are considered.
+ /// - MAXLEN = 4 is a somewhat arbitrary choice, though it seems more than sufficient to address common
+ /// problems this heuristic is intended to help with.
+ ///
/// - Typical word-breaking problems that have been observed empirically involve only one missed character,
/// for which MAXLEN=1 would be sufficient. MAXLEN=4 is chosen as a sufficiently-large tradeoff between
- /// correctness and performance.
- ///
- /// - Also see https://github.com/dotnet/wpf/pull/2753#issuecomment-602120768 for a discussion related to this.
+ /// correctness and performance.
+ ///
+ /// - Also see https://github.com/dotnet/wpf/pull/2753#issuecomment-602120768 for a discussion related to this.
///
public static IReadOnlyList ComprehensiveGetTokens(
this WordsSegmenter segmenter,
@@ -79,7 +79,7 @@ public static IReadOnlyList ComprehensiveGetTokens(
{
// There is a "gap" between the last recorded token and the current token.
// Identify the missing token and add it as a "supplementary word segment" - but only if the token
- // turns out to be a substantial one (i.e., if the string is non-blank/non-empty).
+ // turns out to be a substantial one (i.e., if the string is non-blank/non-empty).
var missingFragment =
new SpellerSegment(
text,
@@ -99,7 +99,6 @@ public static IReadOnlyList ComprehensiveGetTokens(
}
}
-
allTokens.Add(
new SpellerSegment(
text,
@@ -112,7 +111,8 @@ public static IReadOnlyList ComprehensiveGetTokens(
}
if (tokens.Count > 0 &&
- spellChecker?.ComprehensiveCheck(tokens[tokens.Count - 1].Text)?.Count != 0 &&
+ spellChecker != null &&
+ spellChecker.HasErrors(tokens[tokens.Count - 1].Text) &&
predictedNextTokenStartPosition < text.Length)
{
// There is a token possibly missing at the end of the string
@@ -139,8 +139,8 @@ public static IReadOnlyList ComprehensiveGetTokens(
}
///
- /// Checks through combinations of + substrings() and
- /// returns the first spellcheck-clean result.
+ /// Checks through combinations of + substrings() and
+ /// returns the first spellcheck-clean result.
///
/// Spell-checker
/// Overall document text within which the text-ranges are computed
@@ -149,33 +149,41 @@ public static IReadOnlyList ComprehensiveGetTokens(
///
///
/// See note about MAXLEN in
- /// which explains the rationale behind the value of the constant AlternateFormsMaximumCount.
+ /// which explains the rationale behind the value of the constant AlternateFormsMaximumCount.
///
- private static WinRTSpellerInterop.TextRange? GetSpellCheckCleanSubstitutionToken(
- SpellChecker spellChecker,
+ private static WinRTSpellerInterop.TextRange? GetSpellCheckCleanSubstitutionToken(
+ SpellChecker spellChecker,
string documentText,
SpellerSegment lastToken,
SpellerSegment missingFragment)
{
const int AlternateFormsMaximumCount = 4;
- if (string.IsNullOrWhiteSpace(missingFragment?.Text) ||
- string.IsNullOrWhiteSpace(lastToken?.Text) ||
- string.IsNullOrWhiteSpace(documentText))
+ string lastTokenText = lastToken?.Text;
+ string missingFragmentText = missingFragment?.Text.TrimEnd('\0');
+
+ if (string.IsNullOrWhiteSpace(missingFragmentText) ||
+ string.IsNullOrWhiteSpace(lastTokenText) ||
+ string.IsNullOrWhiteSpace(documentText) ||
+ spellChecker == null ||
+ !spellChecker.HasErrors(lastTokenText))
{
return null;
}
- int altFormsCount = Math.Min(missingFragment.TextRange.Length, AlternateFormsMaximumCount);
- var spellingErrors = spellChecker?.ComprehensiveCheck(lastToken.Text);
- if (spellingErrors?.Count != 0)
+ string previousAltForm = lastTokenText;
+ int altFormsCount = Math.Min(missingFragmentText.Length, AlternateFormsMaximumCount);
+
+ // One of the substring-permutations of the missingFragment - when concatenated with 'lastToken' - could be a viable
+ // replacement for 'lastToken'
+ for (int i = 1; i <= altFormsCount; i++)
{
- // One of the substring-permutations of the missingFragment - when concatenated with 'lastToken' - could be a viable
- // replacement for 'lastToken'
- for (int i = 1; i <= altFormsCount; i++)
+ var altForm = documentText.Substring(lastToken.TextRange.Start, lastTokenText.Length + i).TrimEnd('\0').TrimEnd();
+
+ if (altForm.Length > previousAltForm.Length)
{
- var altForm = documentText.Substring(lastToken.TextRange.Start, lastToken.TextRange.Length + i).TrimEnd();
- if (spellChecker?.ComprehensiveCheck(altForm)?.Count == 0)
+ previousAltForm = altForm;
+ if (!spellChecker.HasErrors(altForm))
{
// Use this altForm in place lastToken
return new WinRTSpellerInterop.TextRange(
@@ -183,6 +191,15 @@ public static IReadOnlyList ComprehensiveGetTokens(
altForm.Length);
}
}
+ else
+ {
+ // trimming yielded an altForm we've already checked, don't check again.
+ // We could stop checking now if we knew that an altForm with
+ // embedded trimmable characters could never be a correctly spelled
+ // token. That's probably true, but the spell-checking docs don't
+ // guarantee it. So for safety, leaving the next line commented out.
+ // return null;
+ }
}
return null;