Browse Source

Caret movement: don't stop between a base character and its combining mark (within a single grapheme)

newNRILSpyDebugger
Daniel Grunwald 12 years ago
parent
commit
35a81d3eb4
  1. 97
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextUtilities.cs
  2. 4
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs
  3. 13
      src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs

97
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Document/TextUtilities.cs

@ -14,7 +14,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -14,7 +14,7 @@ namespace ICSharpCode.AvalonEdit.Document
public enum CaretPositioningMode
{
/// <summary>
/// Normal positioning (stop at every caret position)
/// Normal positioning (stop after every grapheme)
/// </summary>
Normal,
/// <summary>
@ -32,7 +32,12 @@ namespace ICSharpCode.AvalonEdit.Document @@ -32,7 +32,12 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary>
/// Stop only on word borders, and anywhere in the middle of symbols.
/// </summary>
WordBorderOrSymbol
WordBorderOrSymbol,
/// <summary>
/// Stop between every Unicode codepoint, even within the same grapheme.
/// This is used to implement deleting the previous grapheme when Backspace is pressed.
/// </summary>
EveryCodepoint
}
/// <summary>
@ -199,37 +204,43 @@ namespace ICSharpCode.AvalonEdit.Document @@ -199,37 +204,43 @@ namespace ICSharpCode.AvalonEdit.Document
{
if (c == '\r' || c == '\n')
return CharacterClass.LineTerminator;
else if (char.IsWhiteSpace(c))
return CharacterClass.Whitespace;
else if (char.IsLetterOrDigit(c) || c == '_')
if (c == '_')
return CharacterClass.IdentifierPart;
else
return CharacterClass.Other;
return GetCharacterClass(char.GetUnicodeCategory(c));
}
static CharacterClass GetCharacterClass(char highSurrogate, char lowSurrogate)
{
if (char.IsSurrogatePair(highSurrogate, lowSurrogate)) {
switch (char.GetUnicodeCategory(highSurrogate.ToString() + lowSurrogate.ToString(), 0)) {
case UnicodeCategory.SpaceSeparator:
case UnicodeCategory.LineSeparator:
case UnicodeCategory.ParagraphSeparator:
return CharacterClass.Whitespace;
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.OtherLetter:
case UnicodeCategory.DecimalDigitNumber:
return CharacterClass.IdentifierPart;
default:
return CharacterClass.Other;
}
return GetCharacterClass(char.GetUnicodeCategory(highSurrogate.ToString() + lowSurrogate.ToString(), 0));
} else {
// malformed surrogate pair
return CharacterClass.Other;
}
}
static CharacterClass GetCharacterClass(UnicodeCategory c)
{
switch (c) {
case UnicodeCategory.SpaceSeparator:
case UnicodeCategory.LineSeparator:
case UnicodeCategory.ParagraphSeparator:
return CharacterClass.Whitespace;
case UnicodeCategory.UppercaseLetter:
case UnicodeCategory.LowercaseLetter:
case UnicodeCategory.TitlecaseLetter:
case UnicodeCategory.ModifierLetter:
case UnicodeCategory.OtherLetter:
case UnicodeCategory.DecimalDigitNumber:
return CharacterClass.IdentifierPart;
case UnicodeCategory.NonSpacingMark:
case UnicodeCategory.SpacingCombiningMark:
case UnicodeCategory.EnclosingMark:
return CharacterClass.CombiningMark;
default:
return CharacterClass.Other;
}
}
#endregion
#region GetNextCaretPosition
@ -251,13 +262,16 @@ namespace ICSharpCode.AvalonEdit.Document @@ -251,13 +262,16 @@ namespace ICSharpCode.AvalonEdit.Document
{
if (textSource == null)
throw new ArgumentNullException("textSource");
if (mode != CaretPositioningMode.Normal
&& mode != CaretPositioningMode.WordBorder
&& mode != CaretPositioningMode.WordStart
&& mode != CaretPositioningMode.WordBorderOrSymbol
&& mode != CaretPositioningMode.WordStartOrSymbol)
{
throw new ArgumentException("Unsupported CaretPositioningMode: " + mode, "mode");
switch (mode) {
case CaretPositioningMode.Normal:
case CaretPositioningMode.EveryCodepoint:
case CaretPositioningMode.WordBorder:
case CaretPositioningMode.WordBorderOrSymbol:
case CaretPositioningMode.WordStart:
case CaretPositioningMode.WordStartOrSymbol:
break; // OK
default:
throw new ArgumentException("Unsupported CaretPositioningMode: " + mode, "mode");
}
if (direction != LogicalDirection.Backward
&& direction != LogicalDirection.Forward)
@ -267,7 +281,7 @@ namespace ICSharpCode.AvalonEdit.Document @@ -267,7 +281,7 @@ namespace ICSharpCode.AvalonEdit.Document
int textLength = textSource.TextLength;
if (textLength <= 0) {
// empty document? has a normal caret position at 0, though no word borders
if (mode == CaretPositioningMode.Normal) {
if (IsNormal(mode)) {
if (offset > 0 && direction == LogicalDirection.Backward) return 0;
if (offset < 0 && direction == LogicalDirection.Forward) return 0;
}
@ -286,14 +300,14 @@ namespace ICSharpCode.AvalonEdit.Document @@ -286,14 +300,14 @@ namespace ICSharpCode.AvalonEdit.Document
if (nextPos == 0) {
// at the document start, there's only a word border
// if the first character is not whitespace
if (mode == CaretPositioningMode.Normal || !char.IsWhiteSpace(textSource.GetCharAt(0)))
if (IsNormal(mode) || !char.IsWhiteSpace(textSource.GetCharAt(0)))
return nextPos;
} else if (nextPos == textLength) {
// at the document end, there's never a word start
if (mode != CaretPositioningMode.WordStart && mode != CaretPositioningMode.WordStartOrSymbol) {
// at the document end, there's only a word border
// if the last character is not whitespace
if (mode == CaretPositioningMode.Normal || !char.IsWhiteSpace(textSource.GetCharAt(textLength - 1)))
if (IsNormal(mode) || !char.IsWhiteSpace(textSource.GetCharAt(textLength - 1)))
return nextPos;
}
} else {
@ -320,9 +334,19 @@ namespace ICSharpCode.AvalonEdit.Document @@ -320,9 +334,19 @@ namespace ICSharpCode.AvalonEdit.Document
}
}
static bool IsNormal(CaretPositioningMode mode)
{
return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
}
static bool StopBetweenCharacters(CaretPositioningMode mode, CharacterClass charBefore, CharacterClass charAfter)
{
// Stop after every character in normal mode
if (mode == CaretPositioningMode.EveryCodepoint)
return true;
// Don't stop in the middle of a grapheme
if (charAfter == CharacterClass.CombiningMark)
return false;
// Stop after every grapheme in normal mode
if (mode == CaretPositioningMode.Normal)
return true;
if (charBefore == charAfter) {
@ -370,6 +394,11 @@ namespace ICSharpCode.AvalonEdit.Document @@ -370,6 +394,11 @@ namespace ICSharpCode.AvalonEdit.Document
/// <summary>
/// The character is line terminator (\r or \n).
/// </summary>
LineTerminator
LineTerminator,
/// <summary>
/// The character is a unicode combining mark that modifies the previous character.
/// Corresponds to the Unicode designations "Mn", "Mc" and "Me".
/// </summary>
CombiningMark
}
}

4
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/SingleCharacterElementGenerator.cs

@ -111,7 +111,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -111,7 +111,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
if (mode == CaretPositioningMode.Normal)
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
return base.GetNextCaretPosition(visualColumn, direction, mode);
else
return -1;
@ -146,7 +146,7 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -146,7 +146,7 @@ namespace ICSharpCode.AvalonEdit.Rendering
public override int GetNextCaretPosition(int visualColumn, LogicalDirection direction, CaretPositioningMode mode)
{
if (mode == CaretPositioningMode.Normal)
if (mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint)
return base.GetNextCaretPosition(visualColumn, direction, mode);
else
return -1;

13
src/Libraries/AvalonEdit/ICSharpCode.AvalonEdit/Rendering/VisualLine.cs

@ -548,6 +548,15 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -548,6 +548,15 @@ namespace ICSharpCode.AvalonEdit.Rendering
return ch.FirstCharacterIndex;
}
/// <summary>
/// Gets the text view position from the specified visual column.
/// </summary>
public TextViewPosition GetTextViewPosition(int visualColumn)
{
int documentOffset = GetRelativeOffset(visualColumn) + this.FirstDocumentLine.Offset;
return new TextViewPosition(this.Document.GetLocation(documentOffset), visualColumn);
}
/// <summary>
/// Gets the text view position from the specified visual position.
/// If the position is within a character, it is rounded to the next character boundary.
@ -690,12 +699,12 @@ namespace ICSharpCode.AvalonEdit.Rendering @@ -690,12 +699,12 @@ namespace ICSharpCode.AvalonEdit.Rendering
static bool HasStopsInVirtualSpace(CaretPositioningMode mode)
{
return mode == CaretPositioningMode.Normal;
return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
}
static bool HasImplicitStopAtLineStart(CaretPositioningMode mode)
{
return mode == CaretPositioningMode.Normal;
return mode == CaretPositioningMode.Normal || mode == CaretPositioningMode.EveryCodepoint;
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "mode",

Loading…
Cancel
Save