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.
211 lines
7.5 KiB
211 lines
7.5 KiB
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) |
|
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt) |
|
|
|
using System; |
|
using System.Diagnostics; |
|
using System.Windows.Documents; |
|
using ICSharpCode.AvalonEdit; |
|
using ICSharpCode.AvalonEdit.Document; |
|
using ICSharpCode.AvalonEdit.Utils; |
|
using ICSharpCode.NRefactory.Editor; |
|
using ICSharpCode.SharpDevelop.Editor.AvalonEdit; |
|
|
|
namespace ICSharpCode.SharpDevelop.Editor |
|
{ |
|
/// <summary> |
|
/// Extension methods for ITextEditor and IDocument. |
|
/// </summary> |
|
public static class DocumentUtilitites |
|
{ |
|
/// <summary> |
|
/// Creates a new mutable document from the specified text buffer. |
|
/// </summary> |
|
/// <remarks> |
|
/// Use the more efficient <see cref="LoadReadOnlyDocumentFromBuffer"/> if you only need a read-only document. |
|
/// </remarks> |
|
[Obsolete("Use the TextDocument constructor instead")] |
|
public static IDocument LoadDocumentFromBuffer(ITextSource buffer) |
|
{ |
|
return new TextDocument(buffer, null); |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new read-only document from the specified text buffer. |
|
/// </summary> |
|
[Obsolete("Use the ReadOnlyDocument constructor instead")] |
|
public static IDocument LoadReadOnlyDocumentFromBuffer(ITextSource buffer) |
|
{ |
|
return new ReadOnlyDocument(buffer, null); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the word in front of the caret. |
|
/// </summary> |
|
public static string GetWordBeforeCaret(this ITextEditor editor) |
|
{ |
|
if (editor == null) |
|
throw new ArgumentNullException("editor"); |
|
int endOffset = editor.Caret.Offset; |
|
int startOffset = FindPrevWordStart(editor.Document, endOffset); |
|
if (startOffset < 0) |
|
return string.Empty; |
|
else |
|
return editor.Document.GetText(startOffset, endOffset - startOffset); |
|
} |
|
|
|
static readonly char[] whitespaceChars = {' ', '\t'}; |
|
|
|
/// <summary> |
|
/// Replaces the text in a line. |
|
/// If only whitespace at the beginning and end of the line was changed, this method |
|
/// only adjusts the whitespace and doesn't replace the other text. |
|
/// </summary> |
|
public static void SmartReplaceLine(this IDocument document, IDocumentLine line, string newLineText) |
|
{ |
|
if (document == null) |
|
throw new ArgumentNullException("document"); |
|
if (line == null) |
|
throw new ArgumentNullException("line"); |
|
if (newLineText == null) |
|
throw new ArgumentNullException("newLineText"); |
|
string newLineTextTrim = newLineText.Trim(whitespaceChars); |
|
string oldLineText = document.GetText(line); |
|
if (oldLineText == newLineText) |
|
return; |
|
int pos = oldLineText.IndexOf(newLineTextTrim, StringComparison.Ordinal); |
|
if (newLineTextTrim.Length > 0 && pos >= 0) { |
|
using (document.OpenUndoGroup()) { |
|
// find whitespace at beginning |
|
int startWhitespaceLength = 0; |
|
while (startWhitespaceLength < newLineText.Length) { |
|
char c = newLineText[startWhitespaceLength]; |
|
if (c != ' ' && c != '\t') |
|
break; |
|
startWhitespaceLength++; |
|
} |
|
// find whitespace at end |
|
int endWhitespaceLength = newLineText.Length - newLineTextTrim.Length - startWhitespaceLength; |
|
|
|
// replace whitespace sections |
|
int lineOffset = line.Offset; |
|
document.Replace(lineOffset + pos + newLineTextTrim.Length, line.Length - pos - newLineTextTrim.Length, newLineText.Substring(newLineText.Length - endWhitespaceLength)); |
|
document.Replace(lineOffset, pos, newLineText.Substring(0, startWhitespaceLength)); |
|
} |
|
} else { |
|
document.Replace(line.Offset, line.Length, newLineText); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Finds the first word start in the document before offset. |
|
/// </summary> |
|
/// <returns>The offset of the word start, or -1 if there is no word start before the specified offset.</returns> |
|
public static int FindPrevWordStart(this ITextSource textSource, int offset) |
|
{ |
|
return TextUtilities.GetNextCaretPosition(textSource, offset, LogicalDirection.Backward, CaretPositioningMode.WordStart); |
|
} |
|
|
|
/// <summary> |
|
/// Finds the first word start in the document before offset. |
|
/// </summary> |
|
/// <returns>The offset of the word start, or -1 if there is no word start before the specified offset.</returns> |
|
public static int FindNextWordStart(this ITextSource textSource, int offset) |
|
{ |
|
return TextUtilities.GetNextCaretPosition(textSource, offset, LogicalDirection.Forward, CaretPositioningMode.WordStart); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the word at the specified position. |
|
/// </summary> |
|
public static string GetWordAt(this ITextSource document, int offset) |
|
{ |
|
if (offset < 0 || offset >= document.TextLength || !IsWordPart(document.GetCharAt(offset))) { |
|
return String.Empty; |
|
} |
|
int startOffset = offset; |
|
int 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); |
|
} |
|
|
|
static bool IsWordPart(char ch) |
|
{ |
|
return char.IsLetterOrDigit(ch) || ch == '_'; |
|
} |
|
|
|
/// <summary> |
|
/// Gets all indentation starting at offset. |
|
/// </summary> |
|
/// <param name="document">The document.</param> |
|
/// <param name="offset">The offset where the indentation starts.</param> |
|
/// <returns>The indentation text.</returns> |
|
public static string GetWhitespaceAfter(ITextSource textSource, int offset) |
|
{ |
|
ISegment segment = TextUtilities.GetWhitespaceAfter(textSource, offset); |
|
return textSource.GetText(segment.Offset, segment.Length); |
|
} |
|
|
|
/// <summary> |
|
/// Gets all indentation before the offset. |
|
/// </summary> |
|
/// <param name="document">The document.</param> |
|
/// <param name="offset">The offset where the indentation ends.</param> |
|
/// <returns>The indentation text.</returns> |
|
public static string GetWhitespaceBefore(ITextSource textSource, int offset) |
|
{ |
|
ISegment segment = TextUtilities.GetWhitespaceBefore(textSource, offset); |
|
return textSource.GetText(segment.Offset, segment.Length); |
|
} |
|
|
|
/// <summary> |
|
/// Gets the line terminator for the document around the specified line number. |
|
/// </summary> |
|
public static string GetLineTerminator(IDocument document, int lineNumber) |
|
{ |
|
IDocumentLine line = document.GetLineByNumber(lineNumber); |
|
if (line.DelimiterLength == 0) { |
|
// at the end of the document, there's no line delimiter, so use the delimiter |
|
// from the previous line |
|
if (lineNumber == 1) |
|
return Environment.NewLine; |
|
line = document.GetLineByNumber(lineNumber - 1); |
|
} |
|
return document.GetText(line.Offset + line.Length, line.DelimiterLength); |
|
} |
|
|
|
public static string NormalizeNewLines(string input, string newLine) |
|
{ |
|
return input.Replace("\r\n", "\n").Replace('\r', '\n').Replace("\n", newLine); |
|
} |
|
|
|
public static string NormalizeNewLines(string input, IDocument document, int lineNumber) |
|
{ |
|
return NormalizeNewLines(input, GetLineTerminator(document, lineNumber)); |
|
} |
|
|
|
public static void InsertNormalized(this IDocument document, int offset, string text) |
|
{ |
|
if (document == null) |
|
throw new ArgumentNullException("document"); |
|
IDocumentLine line = document.GetLineByOffset(offset); |
|
text = NormalizeNewLines(text, document, line.LineNumber); |
|
document.Insert(offset, text); |
|
} |
|
|
|
#region ITextSource implementation |
|
[Obsolete("We now directly use ITextSource everywhere, no need for adapters")] |
|
public static ITextSource GetTextSource(ITextSource textBuffer) |
|
{ |
|
return textBuffer; |
|
} |
|
#endregion |
|
} |
|
}
|
|
|