mirror of https://github.com/icsharpcode/ILSpy.git
2 changed files with 481 additions and 0 deletions
@ -0,0 +1,369 @@
@@ -0,0 +1,369 @@
|
||||
// Copyright (c) 2018 Siegfried Pammer
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
|
||||
// software and associated documentation files (the "Software"), to deal in the Software
|
||||
// without restriction, including without limitation the rights to use, copy, modify, merge,
|
||||
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
|
||||
// to whom the Software is furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all copies or
|
||||
// substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
|
||||
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
|
||||
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
|
||||
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
using System; |
||||
using System.Collections.Generic; |
||||
using System.Diagnostics; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.ILSpy.TextView; |
||||
|
||||
namespace ICSharpCode.ILSpy |
||||
{ |
||||
/// <summary>
|
||||
/// Searches matching brackets for C#.
|
||||
/// </summary>
|
||||
class CSharpBracketSearcher : IBracketSearcher |
||||
{ |
||||
string openingBrackets = "([{"; |
||||
string closingBrackets = ")]}"; |
||||
|
||||
public BracketSearchResult SearchBracket(IDocument document, int offset) |
||||
{ |
||||
if (offset > 0) { |
||||
char c = document.GetCharAt(offset - 1); |
||||
int index = openingBrackets.IndexOf(c); |
||||
int otherOffset = -1; |
||||
if (index > -1) |
||||
otherOffset = SearchBracketForward(document, offset, openingBrackets[index], closingBrackets[index]); |
||||
|
||||
index = closingBrackets.IndexOf(c); |
||||
if (index > -1) |
||||
otherOffset = SearchBracketBackward(document, offset - 2, openingBrackets[index], closingBrackets[index]); |
||||
|
||||
if (otherOffset > -1) { |
||||
var result = new BracketSearchResult(Math.Min(offset - 1, otherOffset), 1, |
||||
Math.Max(offset - 1, otherOffset), 1); |
||||
return result; |
||||
} |
||||
} |
||||
|
||||
return null; |
||||
} |
||||
|
||||
bool IsBracketOnly(IDocument document, IDocumentLine documentLine) |
||||
{ |
||||
string lineText = document.GetText(documentLine).Trim(); |
||||
return lineText == "{" || string.IsNullOrEmpty(lineText) |
||||
|| lineText.StartsWith("//", StringComparison.Ordinal) |
||||
|| lineText.StartsWith("/*", StringComparison.Ordinal) |
||||
|| lineText.StartsWith("*", StringComparison.Ordinal) |
||||
|| lineText.StartsWith("'", StringComparison.Ordinal); |
||||
} |
||||
|
||||
#region SearchBracket helper functions
|
||||
static int ScanLineStart(IDocument document, int offset) |
||||
{ |
||||
for (int i = offset - 1; i > 0; --i) { |
||||
if (document.GetCharAt(i) == '\n') |
||||
return i + 1; |
||||
} |
||||
return 0; |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Gets the type of code at offset.<br/>
|
||||
/// 0 = Code,<br/>
|
||||
/// 1 = Comment,<br/>
|
||||
/// 2 = String<br/>
|
||||
/// Block comments and multiline strings are not supported.
|
||||
/// </summary>
|
||||
static int GetStartType(IDocument document, int linestart, int offset) |
||||
{ |
||||
bool inString = false; |
||||
bool inChar = false; |
||||
bool verbatim = false; |
||||
int result = 0; |
||||
for (int i = linestart; i < offset; i++) { |
||||
switch (document.GetCharAt(i)) { |
||||
case '/': |
||||
if (!inString && !inChar && i + 1 < document.TextLength) { |
||||
if (document.GetCharAt(i + 1) == '/') { |
||||
result = 1; |
||||
} |
||||
} |
||||
break; |
||||
case '"': |
||||
if (!inChar) { |
||||
if (inString && verbatim) { |
||||
if (i + 1 < document.TextLength && document.GetCharAt(i + 1) == '"') { |
||||
++i; // skip escaped quote
|
||||
inString = false; // let the string go on
|
||||
} else { |
||||
verbatim = false; |
||||
} |
||||
} else if (!inString && i > 0 && document.GetCharAt(i - 1) == '@') { |
||||
verbatim = true; |
||||
} |
||||
inString = !inString; |
||||
} |
||||
break; |
||||
case '\'': |
||||
if (!inString) inChar = !inChar; |
||||
break; |
||||
case '\\': |
||||
if ((inString && !verbatim) || inChar) |
||||
++i; // skip next character
|
||||
break; |
||||
} |
||||
} |
||||
|
||||
return (inString || inChar) ? 2 : result; |
||||
} |
||||
#endregion
|
||||
|
||||
#region SearchBracketBackward
|
||||
int SearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket) |
||||
{ |
||||
if (offset + 1 >= document.TextLength) return -1; |
||||
// this method parses a c# document backwards to find the matching bracket
|
||||
|
||||
// first try "quick find" - find the matching bracket if there is no string/comment in the way
|
||||
int quickResult = QuickSearchBracketBackward(document, offset, openBracket, closingBracket); |
||||
if (quickResult >= 0) return quickResult; |
||||
|
||||
// we need to parse the line from the beginning, so get the line start position
|
||||
int linestart = ScanLineStart(document, offset + 1); |
||||
|
||||
// we need to know where offset is - in a string/comment or in normal code?
|
||||
// ignore cases where offset is in a block comment
|
||||
int starttype = GetStartType(document, linestart, offset + 1); |
||||
if (starttype == 1) { |
||||
return -1; // start position is in a comment
|
||||
} |
||||
|
||||
// I don't see any possibility to parse a C# document backwards...
|
||||
// We have to do it forwards and push all bracket positions on a stack.
|
||||
Stack<int> bracketStack = new Stack<int>(); |
||||
bool blockComment = false; |
||||
bool lineComment = false; |
||||
bool inChar = false; |
||||
bool inString = false; |
||||
bool verbatim = false; |
||||
|
||||
for (int i = 0; i <= offset; ++i) { |
||||
char ch = document.GetCharAt(i); |
||||
switch (ch) { |
||||
case '\r': |
||||
case '\n': |
||||
lineComment = false; |
||||
inChar = false; |
||||
if (!verbatim) inString = false; |
||||
break; |
||||
case '/': |
||||
if (blockComment) { |
||||
Debug.Assert(i > 0); |
||||
if (document.GetCharAt(i - 1) == '*') { |
||||
blockComment = false; |
||||
} |
||||
} |
||||
if (!inString && !inChar && i + 1 < document.TextLength) { |
||||
if (!blockComment && document.GetCharAt(i + 1) == '/') { |
||||
lineComment = true; |
||||
} |
||||
if (!lineComment && document.GetCharAt(i + 1) == '*') { |
||||
blockComment = true; |
||||
} |
||||
} |
||||
break; |
||||
case '"': |
||||
if (!(inChar || lineComment || blockComment)) { |
||||
if (inString && verbatim) { |
||||
if (i + 1 < document.TextLength && document.GetCharAt(i + 1) == '"') { |
||||
++i; // skip escaped quote
|
||||
inString = false; // let the string go
|
||||
} else { |
||||
verbatim = false; |
||||
} |
||||
} else if (!inString && offset > 0 && document.GetCharAt(i - 1) == '@') { |
||||
verbatim = true; |
||||
} |
||||
inString = !inString; |
||||
} |
||||
break; |
||||
case '\'': |
||||
if (!(inString || lineComment || blockComment)) { |
||||
inChar = !inChar; |
||||
} |
||||
break; |
||||
case '\\': |
||||
if ((inString && !verbatim) || inChar) |
||||
++i; // skip next character
|
||||
break; |
||||
default: |
||||
if (ch == openBracket) { |
||||
if (!(inString || inChar || lineComment || blockComment)) { |
||||
bracketStack.Push(i); |
||||
} |
||||
} else if (ch == closingBracket) { |
||||
if (!(inString || inChar || lineComment || blockComment)) { |
||||
if (bracketStack.Count > 0) |
||||
bracketStack.Pop(); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
if (bracketStack.Count > 0) return (int)bracketStack.Pop(); |
||||
return -1; |
||||
} |
||||
#endregion
|
||||
|
||||
#region SearchBracketForward
|
||||
int SearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket) |
||||
{ |
||||
bool inString = false; |
||||
bool inChar = false; |
||||
bool verbatim = false; |
||||
|
||||
bool lineComment = false; |
||||
bool blockComment = false; |
||||
|
||||
if (offset < 0) return -1; |
||||
|
||||
// first try "quick find" - find the matching bracket if there is no string/comment in the way
|
||||
int quickResult = QuickSearchBracketForward(document, offset, openBracket, closingBracket); |
||||
if (quickResult >= 0) return quickResult; |
||||
|
||||
// we need to parse the line from the beginning, so get the line start position
|
||||
int linestart = ScanLineStart(document, offset); |
||||
|
||||
// we need to know where offset is - in a string/comment or in normal code?
|
||||
// ignore cases where offset is in a block comment
|
||||
int starttype = GetStartType(document, linestart, offset); |
||||
if (starttype != 0) return -1; // start position is in a comment/string
|
||||
|
||||
int brackets = 1; |
||||
|
||||
while (offset < document.TextLength) { |
||||
char ch = document.GetCharAt(offset); |
||||
switch (ch) { |
||||
case '\r': |
||||
case '\n': |
||||
lineComment = false; |
||||
inChar = false; |
||||
if (!verbatim) inString = false; |
||||
break; |
||||
case '/': |
||||
if (blockComment) { |
||||
Debug.Assert(offset > 0); |
||||
if (document.GetCharAt(offset - 1) == '*') { |
||||
blockComment = false; |
||||
} |
||||
} |
||||
if (!inString && !inChar && offset + 1 < document.TextLength) { |
||||
if (!blockComment && document.GetCharAt(offset + 1) == '/') { |
||||
lineComment = true; |
||||
} |
||||
if (!lineComment && document.GetCharAt(offset + 1) == '*') { |
||||
blockComment = true; |
||||
} |
||||
} |
||||
break; |
||||
case '"': |
||||
if (!(inChar || lineComment || blockComment)) { |
||||
if (inString && verbatim) { |
||||
if (offset + 1 < document.TextLength && document.GetCharAt(offset + 1) == '"') { |
||||
++offset; // skip escaped quote
|
||||
inString = false; // let the string go
|
||||
} else { |
||||
verbatim = false; |
||||
} |
||||
} else if (!inString && offset > 0 && document.GetCharAt(offset - 1) == '@') { |
||||
verbatim = true; |
||||
} |
||||
inString = !inString; |
||||
} |
||||
break; |
||||
case '\'': |
||||
if (!(inString || lineComment || blockComment)) { |
||||
inChar = !inChar; |
||||
} |
||||
break; |
||||
case '\\': |
||||
if ((inString && !verbatim) || inChar) |
||||
++offset; // skip next character
|
||||
break; |
||||
default: |
||||
if (ch == openBracket) { |
||||
if (!(inString || inChar || lineComment || blockComment)) { |
||||
++brackets; |
||||
} |
||||
} else if (ch == closingBracket) { |
||||
if (!(inString || inChar || lineComment || blockComment)) { |
||||
--brackets; |
||||
if (brackets == 0) { |
||||
return offset; |
||||
} |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
++offset; |
||||
} |
||||
return -1; |
||||
} |
||||
#endregion
|
||||
|
||||
int QuickSearchBracketBackward(IDocument document, int offset, char openBracket, char closingBracket) |
||||
{ |
||||
int brackets = -1; |
||||
// first try "quick find" - find the matching bracket if there is no string/comment in the way
|
||||
for (int i = offset; i >= 0; --i) { |
||||
char ch = document.GetCharAt(i); |
||||
if (ch == openBracket) { |
||||
++brackets; |
||||
if (brackets == 0) return i; |
||||
} else if (ch == closingBracket) { |
||||
--brackets; |
||||
} else if (ch == '"') { |
||||
break; |
||||
} else if (ch == '\'') { |
||||
break; |
||||
} else if (ch == '/' && i > 0) { |
||||
if (document.GetCharAt(i - 1) == '/') break; |
||||
if (document.GetCharAt(i - 1) == '*') break; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
|
||||
int QuickSearchBracketForward(IDocument document, int offset, char openBracket, char closingBracket) |
||||
{ |
||||
int brackets = 1; |
||||
// try "quick find" - find the matching bracket if there is no string/comment in the way
|
||||
for (int i = offset; i < document.TextLength; ++i) { |
||||
char ch = document.GetCharAt(i); |
||||
if (ch == openBracket) { |
||||
++brackets; |
||||
} else if (ch == closingBracket) { |
||||
--brackets; |
||||
if (brackets == 0) return i; |
||||
} else if (ch == '"') { |
||||
break; |
||||
} else if (ch == '\'') { |
||||
break; |
||||
} else if (ch == '/' && i > 0) { |
||||
if (document.GetCharAt(i - 1) == '/') break; |
||||
} else if (ch == '*' && i > 0) { |
||||
if (document.GetCharAt(i - 1) == '/') break; |
||||
} |
||||
} |
||||
return -1; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
// 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.Windows.Media; |
||||
using ICSharpCode.AvalonEdit.Document; |
||||
using ICSharpCode.AvalonEdit.Rendering; |
||||
|
||||
namespace ICSharpCode.ILSpy.TextView |
||||
{ |
||||
/// <summary>
|
||||
/// Allows language specific search for matching brackets.
|
||||
/// </summary>
|
||||
public interface IBracketSearcher |
||||
{ |
||||
/// <summary>
|
||||
/// Searches for a matching bracket from the given offset to the start of the document.
|
||||
/// </summary>
|
||||
/// <returns>A BracketSearchResult that contains the positions and lengths of the brackets. Return null if there is nothing to highlight.</returns>
|
||||
BracketSearchResult SearchBracket(IDocument document, int offset); |
||||
} |
||||
|
||||
public class DefaultBracketSearcher : IBracketSearcher |
||||
{ |
||||
public static readonly DefaultBracketSearcher DefaultInstance = new DefaultBracketSearcher(); |
||||
|
||||
public BracketSearchResult SearchBracket(IDocument document, int offset) |
||||
{ |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
/// <summary>
|
||||
/// Describes a pair of matching brackets found by <see cref="IBracketSearcher"/>.
|
||||
/// </summary>
|
||||
public class BracketSearchResult |
||||
{ |
||||
public int OpeningBracketOffset { get; private set; } |
||||
|
||||
public int OpeningBracketLength { get; private set; } |
||||
|
||||
public int ClosingBracketOffset { get; private set; } |
||||
|
||||
public int ClosingBracketLength { get; private set; } |
||||
|
||||
public BracketSearchResult(int openingBracketOffset, int openingBracketLength, |
||||
int closingBracketOffset, int closingBracketLength) |
||||
{ |
||||
this.OpeningBracketOffset = openingBracketOffset; |
||||
this.OpeningBracketLength = openingBracketLength; |
||||
this.ClosingBracketOffset = closingBracketOffset; |
||||
this.ClosingBracketLength = closingBracketLength; |
||||
} |
||||
} |
||||
|
||||
public class BracketHighlightRenderer : IBackgroundRenderer |
||||
{ |
||||
BracketSearchResult result; |
||||
Pen borderPen; |
||||
Brush backgroundBrush; |
||||
ICSharpCode.AvalonEdit.Rendering.TextView textView; |
||||
|
||||
public void SetHighlight(BracketSearchResult result) |
||||
{ |
||||
if (this.result != result) { |
||||
this.result = result; |
||||
textView.InvalidateLayer(this.Layer); |
||||
} |
||||
} |
||||
|
||||
public BracketHighlightRenderer(ICSharpCode.AvalonEdit.Rendering.TextView textView) |
||||
{ |
||||
if (textView == null) |
||||
throw new ArgumentNullException("textView"); |
||||
|
||||
this.borderPen = new Pen(new SolidColorBrush(Color.FromArgb(52, 0, 0, 255)), 1); |
||||
this.borderPen.Freeze(); |
||||
|
||||
this.backgroundBrush = new SolidColorBrush(Color.FromArgb(22, 0, 0, 255)); |
||||
this.backgroundBrush.Freeze(); |
||||
|
||||
this.textView = textView; |
||||
|
||||
this.textView.BackgroundRenderers.Add(this); |
||||
} |
||||
|
||||
public KnownLayer Layer { |
||||
get { |
||||
return KnownLayer.Selection; |
||||
} |
||||
} |
||||
|
||||
public void Draw(ICSharpCode.AvalonEdit.Rendering.TextView textView, DrawingContext drawingContext) |
||||
{ |
||||
if (this.result == null) |
||||
return; |
||||
|
||||
BackgroundGeometryBuilder builder = new BackgroundGeometryBuilder(); |
||||
|
||||
builder.CornerRadius = 1; |
||||
|
||||
builder.AddSegment(textView, new TextSegment() { StartOffset = result.OpeningBracketOffset, Length = result.OpeningBracketLength }); |
||||
builder.CloseFigure(); // prevent connecting the two segments
|
||||
builder.AddSegment(textView, new TextSegment() { StartOffset = result.ClosingBracketOffset, Length = result.ClosingBracketLength }); |
||||
|
||||
Geometry geometry = builder.CreateGeometry(); |
||||
if (geometry != null) { |
||||
drawingContext.DrawGeometry(backgroundBrush, borderPen, geometry); |
||||
} |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue