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.
1995 lines
47 KiB
1995 lines
47 KiB
// |
|
// IndentState.cs |
|
// |
|
// Author: |
|
// Matej Miklečić <matej.miklecic@gmail.com> |
|
// |
|
// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.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 System.Collections.Generic; |
|
using System.Globalization; |
|
using System.Linq; |
|
using System.Text; |
|
|
|
namespace ICSharpCode.NRefactory.CSharp |
|
{ |
|
#region IndentState |
|
|
|
/// <summary> |
|
/// The base class for all indentation states. |
|
/// Each state defines the logic for indentation based on chars that |
|
/// are pushed to it. |
|
/// </summary> |
|
public abstract class IndentState : ICloneable |
|
{ |
|
#region Properties |
|
|
|
/// <summary> |
|
/// The indentation engine using this state. |
|
/// </summary> |
|
public CSharpIndentEngine Engine; |
|
|
|
/// <summary> |
|
/// The parent state. |
|
/// This state can use the indentation levels of its parent. |
|
/// When this state exits, the engine returns to the parent. |
|
/// </summary> |
|
public IndentState Parent; |
|
|
|
/// <summary> |
|
/// The indentation of the current line. |
|
/// This is set when the state is created and will be changed to |
|
/// <see cref="NextLineIndent"/> when the <see cref="CSharpIndentEngine.newLineChar"/> |
|
/// is pushed. |
|
/// </summary> |
|
public Indent ThisLineIndent; |
|
|
|
/// <summary> |
|
/// The indentation of the next line. |
|
/// This is set when the state is created and can change depending |
|
/// on the pushed chars. |
|
/// </summary> |
|
public Indent NextLineIndent; |
|
|
|
#endregion |
|
|
|
#region Constructors |
|
|
|
protected IndentState() |
|
{ |
|
} |
|
|
|
/// <summary> |
|
/// Creates a new indentation state that is a copy of the given |
|
/// prototype. |
|
/// </summary> |
|
/// <param name="prototype"> |
|
/// The prototype state. |
|
/// </param> |
|
/// <param name="engine"> |
|
/// The engine of the new state. |
|
/// </param> |
|
protected IndentState(IndentState prototype, CSharpIndentEngine engine) |
|
{ |
|
Engine = engine; |
|
Parent = prototype.Parent != null ? prototype.Parent.Clone(engine) : null; |
|
|
|
ThisLineIndent = prototype.ThisLineIndent.Clone(); |
|
NextLineIndent = prototype.NextLineIndent.Clone(); |
|
} |
|
|
|
#endregion |
|
|
|
#region IClonable |
|
|
|
object ICloneable.Clone() |
|
{ |
|
return Clone(Engine); |
|
} |
|
|
|
public abstract IndentState Clone(CSharpIndentEngine engine); |
|
|
|
#endregion |
|
|
|
#region Methods |
|
|
|
internal void Initialize (CSharpIndentEngine engine, IndentState parent = null) |
|
{ |
|
Parent = parent; |
|
Engine = engine; |
|
|
|
InitializeState(); |
|
} |
|
|
|
/// <summary> |
|
/// Initializes the state: |
|
/// - sets the default indentation levels. |
|
/// </summary> |
|
/// <remarks> |
|
/// Each state can override this method if it needs a different |
|
/// logic for setting up the default indentations. |
|
/// </remarks> |
|
public virtual void InitializeState() |
|
{ |
|
ThisLineIndent = new Indent(Engine.textEditorOptions); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
|
|
/// <summary> |
|
/// Actions performed when this state exits. |
|
/// </summary> |
|
public virtual void OnExit() |
|
{ |
|
if (Parent != null) |
|
{ |
|
// if a state exits on the newline character, it has to push |
|
// it back to its parent (and so on recursively if the parent |
|
// state also exits). Otherwise, the parent state wouldn't |
|
// know that the engine isn't on the same line anymore. |
|
if (Engine.currentChar == Engine.newLineChar) |
|
{ |
|
Parent.Push(Engine.newLineChar); |
|
} |
|
|
|
// when a state exits the engine stays on the same line and this |
|
// state has to override the Parent.ThisLineIndent. |
|
Parent.ThisLineIndent = ThisLineIndent.Clone(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Changes the current state of the <see cref="CSharpIndentEngine"/> using the current |
|
/// state as the parent for the new one. |
|
/// </summary> |
|
/// <typeparam name="T"> |
|
/// The type of the new state. Must be assignable from <see cref="IndentState"/>. |
|
/// </typeparam> |
|
public void ChangeState<T>() |
|
where T : IndentState, new () |
|
{ |
|
var t = new T(); |
|
t.Initialize(Engine, Engine.currentState); |
|
Engine.currentState = t; |
|
} |
|
|
|
/// <summary> |
|
/// Exits this state by setting the current state of the |
|
/// <see cref="CSharpIndentEngine"/> to this state's parent. |
|
/// </summary> |
|
public void ExitState() |
|
{ |
|
OnExit(); |
|
Engine.currentState = Engine.currentState.Parent ?? new GlobalBodyState(Engine); |
|
} |
|
|
|
/// <summary> |
|
/// Common logic behind the push method. |
|
/// Each state can override this method and implement its own logic. |
|
/// </summary> |
|
/// <param name="ch"> |
|
/// The current character that's being pushed. |
|
/// </param> |
|
public virtual void Push(char ch) |
|
{ |
|
// replace ThisLineIndent with NextLineIndent if the newLineChar is pushed |
|
if (ch == Engine.newLineChar) |
|
{ |
|
var delta = Engine.textEditorOptions.ContinuationIndent; |
|
while (NextLineIndent.CurIndent - ThisLineIndent.CurIndent > delta && |
|
NextLineIndent.PopIf(IndentType.Continuation)) ; |
|
ThisLineIndent = NextLineIndent.Clone(); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// When derived, checks if the given sequence of chars form |
|
/// a valid keyword or variable name, depending on the state. |
|
/// </summary> |
|
/// <param name="keyword"> |
|
/// A possible keyword. |
|
/// </param> |
|
public virtual void CheckKeyword(string keyword) |
|
{ } |
|
|
|
/// <summary> |
|
/// When derived, checks if the given sequence of chars form |
|
/// a valid keyword or variable name, depending on the state. |
|
/// </summary> |
|
/// <param name="keyword"> |
|
/// A possible keyword. |
|
/// </param> |
|
/// <remarks> |
|
/// This method should be called from <see cref="Push(char)"/>. |
|
/// It is left to derived classes to call this method because of |
|
/// performance issues. |
|
/// </remarks> |
|
public virtual void CheckKeywordOnPush(string keyword) |
|
{ } |
|
|
|
#endregion |
|
} |
|
|
|
#endregion |
|
|
|
#region Null state |
|
|
|
/// <summary> |
|
/// Null state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Doesn't define any transitions to new states. |
|
/// </remarks> |
|
public class NullState : IndentState |
|
{ |
|
public NullState() |
|
{ } |
|
|
|
public NullState(NullState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ } |
|
|
|
public override void Push(char ch) |
|
{ } |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new NullState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Brackets body states |
|
|
|
#region Brackets body base |
|
|
|
/// <summary> |
|
/// The base for all brackets body states. |
|
/// </summary> |
|
/// <remarks> |
|
/// Represents a block of code between a pair of brackets. |
|
/// </remarks> |
|
public abstract class BracketsBodyBaseState : IndentState |
|
{ |
|
|
|
/// <summary> |
|
/// When derived in a concrete bracket body state, represents |
|
/// the closed bracket character pair. |
|
/// </summary> |
|
public abstract char ClosedBracket { get; } |
|
|
|
protected BracketsBodyBaseState() |
|
{ } |
|
|
|
protected BracketsBodyBaseState(BracketsBodyBaseState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ } |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
switch (ch) { |
|
case '#': |
|
if (Engine.isLineStart) |
|
ChangeState<PreProcessorState>(); |
|
break; |
|
case '/': |
|
if (Engine.previousChar == '/') |
|
ChangeState<LineCommentState>(); |
|
break; |
|
case '*': |
|
if (Engine.previousChar == '/') |
|
ChangeState<MultiLineCommentState>(); |
|
break; |
|
case '"': |
|
if (Engine.previousChar == '@') |
|
{ |
|
ChangeState<VerbatimStringState>(); |
|
} |
|
else |
|
{ |
|
ChangeState<StringLiteralState>(); |
|
} |
|
break; |
|
case '\'': |
|
ChangeState<CharacterState>(); |
|
break; |
|
case '{': |
|
ChangeState<BracesBodyState>(); |
|
break; |
|
case '(': |
|
ChangeState<ParenthesesBodyState>(); |
|
break; |
|
case '[': |
|
ChangeState<SquareBracketsBodyState>(); |
|
break; |
|
default: |
|
if (ch == ClosedBracket) |
|
ExitState(); |
|
break; |
|
} |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Braces body state |
|
|
|
/// <summary> |
|
/// Braces body state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Represents a block of code between { and }. |
|
/// </remarks> |
|
public class BracesBodyState : BracketsBodyBaseState |
|
{ |
|
/// <summary> |
|
/// Type of the current block body. |
|
/// </summary> |
|
public Body CurrentBody; |
|
|
|
/// <summary> |
|
/// Type of the next block body. |
|
/// Same as <see cref="CurrentBody"/> if none of the |
|
/// <see cref="Body"/> keywords have been read. |
|
/// </summary> |
|
public Body NextBody; |
|
|
|
/// <summary> |
|
/// Type of the current statement. |
|
/// </summary> |
|
public Statement CurrentStatement |
|
{ |
|
get |
|
{ |
|
return currentStatement; |
|
} |
|
set |
|
{ |
|
// clear NestedIfStatementLevels if this statement breaks the sequence |
|
if (currentStatement == Statement.None && value != Statement.Else) |
|
{ |
|
NestedIfStatementLevels.Clear(); |
|
} |
|
|
|
currentStatement = value; |
|
} |
|
} |
|
Statement currentStatement; |
|
|
|
/// <summary> |
|
/// Contains indent levels of nested if statements. |
|
/// </summary> |
|
internal CloneableStack<Indent> NestedIfStatementLevels = new CloneableStack<Indent>(); |
|
|
|
/// <summary> |
|
/// Contains the indent level of the last statement or body keyword. |
|
/// </summary> |
|
public Indent LastBlockIndent; |
|
|
|
/// <summary> |
|
/// True if the engine is on the right side of the equal operator '='. |
|
/// </summary> |
|
public bool IsRightHandExpression; |
|
|
|
/// <summary> |
|
/// True if the '=' char has been pushed and it's not |
|
/// a part of a relational operator (>=, <=, !=, ==). |
|
/// </summary> |
|
public bool IsEqualCharPushed; |
|
|
|
/// <summary> |
|
/// The indentation of the previous line. |
|
/// </summary> |
|
public int PreviousLineIndent; |
|
|
|
/// <summary> |
|
/// True if the dot member (e.g. method invocation) indentation has |
|
/// been handled in the current statement. |
|
/// </summary> |
|
public bool IsMemberReferenceDotHandled; |
|
|
|
public override char ClosedBracket |
|
{ |
|
get { return '}'; } |
|
} |
|
|
|
public BracesBodyState() |
|
{ |
|
} |
|
|
|
public BracesBodyState(BracesBodyState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
CurrentBody = prototype.CurrentBody; |
|
NextBody = prototype.NextBody; |
|
CurrentStatement = prototype.CurrentStatement; |
|
NestedIfStatementLevels = prototype.NestedIfStatementLevels.Clone(); |
|
IsRightHandExpression = prototype.IsRightHandExpression; |
|
IsEqualCharPushed = prototype.IsEqualCharPushed; |
|
IsMemberReferenceDotHandled = prototype.IsMemberReferenceDotHandled; |
|
LastBlockIndent = prototype.LastBlockIndent; |
|
PreviousLineIndent = prototype.PreviousLineIndent; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
// handle IsRightHandExpression property |
|
if (IsEqualCharPushed) |
|
{ |
|
if (IsRightHandExpression) |
|
{ |
|
if (ch == Engine.newLineChar) |
|
{ |
|
NextLineIndent.RemoveAlignment(); |
|
NextLineIndent.Push(IndentType.Continuation); |
|
} |
|
} |
|
// ignore "==" and "=>" operators |
|
else if (ch != '=' && ch != '>') |
|
{ |
|
IsRightHandExpression = true; |
|
|
|
if (ch == Engine.newLineChar) |
|
{ |
|
NextLineIndent.Push(IndentType.Continuation); |
|
} |
|
else |
|
{ |
|
NextLineIndent.SetAlignment(Engine.column - NextLineIndent.CurIndent); |
|
} |
|
} |
|
|
|
IsEqualCharPushed = ch == ' ' || ch == '\t'; |
|
} |
|
|
|
if (ch == ';' || (ch == ',' && IsRightHandExpression)) |
|
{ |
|
OnStatementExit(); |
|
} |
|
else if (ch == '=' && !(Engine.previousChar == '=' || Engine.previousChar == '<' || Engine.previousChar == '>' || Engine.previousChar == '!')) |
|
{ |
|
IsEqualCharPushed = true; |
|
} |
|
else if (ch == '.' && !IsMemberReferenceDotHandled) |
|
{ |
|
// OPTION: CSharpFormattingOptions.AlignToMemberReferenceDot |
|
if (Engine.formattingOptions.AlignToMemberReferenceDot && !Engine.isLineStart) |
|
{ |
|
IsMemberReferenceDotHandled = true; |
|
NextLineIndent.RemoveAlignment(); |
|
NextLineIndent.SetAlignment(Engine.column - NextLineIndent.CurIndent - 1, true); |
|
} |
|
else if (Engine.isLineStart) |
|
{ |
|
IsMemberReferenceDotHandled = true; |
|
|
|
ThisLineIndent.RemoveAlignment(); |
|
while (ThisLineIndent.CurIndent > PreviousLineIndent && |
|
ThisLineIndent.PopIf(IndentType.Continuation)) ; |
|
|
|
ThisLineIndent.Push(IndentType.Continuation); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
} |
|
else if (ch == ':' && Engine.isLineStart && !IsRightHandExpression) |
|
{ |
|
// try to capture ': base(...)', ': this(...)' and inherit statements when they are on a new line |
|
ThisLineIndent.Push(IndentType.Continuation); |
|
} |
|
else if (ch == Engine.newLineChar) |
|
{ |
|
PreviousLineIndent = ThisLineIndent.CurIndent; |
|
} |
|
|
|
if (Engine.wordToken.ToString() == "else") |
|
{ |
|
CheckKeywordOnPush("else"); |
|
} |
|
|
|
base.Push(ch); |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = Parent.NextLineIndent.Clone(); |
|
|
|
// OPTION: IDocumentIndentEngine.EnableCustomIndentLevels |
|
var parent = Parent as BracesBodyState; |
|
if (parent == null || parent.LastBlockIndent == null || !Engine.EnableCustomIndentLevels) |
|
{ |
|
NextLineIndent.RemoveAlignment(); |
|
NextLineIndent.PopIf(IndentType.Continuation); |
|
} |
|
else |
|
{ |
|
NextLineIndent = parent.LastBlockIndent.Clone(); |
|
} |
|
|
|
if (Engine.isLineStart) |
|
{ |
|
ThisLineIndent = NextLineIndent.Clone(); |
|
} |
|
|
|
CurrentBody = extractBody(Parent); |
|
NextBody = Body.None; |
|
CurrentStatement = Statement.None; |
|
|
|
AddIndentation(CurrentBody); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new BracesBodyState(this, engine); |
|
} |
|
|
|
public override void OnExit() |
|
{ |
|
if (Parent is BracesBodyState) |
|
{ |
|
((BracesBodyState)Parent).OnStatementExit(); |
|
} |
|
|
|
if (Engine.isLineStart) |
|
{ |
|
ThisLineIndent.RemoveAlignment(); |
|
ThisLineIndent.PopTry(); |
|
BraceStyle style; |
|
if (TryGetBraceStyle(this.CurrentBody, out style)) { |
|
if (style == BraceStyle.NextLineShifted || |
|
style == BraceStyle.NextLineShifted2|| |
|
style == BraceStyle.BannerStyle) { |
|
ThisLineIndent.Push(IndentType.Block); |
|
} |
|
} |
|
} |
|
|
|
base.OnExit(); |
|
} |
|
|
|
/// <summary> |
|
/// Actions performed when the current statement exits. |
|
/// </summary> |
|
public virtual void OnStatementExit() |
|
{ |
|
IsRightHandExpression = false; |
|
IsMemberReferenceDotHandled = false; |
|
|
|
NextLineIndent.RemoveAlignment(); |
|
NextLineIndent.PopWhile(IndentType.Continuation); |
|
|
|
CurrentStatement = Statement.None; |
|
NextBody = Body.None; |
|
LastBlockIndent = null; |
|
} |
|
|
|
#region Helpers |
|
|
|
/// <summary> |
|
/// Types of braces bodies. |
|
/// </summary> |
|
public enum Body |
|
{ |
|
None, |
|
Namespace, |
|
Class, |
|
Struct, |
|
Interface, |
|
Enum, |
|
Switch, |
|
Case, |
|
Try, |
|
Catch, |
|
Finally |
|
} |
|
|
|
/// <summary> |
|
/// Types of statements. |
|
/// </summary> |
|
public enum Statement |
|
{ |
|
None, |
|
If, |
|
Else, |
|
Do, |
|
While, |
|
For, |
|
Foreach, |
|
Lock, |
|
Using, |
|
Return |
|
} |
|
|
|
static readonly Dictionary<string, Body> bodies = new Dictionary<string, Body> |
|
{ |
|
{ "namespace", Body.Namespace }, |
|
{ "class", Body.Class }, |
|
{ "struct", Body.Struct }, |
|
{ "interface", Body.Interface }, |
|
{ "enum", Body.Enum }, |
|
{ "switch", Body.Switch }, |
|
{ "try", Body.Try }, |
|
{ "catch", Body.Catch }, |
|
{ "finally", Body.Finally }, |
|
}; |
|
|
|
static readonly Dictionary<string, Statement> statements = new Dictionary<string, Statement> |
|
{ |
|
{ "if", Statement.If }, |
|
// { "else", Statement.Else }, // should be handled in CheckKeywordAtPush |
|
{ "do", Statement.Do }, |
|
{ "while", Statement.While }, |
|
{ "for", Statement.For }, |
|
{ "foreach", Statement.Foreach }, |
|
{ "lock", Statement.Lock }, |
|
{ "using", Statement.Using }, |
|
{ "return", Statement.Return }, |
|
}; |
|
|
|
static readonly HashSet<string> blocks = new HashSet<string> |
|
{ |
|
"namespace", |
|
"class", |
|
"struct", |
|
"interface", |
|
"enum", |
|
"switch", |
|
"try", |
|
"catch", |
|
"finally", |
|
"if", |
|
"else", |
|
"do", |
|
"while", |
|
"for", |
|
"foreach", |
|
"lock", |
|
"using", |
|
}; |
|
|
|
readonly string[] caseDefaultKeywords = { |
|
"case", |
|
"default" |
|
}; |
|
|
|
readonly string[] classStructKeywords = { |
|
"class", |
|
"struct" |
|
}; |
|
|
|
/// <summary> |
|
/// Checks if the given string is a keyword and sets the |
|
/// <see cref="NextBody"/> and the <see cref="CurrentStatement"/> |
|
/// variables appropriately. |
|
/// </summary> |
|
/// <param name="keyword"> |
|
/// A possible keyword. |
|
/// </param> |
|
/// <remarks> |
|
/// This method is called from <see cref="Push(char)"/> |
|
/// </remarks> |
|
public override void CheckKeywordOnPush(string keyword) |
|
{ |
|
if (keyword == "else") |
|
{ |
|
CurrentStatement = Statement.Else; |
|
|
|
// OPTION: CSharpFormattingOptions.AlignElseInIfStatements |
|
if (!Engine.formattingOptions.AlignElseInIfStatements && NestedIfStatementLevels.Count > 0) |
|
{ |
|
ThisLineIndent = NestedIfStatementLevels.Pop().Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
|
|
NextLineIndent.Push(IndentType.Continuation); |
|
} |
|
|
|
if (blocks.Contains(keyword) && Engine.NeedsReindent) |
|
{ |
|
LastBlockIndent = Indent.ConvertFrom(Engine.CurrentIndent, ThisLineIndent, Engine.textEditorOptions); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Checks if the given string is a keyword and sets the |
|
/// <see cref="NextBody"/> and the <see cref="CurrentStatement"/> |
|
/// variables appropriately. |
|
/// </summary> |
|
/// <param name="keyword"> |
|
/// A possible keyword. |
|
/// </param> |
|
public override void CheckKeyword(string keyword) |
|
{ |
|
if (bodies.ContainsKey(keyword)) |
|
{ |
|
var isKeywordTemplateConstraint = |
|
classStructKeywords.Contains(keyword) && |
|
(NextBody == Body.Class || NextBody == Body.Struct || NextBody == Body.Interface); |
|
|
|
if (!isKeywordTemplateConstraint) |
|
{ |
|
NextBody = bodies[keyword]; |
|
} |
|
} |
|
else if (caseDefaultKeywords.Contains(keyword) && CurrentBody == Body.Switch && Engine.isLineStartBeforeWordToken) |
|
{ |
|
ChangeState<SwitchCaseState>(); |
|
} |
|
else if (keyword == "where" && Engine.isLineStartBeforeWordToken) |
|
{ |
|
// try to capture where (generic type constraint) |
|
ThisLineIndent.Push(IndentType.Continuation); |
|
} |
|
else if (statements.ContainsKey(keyword)) |
|
{ |
|
Statement previousStatement = CurrentStatement; |
|
CurrentStatement = statements[keyword]; |
|
|
|
// return if this is a using declaration or alias |
|
if (CurrentStatement == Statement.Using && |
|
(this is GlobalBodyState || CurrentBody == Body.Namespace)) |
|
{ |
|
return; |
|
} |
|
|
|
// OPTION: CSharpFormattingOptions.AlignEmbeddedIfStatements |
|
if (Engine.formattingOptions.AlignEmbeddedIfStatements && |
|
previousStatement == Statement.If && |
|
CurrentStatement == Statement.If) |
|
{ |
|
ThisLineIndent.PopIf(IndentType.Continuation); |
|
NextLineIndent.PopIf(IndentType.Continuation); |
|
} |
|
|
|
// OPTION: CSharpFormattingOptions.AlignEmbeddedUsingStatements |
|
if (Engine.formattingOptions.AlignEmbeddedUsingStatements && |
|
previousStatement == Statement.Using && |
|
CurrentStatement == Statement.Using) |
|
{ |
|
ThisLineIndent.PopIf(IndentType.Continuation); |
|
NextLineIndent.PopIf(IndentType.Continuation); |
|
} |
|
|
|
// only add continuation for 'else' in 'else if' statement. |
|
if (!(CurrentStatement == Statement.If && previousStatement == Statement.Else && !Engine.isLineStartBeforeWordToken)) |
|
{ |
|
NextLineIndent.Push(IndentType.Continuation); |
|
} |
|
|
|
if (CurrentStatement == Statement.If) |
|
{ |
|
NestedIfStatementLevels.Push(ThisLineIndent); |
|
} |
|
} |
|
|
|
if (blocks.Contains(keyword) && Engine.NeedsReindent) |
|
{ |
|
LastBlockIndent = Indent.ConvertFrom(Engine.CurrentIndent, ThisLineIndent, Engine.textEditorOptions); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Pushes a new level of indentation depending on the given |
|
/// <paramref name="braceStyle"/>. |
|
/// </summary> |
|
void AddIndentation(BraceStyle braceStyle) |
|
{ |
|
switch (braceStyle) |
|
{ |
|
case BraceStyle.NextLineShifted: |
|
ThisLineIndent.Push(IndentType.Block); |
|
NextLineIndent.Push(IndentType.Block); |
|
break; |
|
case BraceStyle.DoNotChange: |
|
case BraceStyle.EndOfLine: |
|
case BraceStyle.EndOfLineWithoutSpace: |
|
case BraceStyle.NextLine: |
|
case BraceStyle.BannerStyle: |
|
NextLineIndent.Push(IndentType.Block); |
|
break; |
|
case BraceStyle.NextLineShifted2: |
|
ThisLineIndent.Push(IndentType.Block); |
|
NextLineIndent.Push(IndentType.DoubleBlock); |
|
break; |
|
} |
|
} |
|
|
|
bool TryGetBraceStyle (Body body, out BraceStyle style) |
|
{ |
|
style = BraceStyle.DoNotChange; |
|
switch (body) |
|
{ |
|
case Body.None: |
|
if (!Engine.formattingOptions.IndentBlocks) |
|
return false; |
|
style = Engine.formattingOptions.StatementBraceStyle; |
|
return true; |
|
case Body.Namespace: |
|
if (!Engine.formattingOptions.IndentNamespaceBody) |
|
return false; |
|
style = Engine.formattingOptions.NamespaceBraceStyle; |
|
return true; |
|
case Body.Class: |
|
if (!Engine.formattingOptions.IndentClassBody) |
|
return false; |
|
style = Engine.formattingOptions.ClassBraceStyle; |
|
return true; |
|
case Body.Struct: |
|
if (!Engine.formattingOptions.IndentStructBody) |
|
return false; |
|
style = Engine.formattingOptions.StructBraceStyle; |
|
return true; |
|
case Body.Interface: |
|
if (!Engine.formattingOptions.IndentInterfaceBody) |
|
return false; |
|
style = Engine.formattingOptions.InterfaceBraceStyle; |
|
return true; |
|
case Body.Enum: |
|
if (!Engine.formattingOptions.IndentEnumBody) |
|
return false; |
|
style = Engine.formattingOptions.EnumBraceStyle; |
|
return true; |
|
case Body.Switch: |
|
if (!Engine.formattingOptions.IndentSwitchBody) |
|
return false; |
|
style = Engine.formattingOptions.StatementBraceStyle; |
|
return true; |
|
case Body.Try: |
|
case Body.Catch: |
|
case Body.Finally: |
|
style = Engine.formattingOptions.StatementBraceStyle; |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
/// <summary> |
|
/// Pushes a new level of indentation depending on the given |
|
/// <paramref name="body"/>. |
|
/// </summary> |
|
void AddIndentation(Body body) |
|
{ |
|
BraceStyle style; |
|
if (TryGetBraceStyle (body, out style)) { |
|
AddIndentation(style); |
|
} else { |
|
NextLineIndent.Push(IndentType.Empty); |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Extracts the <see cref="CurrentBody"/> from the given state. |
|
/// </summary> |
|
/// <returns> |
|
/// The correct <see cref="Body"/> type for this state. |
|
/// </returns> |
|
static Body extractBody(IndentState state) |
|
{ |
|
if (state != null && state is BracesBodyState) |
|
{ |
|
return ((BracesBodyState)state).NextBody; |
|
} |
|
|
|
return Body.None; |
|
} |
|
|
|
#endregion |
|
} |
|
|
|
#endregion |
|
|
|
#region Global body state |
|
|
|
/// <summary> |
|
/// Global body state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Represents the global space of the program. |
|
/// </remarks> |
|
public class GlobalBodyState : BracesBodyState |
|
{ |
|
public override char ClosedBracket |
|
{ |
|
get { return '\0'; } |
|
} |
|
|
|
public GlobalBodyState() |
|
{ } |
|
|
|
|
|
public GlobalBodyState(CSharpIndentEngine engine) |
|
{ |
|
Initialize (engine, null); |
|
} |
|
|
|
public GlobalBodyState(GlobalBodyState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ } |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new GlobalBodyState(this, engine); |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = new Indent(Engine.textEditorOptions); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Switch-case body state |
|
|
|
/// <summary> |
|
/// Switch-case statement state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Represents the block of code in one switch case (including default). |
|
/// </remarks> |
|
public class SwitchCaseState : BracesBodyState |
|
{ |
|
public SwitchCaseState() |
|
{ } |
|
|
|
public SwitchCaseState(SwitchCaseState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ } |
|
|
|
public override void Push(char ch) |
|
{ |
|
// on ClosedBracket both this state (a case or a default statement) |
|
// and also the whole switch block (handled in the base class) must exit. |
|
if (ch == ClosedBracket) |
|
{ |
|
ExitState(); |
|
} |
|
|
|
base.Push(ch); |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
|
|
// remove all continuations and extra spaces |
|
ThisLineIndent.RemoveAlignment(); |
|
ThisLineIndent.PopWhile(IndentType.Continuation); |
|
|
|
NextLineIndent.RemoveAlignment(); |
|
NextLineIndent.PopWhile(IndentType.Continuation); |
|
|
|
if (Engine.formattingOptions.IndentCaseBody) |
|
{ |
|
NextLineIndent.Push(IndentType.Block); |
|
} |
|
else |
|
{ |
|
NextLineIndent.Push(IndentType.Empty); |
|
} |
|
} |
|
|
|
static readonly string[] caseDefaultKeywords = { |
|
"case", |
|
"default" |
|
}; |
|
|
|
static readonly string[] breakContinueReturnGotoKeywords = { |
|
"break", |
|
"continue", |
|
"return", |
|
"goto" |
|
}; |
|
|
|
public override void CheckKeyword(string keyword) |
|
{ |
|
if (caseDefaultKeywords.Contains(keyword) && Engine.isLineStartBeforeWordToken) |
|
{ |
|
ExitState(); |
|
ChangeState<SwitchCaseState>(); |
|
} |
|
else if (breakContinueReturnGotoKeywords.Contains(keyword) && Engine.isLineStartBeforeWordToken) |
|
{ |
|
// OPTION: Engine.formattingOptions.IndentBreakStatements |
|
if (!Engine.formattingOptions.IndentBreakStatements) |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
} |
|
} |
|
|
|
base.CheckKeyword(keyword); |
|
} |
|
|
|
public override void OnExit() |
|
{ |
|
// override the base.OnExit() logic |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new SwitchCaseState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Parentheses body state |
|
|
|
/// <summary> |
|
/// Parentheses body state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Represents a block of code between ( and ). |
|
/// </remarks> |
|
public class ParenthesesBodyState : BracketsBodyBaseState |
|
{ |
|
/// <summary> |
|
/// True if any char has been pushed. |
|
/// </summary> |
|
public bool IsSomethingPushed; |
|
|
|
public override char ClosedBracket |
|
{ |
|
get { return ')'; } |
|
} |
|
|
|
public ParenthesesBodyState() |
|
{ } |
|
|
|
public ParenthesesBodyState(ParenthesesBodyState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
IsSomethingPushed = prototype.IsSomethingPushed; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
if (ch == Engine.newLineChar) |
|
{ |
|
if (Engine.formattingOptions.AnonymousMethodBraceStyle == BraceStyle.EndOfLine || |
|
Engine.formattingOptions.AnonymousMethodBraceStyle == BraceStyle.EndOfLineWithoutSpace) { |
|
if (NextLineIndent.PopIf(IndentType.Continuation)) { |
|
NextLineIndent.Push(IndentType.Block); |
|
} |
|
} |
|
} |
|
else if (!IsSomethingPushed) |
|
{ |
|
// OPTION: CSharpFormattingOptions.AlignToFirstMethodCallArgument |
|
if (Engine.formattingOptions.AlignToFirstMethodCallArgument) |
|
{ |
|
NextLineIndent.PopTry(); |
|
// align the next line at the beginning of the open bracket |
|
NextLineIndent.ExtraSpaces = Math.Max(0, Engine.column - NextLineIndent.CurIndent - 1); |
|
} |
|
} |
|
|
|
base.Push(ch); |
|
IsSomethingPushed = true; |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
NextLineIndent.Push(IndentType.Continuation); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new ParenthesesBodyState(this, engine); |
|
} |
|
|
|
public override void OnExit() |
|
{ |
|
if (Engine.isLineStart) |
|
{ |
|
if (ThisLineIndent.ExtraSpaces > 0) |
|
{ |
|
ThisLineIndent.ExtraSpaces--; |
|
} |
|
else |
|
{ |
|
ThisLineIndent.PopTry(); |
|
} |
|
} |
|
|
|
base.OnExit(); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Square brackets body state |
|
|
|
/// <summary> |
|
/// Square brackets body state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Represents a block of code between [ and ]. |
|
/// </remarks> |
|
public class SquareBracketsBodyState : BracketsBodyBaseState |
|
{ |
|
/// <summary> |
|
/// True if any char has been pushed. |
|
/// </summary> |
|
public bool IsSomethingPushed; |
|
|
|
public override char ClosedBracket |
|
{ |
|
get { return ']'; } |
|
} |
|
|
|
public SquareBracketsBodyState() |
|
{ } |
|
|
|
public SquareBracketsBodyState(SquareBracketsBodyState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
IsSomethingPushed = prototype.IsSomethingPushed; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
if (ch == Engine.newLineChar) |
|
{ |
|
if (NextLineIndent.PopIf(IndentType.Continuation)) |
|
{ |
|
NextLineIndent.Push(IndentType.Block); |
|
} |
|
} |
|
else if (!IsSomethingPushed) |
|
{ |
|
// OPTION: CSharpFormattingOptions.AlignToFirstIndexerArgument |
|
if (Engine.formattingOptions.AlignToFirstIndexerArgument) |
|
{ |
|
NextLineIndent.PopTry(); |
|
// align the next line at the beginning of the open bracket |
|
NextLineIndent.ExtraSpaces = Math.Max(0, Engine.column - NextLineIndent.CurIndent - 1); |
|
} |
|
} |
|
|
|
base.Push(ch); |
|
IsSomethingPushed = true; |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
NextLineIndent.Push(IndentType.Continuation); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new SquareBracketsBodyState(this, engine); |
|
} |
|
|
|
public override void OnExit() |
|
{ |
|
if (Engine.isLineStart) |
|
{ |
|
if (ThisLineIndent.ExtraSpaces > 0) |
|
{ |
|
ThisLineIndent.ExtraSpaces--; |
|
} |
|
else |
|
{ |
|
ThisLineIndent.PopTry(); |
|
} |
|
} |
|
|
|
base.OnExit(); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#endregion |
|
|
|
#region PreProcessor state |
|
|
|
/// <summary> |
|
/// PreProcessor directive state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Activated when the '#' char is pushed and the |
|
/// <see cref="CSharpIndentEngine.isLineStart"/> is true. |
|
/// </remarks> |
|
public class PreProcessorState : IndentState |
|
{ |
|
/// <summary> |
|
/// The type of the preprocessor directive. |
|
/// </summary> |
|
public PreProcessorDirective DirectiveType; |
|
|
|
/// <summary> |
|
/// If <see cref="DirectiveType"/> is set (not equal to 'None'), this |
|
/// stores the expression of the directive. |
|
/// </summary> |
|
public StringBuilder DirectiveStatement; |
|
|
|
public PreProcessorState() |
|
{ |
|
DirectiveType = PreProcessorDirective.None; |
|
DirectiveStatement = new StringBuilder(); |
|
} |
|
|
|
public PreProcessorState(PreProcessorState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
DirectiveType = prototype.DirectiveType; |
|
DirectiveStatement = new StringBuilder(prototype.DirectiveStatement.ToString()); |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
// HACK: if this change would be left for the CheckKeyword method, we will lose |
|
// it if the next pushed char is newLineChar since ThisLineIndent will be |
|
// immediately replaced with NextLineIndent. As this most likely will |
|
// happen, we check for "endregion" on every push. |
|
if (Engine.wordToken.ToString() == "endregion") |
|
{ |
|
CheckKeywordOnPush("endregion"); |
|
} |
|
|
|
base.Push(ch); |
|
|
|
if (DirectiveType != PreProcessorDirective.None) |
|
{ |
|
DirectiveStatement.Append(ch); |
|
} |
|
|
|
if (ch == Engine.newLineChar) |
|
{ |
|
ExitState(); |
|
switch (DirectiveType) |
|
{ |
|
case PreProcessorDirective.If: |
|
Engine.ifDirectiveEvalResults.Push(eval(DirectiveStatement.ToString())); |
|
if (Engine.ifDirectiveEvalResults.Peek()) |
|
{ |
|
// the if/elif directive is true -> continue with the previous state |
|
} |
|
else |
|
{ |
|
// the if/elif directive is false -> change to a state that will |
|
// ignore any chars until #endif or #elif |
|
ChangeState<PreProcessorCommentState>(); |
|
} |
|
break; |
|
case PreProcessorDirective.Elif: |
|
if (Engine.ifDirectiveEvalResults.Count > 0) |
|
{ |
|
if (!Engine.ifDirectiveEvalResults.Peek()) |
|
{ |
|
Engine.ifDirectiveEvalResults.Pop(); |
|
goto case PreProcessorDirective.If; |
|
} |
|
} |
|
// previous if was true -> comment |
|
ChangeState<PreProcessorCommentState>(); |
|
break; |
|
case PreProcessorDirective.Else: |
|
if (Engine.ifDirectiveEvalResults.Count > 0 && Engine.ifDirectiveEvalResults.Peek()) |
|
{ |
|
// some if/elif directive was true -> change to a state that will |
|
// ignore any chars until #endif |
|
ChangeState<PreProcessorCommentState>(); |
|
} |
|
else |
|
{ |
|
// none if/elif directives were true -> continue with the previous state |
|
} |
|
break; |
|
case PreProcessorDirective.Define: |
|
var defineSymbol = DirectiveStatement.ToString().Trim(); |
|
if (!Engine.conditionalSymbols.Contains(defineSymbol)) |
|
{ |
|
Engine.conditionalSymbols.Add(defineSymbol); |
|
} |
|
break; |
|
case PreProcessorDirective.Undef: |
|
var undefineSymbol = DirectiveStatement.ToString().Trim(); |
|
if (Engine.conditionalSymbols.Contains(undefineSymbol)) |
|
{ |
|
Engine.conditionalSymbols.Remove(undefineSymbol); |
|
} |
|
break; |
|
case PreProcessorDirective.Endif: |
|
// marks the end of this block |
|
Engine.ifDirectiveEvalResults.Pop(); |
|
Engine.ifDirectiveIndents.Pop(); |
|
break; |
|
case PreProcessorDirective.Region: |
|
case PreProcessorDirective.Pragma: |
|
case PreProcessorDirective.Warning: |
|
case PreProcessorDirective.Error: |
|
case PreProcessorDirective.Line: |
|
// continue with the previous state |
|
break; |
|
} |
|
} |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
// OPTION: IndentPreprocessorStatements |
|
if (Engine.formattingOptions.IndentPreprocessorDirectives) |
|
{ |
|
if (Engine.ifDirectiveIndents.Count > 0) |
|
{ |
|
ThisLineIndent = Engine.ifDirectiveIndents.Peek().Clone(); |
|
} |
|
else |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
} |
|
} |
|
else |
|
{ |
|
ThisLineIndent = new Indent(Engine.textEditorOptions); |
|
} |
|
|
|
NextLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
|
|
static readonly Dictionary<string, PreProcessorDirective> preProcessorDirectives = new Dictionary<string, PreProcessorDirective> |
|
{ |
|
{ "if", PreProcessorDirective.If }, |
|
{ "elif", PreProcessorDirective.Elif }, |
|
{ "else", PreProcessorDirective.Else }, |
|
{ "endif", PreProcessorDirective.Endif }, |
|
{ "region", PreProcessorDirective.Region }, |
|
{ "endregion", PreProcessorDirective.Endregion }, |
|
{ "pragma", PreProcessorDirective.Pragma }, |
|
{ "warning", PreProcessorDirective.Warning }, |
|
{ "error", PreProcessorDirective.Error }, |
|
{ "line", PreProcessorDirective.Line }, |
|
{ "define", PreProcessorDirective.Define }, |
|
{ "undef", PreProcessorDirective.Undef } |
|
}; |
|
|
|
public override void CheckKeywordOnPush(string keyword) |
|
{ |
|
if (keyword == "endregion") |
|
{ |
|
DirectiveType = PreProcessorDirective.Endregion; |
|
ThisLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
} |
|
|
|
public override void CheckKeyword(string keyword) |
|
{ |
|
// check if the directive type has already been set |
|
if (DirectiveType != PreProcessorDirective.None) |
|
{ |
|
return; |
|
} |
|
|
|
if (preProcessorDirectives.ContainsKey(keyword)) |
|
{ |
|
DirectiveType = preProcessorDirectives[keyword]; |
|
|
|
// adjust the indentation for the region directive |
|
if (DirectiveType == PreProcessorDirective.Region) |
|
{ |
|
ThisLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
else if (DirectiveType == PreProcessorDirective.If) |
|
{ |
|
Engine.ifDirectiveIndents.Push(ThisLineIndent.Clone()); |
|
} |
|
} |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new PreProcessorState(this, engine); |
|
} |
|
|
|
/// <summary> |
|
/// Types of preprocessor directives. |
|
/// </summary> |
|
public enum PreProcessorDirective |
|
{ |
|
None, |
|
If, |
|
Elif, |
|
Else, |
|
Endif, |
|
Region, |
|
Endregion, |
|
Pragma, |
|
Warning, |
|
Error, |
|
Line, |
|
Define, |
|
Undef |
|
} |
|
|
|
#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 Engine.conditionalSymbols != null && Engine.conditionalSymbols.Contains(s) || |
|
Engine.customConditionalSymbols != null && Engine.customConditionalSymbols.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 |
|
} |
|
|
|
#endregion |
|
|
|
#region PreProcessorComment state |
|
|
|
/// <summary> |
|
/// PreProcessor comment state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Activates when the #if or #elif directive is false and ignores |
|
/// all pushed chars until the next '#'. |
|
/// </remarks> |
|
public class PreProcessorCommentState : IndentState |
|
{ |
|
public PreProcessorCommentState() |
|
{ } |
|
|
|
public PreProcessorCommentState(PreProcessorCommentState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ } |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (ch == '#' && Engine.isLineStart) |
|
{ |
|
// TODO: Return back only on #if/#elif/#else/#endif |
|
// Ignore any of the other directives (especially #define/#undef) |
|
ExitState(); |
|
ChangeState<PreProcessorState>(); |
|
} |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
if (Engine.formattingOptions.IndentPreprocessorDirectives && |
|
Engine.ifDirectiveIndents.Count > 0) |
|
{ |
|
ThisLineIndent = Engine.ifDirectiveIndents.Peek().Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
else |
|
{ |
|
ThisLineIndent = Parent.NextLineIndent.Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new PreProcessorCommentState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region LineComment state |
|
|
|
/// <summary> |
|
/// Single-line comment state. |
|
/// </summary> |
|
public class LineCommentState : IndentState |
|
{ |
|
/// <summary> |
|
/// It's possible that this should be the DocComment state: |
|
/// check if the first next pushed char is equal to '/'. |
|
/// </summary> |
|
public bool CheckForDocComment = true; |
|
|
|
public LineCommentState() |
|
{ |
|
/* if (engine.formattingOptions.KeepCommentsAtFirstColumn && engine.column == 2) |
|
ThisLineIndent.Reset();*/ |
|
} |
|
|
|
public LineCommentState(LineCommentState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
CheckForDocComment = prototype.CheckForDocComment; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (ch == Engine.newLineChar) |
|
{ |
|
// to handle cases like //\n/* |
|
// Otherwise line 2 would be treated as line comment. |
|
Engine.previousChar = '\0'; |
|
ExitState(); |
|
} |
|
else if (ch == '/' && CheckForDocComment) |
|
{ |
|
// wrong state, should be DocComment. |
|
ExitState(); |
|
ChangeState<DocCommentState>(); |
|
} |
|
|
|
CheckForDocComment = false; |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new LineCommentState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region DocComment state |
|
|
|
/// <summary> |
|
/// XML documentation comment state. |
|
/// </summary> |
|
public class DocCommentState : IndentState |
|
{ |
|
public DocCommentState() |
|
{ } |
|
|
|
public DocCommentState(DocCommentState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ } |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (ch == Engine.newLineChar) |
|
{ |
|
ExitState(); |
|
} |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new DocCommentState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region MultiLineComment state |
|
|
|
/// <summary> |
|
/// Multi-line comment state. |
|
/// </summary> |
|
public class MultiLineCommentState : IndentState |
|
{ |
|
/// <summary> |
|
/// True if any char has been pushed to this state. |
|
/// </summary> |
|
/// <remarks> |
|
/// Needed to resolve an issue when the first pushed char is '/'. |
|
/// The state would falsely exit on this sequence of chars '/*/', |
|
/// since it only checks if the last two chars are '/' and '*'. |
|
/// </remarks> |
|
public bool IsAnyCharPushed; |
|
|
|
public MultiLineCommentState() |
|
{ } |
|
|
|
public MultiLineCommentState(MultiLineCommentState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
IsAnyCharPushed = prototype.IsAnyCharPushed; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (ch == '/' && Engine.previousChar == '*' && IsAnyCharPushed) |
|
{ |
|
ExitState(); |
|
} |
|
|
|
IsAnyCharPushed = true; |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = ThisLineIndent.Clone(); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new MultiLineCommentState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region StringLiteral state |
|
|
|
/// <summary> |
|
/// StringLiteral state. |
|
/// </summary> |
|
public class StringLiteralState : IndentState |
|
{ |
|
/// <summary> |
|
/// True if the next char is escaped with '\'. |
|
/// </summary> |
|
public bool IsEscaped; |
|
|
|
public StringLiteralState() |
|
{ } |
|
|
|
public StringLiteralState(StringLiteralState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
IsEscaped = prototype.IsEscaped; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (ch == Engine.newLineChar || (!IsEscaped && ch == '"')) { |
|
ExitState(); |
|
} else { |
|
IsEscaped = ch == '\\' && !IsEscaped; |
|
} |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new StringLiteralState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Verbatim string state |
|
|
|
/// <summary> |
|
/// Verbatim string state. |
|
/// </summary> |
|
public class VerbatimStringState : IndentState |
|
{ |
|
/// <summary> |
|
/// True if there is an odd number of '"' in a row. |
|
/// </summary> |
|
public bool IsEscaped; |
|
|
|
public VerbatimStringState() |
|
{ } |
|
|
|
public VerbatimStringState(VerbatimStringState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
IsEscaped = prototype.IsEscaped; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (IsEscaped && ch != '"') |
|
{ |
|
ExitState(); |
|
// the char has been pushed to the wrong state, push it back |
|
Engine.currentState.Push(ch); |
|
} |
|
|
|
IsEscaped = ch == '"' && !IsEscaped; |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = new Indent(Engine.textEditorOptions); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new VerbatimStringState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
|
|
#region Character state |
|
|
|
/// <summary> |
|
/// Character state. |
|
/// </summary> |
|
public class CharacterState : IndentState |
|
{ |
|
/// <summary> |
|
/// True if the next char is escaped with '\'. |
|
/// </summary> |
|
public bool IsEscaped; |
|
|
|
public CharacterState() |
|
{ } |
|
|
|
public CharacterState(CharacterState prototype, CSharpIndentEngine engine) |
|
: base(prototype, engine) |
|
{ |
|
IsEscaped = prototype.IsEscaped; |
|
} |
|
|
|
public override void Push(char ch) |
|
{ |
|
base.Push(ch); |
|
|
|
if (ch == Engine.newLineChar) |
|
{ |
|
ExitState(); |
|
} |
|
else if (!IsEscaped && ch == '\'') |
|
{ |
|
ExitState(); |
|
} |
|
|
|
IsEscaped = ch == '\\' && !IsEscaped; |
|
} |
|
|
|
public override void InitializeState() |
|
{ |
|
ThisLineIndent = Parent.ThisLineIndent.Clone(); |
|
NextLineIndent = Parent.NextLineIndent.Clone(); |
|
} |
|
|
|
public override IndentState Clone(CSharpIndentEngine engine) |
|
{ |
|
return new CharacterState(this, engine); |
|
} |
|
} |
|
|
|
#endregion |
|
}
|
|
|