private static int CalculateIndentation()

in Python/Product/PythonTools/PythonTools/Editor/Indent/AutoIndent.cs [50:178]


        private static int CalculateIndentation(
            string baseline,
            ITextSnapshotLine line,
            IEditorOptions options,
            PythonTextBufferInfo buffer
        ) {
            var snapshot = line.Snapshot;
            if (snapshot.TextBuffer != buffer.Buffer) {
                throw new ArgumentException("buffer mismatch");
            }

            int indentation = GetIndentation(baseline, options.GetTabSize());
            int tabSize = options.GetIndentSize();
            var tokens = buffer.GetTokens(line).ToList();

            while (tokens.Count > 0 && IsWhitespace(tokens[tokens.Count - 1].Category)) {
                tokens.RemoveAt(tokens.Count - 1);
            }

            if (tokens.Count == 0 || IsUnterminatedStringToken(tokens[tokens.Count - 1], snapshot)) {
                return indentation;
            }

            if (HasExplicitLineJoin(tokens, snapshot)) {
                // explicit line continuation, we indent 1 level for the continued line unless
                // we're already indented because of multiple line continuation characters.
                indentation = GetIndentation(line.GetText(), options.GetTabSize());
                var joinedLine = line.LineNumber - 1;
                if (joinedLine >= 0) {
                    var prevLineTokens = buffer.GetTokens(snapshot.GetLineFromLineNumber(joinedLine)).ToList();
                    if (prevLineTokens.Count == 0 || !HasExplicitLineJoin(prevLineTokens, snapshot)) {
                        indentation += tabSize;
                    }
                } else {
                    indentation += tabSize;
                }

                return indentation;
            }

            var tokenStack = new Stack<TrackingTokenInfo?>();
            tokenStack.Push(null);  // end with an implicit newline
            int endAtLine = -1, currentLine = tokens.Last().LineNumber;

            foreach (var t in buffer.GetTokensInReverseFromPoint(tokens.Last().ToSnapshotSpan(snapshot).Start)) {
                if (t.LineNumber == currentLine) {
                    tokenStack.Push(t);
                } else {
                    tokenStack.Push(null);
                }

                if (t.LineNumber == endAtLine) {
                    break;
                } else if (t.Category == TokenCategory.Keyword && PythonKeywords.IsOnlyStatementKeyword(t.GetText(snapshot), buffer.LanguageVersion)) {
                    endAtLine = t.LineNumber - 1;
                }

                if (t.LineNumber != currentLine) {
                    currentLine = t.LineNumber;
                    if (t.Category != TokenCategory.WhiteSpace && t.Category != TokenCategory.Comment && t.Category != TokenCategory.LineComment) {
                        tokenStack.Push(t);
                    }
                }
            }

            var indentStack = new Stack<LineInfo>();
            var current = LineInfo.Empty;

            while (tokenStack.Count > 0) {
                var t = tokenStack.Pop();
                if (t == null) {
                    current.NeedsUpdate = true;
                    continue;
                }

                var tline = new Lazy<string>(() => snapshot.GetLineFromLineNumber(t.Value.LineNumber).GetText());

                if (IsOpenGrouping(t.Value, snapshot)) {
                    indentStack.Push(current);
                    var next = tokenStack.Count > 0 ? tokenStack.Peek() : null;
                    if (next != null && next.Value.LineNumber == t.Value.LineNumber) {
                        // Put indent at same depth as grouping
                        current = new LineInfo {
                            Indentation = t.Value.ToSourceSpan().End.Column - 1
                        };
                    } else {
                        // Put indent at one indent deeper than this line
                        current = new LineInfo {
                            Indentation = GetIndentation(tline.Value, tabSize) + tabSize
                        };
                    }
                } else if (IsCloseGrouping(t.Value, snapshot)) {
                    if (indentStack.Count > 0) {
                        current = indentStack.Pop();
                    } else {
                        current.NeedsUpdate = true;
                    }
                } else if (IsExplicitLineJoin(t.Value, snapshot)) {
                    while (t != null && tokenStack.Count > 0) {
                        t = tokenStack.Pop();
                    }
                    if (!t.HasValue) {
                        continue;
                    }
                } else if (current.NeedsUpdate == true) {
                    current = new LineInfo {
                        Indentation = GetIndentation(tline.Value, tabSize)
                    };
                }

                if (ShouldDedentAfterKeyword(t.Value, snapshot)) {    // dedent after some statements
                    current.ShouldDedentAfter = true;
                }

                if (IsColon(t.Value, snapshot) &&       // indent after a colon
                    indentStack.Count == 0) {           // except in a grouping
                    current.ShouldIndentAfter = true;
                    // If the colon isn't at the end of the line, cancel it out.
                    // If the following is a ShouldDedentAfterKeyword, only one dedent will occur.
                    current.ShouldDedentAfter = (tokenStack.Count != 0 && tokenStack.Peek() != null);
                }
            }

            indentation = current.Indentation +
                (current.ShouldIndentAfter ? tabSize : 0) -
                (current.ShouldDedentAfter ? tabSize : 0);

            return indentation;
        }