// ----------------------------------------------------------------------- // // Copyright 2014 MIT Licence. See licence.md for more information. // // ----------------------------------------------------------------------- namespace Perspex.Controls.Utils { using System.Globalization; internal static class StringUtils { private enum CharClass { CharClassUnknown, CharClassWhitespace, CharClassAlphaNumeric, } public static bool IsEol(char c) { return c == '\r' || c == '\n'; } public static bool IsStartOfWord(string text, int index) { // A 'word' starts with an AlphaNumeric or some punctuation symbols immediately // preceeded by lwsp. if (index > 0 && !char.IsWhiteSpace(text[index - 1])) { return false; } switch (CharUnicodeInfo.GetUnicodeCategory(text[index])) { case UnicodeCategory.LowercaseLetter: case UnicodeCategory.TitlecaseLetter: case UnicodeCategory.UppercaseLetter: case UnicodeCategory.DecimalDigitNumber: case UnicodeCategory.LetterNumber: case UnicodeCategory.OtherNumber: case UnicodeCategory.DashPunctuation: case UnicodeCategory.InitialQuotePunctuation: case UnicodeCategory.OpenPunctuation: case UnicodeCategory.CurrencySymbol: case UnicodeCategory.MathSymbol: return true; // TODO: How do you do this in .NET? // case UnicodeCategory.OtherPunctuation: // // words cannot start with '.', but they can start with '&' or '*' (for example) // return g_unichar_break_type(buffer->text[index]) == G_UNICODE_BREAK_ALPHABETIC; default: return false; } } public static int PreviousWord(string text, int cursor, bool gtkMode) { int begin; int i; int cr; int lf; lf = LineBegin(text, cursor) - 1; if (lf > 0 && text[lf] == '\n' && text[lf - 1] == '\r') { cr = lf - 1; } else { cr = lf; } // if the cursor is at the beginning of the line, return the end of the prev line if (cursor - 1 == lf) { return (cr > 0) ? cr : 0; } if (gtkMode) { CharClass cc = GetCharClass(text[cursor - 1]); begin = lf + 1; i = cursor; // skip over the word, punctuation, or run of whitespace while (i > begin && GetCharClass(text[i - 1]) == cc) { i--; } // if the cursor was at whitespace, skip back a word too if (cc == CharClass.CharClassWhitespace && i > begin) { cc = GetCharClass(text[i - 1]); while (i > begin && GetCharClass(text[i - 1]) == cc) { i--; } } } else { begin = lf + 1; i = cursor; if (cursor < text.Length) { // skip to the beginning of this word while (i > begin && !char.IsWhiteSpace(text[i - 1])) { i--; } if (i < cursor && IsStartOfWord(text, i)) { return i; } } // skip to the start of the lwsp while (i > begin && char.IsWhiteSpace(text[i - 1])) { i--; } if (i > begin) { i--; } // skip to the beginning of the word while (i > begin && !IsStartOfWord(text, i)) { i--; } } return i; } public static int NextWord(string text, int cursor, bool gtkMode) { int i, lf, cr; cr = LineEnd(text, cursor); if (cr < text.Length && text[cr] == '\r' && text[cr + 1] == '\n') { lf = cr + 1; } else { lf = cr; } // if the cursor is at the end of the line, return the starting offset of the next line if (cursor == cr || cursor == lf) { if (lf < text.Length) { return lf + 1; } return cursor; } if (gtkMode) { CharClass cc = GetCharClass(text[cursor]); i = cursor; // skip over the word, punctuation, or run of whitespace while (i < cr && GetCharClass(text[i]) == cc) { i++; } // skip any whitespace after the word/punct while (i < cr && char.IsWhiteSpace(text[i])) { i++; } } else { i = cursor; // skip to the end of the current word while (i < cr && !char.IsWhiteSpace(text[i])) { i++; } // skip any whitespace after the word while (i < cr && char.IsWhiteSpace(text[i])) { i++; } // find the start of the next word while (i < cr && !IsStartOfWord(text, i)) { i++; } } return i; } private static CharClass GetCharClass(char c) { if (char.IsWhiteSpace(c)) { return CharClass.CharClassWhitespace; } else if (char.IsLetterOrDigit(c)) { return CharClass.CharClassAlphaNumeric; } else { return CharClass.CharClassUnknown; } } private static int LineBegin(string text, int pos) { while (pos > 0 && !IsEol(text[pos - 1])) { pos--; } return pos; } private static int LineEnd(string text, int cursor, bool include = false) { while (cursor < text.Length && !IsEol(text[cursor])) { cursor++; } if (include && cursor < text.Length) { if (text[cursor] == '\r' && text[cursor + 1] == '\n') { cursor += 2; } else { cursor++; } } return cursor; } } }