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.
620 lines
15 KiB
620 lines
15 KiB
// |
|
// CSharpIndentEngine.cs |
|
// |
|
// Author: |
|
// Mike Krüger <mkrueger@xamarin.com> |
|
// |
|
// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com) |
|
// |
|
// 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 ICSharpCode.NRefactory.Editor; |
|
using System.Text; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using System.Globalization; |
|
|
|
namespace ICSharpCode.NRefactory.CSharp |
|
{ |
|
public class CSharpIndentEngine |
|
{ |
|
readonly IDocument document; |
|
readonly CSharpFormattingOptions options; |
|
readonly TextEditorOptions textEditorOptions; |
|
readonly StringBuilder wordBuf = new StringBuilder(); |
|
|
|
Indent thisLineindent; |
|
Indent indent; |
|
|
|
public IList<string> ConditionalSymbols { |
|
get; |
|
set; |
|
} |
|
|
|
public string ThisLineIndent { |
|
get { |
|
return thisLineindent.IndentString; |
|
} |
|
} |
|
|
|
public string NewLineIndent { |
|
get { |
|
return indent.IndentString; |
|
} |
|
} |
|
|
|
public CSharpIndentEngine(IDocument document, TextEditorOptions textEditorOptions, CSharpFormattingOptions formattingOptions) |
|
{ |
|
this.document = document; |
|
this.options = formattingOptions; |
|
this.textEditorOptions = textEditorOptions; |
|
this.indent = new Indent(textEditorOptions); |
|
this.thisLineindent = new Indent(textEditorOptions); |
|
} |
|
|
|
CSharpIndentEngine (CSharpIndentEngine prototype) |
|
{ |
|
this.document = prototype.document; |
|
this.options = prototype.options; |
|
this.textEditorOptions = prototype.textEditorOptions; |
|
this.indent = prototype.indent.Clone(); |
|
this.thisLineindent = prototype.thisLineindent.Clone(); |
|
this.offset = prototype.offset; |
|
this.inside = prototype.inside; |
|
this.IsLineStart = prototype.IsLineStart; |
|
this.pc = prototype.pc; |
|
this.parenStack = new Stack<TextLocation>(prototype.parenStack.Reverse ()); |
|
this.currentBody = prototype.currentBody; |
|
this.nextBody = prototype.nextBody; |
|
this.addContinuation = prototype.addContinuation; |
|
this.line = prototype.line; |
|
this.col = prototype.col; |
|
this.popNextParenBlock = prototype.popNextParenBlock; |
|
} |
|
|
|
public CSharpIndentEngine Clone () |
|
{ |
|
return new CSharpIndentEngine(this); |
|
} |
|
|
|
int offset; |
|
Inside inside = Inside.Empty; |
|
bool IsLineStart = true; |
|
char pc; |
|
Stack<TextLocation> parenStack = new Stack<TextLocation> (); |
|
Body currentBody; |
|
Body nextBody; |
|
bool addContinuation; |
|
int line, col; |
|
bool popNextParenBlock; |
|
|
|
bool readPreprocessorExpression; |
|
|
|
|
|
void Reset() |
|
{ |
|
offset = 0; |
|
thisLineindent.Reset(); |
|
indent.Reset(); |
|
pc = '\0'; |
|
IsLineStart = true; |
|
addContinuation = false; |
|
popNextParenBlock = false; |
|
parenStack.Clear(); |
|
inside = Inside.Empty; |
|
nextBody = currentBody = Body.None; |
|
line = col = 1; |
|
} |
|
|
|
public void UpdateToOffset (int toOffset) |
|
{ |
|
if (toOffset < offset) |
|
Reset(); |
|
for (int i = offset; i < toOffset; i++) |
|
Push(document.GetCharAt(i)); |
|
} |
|
|
|
|
|
bool IsInStringOrChar { |
|
get { |
|
return inside.HasFlag (Inside.StringOrChar); |
|
} |
|
} |
|
|
|
bool IsInComment { |
|
get { |
|
return inside.HasFlag (Inside.Comment); |
|
} |
|
} |
|
|
|
bool IsInPreProcessorComment { |
|
get { |
|
return inside.HasFlag (Inside.PreProcessorComment); |
|
} |
|
} |
|
|
|
[Flags] |
|
public enum Inside { |
|
Empty = 0, |
|
|
|
PreProcessor = (1 << 0), |
|
PreProcessorComment = (1 << 12), |
|
|
|
MultiLineComment = (1 << 1), |
|
LineComment = (1 << 2), |
|
DocComment = (1 << 11), |
|
Comment = (MultiLineComment | LineComment | DocComment), |
|
|
|
VerbatimString = (1 << 3), |
|
StringLiteral = (1 << 4), |
|
CharLiteral = (1 << 5), |
|
String = (VerbatimString | StringLiteral), |
|
StringOrChar = (String | CharLiteral), |
|
|
|
Attribute = (1 << 6), |
|
ParenList = (1 << 7), |
|
|
|
FoldedStatement = (1 << 8), |
|
Block = (1 << 9), |
|
Case = (1 << 10), |
|
|
|
FoldedOrBlock = (FoldedStatement | Block), |
|
FoldedBlockOrCase = (FoldedStatement | Block | Case) |
|
} |
|
|
|
#region Pre processor evaluation (from cs-tokenizer.cs) |
|
static bool is_identifier_start_character (int c) |
|
{ |
|
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || Char.IsLetter ((char)c); |
|
} |
|
|
|
static bool is_identifier_part_character (char c) |
|
{ |
|
if (c >= 'a' && c <= 'z') |
|
return true; |
|
|
|
if (c >= 'A' && c <= 'Z') |
|
return true; |
|
|
|
if (c == '_' || (c >= '0' && c <= '9')) |
|
return true; |
|
|
|
if (c < 0x80) |
|
return false; |
|
|
|
return Char.IsLetter (c) || Char.GetUnicodeCategory (c) == UnicodeCategory.ConnectorPunctuation; |
|
} |
|
|
|
bool eval_val (string s) |
|
{ |
|
if (s == "true") |
|
return true; |
|
if (s == "false") |
|
return false; |
|
|
|
return ConditionalSymbols != null && ConditionalSymbols.Contains (s); |
|
} |
|
|
|
bool pp_primary (ref string s) |
|
{ |
|
s = s.Trim (); |
|
int len = s.Length; |
|
|
|
if (len > 0){ |
|
char c = s [0]; |
|
|
|
if (c == '('){ |
|
s = s.Substring (1); |
|
bool val = pp_expr (ref s, false); |
|
if (s.Length > 0 && s [0] == ')'){ |
|
s = s.Substring (1); |
|
return val; |
|
} |
|
return false; |
|
} |
|
|
|
if (is_identifier_start_character (c)){ |
|
int j = 1; |
|
|
|
while (j < len){ |
|
c = s [j]; |
|
|
|
if (is_identifier_part_character (c)){ |
|
j++; |
|
continue; |
|
} |
|
bool v = eval_val (s.Substring (0, j)); |
|
s = s.Substring (j); |
|
return v; |
|
} |
|
bool vv = eval_val (s); |
|
s = ""; |
|
return vv; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
bool pp_unary (ref string s) |
|
{ |
|
s = s.Trim (); |
|
int len = s.Length; |
|
|
|
if (len > 0){ |
|
if (s [0] == '!'){ |
|
if (len > 1 && s [1] == '='){ |
|
return false; |
|
} |
|
s = s.Substring (1); |
|
return ! pp_primary (ref s); |
|
} else |
|
return pp_primary (ref s); |
|
} else { |
|
return false; |
|
} |
|
} |
|
|
|
bool pp_eq (ref string s) |
|
{ |
|
bool va = pp_unary (ref s); |
|
|
|
s = s.Trim (); |
|
int len = s.Length; |
|
if (len > 0){ |
|
if (s [0] == '='){ |
|
if (len > 2 && s [1] == '='){ |
|
s = s.Substring (2); |
|
return va == pp_unary (ref s); |
|
} else { |
|
return false; |
|
} |
|
} else if (s [0] == '!' && len > 1 && s [1] == '='){ |
|
s = s.Substring (2); |
|
|
|
return va != pp_unary (ref s); |
|
|
|
} |
|
} |
|
|
|
return va; |
|
|
|
} |
|
|
|
bool pp_and (ref string s) |
|
{ |
|
bool va = pp_eq (ref s); |
|
|
|
s = s.Trim (); |
|
int len = s.Length; |
|
if (len > 0){ |
|
if (s [0] == '&'){ |
|
if (len > 2 && s [1] == '&'){ |
|
s = s.Substring (2); |
|
return (va & pp_and (ref s)); |
|
} else { |
|
return false; |
|
} |
|
} |
|
} |
|
return va; |
|
} |
|
|
|
// |
|
// Evaluates an expression for `#if' or `#elif' |
|
// |
|
bool pp_expr (ref string s, bool isTerm) |
|
{ |
|
bool va = pp_and (ref s); |
|
s = s.Trim (); |
|
int len = s.Length; |
|
if (len > 0){ |
|
char c = s [0]; |
|
|
|
if (c == '|'){ |
|
if (len > 2 && s [1] == '|'){ |
|
s = s.Substring (2); |
|
return va | pp_expr (ref s, isTerm); |
|
} else { |
|
|
|
return false; |
|
} |
|
} |
|
if (isTerm) { |
|
return false; |
|
} |
|
} |
|
|
|
return va; |
|
} |
|
|
|
bool eval (string s) |
|
{ |
|
bool v = pp_expr (ref s, true); |
|
s = s.Trim (); |
|
if (s.Length != 0){ |
|
return false; |
|
} |
|
|
|
return v; |
|
} |
|
#endregion |
|
void Push(char ch) |
|
{ |
|
if (readPreprocessorExpression) { |
|
wordBuf.Append(ch); |
|
} |
|
|
|
if (inside.HasFlag (Inside.VerbatimString) && pc == '"' && ch != '"') { |
|
inside &= ~Inside.String; |
|
} |
|
switch (ch) { |
|
case '#': |
|
if (IsLineStart) |
|
inside = Inside.PreProcessor; |
|
break; |
|
case '/': |
|
if (IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
if (pc == '/') { |
|
if (inside.HasFlag (Inside.Comment)) { |
|
inside |= Inside.DocComment; |
|
} else { |
|
inside |= Inside.Comment; |
|
} |
|
} |
|
break; |
|
case '*': |
|
if (IsInStringOrChar || IsInComment || IsInPreProcessorComment) |
|
break; |
|
if (pc == '/') |
|
inside |= Inside.MultiLineComment; |
|
break; |
|
case '\t': |
|
var nextTabStop = (col - 1 + textEditorOptions.IndentSize) / textEditorOptions.IndentSize; |
|
col = 1 + nextTabStop * textEditorOptions.IndentSize; |
|
return; |
|
case '\r': |
|
|
|
if (readPreprocessorExpression) { |
|
if (!eval (wordBuf.ToString ())) |
|
inside |= Inside.PreProcessorComment; |
|
} |
|
|
|
inside &= ~(Inside.Comment | Inside.String | Inside.CharLiteral | Inside.PreProcessor); |
|
CheckKeyword(wordBuf.ToString()); |
|
wordBuf.Length = 0; |
|
if (addContinuation) { |
|
indent.Push (IndentType.Continuation); |
|
} |
|
thisLineindent = indent.Clone (); |
|
addContinuation = false; |
|
IsLineStart = true; |
|
readPreprocessorExpression = false; |
|
col = 1; |
|
line++; |
|
break; |
|
case '\n': |
|
if (pc == '\r') |
|
break; |
|
goto case '\r'; |
|
case '"': |
|
if (IsInComment || IsInPreProcessorComment) |
|
break; |
|
if (inside.HasFlag (Inside.String)) { |
|
if (pc != '\\') |
|
inside &= ~Inside.String; |
|
break; |
|
} |
|
|
|
if (pc =='@') { |
|
inside |= Inside.VerbatimString; |
|
} else { |
|
inside |= Inside.String; |
|
} |
|
break; |
|
case '<': |
|
case '[': |
|
case '(': |
|
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
parenStack.Push (new TextLocation (line, col)); |
|
popNextParenBlock = true; |
|
indent.Push (IndentType.Block); |
|
break; |
|
case '>': |
|
case ']': |
|
case ')': |
|
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
if (popNextParenBlock) |
|
parenStack.Pop (); |
|
indent.Pop (); |
|
indent.ExtraSpaces = 0; |
|
break; |
|
case ',': |
|
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
if (parenStack.Count > 0 && parenStack.Peek ().Line == line) { |
|
indent.Pop (); |
|
popNextParenBlock = false; |
|
indent.ExtraSpaces = parenStack.Peek ().Column - 1 - thisLineindent.CurIndent; |
|
} |
|
break; |
|
case '{': |
|
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
currentBody = nextBody; |
|
if (indent.Count > 0 && indent.Peek() == IndentType.Continuation) |
|
indent.Pop(); |
|
addContinuation = false; |
|
AddIndentation (currentBody); |
|
break; |
|
case '}': |
|
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
indent.Pop (); |
|
if (indent.Count > 0 && indent.Peek() == IndentType.Continuation) |
|
indent.Pop(); |
|
break; |
|
case ';': |
|
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment) |
|
break; |
|
if (indent.Count > 0 && indent.Peek() == IndentType.Continuation) |
|
indent.Pop(); |
|
break; |
|
case '\'': |
|
if (IsInComment || inside.HasFlag (Inside.String) || IsInPreProcessorComment) |
|
break; |
|
if (inside.HasFlag (Inside.CharLiteral)) { |
|
if (pc != '\\') |
|
inside &= ~Inside.CharLiteral; |
|
} else { |
|
inside &= Inside.CharLiteral; |
|
} |
|
break; |
|
} |
|
|
|
if (!IsInComment && !IsInStringOrChar && !readPreprocessorExpression) { |
|
if ((wordBuf.Length == 0 ? char.IsLetter(ch) : char.IsLetterOrDigit(ch)) || ch == '_') { |
|
wordBuf.Append(ch); |
|
} else { |
|
if (inside.HasFlag (Inside.PreProcessor)) { |
|
if (wordBuf.ToString () == "endif") { |
|
inside &= ~Inside.PreProcessorComment; |
|
} else if (wordBuf.ToString () == "if") { |
|
readPreprocessorExpression = true; |
|
} else if (wordBuf.ToString () == "elif") { |
|
inside &= ~Inside.PreProcessorComment; |
|
readPreprocessorExpression = true; |
|
} |
|
} else { |
|
CheckKeyword(wordBuf.ToString()); |
|
} |
|
wordBuf.Length = 0; |
|
} |
|
} |
|
if (addContinuation) { |
|
indent.Push (IndentType.Continuation); |
|
addContinuation = false; |
|
} |
|
IsLineStart &= ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r'; |
|
pc = ch; |
|
if (ch != '\n' && ch != '\r') |
|
col++; |
|
} |
|
|
|
void AddIndentation(BraceStyle braceStyle) |
|
{ |
|
switch (braceStyle) { |
|
case BraceStyle.DoNotChange: |
|
case BraceStyle.EndOfLine: |
|
case BraceStyle.EndOfLineWithoutSpace: |
|
case BraceStyle.NextLine: |
|
case BraceStyle.NextLineShifted: |
|
case BraceStyle.BannerStyle: |
|
indent.Push (IndentType.Block); |
|
break; |
|
|
|
case BraceStyle.NextLineShifted2: |
|
indent.Push (IndentType.DoubleBlock); |
|
break; |
|
} |
|
} |
|
|
|
void AddIndentation(Body body) |
|
{ |
|
switch (body) { |
|
case Body.None: |
|
indent.Push (IndentType.Block); |
|
break; |
|
case Body.Namespace: |
|
AddIndentation (options.NamespaceBraceStyle); |
|
break; |
|
case Body.Class: |
|
AddIndentation (options.ClassBraceStyle); |
|
break; |
|
case Body.Struct: |
|
AddIndentation (options.StructBraceStyle); |
|
break; |
|
case Body.Interface: |
|
AddIndentation (options.InterfaceBraceStyle); |
|
break; |
|
case Body.Enum: |
|
AddIndentation (options.EnumBraceStyle); |
|
break; |
|
case Body.Switch: |
|
if (options.IndentSwitchBody) |
|
indent.Push (IndentType.Empty); |
|
break; |
|
default: |
|
throw new ArgumentOutOfRangeException(); |
|
} |
|
} |
|
|
|
enum Body |
|
{ |
|
None, |
|
Namespace, |
|
Class, Struct, Interface, Enum, |
|
Switch |
|
} |
|
|
|
void CheckKeyword (string keyword) |
|
{ |
|
switch (currentBody) { |
|
case Body.None: |
|
if (keyword == "namespace") { |
|
nextBody = Body.Namespace; |
|
return; |
|
} |
|
goto case Body.Namespace; |
|
case Body.Namespace: |
|
if (keyword == "class") { |
|
nextBody = Body.Class; |
|
return; |
|
} |
|
if (keyword == "enum") { |
|
nextBody = Body.Enum; |
|
return; |
|
} |
|
if (keyword == "struct") { |
|
nextBody = Body.Struct; |
|
return; |
|
} |
|
if (keyword == "interface") { |
|
nextBody = Body.Interface; |
|
return; |
|
} |
|
break; |
|
case Body.Class: |
|
case Body.Enum: |
|
case Body.Struct: |
|
case Body.Interface: |
|
if (keyword == "switch") |
|
nextBody = Body.Switch; |
|
if (keyword == "do" || keyword == "if" || keyword == "for" || keyword == "foreach" || keyword == "while") { |
|
addContinuation = true; |
|
} |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
|