/// <summary> /// Get misspelled words in the given set of spans /// </summary> /// <param name="spans">The spans to check</param> /// <returns>An enumerable list of misspelling tags</returns> private IEnumerable <MisspellingTag> GetMisspellingsInSpans(NormalizedSnapshotSpanCollection spans) { List <Match> rangeExclusions = new List <Match>(); IList <string> spellingAlternates; SnapshotSpan errorSpan, deleteWordSpan; Microsoft.VisualStudio.Text.Span lastWord; string textToSplit, actualWord, textToCheck, preferredTerm; int mnemonicPos; var ignoredWords = wordsIgnoredOnce; // ************************************************************************************************** // NOTE: If anything changes here, update the related solution/project spell checking code in // ToolWindows\SolutionProjectSpellCheckControl.xaml.cs\GetMisspellingsInSpans(). // ************************************************************************************************** foreach (var span in spans) { textToSplit = span.GetText(); rangeExclusions.Clear(); // Note the location of all XML elements if needed if (configuration.IgnoreXmlElementsInText) { rangeExclusions.AddRange(WordSplitter.XmlElement.Matches(textToSplit).Cast <Match>()); } // Add exclusions from the configuration if any foreach (var exclude in configuration.ExclusionExpressions) { try { rangeExclusions.AddRange(exclude.Matches(textToSplit).Cast <Match>()); } catch (RegexMatchTimeoutException ex) { // Ignore expression timeouts Debug.WriteLine(ex); } } // Get any ignored words specified inline within the span foreach (Match m in reIgnoreSpelling.Matches(textToSplit)) { string ignored = m.Groups["IgnoredWords"].Value; bool caseSensitive = !String.IsNullOrWhiteSpace(m.Groups["CaseSensitive"].Value); int start = m.Groups["IgnoredWords"].Index; foreach (var ignoreSpan in wordSplitter.GetWordsInText(ignored)) { var ss = new SnapshotSpan(span.Snapshot, span.Start + start + ignoreSpan.Start, ignoreSpan.Length); var match = inlineIgnoredWords.FirstOrDefault(i => i.Span.GetSpan(span.Snapshot).IntersectsWith(ss)); if (match != null) { // If the span is already there, ignore it if (match.Word == ss.GetText() && match.CaseSensitive == caseSensitive) { continue; } // If different, replace it inlineIgnoredWords.Remove(match); } var ts = span.Snapshot.CreateTrackingSpan(ss, SpanTrackingMode.EdgeExclusive); inlineIgnoredWords.Add(new InlineIgnoredWord { Word = ignored.Substring(ignoreSpan.Start, ignoreSpan.Length), CaseSensitive = caseSensitive, Span = ts, IsNew = true }); } } lastWord = new Microsoft.VisualStudio.Text.Span(); foreach (var word in wordSplitter.GetWordsInText(textToSplit)) { if (_isClosed) { yield break; } actualWord = textToSplit.Substring(word.Start, word.Length); mnemonicPos = actualWord.IndexOf(wordSplitter.Mnemonic); if (mnemonicPos == -1) { textToCheck = actualWord; } else { textToCheck = actualWord.Substring(0, mnemonicPos) + actualWord.Substring(mnemonicPos + 1); } if (unescapeApostrophes && textToCheck.IndexOf("''", StringComparison.Ordinal) != -1) { textToCheck = textToCheck.Replace("''", "'"); } if (inlineIgnoredWords.Any(w => w.IsMatch(textToCheck))) { continue; } // Spell check the word if it looks like one and is not ignored if (wordSplitter.IsProbablyARealWord(textToCheck) && (rangeExclusions.Count == 0 || !rangeExclusions.Any(match => word.Start >= match.Index && word.Start <= match.Index + match.Length - 1))) { errorSpan = new SnapshotSpan(span.Start + word.Start, word.Length); // Check for a doubled word. This isn't perfect as it won't detected doubled words // across a line break. if (configuration.DetectDoubledWords && lastWord.Length != 0 && textToSplit.Substring(lastWord.Start, lastWord.Length).Equals(actualWord, StringComparison.OrdinalIgnoreCase) && String.IsNullOrWhiteSpace(textToSplit.Substring( lastWord.Start + lastWord.Length, word.Start - lastWord.Start - lastWord.Length))) { // If the doubled word is not being ignored at the current location, return it if (!ignoredWords.Any(w => w.StartPoint == errorSpan.Start && w.Word.Equals(actualWord, StringComparison.OrdinalIgnoreCase))) { // Delete the whitespace ahead of it too deleteWordSpan = new SnapshotSpan(span.Start + lastWord.Start + lastWord.Length, word.Length + word.Start - lastWord.Start - lastWord.Length); yield return(new MisspellingTag(errorSpan, deleteWordSpan)); lastWord = word; continue; } } lastWord = word; // If the word is not being ignored, perform the other checks if (!_dictionary.ShouldIgnoreWord(textToCheck) && !ignoredWords.Any( w => w.StartPoint == errorSpan.Start && w.Word.Equals(actualWord, StringComparison.OrdinalIgnoreCase))) { // Handle code analysis dictionary checks first as they may be not be recognized as // correctly spelled words but have alternate handling. if (configuration.CadOptions.TreatDeprecatedTermsAsMisspelled && configuration.DeprecatedTerms.TryGetValue(textToCheck, out preferredTerm)) { yield return(new MisspellingTag(MisspellingType.DeprecatedTerm, errorSpan, new[] { new SpellingSuggestion(null, preferredTerm) })); continue; } if (configuration.CadOptions.TreatCompoundTermsAsMisspelled && configuration.CompoundTerms.TryGetValue(textToCheck, out preferredTerm)) { yield return(new MisspellingTag(MisspellingType.CompoundTerm, errorSpan, new[] { new SpellingSuggestion(null, preferredTerm) })); continue; } if (configuration.CadOptions.TreatUnrecognizedWordsAsMisspelled && configuration.UnrecognizedWords.TryGetValue(textToCheck, out spellingAlternates)) { yield return(new MisspellingTag(MisspellingType.UnrecognizedWord, errorSpan, spellingAlternates.Select(a => new SpellingSuggestion(null, a)))); continue; } if (!_dictionary.IsSpelledCorrectly(textToCheck)) { // Sometimes it flags a word as misspelled if it ends with "'s". Try checking the // word without the "'s". If ignored or correct without it, don't flag it. This // appears to be caused by the definitions in the dictionary rather than Hunspell. if (textToCheck.EndsWith("'s", StringComparison.OrdinalIgnoreCase) || textToCheck.EndsWith("\u2019s", StringComparison.OrdinalIgnoreCase)) { string aposEss = textToCheck.Substring(textToCheck.Length - 2); textToCheck = textToCheck.Substring(0, textToCheck.Length - 2); if (_dictionary.ShouldIgnoreWord(textToCheck) || _dictionary.IsSpelledCorrectly(textToCheck)) { continue; } textToCheck += aposEss; } // Some dictionaries include a trailing period on certain words such as "etc." which // we don't include. If the word is followed by a period, try it with the period to // see if we get a match. If so, consider it valid. if (word.Start + word.Length < textToSplit.Length && textToSplit[word.Start + word.Length] == '.') { if (_dictionary.ShouldIgnoreWord(textToCheck + ".") || _dictionary.IsSpelledCorrectly(textToCheck + ".")) { continue; } } yield return(new MisspellingTag(errorSpan) { EscapeApostrophes = unescapeApostrophes }); } } } } } }
/// <summary> /// Get misspelled words in the given set of spans /// </summary> /// <param name="spans">The spans to check</param> /// <returns>An enumerable list of misspelling tags</returns> private IEnumerable <MisspellingTag> GetMisspellingsInSpans(NormalizedSnapshotSpanCollection spans) { List <Match> xmlTags = null; SnapshotSpan errorSpan, deleteWordSpan; Microsoft.VisualStudio.Text.Span lastWord; string text, textToParse; var ignoredWords = wordsIgnoredOnce; foreach (var span in spans) { text = span.GetText(); // Note the location of all XML elements if needed if (configuration.IgnoreXmlElementsInText) { xmlTags = reXml.Matches(text).OfType <Match>().ToList(); } lastWord = new Microsoft.VisualStudio.Text.Span(); foreach (var word in GetWordsInText(text)) { textToParse = text.Substring(word.Start, word.Length); // Spell check the word if it looks like one and is not ignored if (IsProbablyARealWord(textToParse) && (xmlTags == null || xmlTags.Count == 0 || !xmlTags.Any(match => word.Start >= match.Index && word.Start <= match.Index + match.Length - 1))) { // Check for a doubled word. This isn't perfect as it won't detected doubled words // across a line break. if (lastWord.Length != 0 && text.Substring(lastWord.Start, lastWord.Length).Equals( textToParse, StringComparison.OrdinalIgnoreCase) && String.IsNullOrWhiteSpace( text.Substring(lastWord.Start + lastWord.Length, word.Start - lastWord.Start - lastWord.Length))) { errorSpan = new SnapshotSpan(span.Start + word.Start, word.Length); // If the doubled word is not being ignored at the current location, return it if (!ignoredWords.Any(w => w.StartPoint == errorSpan.Start && w.Word.Equals(textToParse, StringComparison.OrdinalIgnoreCase))) { // Delete the whitespace ahead of it too deleteWordSpan = new SnapshotSpan(span.Start + lastWord.Start + lastWord.Length, word.Length + word.Start - lastWord.Start - lastWord.Length); yield return(new MisspellingTag(errorSpan, deleteWordSpan)); lastWord = word; continue; } } lastWord = word; if (!_dictionary.ShouldIgnoreWord(textToParse) && !_dictionary.IsSpelledCorrectly(textToParse)) { // Sometimes it flags a word as misspelled if it ends with "'s". Try checking the // word without the "'s". If ignored or correct without it, don't flag it. This // appears to be caused by the definitions in the dictionary rather than Hunspell. if (textToParse.EndsWith("'s", StringComparison.OrdinalIgnoreCase)) { textToParse = textToParse.Substring(0, textToParse.Length - 2); if (_dictionary.ShouldIgnoreWord(textToParse) || _dictionary.IsSpelledCorrectly(textToParse)) { continue; } textToParse += "'s"; } // Some dictionaries include a trailing period on certain words such as "etc." which // we don't include. If the word is followed by a period, try it with the period to // see if we get a match. If so, consider it valid. if (word.Start + word.Length < text.Length && text[word.Start + word.Length] == '.') { if (_dictionary.ShouldIgnoreWord(textToParse + ".") || _dictionary.IsSpelledCorrectly(textToParse + ".")) { continue; } } errorSpan = new SnapshotSpan(span.Start + word.Start, word.Length); // If the word is not being ignored at the current location, return it and its // suggested corrections. if (!ignoredWords.Any(w => w.StartPoint == errorSpan.Start && w.Word.Equals(textToParse, StringComparison.OrdinalIgnoreCase))) { yield return(new MisspellingTag(errorSpan, _dictionary.SuggestCorrections(textToParse))); } } } } } }