private void ExpandToWordBreakAndContext(ITextPointer position, LogicalDirection direction, XmlLanguage language, out ITextPointer contentPosition, out ITextPointer contextPosition) { ITextPointer start; ITextPointer end; ITextPointer outwardPosition; ITextPointer inwardPosition; TextMap textMap; ArrayList segments; SpellerInterop.STextRange sTextRange; LogicalDirection inwardDirection; int i; contentPosition = position; contextPosition = position; if (position.GetPointerContext(direction) == TextPointerContext.None) { // There is no following context, we're at document start/end. return; } // Disable spell checking functionality since we're only // interested in word breaks here. This greatly cuts down // the engine's workload. _spellerInterop.SetContextOption("IsSpellChecking", false); // // Build an array of wordbreak offsets surrounding the position. // // 1. Search outward, into surrounding text. We need MinWordBreaksForContext // word breaks to handle multi-word errors. outwardPosition = SearchForWordBreaks(position, direction, language, MinWordBreaksForContext, true /* stopOnError */); // 2. Search inward, towards content. We just need one word break inward. inwardDirection = direction == LogicalDirection.Forward ? LogicalDirection.Backward : LogicalDirection.Forward; inwardPosition = SearchForWordBreaks(position, inwardDirection, language, 1, false /* stopOnError */); // Get combined word breaks. This may not be the same as we calculated // in two parts above, since we don't know yet whether or not position is // on a word break. if (direction == LogicalDirection.Backward) { start = outwardPosition; end = inwardPosition; } else { start = inwardPosition; end = outwardPosition; } textMap = new TextMap(start, end, position, position); segments = new ArrayList(MinWordBreaksForContext + 1); _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, null, new SpellerInterop.EnumTextSegmentsCallback(ExpandToWordBreakCallback), segments); // // Use our table of word breaks to calculate context and content positions. // if (segments.Count == 0) { // No segments. This can happen if position is surrounded by // nothing but white space. We've already initialized contentPosition // and contextPosition so there's nothing to do. } else { int leftWordBreak; int rightWordBreak; int contentOffset; int contextOffset; // Figure out where position lives in the segment list. i = FindPositionInSegmentList(textMap, direction, segments, out leftWordBreak, out rightWordBreak); // contentPosition should be an edge on the segment we found. if (direction == LogicalDirection.Backward) { contentOffset = textMap.ContentStartOffset == rightWordBreak ? rightWordBreak : leftWordBreak; } else { contentOffset = textMap.ContentStartOffset == leftWordBreak ? leftWordBreak : rightWordBreak; } contentPosition = textMap.MapOffsetToPosition(contentOffset); // contextPosition should be MinWordBreaksForContext - 1 words away. if (direction == LogicalDirection.Backward) { i -= (MinWordBreaksForContext - 1); sTextRange = (SpellerInterop.STextRange)segments[Math.Max(i, 0)]; // We might actually follow contentOffset if we're at the document edge. // Don't let that happen. contextOffset = Math.Min(sTextRange.Start, contentOffset); } else { i += MinWordBreaksForContext; sTextRange = (SpellerInterop.STextRange)segments[Math.Min(i, segments.Count-1)]; // We might actually preceed contentOffset if we're at the document edge. // Don't let that happen. contextOffset = Math.Max(sTextRange.Start + sTextRange.Length, contentOffset); } contextPosition = textMap.MapOffsetToPosition(contextOffset); } // Final fixup: if the dirty range covers only formatting (which is not passed // to the speller engine) then we might actually "expand" in the wrong // direction, since the TextMap will jump over formatting. // Backup if necessary. if (direction == LogicalDirection.Backward) { if (position.CompareTo(contentPosition) < 0) { contentPosition = position; } if (position.CompareTo(contextPosition) < 0) { contextPosition = position; } } else { if (position.CompareTo(contentPosition) > 0) { contentPosition = position; } if (position.CompareTo(contextPosition) > 0) { contextPosition = position; } } }
private void AdjustScanRangeAroundComposition(ITextPointer rawStart, ITextPointer rawEnd, out ITextPointer start, out ITextPointer end) { start = rawStart; end = rawEnd; if (!_textEditor.Selection.IsEmpty) { // No caret to adjust around. return; } if (!_textEditor.UiScope.IsKeyboardFocused) { // Document isn't focused, no caret rendered. return; } // Get the word surrounding the caret. ITextPointer wordBreakLeft; ITextPointer wordBreakRight; ITextPointer caretPosition; TextMap textMap; ArrayList segments; caretPosition = _textEditor.Selection.Start; // Disable spell checking functionality since we're only // interested in word breaks here. This greatly cuts down // the engine's workload. _spellerInterop.SetContextOption("IsSpellChecking", false); XmlLanguage language = GetCurrentLanguage(caretPosition); wordBreakLeft = SearchForWordBreaks(caretPosition, LogicalDirection.Backward, language, 1, false /* stopOnError */); wordBreakRight = SearchForWordBreaks(caretPosition, LogicalDirection.Forward, language, 1, false /* stopOnError */); textMap = new TextMap(wordBreakLeft, wordBreakRight, caretPosition, caretPosition); segments = new ArrayList(2); _spellerInterop.EnumTextSegments(textMap.Text, textMap.TextLength, null, new SpellerInterop.EnumTextSegmentsCallback(ExpandToWordBreakCallback), segments); // We will have no segments when position is surrounded by // nothing but white space. if (segments.Count != 0) { int leftBreakOffset; int rightBreakOffset; // Figure out where caretPosition lives in the segment list. FindPositionInSegmentList(textMap, LogicalDirection.Backward, segments, out leftBreakOffset, out rightBreakOffset); wordBreakLeft = textMap.MapOffsetToPosition(leftBreakOffset); wordBreakRight = textMap.MapOffsetToPosition(rightBreakOffset); } // Overlap? if (wordBreakLeft.CompareTo(rawEnd) < 0 && wordBreakRight.CompareTo(rawStart) > 0) { if (wordBreakLeft.CompareTo(rawStart) > 0) { // Truncate the right half of the input range. end = wordBreakLeft; } else if (wordBreakRight.CompareTo(rawEnd) < 0) { // Truncate the left half of the input range. start = wordBreakRight; } else { // The entire dirty range is covered by the caret word. // Try to find a following dirty range. GetNextScanRangeRaw(wordBreakRight, out start, out end); } // Schedule a future callback to deal with the skipped // overlapping section. ScheduleCaretMovedCallback(); } }
// Flags a run of text with an error. // In two exceptional circumstances we schedule an idle-time callback // to re-analyze the run instead of marking it: // - when the caret is within the error text. // - when an IME composition covers the text. private void MarkErrorRange(TextMap textMap, SpellerInterop.STextRange sTextRange) { ITextPointer errorStart; ITextPointer errorEnd; if (sTextRange.Start + sTextRange.Length > textMap.ContentEndOffset) { // We found an error that starts in the content but extends into // the context. This must be a multi-word error. // For now, ignore it. // return; } errorStart = textMap.MapOffsetToPosition(sTextRange.Start); errorEnd = textMap.MapOffsetToPosition(sTextRange.Start + sTextRange.Length); if (sTextRange.Start < textMap.ContentStartOffset) { Invariant.Assert(sTextRange.Start + sTextRange.Length > textMap.ContentStartOffset); // We've found an error that start in the context and extends into // the content. This can happen as more text is revealed to the // speller engine as the caret moves forward. // E.g., while scanning "avalon's" we flag an error over "avalon", // ignoring the "'s" because the caret is positioned within that segment. // Then, the user hits space and now we analyze "'s" along with its // preceding context "avalon". In this final scan, "avalon's" as a while // is flagged as an error and we enter this if statement. // We must mark the range clean before we can mark it dirty. // _statusTable.MarkErrorRange can only handle clean runs. _statusTable.MarkCleanRange(errorStart, errorEnd); } _statusTable.MarkErrorRange(errorStart, errorEnd); }