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
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);
|
|
}
|
|
}
|
|
}
|