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;
}