for WinForms
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
12 KiB

// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Mike Krüger" email="mike@icsharpcode.net"/>
// <version>$Revision$</version>
// </file>
using System.Diagnostics;
using System.Text;
namespace ICSharpCode.TextEditor.Document
{
public sealed class TextUtilities
{
public enum CharacterType
{
LetterDigitOrUnderscore,
WhiteSpace,
Other
}
/// <remarks>
/// This function takes a string and converts the whitespace in front of
/// it to tabs. If the length of the whitespace at the start of the string
/// was not a whole number of tabs then there will still be some spaces just
/// before the text starts.
/// the output string will be of the form:
/// 1. zero or more tabs
/// 2. zero or more spaces (less than tabIndent)
/// 3. the rest of the line
/// </remarks>
public static string LeadingWhiteSpaceToTabs(string line, int tabIndent)
{
var sb = new StringBuilder(line.Length);
var consecutiveSpaces = 0;
int i;
for (i = 0; i < line.Length; i++)
if (line[i] == ' ')
{
consecutiveSpaces++;
if (consecutiveSpaces == tabIndent)
{
sb.Append(value: '\t');
consecutiveSpaces = 0;
}
}
else if (line[i] == '\t')
{
sb.Append(value: '\t');
// if we had say 3 spaces then a tab and tabIndent was 4 then
// we would want to simply replace all of that with 1 tab
consecutiveSpaces = 0;
}
else
{
break;
}
if (i < line.Length)
sb.Append(line.Substring(i - consecutiveSpaces));
return sb.ToString();
}
public static bool IsLetterDigitOrUnderscore(char c)
{
if (!char.IsLetterOrDigit(c))
return c == '_';
return true;
}
/// <remarks>
/// This method returns the expression before a specified offset.
/// That method is used in code completion to determine the expression given
/// to the parser for type resolve.
/// </remarks>
public static string GetExpressionBeforeOffset(TextArea textArea, int initialOffset)
{
var document = textArea.Document;
var offset = initialOffset;
while (offset - 1 > 0)
switch (document.GetCharAt(offset - 1))
{
case '\n':
case '\r':
case '}':
goto done;
// offset = SearchBracketBackward(document, offset - 2, '{','}');
// break;
case ']':
offset = SearchBracketBackward(document, offset - 2, openBracket: '[', closingBracket: ']');
break;
case ')':
offset = SearchBracketBackward(document, offset - 2, openBracket: '(', closingBracket: ')');
break;
case '.':
--offset;
break;
case '"':
if (offset < initialOffset - 1)
return null;
return "\"\"";
case '\'':
if (offset < initialOffset - 1)
return null;
return "'a'";
case '>':
if (document.GetCharAt(offset - 2) == '-')
{
offset -= 2;
break;
}
goto done;
default:
if (char.IsWhiteSpace(document.GetCharAt(offset - 1)))
{
--offset;
break;
}
var start = offset - 1;
if (!IsLetterDigitOrUnderscore(document.GetCharAt(start)))
goto done;
while (start > 0 && IsLetterDigitOrUnderscore(document.GetCharAt(start - 1)))
--start;
var word = document.GetText(start, offset - start).Trim();
switch (word)
{
case "ref":
case "out":
case "in":
case "return":
case "throw":
case "case":
goto done;
}
if (word.Length > 0 && !IsLetterDigitOrUnderscore(word[index: 0]))
goto done;
offset = start;
break;
}
done:
//// simple exit fails when : is inside comment line or any other character
//// we have to check if we got several ids in resulting line, which usually happens when
//// id. is typed on next line after comment one
//// Would be better if lexer would parse properly such expressions. However this will cause
//// modifications in this area too - to get full comment line and remove it afterwards
if (offset < 0)
return string.Empty;
var resText = document.GetText(offset, textArea.Caret.Offset - offset).Trim();
var pos = resText.LastIndexOf(value: '\n');
if (pos >= 0)
offset += pos + 1;
var expression = document.GetText(offset, textArea.Caret.Offset - offset).Trim();
return expression;
}
public static CharacterType GetCharacterType(char c)
{
if (IsLetterDigitOrUnderscore(c))
return CharacterType.LetterDigitOrUnderscore;
if (char.IsWhiteSpace(c))
return CharacterType.WhiteSpace;
return CharacterType.Other;
}
public static int GetFirstNonWSChar(IDocument document, int offset)
{
while (offset < document.TextLength && char.IsWhiteSpace(document.GetCharAt(offset)))
++offset;
return offset;
}
public static int FindWordEnd(IDocument document, int offset)
{
var line = document.GetLineSegmentForOffset(offset);
var endPos = line.Offset + line.Length;
while (offset < endPos && IsLetterDigitOrUnderscore(document.GetCharAt(offset)))
++offset;
return offset;
}
public static int FindWordStart(IDocument document, int offset)
{
var line = document.GetLineSegmentForOffset(offset);
var lineOffset = line.Offset;
while (offset > lineOffset && IsLetterDigitOrUnderscore(document.GetCharAt(offset - 1)))
--offset;
return offset;
}
// go forward to the start of the next word
// if the cursor is at the start or in the middle of a word we move to the end of the word
// and then past any whitespace that follows it
// if the cursor is at the start or in the middle of some whitespace we move to the start of the
// next word
public static int FindNextWordStart(IDocument document, int offset)
{
// var originalOffset = offset;
var line = document.GetLineSegmentForOffset(offset);
var endPos = line.Offset + line.Length;
// lets go to the end of the word, whitespace or operator
var t = GetCharacterType(document.GetCharAt(offset));
while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == t)
++offset;
// now we're at the end of the word, lets find the start of the next one by skipping whitespace
while (offset < endPos && GetCharacterType(document.GetCharAt(offset)) == CharacterType.WhiteSpace)
++offset;
return offset;
}
// go back to the start of the word we are on
// if we are already at the start of a word or if we are in whitespace, then go back
// to the start of the previous word
public static int FindPrevWordStart(IDocument document, int offset)
{
// var originalOffset = offset;
if (offset > 0)
{
var line = document.GetLineSegmentForOffset(offset);
var t = GetCharacterType(document.GetCharAt(offset - 1));
while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t)
--offset;
// if we were in whitespace, and now we're at the end of a word or operator, go back to the beginning of it
if (t == CharacterType.WhiteSpace && offset > line.Offset)
{
t = GetCharacterType(document.GetCharAt(offset - 1));
while (offset > line.Offset && GetCharacterType(document.GetCharAt(offset - 1)) == t)
--offset;
}
}
return offset;
}
public static string GetLineAsString(IDocument document, int lineNumber)
{
var line = document.GetLineSegment(lineNumber);
return document.GetText(line.Offset, line.Length);
}
public static int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket)
{
return document.FormattingStrategy.SearchBracketBackward(document, offset, openBracket, closingBracket);
}
public static int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket)
{
return document.FormattingStrategy.SearchBracketForward(document, offset, openBracket, closingBracket);
}
/// <remarks>
/// Returns true, if the line lineNumber is empty or filled with whitespaces.
/// </remarks>
public static bool IsEmptyLine(IDocument document, int lineNumber)
{
return IsEmptyLine(document, document.GetLineSegment(lineNumber));
}
/// <remarks>
/// Returns true, if the line lineNumber is empty or filled with whitespaces.
/// </remarks>
public static bool IsEmptyLine(IDocument document, LineSegment line)
{
for (var i = line.Offset; i < line.Offset + line.Length; ++i)
{
var ch = document.GetCharAt(i);
if (!char.IsWhiteSpace(ch))
return false;
}
return true;
}
private static bool IsWordPart(char ch)
{
return IsLetterDigitOrUnderscore(ch) || ch == '.';
}
public static string GetWordAt(IDocument document, int offset)
{
if (offset < 0 || offset >= document.TextLength - 1 || !IsWordPart(document.GetCharAt(offset)))
return string.Empty;
var startOffset = offset;
var endOffset = offset;
while (startOffset > 0 && IsWordPart(document.GetCharAt(startOffset - 1)))
--startOffset;
while (endOffset < document.TextLength - 1 && IsWordPart(document.GetCharAt(endOffset + 1)))
++endOffset;
Debug.Assert(endOffset >= startOffset);
return document.GetText(startOffset, endOffset - startOffset + 1);
}
}
}