private static int CalculateIndentation(string baseline, ITextSnapshotLine line, IEditorOptions options, IClassifier classifier, ITextView textView) { int indentation = GetIndentation(baseline, options.GetTabSize()); int tabSize = options.GetIndentSize(); var tokens = classifier.GetClassificationSpans(line.Extent); if (tokens.Count > 0 && !IsUnterminatedStringToken(tokens[tokens.Count - 1])) { int tokenIndex = tokens.Count - 1; while (tokenIndex >= 0 && (tokens[tokenIndex].ClassificationType.IsOfType(PredefinedClassificationTypeNames.Comment) || tokens[tokenIndex].ClassificationType.IsOfType(PredefinedClassificationTypeNames.WhiteSpace))) { tokenIndex--; } if (tokenIndex < 0) { return indentation; } if (ReverseExpressionParser.IsExplicitLineJoin(tokens[tokenIndex])) { // 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 = tokens[tokenIndex].Span.Start.GetContainingLine(); if (joinedLine.LineNumber > 0) { var prevLineSpans = classifier.GetClassificationSpans(tokens[tokenIndex].Span.Snapshot.GetLineFromLineNumber(joinedLine.LineNumber - 1).Extent); if (prevLineSpans.Count == 0 || !ReverseExpressionParser.IsExplicitLineJoin(prevLineSpans[prevLineSpans.Count - 1])) { indentation += tabSize; } } else { indentation += tabSize; } return indentation; } string sline = tokens[tokenIndex].Span.GetText(); var lastChar = sline.Length == 0 ? '\0' : sline[sline.Length - 1]; // use the expression parser to figure out if we're in a grouping... var spans = textView.BufferGraph.MapDownToFirstMatch( tokens[tokenIndex].Span, SpanTrackingMode.EdgePositive, PythonContentTypePrediciate ); if (spans.Count == 0) { return indentation; } var revParser = new ReverseExpressionParser( spans[0].Snapshot, spans[0].Snapshot.TextBuffer, spans[0].Snapshot.CreateTrackingSpan( spans[0].Span, SpanTrackingMode.EdgePositive ) ); var tokenStack = new System.Collections.Generic.Stack<ClassificationSpan>(); tokenStack.Push(null); // end with an implicit newline bool endAtNextNull = false; foreach (var token in revParser) { tokenStack.Push(token); if (token == null && endAtNextNull) { break; } else if (token != null && token.ClassificationType == revParser.Classifier.Provider.Keyword && PythonKeywords.IsOnlyStatementKeyword(token.Span.GetText())) { endAtNextNull = true; } } var indentStack = new System.Collections.Generic.Stack<LineInfo>(); var current = LineInfo.Empty; while (tokenStack.Count > 0) { var token = tokenStack.Pop(); if (token == null) { current.NeedsUpdate = true; } else if (token.IsOpenGrouping()) { indentStack.Push(current); var start = token.Span.Start; var line2 = start.GetContainingLine(); var next = tokenStack.Count > 0 ? tokenStack.Peek() : null; if (next != null && next.Span.End <= line2.End) { current = new LineInfo { Indentation = start.Position - line2.Start.Position + 1 }; } else { current = new LineInfo { Indentation = GetIndentation(line2.GetText(), tabSize) + tabSize }; } } else if (token.IsCloseGrouping()) { if (indentStack.Count > 0) { current = indentStack.Pop(); } else { current.NeedsUpdate = true; } } else if (ReverseExpressionParser.IsExplicitLineJoin(token)) { while (token != null && tokenStack.Count > 0) { token = tokenStack.Pop(); } } else if (current.NeedsUpdate == true) { var line2 = token.Span.Start.GetContainingLine(); current = new LineInfo { Indentation = GetIndentation(line2.GetText(), tabSize) }; } if (token != null && ShouldDedentAfterKeyword(token)) { // dedent after some statements current.ShouldDedentAfter = true; } if (token != null && token.Span.GetText() == ":" && // 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); } // Map indentation back to the view's text buffer. int offset = 0; var viewLineStart = textView.BufferGraph.MapUpToSnapshot(line.Start, PointTrackingMode.Positive, PositionAffinity.Successor, textView.TextSnapshot); if (viewLineStart.HasValue) { offset = viewLineStart.Value.Position - viewLineStart.Value.GetContainingLine().Start.Position; } return offset + indentation; }
void TextBuffer_Changed(object sender, TextContentChangedEventArgs e) { string filename = e.After.TextBuffer.GetFilePath(); if (_textView != null && _textView.Caret.Position.BufferPosition.Position >= 0 && _textView.Caret.Position.BufferPosition.Position <= e.After.Length && _ignoreNextChange != _textView.Caret.Position.BufferPosition.Position) { if (e.Changes.Count == 0 || e.Changes[0].Delta < 0 || string.IsNullOrWhiteSpace(e.Changes[0].NewText)) { return; } var line = e.After.GetLineFromPosition(_textView.Caret.Position.BufferPosition.Position); var currLineTokens = _classifier.GetClassificationSpans(line); var lineStr = line.GetText(); TokenKind alignWith = TokenKind.EndOfFile; bool indentAfterAlign = false; bool useContains = false; if (currLineTokens.Count > 0 && currLineTokens[0].ClassificationType.IsOfType(PredefinedClassificationTypeNames.Keyword)) { if (currLineTokens[0].Span.GetText().Equals("end", StringComparison.OrdinalIgnoreCase)) { if (currLineTokens.Count > 1) { var tok = Tokens.GetToken(currLineTokens[1].Span.GetText()); if (tok != null && AutoIndent.BlockKeywords.ContainsKey(tok.Kind)) { useContains = AutoIndent.BlockKeywordsContainsCheck.Contains(tok.Kind); alignWith = tok.Kind; } } } else { var tok = Tokens.GetToken(currLineTokens[0].Span.GetText()); if (tok != null && AutoIndent.SubBlockKeywords.ContainsKey(tok.Kind)) { alignWith = AutoIndent.SubBlockKeywords[tok.Kind].Item1; indentAfterAlign = AutoIndent.SubBlockKeywords[tok.Kind].Item2; } } } if (alignWith != TokenKind.EndOfFile) { var keyword = Tokens.TokenKinds[alignWith]; ITextSnapshotLine prevLine; int prevLineNo = line.LineNumber - 1; bool found = false; string prevLineStr = null; // find the line that corresponds int inNestedBlockCount = 0; IList <ClassificationSpan> lineTokens = null; while (prevLineNo >= 0) { prevLine = e.After.GetLineFromLineNumber(prevLineNo); prevLineStr = prevLine.GetText(); lineTokens = _classifier.GetClassificationSpans(prevLine); if (lineTokens.Count > 0 && lineTokens[0].ClassificationType.IsOfType(PredefinedClassificationTypeNames.Keyword)) { if (lineTokens[0].Span.GetText().Equals("end", StringComparison.OrdinalIgnoreCase) && lineTokens.Count > 1 && lineTokens[1].Span.GetText().Equals(keyword, StringComparison.OrdinalIgnoreCase)) { inNestedBlockCount++; } else if ((!useContains && lineTokens[0].Span.GetText().Equals(keyword, StringComparison.OrdinalIgnoreCase)) || (useContains && lineTokens.Any(x => x.Span.GetText().Equals(keyword, StringComparison.OrdinalIgnoreCase)))) { if (inNestedBlockCount > 0) { inNestedBlockCount--; } else { found = true; break; } } else if (!useContains && keyword.Equals("function", StringComparison.OrdinalIgnoreCase) && lineTokens.Count > 1 && lineTokens[1].Span.GetText().Equals(keyword, StringComparison.OrdinalIgnoreCase)) { found = true; break; } } prevLineNo--; } // get the line's indentation if (found) { int indentSize = _options.GetIndentSize(); int desiredIndentation = AutoIndent.GetIndentation(prevLineStr, indentSize); if (indentAfterAlign) { desiredIndentation += indentSize; } int currIndentation = AutoIndent.GetIndentation(lineStr, indentSize); if (desiredIndentation != currIndentation) { string replacement = null; if (desiredIndentation < currIndentation) { replacement = lineStr.Substring(currIndentation - desiredIndentation); } else { StringBuilder sb = new StringBuilder(); for (int i = 0; i < (desiredIndentation - currIndentation); i++) { sb.Append(' '); } sb.Append(lineStr); replacement = sb.ToString(); } _ignoreNextChange = _textView.Caret.Position.BufferPosition.Position; ITextEdit edit = _textView.TextBuffer.CreateEdit(); edit.Replace(new Span(line.Start, line.Length), replacement); edit.Apply(); return; } } } } _ignoreNextChange = -1; }
private static int CalculateIndentation(string baseline, ITextSnapshotLine line, IEditorOptions options, IClassifier classifier, ITextView textView) { int indentation = GetIndentation(baseline, options.GetTabSize()); int tabSize = options.GetIndentSize(); var tokens = classifier.GetClassificationSpans(line.Extent); if (tokens.Count > 0 && !IsUnterminatedStringToken(tokens[tokens.Count - 1])) { int tokenIndex = tokens.Count - 1; while (tokenIndex >= 0 && (tokens[tokenIndex].ClassificationType.IsOfType(PredefinedClassificationTypeNames.Comment) || tokens[tokenIndex].ClassificationType.IsOfType(PredefinedClassificationTypeNames.WhiteSpace))) { tokenIndex--; } if (tokenIndex < 0) { return(indentation); } if (Genero4glReverseParser.IsExplicitLineJoin(tokens[tokenIndex])) { // 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 = tokens[tokenIndex].Span.Start.GetContainingLine(); if (joinedLine.LineNumber > 0) { var prevLineSpans = classifier.GetClassificationSpans(tokens[tokenIndex].Span.Snapshot.GetLineFromLineNumber(joinedLine.LineNumber - 1).Extent); if (prevLineSpans.Count == 0 || !Genero4glReverseParser.IsExplicitLineJoin(prevLineSpans[prevLineSpans.Count - 1])) { indentation += tabSize; } } else { indentation += tabSize; } return(indentation); } string sline = tokens[tokenIndex].Span.GetText(); var lastChar = sline.Length == 0 ? '\0' : sline[sline.Length - 1]; // use the expression parser to figure out if we're in a grouping... var spans = textView.BufferGraph.MapDownToFirstMatch( tokens[tokenIndex].Span, SpanTrackingMode.EdgePositive, PythonContentTypePrediciate ); if (spans.Count == 0) { return(indentation); } var revParser = new Genero4glReverseParser( spans[0].Snapshot, spans[0].Snapshot.TextBuffer, spans[0].Snapshot.CreateTrackingSpan( spans[0].Span, SpanTrackingMode.EdgePositive ) ); var tokenStack = new List <ClassificationSpan>(); tokenStack.Insert(0, null); bool endAtNextNull = false; foreach (var token in revParser) { tokenStack.Insert(0, token); if (token == null && endAtNextNull) { break; } else if (token != null && token.ClassificationType == Genero4glClassifierProvider.Keyword) { var tok = Tokens.GetToken(token.Span.GetText()); if (tok != null && Genero4glAst.ValidStatementKeywords.Contains(tok.Kind)) { switch (tok.Kind) { // Handle any tokens that are valid statement keywords in the autocomplete context but not in the "statement start" context case TokenKind.EndKeyword: continue; default: endAtNextNull = true; break; } } } } var indentStack = new System.Collections.Generic.Stack <LineInfo>(); var current = LineInfo.Empty; List <CancelIndent> cancelIndent = null; int cancelIndentStartingAt = -1; TokenKind firstStatement = TokenKind.EndOfFile; TokenKind latestIndentChangeToken = TokenKind.EndOfFile; ClassificationSpan firstToken = null; for (int i = 0; i < tokenStack.Count; i++) { var token = tokenStack[i]; if (token != null && firstToken == null) { firstToken = token; } if (token == null) { current.NeedsUpdate = true; } else if (token.IsOpenGrouping()) { indentStack.Push(current); var start = token.Span.Start; var line2 = start.GetContainingLine(); current = new LineInfo { Indentation = start.Position - line2.Start.Position + 1 }; } else if (token.IsCloseGrouping()) { if (indentStack.Count > 0) { current = indentStack.Pop(); } else { current.NeedsUpdate = true; } } else if (Genero4glReverseParser.IsExplicitLineJoin(token)) { while (token != null && i + 1 < tokenStack.Count) { i++; token = tokenStack[i]; } } else if (current.NeedsUpdate == true) { var tok = Tokens.GetToken(token.Span.GetText()); if (tok == null || !Genero4glAst.ValidStatementKeywords.Contains(tok.Kind)) { current.NeedsUpdate = false; } else { switch (tok.Kind) { // Handle any tokens that are valid statement keywords in the autocomplete context but not in the "statement start" context case TokenKind.EndKeyword: if (firstStatement != TokenKind.EndOfFile) { current.NeedsUpdate = false; } else { latestIndentChangeToken = tok.Kind; } break; default: { if (firstStatement == TokenKind.EndOfFile) { firstStatement = tok.Kind; } var line2 = token.Span.Start.GetContainingLine(); current = new LineInfo { Indentation = GetIndentation(line2.GetText(), tabSize) }; break; } } } } if (token != null && current.ShouldIndentAfter && cancelIndent != null) { // Check to see if we have following tokens that would cancel the current indent. var tok = Tokens.GetToken(token.Span.GetText()); var tokenCategory = token.ClassificationType; bool allPast = true; bool cancel = false; foreach (var ci in cancelIndent) { if (ci.TokensAhead < (i - cancelIndentStartingAt)) { continue; } else { allPast = false; if (ci.TokensAhead == (i - cancelIndentStartingAt)) { if (ci.UseCategory && ci.CancelCategory != null) { cancel = tokenCategory == ci.CancelCategory; } else if (tok != null) { cancel = tok.Kind == ci.CancelToken; } if (cancel) { break; } } } } if (cancel) { current.ShouldIndentAfter = false; } if (cancel || allPast) { cancelIndent = null; cancelIndentStartingAt = -1; latestIndentChangeToken = TokenKind.EndOfFile; } } if (token != null && ShouldDedentAfterKeyword(token)) { // dedent after some statements current.ShouldDedentAfter = true; } TokenKind tempChangeToken; if (token != null && indentStack.Count == 0 && firstToken == token && ShouldIndentAfterKeyword(token, out tempChangeToken, out cancelIndent)) { // except in a grouping if (latestIndentChangeToken != TokenKind.EndKeyword) { current.ShouldIndentAfter = true; } latestIndentChangeToken = tempChangeToken; if (cancelIndent != null) { cancelIndentStartingAt = i; } } } if (tokenStack.Count > 2 && tokenStack[tokenStack.Count - 2] != null) { if (latestIndentChangeToken != TokenKind.EndOfFile && _customIndentingRules.ContainsKey(latestIndentChangeToken)) { var potentialIndent = _customIndentingRules[latestIndentChangeToken](tokenStack, tabSize); if (potentialIndent != 0) { return(potentialIndent); } } // see if we have specific alignment rules if (firstStatement != TokenKind.EndOfFile && _customIndentingRules.ContainsKey(firstStatement)) { var potentialIndent = _customIndentingRules[firstStatement](tokenStack, tabSize); if (potentialIndent != 0) { return(potentialIndent); } } } indentation = current.Indentation + (current.ShouldIndentAfter ? tabSize : 0) - (current.ShouldDedentAfter ? tabSize : 0); } return(indentation); }
private static int CalculateIndentation(string baseline, ITextSnapshotLine line, IEditorOptions options, IClassifier classifier, ITextView textView) { int indentation = GetIndentation(baseline, options.GetTabSize()); int tabSize = options.GetIndentSize(); var tokens = classifier.GetClassificationSpans(line.Extent); if (tokens.Count > 0 && !IsUnterminatedStringToken(tokens[tokens.Count - 1])) { int tokenIndex = tokens.Count - 1; while (tokenIndex >= 0 && (tokens[tokenIndex].ClassificationType.IsOfType(PredefinedClassificationTypeNames.Comment) || tokens[tokenIndex].ClassificationType.IsOfType(PredefinedClassificationTypeNames.WhiteSpace))) { tokenIndex--; } if (tokenIndex < 0) { return(indentation); } if (ReverseExpressionParser.IsExplicitLineJoin(tokens[tokenIndex])) { // 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 = tokens[tokenIndex].Span.Start.GetContainingLine(); if (joinedLine.LineNumber > 0) { var prevLineSpans = classifier.GetClassificationSpans(tokens[tokenIndex].Span.Snapshot.GetLineFromLineNumber(joinedLine.LineNumber - 1).Extent); if (prevLineSpans.Count == 0 || !ReverseExpressionParser.IsExplicitLineJoin(prevLineSpans[prevLineSpans.Count - 1])) { indentation += tabSize; } } else { indentation += tabSize; } return(indentation); } string sline = tokens[tokenIndex].Span.GetText(); var lastChar = sline.Length == 0 ? '\0' : sline[sline.Length - 1]; // use the expression parser to figure out if we're in a grouping... var spans = textView.BufferGraph.MapDownToFirstMatch( tokens[tokenIndex].Span, SpanTrackingMode.EdgePositive, PythonContentTypePrediciate ); if (spans.Count == 0) { return(indentation); } var revParser = new ReverseExpressionParser( spans[0].Snapshot, spans[0].Snapshot.TextBuffer, spans[0].Snapshot.CreateTrackingSpan( spans[0].Span, SpanTrackingMode.EdgePositive ) ); var tokenStack = new System.Collections.Generic.Stack <ClassificationSpan>(); tokenStack.Push(null); // end with an implicit newline bool endAtNextNull = false; foreach (var token in revParser) { tokenStack.Push(token); if (token == null && endAtNextNull) { break; } else if (token != null && token.ClassificationType == revParser.Classifier.Provider.Keyword && PythonKeywords.IsOnlyStatementKeyword(token.Span.GetText())) { endAtNextNull = true; } } var indentStack = new System.Collections.Generic.Stack <LineInfo>(); var current = LineInfo.Empty; while (tokenStack.Count > 0) { var token = tokenStack.Pop(); if (token == null) { current.NeedsUpdate = true; } else if (token.IsOpenGrouping()) { indentStack.Push(current); var start = token.Span.Start; var line2 = start.GetContainingLine(); var next = tokenStack.Count > 0 ? tokenStack.Peek() : null; if (next != null && next.Span.End <= line2.End) { current = new LineInfo { Indentation = start.Position - line2.Start.Position + 1 }; } else { current = new LineInfo { Indentation = GetIndentation(line2.GetText(), tabSize) + tabSize }; } } else if (token.IsCloseGrouping()) { if (indentStack.Count > 0) { current = indentStack.Pop(); } else { current.NeedsUpdate = true; } } else if (ReverseExpressionParser.IsExplicitLineJoin(token)) { while (token != null && tokenStack.Count > 0) { token = tokenStack.Pop(); } } else if (current.NeedsUpdate == true) { var line2 = token.Span.Start.GetContainingLine(); current = new LineInfo { Indentation = GetIndentation(line2.GetText(), tabSize) }; } if (token != null && ShouldDedentAfterKeyword(token)) // dedent after some statements { current.ShouldDedentAfter = true; } if (token != null && token.Span.GetText() == ":" && // 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); }
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); }