// Apply typing heuristics // - extend for overtype. // - prevent paragraph merges when only the leading edge of that // last paragraph is selected. // // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are // called together, with a an extra step in between for TextSelection // overrides of the ApplyTypingHueristic method. internal static void ApplyFinalTypingHeuristics(ITextRange thisRange, bool overType) { // Expand empty selection forward in overtype mode if (overType && thisRange.IsEmpty && !TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Forward)) { // ITextPointer nextPosition = thisRange.End.CreatePointer(); nextPosition.MoveToNextInsertionPosition(LogicalDirection.Forward); if (!TextRangeEditTables.IsTableStructureCrossed(thisRange.Start, nextPosition)) { TextRange range = new TextRange(thisRange.Start, nextPosition); Invariant.Assert(!range.IsTableCellRange); range.Text = String.Empty; } } // If the range is non-empty, and its end just passes a paragraph break, // pull the end back to stop a paragraph merge on the next keystroke. if (!thisRange.IsEmpty && (TextPointerBase.IsNextToAnyBreak(thisRange.End, LogicalDirection.Backward) || TextPointerBase.IsAfterLastParagraph(thisRange.End))) { ITextPointer newEnd = thisRange.End.GetNextInsertionPosition(LogicalDirection.Backward); thisRange.Select(thisRange.Start, newEnd); } }
// Implementation of a setter fot ITextRange.Text property internal static void SetText(ITextRange thisRange, string textData) { NormalizeRange(thisRange); if (textData == null) { throw new ArgumentNullException("textData"); } ITextPointer explicitInsertPosition = null; TextRangeBase.BeginChange(thisRange); try { // Delete content covered by this range if (!thisRange.IsEmpty) { if (thisRange.Start is TextPointer && ((TextPointer)thisRange.Start).Parent == ((TextPointer)thisRange.End).Parent && ((TextPointer)thisRange.Start).Parent is Run && textData.Length > 0) { // When textrange start/end are parented by the same Run, we can optimize // and delete content without any checks. // // Note that NOT doing so has a serious side effect in this case. // Low-level code in TextRangeEdit does not preserve an empty run // with no formatting properties after deletion. // We dont want to loose the empty Run, // when we are just about to set the range text to non-empty string. // Otherwise, newly inserted text might have undesirable formatting properties // applied due to an insertion position within an adjacent Run. if (thisRange.Start.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text && thisRange.End.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) { // If we're deleting with surrounding text, make sure we insert later between the surrounding text. // Because we will invalidate layout with the delete, it's possible that thisRange.Start // will normalize itself to a different character offset on the next reference. // This is because -- unfortunately -- when layout is valid we use ITextView.IsAtCaretUnitBoundary // to normalize unicode offsets, but when layout is dirty we use a different code path // that ignores the current font and simply checks Unicode values for surrogates and // combining marks. See bug 1683515 for an example. explicitInsertPosition = thisRange.Start; } TextContainer textContainer = ((TextPointer)thisRange.Start).TextContainer; textContainer.DeleteContentInternal((TextPointer)thisRange.Start, (TextPointer)thisRange.End); } else { thisRange.Start.DeleteContentToPosition(thisRange.End); } if (thisRange.Start is TextPointer) { TextRangeEdit.MergeFlowDirection((TextPointer)thisRange.Start); } thisRange.Select(thisRange.Start, thisRange.Start); } // Insert text at end position // Note that the non-emptiness check below is not an optimization: // In case of empty text the code block in it would change an empty range // orientation, which is undesirable side effect. // Also if the inserted text is empty we need to avoid ensuring insertion position, // which can create paragraphs etc. if (textData.Length > 0) { ITextPointer insertPosition = (explicitInsertPosition == null) ? thisRange.Start : explicitInsertPosition; // Ensure last paragraph existence and prepare ends for the new selection bool pastedFragmentEndsWithNewLine = textData.EndsWith("\n", StringComparison.Ordinal); // We are going to insert paragraph implicitly when the block content becomes totally empty. // Store the fact that implicit paragraph was inserted to exclude ane extra paragraph break // from the end of pasted fragment bool implicitParagraphInserted = insertPosition is TextPointer && TextSchema.IsValidChild(/*position*/insertPosition, /*childType*/typeof(Block)) && (insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.None || insertPosition.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart) && (insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.None || insertPosition.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.ElementEnd); // Make sure that the range is positioned at insertion position if (insertPosition is TextPointer && explicitInsertPosition == null) { TextPointer insertionPosition = TextRangeEditTables.EnsureInsertionPosition((TextPointer)insertPosition); thisRange.Select(insertionPosition, insertionPosition); insertPosition = thisRange.Start; } Invariant.Assert(TextSchema.IsInTextContent(insertPosition), "range.Start is expected to be in text content"); ITextPointer newStart = insertPosition.GetFrozenPointer(LogicalDirection.Backward); ITextPointer newEnd = insertPosition.CreatePointer(LogicalDirection.Forward); if ((newStart is TextPointer) && ((TextPointer)newStart).Paragraph != null) { // Rich text - '\n' must be replaced by Paragraphs TextPointer insertionPosition = (TextPointer)newStart.CreatePointer(LogicalDirection.Forward); string[] textParagraphs = textData.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None); int length = textParagraphs.Length; if (implicitParagraphInserted && pastedFragmentEndsWithNewLine) { length--; } for (int i = 0; i < length; i++) { insertionPosition.InsertTextInRun(textParagraphs[i]); if (i < length - 1) { if (insertionPosition.HasNonMergeableInlineAncestor) { // We cannot split a Hyperlink or other non-mergeable Inline element, // so insert a space character instead (similar to embedded object). // Note that this means, SetText would loose // paragraph break information in this case. insertionPosition.InsertTextInRun(" "); } else { // insertionPosition gets repositioned to just inside // the following Paragraph. insertionPosition = insertionPosition.InsertParagraphBreak(); } // Keep newEnd in [....] with the paragraph break. // We can't rely on LogicalDirection alone for // anything other than simple text inserts. newEnd = insertionPosition; } } if (implicitParagraphInserted && pastedFragmentEndsWithNewLine) { // We must include ending paragraph break into a resulting range newEnd = newEnd.GetNextInsertionPosition(LogicalDirection.Forward); if (newEnd == null) { newEnd = newStart.TextContainer.End; // set end of range to IsAfterLastParagraph position } // Note: As a result of this logic with implicitParagraphInserted && pastedFragmentEndsWithNewLine // we have the following behavior: // Given that: // range = new TextRange(flowDocument.ContentStart, flowDocument.ContentEnd); // the statement: // range.Text = "foo\r\n"; // has the effect of leaving flowDocument with this content (note: just one paragraph): // <Paragraph>foo</Paragraph> // and range selecting the whole content: // range.Text == "foo\r\n" // // the statement: // range.Text = "foo"; // results with the same content in flowDocument (one paragraph) // but the range is not extended beyond last paragraph end: // range.Text == "foo". } } else { // Non-paragraph text - insert without '\n' conversion newStart.InsertTextInRun(textData); } // Select the range TextRangeBase.SelectPrivate(thisRange, newStart, newEnd, /*includeCellAtMovingPosition:*/false, /*markRangeChanged*/true); } } finally { TextRangeBase.EndChange(thisRange); } }
// Apply initial typing heuristics -- adjust range for typing // when it spans one or more TableCells. // // ApplyInitialTypingHeuristics/ApplyFinalTypingHeuristics are // called together, with a an extra step in between for TextSelection // overrides of the ApplyTypingHueristic method. internal static void ApplyInitialTypingHeuristics(ITextRange thisRange) { // When table cells selected, clear the start cell and collapse selection into it if (thisRange.IsTableCellRange) { TableCell cell; if (thisRange.Start is TextPointer && (cell = TextRangeEditTables.GetTableCellFromPosition((TextPointer)thisRange.Start)) != null) { // Select the first cell content to make springload formatting happen below thisRange.Select(cell.ContentStart, cell.ContentEnd); } else { thisRange.Select(thisRange.Start, thisRange.Start); } } }
internal static ITextRange Find(FindToolBar findToolBar, TextEditor textEditor, ITextView textView, ITextView masterPageTextView) { ITextPointer textPointer = null; Invariant.Assert(findToolBar != null); Invariant.Assert(textEditor != null); FindFlags findFlags = FindFlags.None; findFlags |= (findToolBar.SearchUp ? FindFlags.FindInReverse : FindFlags.None); findFlags |= (findToolBar.MatchCase ? FindFlags.MatchCase : FindFlags.None); findFlags |= (findToolBar.MatchWholeWord ? FindFlags.FindWholeWordsOnly : FindFlags.None); findFlags |= (findToolBar.MatchDiacritic ? FindFlags.MatchDiacritics : FindFlags.None); findFlags |= (findToolBar.MatchKashida ? FindFlags.MatchKashida : FindFlags.None); findFlags |= (findToolBar.MatchAlefHamza ? FindFlags.MatchAlefHamza : FindFlags.None); ITextContainer textContainer = textEditor.TextContainer; ITextRange selection = textEditor.Selection; string searchText = findToolBar.SearchText; CultureInfo documentCultureInfo = DocumentViewerHelper.GetDocumentCultureInfo(textContainer); ITextPointer textPointer2; ITextPointer textPointer3; ITextRange textRange; if (selection.IsEmpty) { if (textView != null && !textView.IsValid) { textView = null; } if (textView != null && textView.Contains(selection.Start)) { textPointer2 = (findToolBar.SearchUp ? textContainer.Start : selection.Start); textPointer3 = (findToolBar.SearchUp ? selection.Start : textContainer.End); } else { if (masterPageTextView != null && masterPageTextView.IsValid) { foreach (TextSegment textSegment in masterPageTextView.TextSegments) { if (!textSegment.IsNull) { if (textPointer == null) { textPointer = ((!findToolBar.SearchUp) ? textSegment.Start : textSegment.End); } else if (!findToolBar.SearchUp) { if (textSegment.Start.CompareTo(textPointer) < 0) { textPointer = textSegment.Start; } } else if (textSegment.End.CompareTo(textPointer) > 0) { textPointer = textSegment.End; } } } } if (textPointer != null) { textPointer2 = (findToolBar.SearchUp ? textContainer.Start : textPointer); textPointer3 = (findToolBar.SearchUp ? textPointer : textContainer.End); } else { textPointer2 = textContainer.Start; textPointer3 = textContainer.End; } } } else { textRange = TextFindEngine.Find(selection.Start, selection.End, searchText, findFlags, documentCultureInfo); if (textRange != null && textRange.Start != null && textRange.Start.CompareTo(selection.Start) == 0 && textRange.End.CompareTo(selection.End) == 0) { textPointer2 = (findToolBar.SearchUp ? selection.Start : selection.End); textPointer3 = (findToolBar.SearchUp ? textContainer.Start : textContainer.End); } else { textPointer2 = (findToolBar.SearchUp ? selection.End : selection.Start); textPointer3 = (findToolBar.SearchUp ? textContainer.Start : textContainer.End); } } textRange = null; if (textPointer2 != null && textPointer3 != null && textPointer2.CompareTo(textPointer3) != 0) { if (textPointer2.CompareTo(textPointer3) > 0) { ITextPointer textPointer4 = textPointer2; textPointer2 = textPointer3; textPointer3 = textPointer4; } textRange = TextFindEngine.Find(textPointer2, textPointer3, searchText, findFlags, documentCultureInfo); if (textRange != null && !textRange.IsEmpty) { selection.Select(textRange.Start, textRange.End); } } return(textRange); }