Free cookie consent management tool by TermsFeed Policy Generator

source: stable/HeuristicLab.ExtLibs/HeuristicLab.NRefactory/5.5.0/NRefactory.CSharp-5.5.0/IndentEngine/CSharpIndentEngine.cs @ 17120

Last change on this file since 17120 was 11700, checked in by jkarder, 10 years ago

#2077: created branch and added first version

File size: 14.2 KB
Line 
1//
2// CSharpIndentEngine.cs
3//
4// Author:
5//       Matej Miklečić <matej.miklecic@gmail.com>
6//
7// Copyright (c) 2013 Matej Miklečić (matej.miklecic@gmail.com)
8//
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:
15//
16// The above copyright notice and this permission notice shall be included in
17// all copies or substantial portions of the Software.
18//
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
25// THE SOFTWARE.
26using ICSharpCode.NRefactory.Editor;
27using System;
28using System.Collections.Generic;
29using System.Linq;
30using System.Text;
31
32namespace ICSharpCode.NRefactory.CSharp
33{
34  /// <summary>
35  ///     Indentation engine based on a state machine.
36  ///     Supports only pushing new chars to the end.
37  /// </summary>
38  /// <remarks>
39  ///     Represents the context for transitions between <see cref="IndentState"/>.
40  ///     Delegates the responsibility for pushing a new char to the current
41  ///     state and changes between states depending on the pushed chars.
42  /// </remarks>
43  public class CSharpIndentEngine : IStateMachineIndentEngine
44  {
45    #region Properties
46
47    /// <summary>
48    ///     Formatting options.
49    /// </summary>
50    internal readonly CSharpFormattingOptions formattingOptions;
51
52    /// <summary>
53    ///     Text editor options.
54    /// </summary>
55    internal readonly TextEditorOptions textEditorOptions;
56
57    /// <summary>
58    ///     A readonly reference to the document that's parsed
59    ///     by the engine.
60    /// </summary>
61    internal readonly IDocument document;
62
63    /// <summary>
64    ///     Represents the new line character.
65    /// </summary>
66    internal readonly char newLineChar;
67
68    /// <summary>
69    ///     The current indentation state.
70    /// </summary>
71    internal IndentState currentState;
72
73    /// <summary>
74    ///     Stores conditional symbols of #define directives.
75    /// </summary>
76    internal HashSet<string> conditionalSymbols;
77
78    /// <summary>
79    ///     Stores custom conditional symbols.
80    /// </summary>
81    internal HashSet<string> customConditionalSymbols;
82
83    /// <summary>
84    ///     Stores the results of evaluations of the preprocessor if/elif directives
85    ///     in the current block (between #if and #endif).
86    /// </summary>
87    internal CloneableStack<bool> ifDirectiveEvalResults = new CloneableStack<bool> ();
88
89    /// <summary>
90    ///     Stores the indentation levels of the if directives in the current block.
91    /// </summary>
92    internal CloneableStack<Indent> ifDirectiveIndents = new CloneableStack<Indent>();
93
94    /// <summary>
95    ///     Stores the last sequence of characters that can form a
96    ///     valid keyword or variable name.
97    /// </summary>
98    internal StringBuilder wordToken;
99
100    /// <summary>
101    ///     Stores the previous sequence of chars that formed a
102    ///     valid keyword or variable name.
103    /// </summary>
104    internal string previousKeyword;
105
106    #endregion
107
108    #region IDocumentIndentEngine
109
110    /// <inheritdoc />
111    public IDocument Document
112    {
113      get { return document; }
114    }
115
116    /// <inheritdoc />
117    public string ThisLineIndent
118    {
119      get
120      {
121        // OPTION: IndentBlankLines
122        // remove the indentation of this line if isLineStart is true
123//        if (!textEditorOptions.IndentBlankLines && isLineStart)
124//        {
125//          return string.Empty;
126//        }
127
128        return currentState.ThisLineIndent.IndentString;
129      }
130    }
131
132    /// <inheritdoc />
133    public string NextLineIndent
134    {
135      get
136      {
137        return currentState.NextLineIndent.IndentString;
138      }
139    }
140
141    /// <inheritdoc />
142    public string CurrentIndent
143    {
144      get
145      {
146        return currentIndent.ToString();
147      }
148    }
149
150    /// <inheritdoc />
151    /// <remarks>
152    ///     This is set depending on the current <see cref="Location"/> and
153    ///     can change its value until the <see cref="newLineChar"/> char is
154    ///     pushed. If this is true, that doesn't necessarily mean that the
155    ///     current line has an incorrect indent (this can be determined
156    ///     only at the end of the current line).
157    /// </remarks>
158    public bool NeedsReindent
159    {
160      get
161      {
162        // return true if it's the first column of the line and it has an indent
163        if (Location.Column == 1)
164        {
165          return ThisLineIndent.Length > 0;
166        }
167
168        // ignore incorrect indentations when there's only ws on this line
169        if (isLineStart)
170        {
171          return false;
172        }
173
174        return ThisLineIndent != CurrentIndent.ToString();
175      }
176    }
177
178    /// <inheritdoc />
179    public int Offset
180    {
181      get
182      {
183        return offset;
184      }
185    }
186
187    /// <inheritdoc />
188    public TextLocation Location
189    {
190      get
191      {
192        return new TextLocation(line, column);
193      }
194    }
195
196    /// <inheritdoc />
197    public bool EnableCustomIndentLevels
198    {
199      get;
200      set;
201    }
202
203    #endregion
204
205    #region Fields
206
207    /// <summary>
208    ///    Represents the number of pushed chars.
209    /// </summary>
210    internal int offset = 0;
211
212    /// <summary>
213    ///    The current line number.
214    /// </summary>
215    internal int line = 1;
216
217    /// <summary>
218    ///    The current column number.
219    /// </summary>
220    /// <remarks>
221    ///    One char can take up multiple columns (e.g. \t).
222    /// </remarks>
223    internal int column = 1;
224
225    /// <summary>
226    ///    True if <see cref="char.IsWhiteSpace(char)"/> is true for all
227    ///    chars at the current line.
228    /// </summary>
229    internal bool isLineStart = true;
230
231    /// <summary>
232    ///    True if <see cref="isLineStart"/> was true before the current
233    ///    <see cref="wordToken"/>.
234    /// </summary>
235    internal bool isLineStartBeforeWordToken = true;
236
237    /// <summary>
238    ///    Current char that's being pushed.
239    /// </summary>
240    internal char currentChar = '\0';
241
242    /// <summary>
243    ///    Last non-whitespace char that has been pushed.
244    /// </summary>
245    internal char previousChar = '\0';
246
247    /// <summary>
248    ///    Previous new line char
249    /// </summary>
250    internal char previousNewline = '\0';
251
252    /// <summary>
253    ///    Current indent level on this line.
254    /// </summary>
255    internal StringBuilder currentIndent = new StringBuilder();
256
257    /// <summary>
258    ///     True if this line began in <see cref="VerbatimStringState"/>.
259    /// </summary>
260    internal bool lineBeganInsideVerbatimString = false;
261
262    /// <summary>
263    ///     True if this line began in <see cref="MultiLineCommentState"/>.
264    /// </summary>
265    internal bool lineBeganInsideMultiLineComment = false;
266
267    #endregion
268
269    #region Constructors
270
271    /// <summary>
272    ///     Creates a new CSharpIndentEngine instance.
273    /// </summary>
274    /// <param name="document">
275    ///     An instance of <see cref="IDocument"/> which is being parsed.
276    /// </param>
277    /// <param name="formattingOptions">
278    ///     C# formatting options.
279    /// </param>
280    /// <param name="textEditorOptions">
281    ///    Text editor options for indentation.
282    /// </param>
283    public CSharpIndentEngine(IDocument document, TextEditorOptions textEditorOptions, CSharpFormattingOptions formattingOptions)
284    {
285      this.formattingOptions = formattingOptions;
286      this.textEditorOptions = textEditorOptions;
287      this.document = document;
288
289      this.currentState = new GlobalBodyState(this);
290
291      this.conditionalSymbols = new HashSet<string>();
292      this.customConditionalSymbols = new HashSet<string>();
293      this.wordToken = new StringBuilder();
294      this.previousKeyword = string.Empty;
295      this.newLineChar = textEditorOptions.EolMarker[0];
296    }
297
298    /// <summary>
299    ///     Creates a new CSharpIndentEngine instance from the given prototype.
300    /// </summary>
301    /// <param name="prototype">
302    ///     An CSharpIndentEngine instance.
303    /// </param>
304    public CSharpIndentEngine(CSharpIndentEngine prototype)
305    {
306      this.formattingOptions = prototype.formattingOptions;
307      this.textEditorOptions = prototype.textEditorOptions;
308      this.document = prototype.document;
309
310      this.newLineChar = prototype.newLineChar;
311      this.currentState = prototype.currentState.Clone(this);
312      this.conditionalSymbols = new HashSet<string>(prototype.conditionalSymbols);
313      this.customConditionalSymbols = new HashSet<string>(prototype.customConditionalSymbols);
314
315      this.wordToken = new StringBuilder(prototype.wordToken.ToString());
316      this.previousKeyword = string.Copy(prototype.previousKeyword);
317
318      this.offset = prototype.offset;
319      this.line = prototype.line;
320      this.column = prototype.column;
321      this.isLineStart = prototype.isLineStart;
322      this.isLineStartBeforeWordToken = prototype.isLineStartBeforeWordToken;
323      this.currentChar = prototype.currentChar;
324      this.previousChar = prototype.previousChar;
325      this.previousNewline = prototype.previousNewline;
326      this.currentIndent = new StringBuilder(prototype.CurrentIndent.ToString());
327      this.lineBeganInsideMultiLineComment = prototype.lineBeganInsideMultiLineComment;
328      this.lineBeganInsideVerbatimString = prototype.lineBeganInsideVerbatimString;
329      this.ifDirectiveEvalResults = prototype.ifDirectiveEvalResults.Clone();
330      this.ifDirectiveIndents = prototype.ifDirectiveIndents.Clone();
331
332      this.EnableCustomIndentLevels = prototype.EnableCustomIndentLevels;
333    }
334
335    #endregion
336
337    #region IClonable
338
339    object ICloneable.Clone()
340    {
341      return Clone();
342    }
343
344    /// <inheritdoc />
345    IDocumentIndentEngine IDocumentIndentEngine.Clone()
346    {
347      return Clone();
348    }
349
350    public IStateMachineIndentEngine Clone()
351    {
352      return new CSharpIndentEngine(this);
353    }
354
355    #endregion
356
357    #region Methods
358
359    /// <inheritdoc />
360    public void Push(char ch)
361    {
362      // append this char to the wordbuf if it can form a valid keyword, otherwise check
363      // if the last sequence of chars form a valid keyword and reset the wordbuf.
364      if ((wordToken.Length == 0 ? char.IsLetter(ch) : char.IsLetterOrDigit(ch)) || ch == '_')
365      {
366        wordToken.Append(ch);
367      }
368      else if (wordToken.Length > 0)
369      {
370        currentState.CheckKeyword(wordToken.ToString());
371        previousKeyword = wordToken.ToString();
372        wordToken.Length = 0;
373        isLineStartBeforeWordToken = false;
374      }
375
376      var isNewLine = NewLine.IsNewLine(ch);
377      if (!isNewLine) {
378        currentState.Push(currentChar = ch);
379        offset++;
380        previousNewline = '\0';
381        // ignore whitespace and newline chars
382        var isWhitespace = currentChar == ' ' || currentChar == '\t';
383        if (!isWhitespace)
384        {
385          previousChar = currentChar;
386          isLineStart = false;
387        }
388
389        if (isLineStart)
390        {
391          currentIndent.Append(ch);
392        }
393
394        if (ch == '\t')
395        {
396          var nextTabStop = (column - 1 + textEditorOptions.IndentSize) / textEditorOptions.IndentSize;
397          column = 1 + nextTabStop * textEditorOptions.IndentSize;
398        }
399        else
400        {
401          column++;
402        }
403      } else {
404        if (ch == NewLine.LF && previousNewline == NewLine.CR) {
405          offset++;
406          return;
407        }
408        currentState.Push(currentChar = newLineChar);
409        offset++;
410
411        previousNewline = ch;
412        // there can be more than one chars that determine the EOL,
413        // the engine uses only one of them defined with newLineChar
414        if (currentChar != newLineChar)
415        {
416          return;
417        }
418        currentIndent.Length = 0;
419        isLineStart = true;
420        isLineStartBeforeWordToken = true;
421        column = 1;
422        line++;
423
424        lineBeganInsideMultiLineComment = IsInsideMultiLineComment;
425        lineBeganInsideVerbatimString = IsInsideVerbatimString;
426      }
427    }
428
429    /// <inheritdoc />
430    public void Reset()
431    {
432      currentState = new GlobalBodyState(this);
433      conditionalSymbols.Clear();
434      ifDirectiveEvalResults.Clear();
435      ifDirectiveIndents.Clear();
436
437      offset = 0;
438      line = 1;
439      column = 1;
440      isLineStart = true;
441      currentChar = '\0';
442      previousChar = '\0';
443      currentIndent.Length = 0;
444      lineBeganInsideMultiLineComment = false;
445      lineBeganInsideVerbatimString = false;
446    }
447
448    /// <inheritdoc />
449    public void Update(int offset)
450    {
451      if (Offset > offset)
452      {
453        Reset();
454      }
455
456      while (Offset < offset)
457      {
458        Push(Document.GetCharAt(Offset));
459      }
460    }
461
462    /// <summary>
463    /// Defines the conditional symbol.
464    /// </summary>
465    /// <param name="defineSymbol">The symbol to define.</param>
466    public void DefineSymbol(string defineSymbol)
467    {
468      if (!customConditionalSymbols.Contains(defineSymbol))
469        customConditionalSymbols.Add(defineSymbol);
470    }
471
472    /// <summary>
473    /// Removes the symbol.
474    /// </summary>
475    /// <param name="undefineSymbol">The symbol to undefine.</param>
476    public void RemoveSymbol(string undefineSymbol)
477    {
478      if (customConditionalSymbols.Contains(undefineSymbol))
479        customConditionalSymbols.Remove(undefineSymbol);
480    }
481    #endregion
482
483    #region IStateMachineIndentEngine
484
485    public bool IsInsidePreprocessorDirective
486    {
487      get { return currentState is PreProcessorState; }
488    }
489
490    public bool IsInsidePreprocessorComment
491    {
492      get { return currentState is PreProcessorCommentState; }
493    }
494
495    public bool IsInsideStringLiteral
496    {
497      get { return currentState is StringLiteralState; }
498    }
499
500    public bool IsInsideVerbatimString
501    {
502      get { return currentState is VerbatimStringState; }
503    }
504
505    public bool IsInsideCharacter
506    {
507      get { return currentState is CharacterState; }
508    }
509
510    public bool IsInsideString
511    {
512      get { return IsInsideStringLiteral || IsInsideVerbatimString || IsInsideCharacter; }
513    }
514
515    public bool IsInsideLineComment
516    {
517      get { return currentState is LineCommentState; }
518    }
519
520    public bool IsInsideMultiLineComment
521    {
522      get { return currentState is MultiLineCommentState; }
523    }
524
525    public bool IsInsideDocLineComment
526    {
527      get { return currentState is DocCommentState; }
528    }
529
530    public bool IsInsideComment
531    {
532      get { return IsInsideLineComment || IsInsideMultiLineComment || IsInsideDocLineComment; }
533    }
534
535    public bool IsInsideOrdinaryComment
536    {
537      get { return IsInsideLineComment || IsInsideMultiLineComment; }
538    }
539
540    public bool IsInsideOrdinaryCommentOrString
541    {
542      get { return IsInsideOrdinaryComment || IsInsideString; }
543    }
544
545    public bool LineBeganInsideVerbatimString
546    {
547      get { return lineBeganInsideVerbatimString; }
548    }
549
550    public bool LineBeganInsideMultiLineComment
551    {
552      get { return lineBeganInsideMultiLineComment; }
553    }
554
555    #endregion
556  }
557}
Note: See TracBrowser for help on using the repository browser.