/// <summary>
/// Moves one endpoint of the range the specified number of units in the text.
/// If the endpoint being moved crosses the other endpoint then the other endpoint
/// is moved along too resulting in a degenerate range and ensuring the correct ordering
/// of the endpoints. (i.e. always Start<=End)
/// </summary>
/// <param name="endpoint">The endpoint to move.</param>
/// <param name="unit">The textual unit for moving.</param>
/// <param name="count">The number of units to move. A positive count moves the endpoint forward.
/// A negative count moves backward. A count of 0 has no effect.</param>
/// <returns>The number of units actually moved, which can be less than the number requested if
/// moving the endpoint runs into the beginning or end of the document.</returns>
int ITextRangeProvider.MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
{
Normalize();
int movedCount = 0;
if (count != 0)
{
// Move endpoint by number of units.
bool start = (endpoint == TextPatternRangeEndpoint.Start);
ITextPointer positionRef = start ? _start : _end;
ITextPointer position = positionRef.CreatePointer();
if (MoveToUnitBoundary(position, start, count < 0 ? LogicalDirection.Backward : LogicalDirection.Forward, unit))
{
movedCount = (count > 0) ? 1 : -1;
}
if (count != movedCount)
{
movedCount += MovePositionByUnits(position, unit, count - movedCount);
}
// If endpoint has been moved at least by one unit, snap it to TextUnit boundary,
// because movement done by MovePositionByUnits does not guarantee position snapping.
if ((count > 0 && position.CompareTo(positionRef) > 0) ||
(count < 0 && position.CompareTo(positionRef) < 0) ||
(position.CompareTo(positionRef) == 0 && position.LogicalDirection != positionRef.LogicalDirection))
{
if (start)
{
_start = position;
}
else
{
_end = position;
}
if (unit != TextUnit.Page)
{
ExpandToEnclosingUnit(unit, start, !start);
}
// If endpoint has been moved, but 'movedCount' is 0, it means that we snapped to neariest
// unit boundary. Treat this situation as actual move.
if (movedCount == 0)
{
movedCount = (count > 0) ? 1 : -1;
}
}
// Ensure the correct ordering of the endpoint.
if (_start.CompareTo(_end) > 0)
{
if (start)
{
_end = _start.CreatePointer();
}
else
{
_start = _end.CreatePointer();
}
}
}
return movedCount;
}
public int Move(TextUnit unit, int count)
{
Log("{0}.Move({1}, {2})", ID, unit, count);
int movedCount = MoveEndpointByUnit(TextPatternRangeEndpoint.Start, unit, count);
segment = new SimpleSegment(segment.Offset, 0); // Collapse to empty range
ExpandToEnclosingUnit(unit);
return movedCount;
}
int ITextRangeProvider.MoveEndpointByUnit(TextPatternRangeEndpoint endpoint, TextUnit unit, int count)
{
Misc.SetFocus(_provider._hwnd);
// positive count means move forward. negative count means move backwards.
int moved = 0;
bool moveStart = endpoint == TextPatternRangeEndpoint.Start;
int start = Start;
int end = End;
if (count > 0)
{
if (moveStart)
{
Start = MoveEndpointForward(Start, unit, count, out moved);
// if the start did not change then no move was done.
if (start == Start)
{
moved = 0;
}
}
else
{
End = MoveEndpointForward(End, unit, count, out moved);
// if the end did not change then no move was done.
if (end == End)
{
moved = 0;
}
}
}
else if (count < 0)
{
if (moveStart)
{
Start = MoveEndpointBackward(Start, unit, count, out moved);
// if the start did not change then no move was done.
if (start == Start)
{
moved = 0;
}
}
else
{
End = MoveEndpointBackward(End, unit, count, out moved);
// if the end did not change then no move was done.
if (end == End)
{
moved = 0;
}
}
}
else
{
// moving zero of any unit has no effect.
moved = 0;
}
return moved;
}
/// <summary>
/// Re-positions the given position by an integral number of text units, but it does
/// not guarantee that position is snapped to TextUnit boundary.
/// This method assumes that input position is already snapped to appropriate TextUnit boundary.
/// </summary>
/// <param name="position">The position to move</param>
/// <param name="unit">Text units to step by</param>
/// <param name="count">Number of units to step over. Also specifies the direction of moving:
/// forward if positive, backward otherwise</param>
/// <returns>The actual number of units the position was moved over</returns>
private int MovePositionByUnits(ITextPointer position, TextUnit unit, int count)
{
ITextView textView;
int moved = 0;
int absCount = (count == int.MinValue) ? int.MaxValue : Math.Abs(count);
LogicalDirection direction = (count > 0) ? LogicalDirection.Forward : LogicalDirection.Backward;
// This method assumes that position is already snapped to appropriate TextUnit.
switch (unit)
{
case TextUnit.Character:
while (moved < absCount)
{
if (!TextPointerBase.MoveToNextInsertionPosition(position, direction))
{
break;
}
moved++;
}
break;
case TextUnit.Word:
while (moved < absCount)
{
if (!MoveToNextWordBoundary(position, direction))
{
break;
}
moved++;
}
break;
case TextUnit.Format:
// Formatting changes can be introduced by elements. Hence it is fair to
// assume that formatting boundaries are defined by non-text context.
while (moved < absCount)
{
ITextPointer positionOrig = position.CreatePointer();
// First skip all text in given direction.
while (position.GetPointerContext(direction) == TextPointerContext.Text)
{
if (!position.MoveToNextContextPosition(direction))
{
break;
}
}
// Move to next context
if (!position.MoveToNextContextPosition(direction))
{
break;
}
// Skip all formatting elements and position the pointer next to text.
while (position.GetPointerContext(direction) != TextPointerContext.Text)
{
if (!position.MoveToNextContextPosition(direction))
{
break;
}
}
// If moving backwards, position the pointer at the beginning of formatting range.
if (direction == LogicalDirection.Backward)
{
while (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.Text)
{
if (!position.MoveToNextContextPosition(LogicalDirection.Backward))
{
break;
}
}
}
if (position.GetPointerContext(direction) != TextPointerContext.None)
{
moved++;
}
else
{
position.MoveToPosition(positionOrig);
break;
}
}
// Adjust logical direction to point to the following text (forward or backward movement).
// If we don't do this, we'll normalize in the wrong direction and get stuck in a loop
// if caller tries to advance again.
position.SetLogicalDirection(LogicalDirection.Forward);
break;
//.........这里部分代码省略.........
/// <summary>
/// Moves the position to the closes unit boundary.
/// </summary>
private bool MoveToUnitBoundary(ITextPointer position, bool isStart, LogicalDirection direction, TextUnit unit)
{
bool moved = false;
ITextView textView;
switch (unit)
{
case TextUnit.Character:
if (!TextPointerBase.IsAtInsertionPosition(position))
{
if (TextPointerBase.MoveToNextInsertionPosition(position, direction))
{
moved = true;
}
}
break;
case TextUnit.Word:
if (!IsAtWordBoundary(position))
{
if (MoveToNextWordBoundary(position, direction))
{
moved = true;
}
}
break;
case TextUnit.Format:
// Formatting changes can be introduced by elements. Hence it is fair to
// assume that formatting boundaries are defined by non-text context.
while (position.GetPointerContext(direction) == TextPointerContext.Text)
{
if (position.MoveToNextContextPosition(direction))
{
moved = true;
}
}
// Make sure we end with text on the right, so that later ExpandToEnclosingUnit calls
// do the right thing.
if (moved && direction == LogicalDirection.Forward)
{
while (true)
{
TextPointerContext context = position.GetPointerContext(LogicalDirection.Forward);
if (context != TextPointerContext.ElementStart && context != TextPointerContext.ElementEnd)
break;
position.MoveToNextContextPosition(LogicalDirection.Forward);
}
}
break;
case TextUnit.Line:
// Positions are snapped to closest line boundaries. But since line information
// is based on the layout, positions are not changed, if:
// a) they are not currently in the view, or
// b) containing line cannot be found.
textView = _textAdaptor.GetUpdatedTextView();
if (textView != null && textView.IsValid && textView.Contains(position))
{
TextSegment lineRange = textView.GetLineRange(position);
if (!lineRange.IsNull)
{
double newSuggestedX;
int linesMoved = 0;
if (direction == LogicalDirection.Forward)
{
ITextPointer nextLineStart = null;
if (isStart)
{
nextLineStart = textView.GetPositionAtNextLine(lineRange.End, Double.NaN, 1, out newSuggestedX, out linesMoved);
}
if (linesMoved != 0)
{
lineRange = textView.GetLineRange(nextLineStart);
nextLineStart = lineRange.Start;
}
else
{
nextLineStart = lineRange.End;
}
nextLineStart = GetInsertionPosition(nextLineStart, LogicalDirection.Forward);
if (position.CompareTo(nextLineStart) != 0)
{
position.MoveToPosition(nextLineStart);
position.SetLogicalDirection(isStart ? LogicalDirection.Forward : LogicalDirection.Backward);
moved = true;
}
}
else
{
ITextPointer previousLineEnd = null;
//.........这里部分代码省略.........
/// <summary>
/// Expands the range to an integral number of enclosing units. If the range is already an
/// integral number of the specified units then it remains unchanged.
/// </summary>
private void ExpandToEnclosingUnit(TextUnit unit, bool expandStart, bool expandEnd)
{
ITextView textView;
switch (unit)
{
case TextUnit.Character:
if (expandStart && !TextPointerBase.IsAtInsertionPosition(_start))
{
TextPointerBase.MoveToNextInsertionPosition(_start, LogicalDirection.Backward);
}
if (expandEnd && !TextPointerBase.IsAtInsertionPosition(_end))
{
TextPointerBase.MoveToNextInsertionPosition(_end, LogicalDirection.Forward);
}
break;
case TextUnit.Word:
if (expandStart && !IsAtWordBoundary(_start))
{
MoveToNextWordBoundary(_start, LogicalDirection.Backward);
}
if (expandEnd && !IsAtWordBoundary(_end))
{
MoveToNextWordBoundary(_end, LogicalDirection.Forward);
}
break;
case TextUnit.Format:
// Formatting changes can be introduced by elements. Hence it is fair to
// assume that formatting boundaries are defined by non-text context.
if (expandStart)
{
TextPointerContext forwardContext = _start.GetPointerContext(LogicalDirection.Forward);
while (true)
{
TextPointerContext backwardContext = _start.GetPointerContext(LogicalDirection.Backward);
if (backwardContext == TextPointerContext.None)
break;
if (forwardContext == TextPointerContext.Text && backwardContext != TextPointerContext.Text)
break;
forwardContext = backwardContext;
_start.MoveToNextContextPosition(LogicalDirection.Backward);
}
}
if (expandEnd)
{
TextPointerContext backwardContext = _end.GetPointerContext(LogicalDirection.Backward);
while (true)
{
TextPointerContext forwardContext = _end.GetPointerContext(LogicalDirection.Forward);
if (forwardContext == TextPointerContext.None)
break;
if (forwardContext == TextPointerContext.Text && backwardContext != TextPointerContext.Text)
break;
backwardContext = forwardContext;
_end.MoveToNextContextPosition(LogicalDirection.Forward);
}
}
// Set LogicalDirection to prevent end points from crossing a formatting
// boundary when normalized.
_start.SetLogicalDirection(LogicalDirection.Forward);
_end.SetLogicalDirection(LogicalDirection.Forward);
break;
case TextUnit.Line:
// Positions are snapped to closest line boundaries. But since line information
// is based on the layout, positions are not changed, if:
// a) they are not currently in the view, or
// b) containing line cannot be found.
textView = _textAdaptor.GetUpdatedTextView();
if (textView != null && textView.IsValid)
{
bool snapEndPosition = true;
if (expandStart && textView.Contains(_start))
{
TextSegment lineRange = textView.GetLineRange(_start);
if (!lineRange.IsNull)
{
// Move start position to the beginning of containing line.
if (_start.CompareTo(lineRange.Start) != 0)
{
_start = lineRange.Start.CreatePointer();
}
// If this line contains also end position, move it to the
// end of this line.
if (lineRange.Contains(_end))
{
snapEndPosition = false;
if (_end.CompareTo(lineRange.End) != 0)
//.........这里部分代码省略.........
// moves an endpoint forward a certain number of units.
// the endpoint is just an index into the text so it could represent either
// the endpoint.
private int MoveEndpointForward(int index, TextUnit unit, int count, out int moved)
{
switch (unit)
{
case TextUnit.Character:
{
int limit = _provider.GetTextLength() ;
ValidateEndpoints();
moved = Math.Min(count, limit - index);
index = index + moved;
index = index > limit ? limit : index;
}
break;
case TextUnit.Word:
{
string text = _provider.GetText();
ValidateEndpoints();
#if WCP_NLS_ENABLED
// use the same word breaker as Avalon Text.
WordBreaker breaker = new WordBreaker();
TextContainer container = new TextContainer(text);
TextNavigator navigator = new TextNavigator(index, container);
// move forward one word break for each count
for (moved = 0; moved < count && index < text.Length; moved++)
{
if (!breaker.MoveToNextWordBreak(navigator))
break;
}
index = navigator.Position;
#else
for (moved = 0; moved < count && index < text.Length; moved++)
{
for (index++; !AtWordBoundary(text, index); index++) ;
}
#endif
}
break;
case TextUnit.Line:
{
// figure out what line we are on. if we are in the middle of a line and
// are moving left then we'll round up to the next line so that we move
// to the beginning of the current line.
int line = _provider.LineFromChar(index);
// limit the number of lines moved to the number of lines available to move
// Note lineMax is always >= 1.
int lineMax = _provider.GetLineCount();
moved = Math.Min(count, lineMax - line - 1);
if (moved > 0)
{
// move the endpoint to the beginning of the destination line.
index = _provider.LineIndex(line + moved);
}
else if (moved == 0 && lineMax == 1)
{
// There is only one line so get the text length as endpoint
index = _provider.GetTextLength();
moved = 1;
}
}
break;
case TextUnit.Paragraph:
{
// just like moving words but we look for paragraph boundaries instead of
// word boundaries.
string text = _provider.GetText();
ValidateEndpoints();
for (moved = 0; moved < count && index < text.Length; moved++)
{
for (index++; !AtParagraphBoundary(text, index); index++) ;
}
}
break;
case TextUnit.Format:
case TextUnit.Page:
case TextUnit.Document:
{
// since edit controls are plain text moving one uniform format unit will
// take us all the way to the end of the document, just like
// "pages" and document.
int limit = _provider.GetTextLength();
ValidateEndpoints();
// we'll move 1 format unit if we aren't already at the end of the
// document. Otherwise, we won't move at all.
moved = index < limit ? 1 : 0;
//.........这里部分代码省略.........
void ITextRangeProvider.ExpandToEnclosingUnit(TextUnit unit)
{
Misc.SetFocus(_provider._hwnd);
switch (unit)
{
case TextUnit.Character:
// if it is a degenerate range then expand it to be one character.
// otherwise, leave it as it is.
if (Start == End)
{
int moved;
End = MoveEndpointForward(End, TextUnit.Character, 1, out moved);
}
break;
case TextUnit.Word:
{
// this works same as paragraph except we look for word boundaries instead of paragraph boundaries.
// get the text so we can figure out where the boundaries are
string text = _provider.GetText();
ValidateEndpoints();
#if WCP_NLS_ENABLED
// use the same word breaker that Avalon Text uses.
WordBreaker breaker = new WordBreaker();
TextContainer container = new TextContainer(text);
// if the starting point of the range is not already at a word break
// then move it backwards to the nearest word break.
TextNavigator startNavigator = new TextNavigator(Start, container);
if (!breaker.IsAtWordBreak(startNavigator))
{
breaker.MoveToPreviousWordBreak(startNavigator);
Start = startNavigator.Position;
}
// if the range is degenerate or the ending point of the range is not already at a word break
// then move it forwards to the nearest word break.
TextNavigator endNavigator = new TextNavigator(End, container);
if (Start==End || !breaker.IsAtWordBreak(endNavigator))
{
breaker.MoveToNextWordBreak(endNavigator);
End = endNavigator.Position;
}
#else
// move start left until we reach a word boundary.
for (; !AtWordBoundary(text, Start); Start--) ;
// move end right until we reach word boundary (different from Start).
End = Math.Min(Math.Max(End, Start + 1), text.Length);
for (; !AtWordBoundary(text, End); End++) ;
#endif
}
break;
case TextUnit.Line:
{
if (_provider.GetLineCount() != 1)
{
int startLine = _provider.LineFromChar(Start);
int endLine = _provider.LineFromChar(End);
MoveTo(_provider.LineIndex(startLine), _provider.LineIndex(endLine + 1));
}
else
{
MoveTo(0, _provider.GetTextLength());
}
}
break;
case TextUnit.Paragraph:
{
// this works same as paragraph except we look for word boundaries instead of paragraph boundaries.
// get the text so we can figure out where the boundaries are
string text = _provider.GetText();
ValidateEndpoints();
// move start left until we reach a paragraph boundary.
for (; !AtParagraphBoundary(text, Start); Start--);
// move end right until we reach a paragraph boundary (different from Start).
End = Math.Min(Math.Max(End, Start + 1), text.Length);
for (; !AtParagraphBoundary(text, End); End++);
}
break;
case TextUnit.Format:
case TextUnit.Page:
case TextUnit.Document:
MoveTo(0, _provider.GetTextLength());
break;
//break;
default:
throw new System.ComponentModel.InvalidEnumArgumentException("unit", (int)unit, typeof(TextUnit));
}
}
请发表评论