2
// CSharpIndentEngine.cs
5
// Mike KrĆ¼ger <mkrueger@xamarin.com>
7
// Copyright (c) 2012 Xamarin Inc. (http://xamarin.com)
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
27
using ICSharpCode.NRefactory.Editor;
29
using System.Collections.Generic;
31
using System.Globalization;
33
namespace ICSharpCode.NRefactory.CSharp
35
public class CSharpIndentEngine
37
readonly IDocument document;
38
readonly CSharpFormattingOptions options;
39
readonly TextEditorOptions textEditorOptions;
40
readonly StringBuilder wordBuf = new StringBuilder();
41
readonly StringBuilder currentIndent = new StringBuilder();
42
Indent thisLineindent;
46
public IList<string> ConditionalSymbols {
51
public TextLocation Location {
53
return new TextLocation(line, col);
63
public string ThisLineIndent {
65
return thisLineindent.IndentString;
69
public string NewLineIndent {
71
return indent.IndentString + indentDelta.IndentString;
75
public bool NeedsReindent {
77
return ThisLineIndent != currentIndent.ToString();
81
public CSharpIndentEngine(IDocument document, TextEditorOptions textEditorOptions, CSharpFormattingOptions formattingOptions)
83
this.document = document;
84
this.options = formattingOptions;
85
this.textEditorOptions = textEditorOptions;
86
this.indent = new Indent(textEditorOptions);
87
this.indentDelta = new Indent(textEditorOptions);
88
this.thisLineindent = new Indent(textEditorOptions);
91
CSharpIndentEngine(CSharpIndentEngine prototype)
93
this.document = prototype.document;
94
this.options = prototype.options;
95
this.textEditorOptions = prototype.textEditorOptions;
96
this.indent = prototype.indent.Clone();
97
this.indentDelta = prototype.indentDelta.Clone();
98
this.thisLineindent = prototype.thisLineindent.Clone();
99
this.offset = prototype.offset;
100
this.inside = prototype.inside;
101
this.IsLineStart = prototype.IsLineStart;
102
this.pc = prototype.pc;
103
this.parenStack = new Stack<TextLocation>(prototype.parenStack.Reverse());
104
this.currentBody = prototype.currentBody;
105
this.nextBody = prototype.nextBody;
106
this.addContinuation = prototype.addContinuation;
107
this.line = prototype.line;
108
this.col = prototype.col;
109
this.popNextParenBlock = prototype.popNextParenBlock;
112
public CSharpIndentEngine Clone()
114
return new CSharpIndentEngine(this);
118
Inside inside = Inside.Empty;
119
bool IsLineStart = true;
121
Stack<TextLocation> parenStack = new Stack<TextLocation>();
124
bool addContinuation;
126
bool popNextParenBlock;
127
bool readPreprocessorExpression;
132
thisLineindent.Reset();
136
addContinuation = false;
137
popNextParenBlock = false;
139
inside = Inside.Empty;
140
nextBody = currentBody = Body.None;
144
public void UpdateToOffset(int toOffset)
146
if (toOffset < offset)
148
for (int i = offset; i < toOffset; i++)
149
Push(document.GetCharAt(i));
152
public bool IsInStringOrChar {
154
return inside.HasFlag(Inside.StringOrChar);
158
public bool IsInComment {
160
return inside.HasFlag(Inside.Comment);
164
public bool IsInPreProcessorComment {
166
return inside.HasFlag(Inside.PreProcessorComment);
170
public bool IsInPreProcessorDirective {
172
return inside.HasFlag(Inside.PreProcessor);
176
public bool IsInVerbatimString {
178
return inside.HasFlag(Inside.VerbatimString);
182
public bool IsInsideDocLineComment {
184
return inside.HasFlag(Inside.DocComment);
188
public bool IsInsideMultiLineComment {
190
return inside.HasFlag(Inside.MultiLineComment);
194
public bool IsInsideStringLiteral {
196
return inside.HasFlag(Inside.StringLiteral);
205
PreProcessor = (1 << 0),
206
PreProcessorComment = (1 << 12),
208
MultiLineComment = (1 << 1),
209
LineComment = (1 << 2),
210
DocComment = (1 << 11),
211
Comment = (MultiLineComment | LineComment | DocComment),
213
VerbatimString = (1 << 3),
214
StringLiteral = (1 << 4),
215
CharLiteral = (1 << 5),
216
String = (VerbatimString | StringLiteral),
217
StringOrChar = (String | CharLiteral),
219
Attribute = (1 << 6),
220
ParenList = (1 << 7),
222
FoldedStatement = (1 << 8),
226
FoldedOrBlock = (FoldedStatement | Block),
227
FoldedBlockOrCase = (FoldedStatement | Block | Case)
230
#region Pre processor evaluation (from cs-tokenizer.cs)
231
static bool is_identifier_start_character(int c)
233
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_' || Char.IsLetter((char)c);
236
static bool is_identifier_part_character(char c)
238
if (c >= 'a' && c <= 'z')
241
if (c >= 'A' && c <= 'Z')
244
if (c == '_' || (c >= '0' && c <= '9'))
250
return Char.IsLetter(c) || Char.GetUnicodeCategory(c) == UnicodeCategory.ConnectorPunctuation;
253
bool eval_val(string s)
260
return ConditionalSymbols != null && ConditionalSymbols.Contains(s);
263
bool pp_primary(ref string s)
273
bool val = pp_expr(ref s, false);
274
if (s.Length > 0 && s [0] == ')') {
281
if (is_identifier_start_character(c)) {
287
if (is_identifier_part_character(c)) {
291
bool v = eval_val(s.Substring(0, j));
295
bool vv = eval_val(s);
303
bool pp_unary(ref string s)
310
if (len > 1 && s [1] == '=') {
314
return ! pp_primary(ref s);
316
return pp_primary(ref s);
322
bool pp_eq(ref string s)
324
bool va = pp_unary(ref s);
330
if (len > 2 && s [1] == '=') {
332
return va == pp_unary(ref s);
336
} else if (s [0] == '!' && len > 1 && s [1] == '=') {
339
return va != pp_unary(ref s);
348
bool pp_and(ref string s)
350
bool va = pp_eq(ref s);
356
if (len > 2 && s [1] == '&') {
358
return (va & pp_and(ref s));
368
// Evaluates an expression for `#if' or `#elif'
370
bool pp_expr(ref string s, bool isTerm)
372
bool va = pp_and(ref s);
379
if (len > 2 && s [1] == '|') {
381
return va | pp_expr(ref s, isTerm);
397
bool v = pp_expr(ref s, true);
407
public void Push(char ch)
409
if (readPreprocessorExpression) {
413
if (inside.HasFlag(Inside.VerbatimString) && pc == '"' && ch != '"') {
414
inside &= ~Inside.StringLiteral;
419
inside = Inside.PreProcessor;
422
if (IsInStringOrChar || IsInPreProcessorComment)
425
if (inside.HasFlag(Inside.Comment)) {
426
inside |= Inside.DocComment;
428
inside |= Inside.Comment;
433
if (IsInStringOrChar || IsInComment || IsInPreProcessorComment)
436
inside |= Inside.MultiLineComment;
439
currentIndent.Append(' ');
442
var nextTabStop = (col - 1 + textEditorOptions.IndentSize) / textEditorOptions.IndentSize;
443
col = 1 + nextTabStop * textEditorOptions.IndentSize;
444
currentIndent.Append('\t');
449
if (readPreprocessorExpression) {
450
if (!eval(wordBuf.ToString()))
451
inside |= Inside.PreProcessorComment;
454
inside &= ~(Inside.Comment | Inside.String | Inside.CharLiteral | Inside.PreProcessor);
455
CheckKeyword(wordBuf.ToString());
457
indent.Push(indentDelta);
458
indentDelta = new Indent(textEditorOptions);
461
if (addContinuation) {
462
indent.Push(IndentType.Continuation);
464
thisLineindent = indent.Clone();
465
addContinuation = false;
467
readPreprocessorExpression = false;
470
currentIndent.Length = 0;
477
if (IsInComment || IsInPreProcessorComment)
479
if (inside.HasFlag(Inside.StringLiteral)) {
481
inside &= ~Inside.StringLiteral;
486
inside |= Inside.VerbatimString;
488
inside |= Inside.StringLiteral;
494
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment)
496
parenStack.Push(new TextLocation(line, col));
497
popNextParenBlock = true;
498
indent.Push(IndentType.Block);
503
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment)
505
if (popNextParenBlock && parenStack.Count > 0)
507
if (indent.Count > 0)
509
indent.ExtraSpaces = 0;
512
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment)
514
if (parenStack.Count > 0 && parenStack.Peek().Line == line) {
515
if (indent.Count > 0)
517
popNextParenBlock = false;
518
indent.ExtraSpaces = parenStack.Peek().Column - 1 - thisLineindent.CurIndent;
522
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment)
524
currentBody = nextBody;
525
if (indent.Count > 0 && indent.Peek() == IndentType.Continuation)
527
addContinuation = false;
528
AddIndentation(currentBody);
531
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment)
533
if (indentDelta.CurIndent > 0) {
535
if (indentDelta.Count > 0 && indentDelta.Peek() == IndentType.Continuation)
538
if (thisLineindent.Count > 0)
539
thisLineindent.Pop();
540
if (indent.Count > 0)
545
if (IsInComment || IsInStringOrChar || IsInPreProcessorComment)
547
if (indent.Count > 0 && indent.Peek() == IndentType.Continuation)
551
if (IsInComment || inside.HasFlag(Inside.StringLiteral) || IsInPreProcessorComment)
553
if (inside.HasFlag(Inside.CharLiteral)) {
555
inside &= ~Inside.CharLiteral;
557
inside &= Inside.CharLiteral;
562
if (!IsInComment && !IsInStringOrChar && !readPreprocessorExpression) {
563
if ((wordBuf.Length == 0 ? char.IsLetter(ch) : char.IsLetterOrDigit(ch)) || ch == '_') {
566
if (inside.HasFlag(Inside.PreProcessor)) {
567
if (wordBuf.ToString() == "endif") {
568
inside &= ~Inside.PreProcessorComment;
569
} else if (wordBuf.ToString() == "if") {
570
readPreprocessorExpression = true;
571
} else if (wordBuf.ToString() == "elif") {
572
inside &= ~Inside.PreProcessorComment;
573
readPreprocessorExpression = true;
576
CheckKeyword(wordBuf.ToString());
581
if (addContinuation) {
582
indent.Push(IndentType.Continuation);
583
addContinuation = false;
585
IsLineStart &= ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
587
if (ch != '\n' && ch != '\r')
592
void AddIndentation(BraceStyle braceStyle)
594
switch (braceStyle) {
595
case BraceStyle.DoNotChange:
596
case BraceStyle.EndOfLine:
597
case BraceStyle.EndOfLineWithoutSpace:
598
case BraceStyle.NextLine:
599
case BraceStyle.NextLineShifted:
600
case BraceStyle.BannerStyle:
601
indentDelta.Push(IndentType.Block);
604
case BraceStyle.NextLineShifted2:
605
indentDelta.Push(IndentType.DoubleBlock);
610
void AddIndentation(Body body)
614
indentDelta.Push(IndentType.Block);
617
AddIndentation(options.NamespaceBraceStyle);
620
AddIndentation(options.ClassBraceStyle);
623
AddIndentation(options.StructBraceStyle);
626
AddIndentation(options.InterfaceBraceStyle);
629
AddIndentation(options.EnumBraceStyle);
632
if (options.IndentSwitchBody)
633
indentDelta.Push(IndentType.Empty);
636
throw new ArgumentOutOfRangeException();
651
void CheckKeyword(string keyword)
653
switch (currentBody) {
655
if (keyword == "namespace") {
656
nextBody = Body.Namespace;
659
goto case Body.Namespace;
661
if (keyword == "class") {
662
nextBody = Body.Class;
665
if (keyword == "enum") {
666
nextBody = Body.Enum;
669
if (keyword == "struct") {
670
nextBody = Body.Struct;
673
if (keyword == "interface") {
674
nextBody = Body.Interface;
682
if (keyword == "switch")
683
nextBody = Body.Switch;
684
if (keyword == "do" || keyword == "if" || keyword == "for" || keyword == "foreach" || keyword == "while") {
685
addContinuation = true;