private bool FormatRangeExact(ITextRange formatRange) { var snapshot = _editorBuffer.CurrentSnapshot; var spanText = snapshot.GetText(formatRange); var trimmedSpanText = spanText.Trim(); var formatter = new RFormatter(_settings.FormatOptions); var formattedText = formatter.Format(trimmedSpanText); formattedText = formattedText.Trim(); // There may be inserted line breaks after { // Extract existing indent before applying changes. Existing indent // may be used by the smart indenter for function argument lists. var startLine = snapshot.GetLineFromPosition(formatRange.Start); var originalIndentSizeInSpaces = IndentBuilder.TextIndentInSpaces(startLine.GetText(), _settings.IndentSize); var selectionTracker = GetSelectionTracker(formatRange); var tokenizer = new RTokenizer(); var oldTokens = tokenizer.Tokenize(spanText); var newTokens = tokenizer.Tokenize(formattedText); _changeHandler.ApplyChange( _editorBuffer, new TextStream(spanText), new TextStream(formattedText), oldTokens, newTokens, formatRange, Microsoft.R.Editor.Resources.AutoFormat, selectionTracker, () => { var ast = UpdateAst(_editorBuffer); // Apply indentation IndentLines(_editorBuffer, new TextRange(formatRange.Start, formattedText.Length), ast, originalIndentSizeInSpaces); }); return(true); }
/// <summary> /// Appends indentation to each line so formatted text appears properly /// indented inside the host document (script block in HTML page). /// </summary> private void IndentLines(IEditorBuffer textBuffer, ITextRange range, AstRoot ast, int originalIndentSizeInSpaces) { var snapshot = textBuffer.CurrentSnapshot; var firstLine = snapshot.GetLineFromPosition(range.Start); var lastLine = snapshot.GetLineFromPosition(range.End); var document = textBuffer.GetEditorDocument <IREditorDocument>(); for (var i = firstLine.LineNumber; i <= lastLine.LineNumber; i++) { // Snapshot is updated after each insertion so do not cache var line = textBuffer.CurrentSnapshot.GetLineFromLineNumber(i); var indent = SmartIndenter.GetSmartIndent(line, _settings, ast, originalIndentSizeInSpaces, formatting: true); if (indent > 0 && line.Length > 0 && line.Start >= range.Start) { // Check current indentation and correct for the difference int currentIndentSize = IndentBuilder.TextIndentInSpaces(line.GetText(), _settings.TabSize); indent = Math.Max(0, indent - currentIndentSize); if (indent > 0) { var indentString = IndentBuilder.GetIndentString(indent, _settings.IndentType, _settings.TabSize); textBuffer.Insert(line.Start, indentString); if (document == null) { // Typically this is a test scenario only. In the real editor // instance document is available and automatically updates AST // when whitespace inserted, not no manual update is necessary. ast.ReflectTextChange(line.Start, 0, indentString.Length, textBuffer.CurrentSnapshot); } } } } }
public RFormatter(RFormatOptions options) { _options = options; _indentBuilder = new IndentBuilder(_options.IndentType, _options.IndentSize, _options.TabSize); _tb = new TextBuilder(_indentBuilder); _formattingScopes.Push(new FormattingScope(_indentBuilder)); }
/// <summary> /// Appends indentation to each line so formatted text appears properly /// indented inside the host document (script block in HTML page). /// </summary> private static void IndentLines(ITextView textView, ITextBuffer textBuffer, ITextRange range, AstRoot ast, RFormatOptions options, int originalIndentSizeInSpaces) { ITextSnapshot snapshot = textBuffer.CurrentSnapshot; ITextSnapshotLine firstLine = snapshot.GetLineFromPosition(range.Start); ITextSnapshotLine lastLine = snapshot.GetLineFromPosition(range.End); IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer); for (int i = firstLine.LineNumber; i <= lastLine.LineNumber; i++) { // Snapshot is updated after each insertion so do not cache ITextSnapshotLine line = textBuffer.CurrentSnapshot.GetLineFromLineNumber(i); int indent = SmartIndenter.GetSmartIndent(line, ast, originalIndentSizeInSpaces); if (indent > 0 && line.Length > 0 && line.Start >= range.Start) { // Check current indentation and correct for the difference int currentIndentSize = line.Length - line.GetText().TrimStart().Length; indent = Math.Max(0, indent - currentIndentSize); if (indent > 0) { string indentString = IndentBuilder.GetIndentString(indent, options.IndentType, options.TabSize); textBuffer.Insert(line.Start, indentString); if (document == null) { // Typically this is a test scenario only. In the real editor // instance document is available and automatically updates AST // when whitespace inserted, not no manual update is necessary. ast.ReflectTextChange(line.Start, 0, indentString.Length); } } } } }
/// <summary> /// Iterates over tokens in the current scope and constructs formatted text /// </summary> /// <param name="stopAtLineBreak"> /// If true scope stops at the nearest line break. Used when formatting /// simple conditional statements like 'if() stmt1 else stmt1' that /// should not be broken into multiple lines. /// </param> private void AppendScopeContent(bool stopAtLineBreak, bool stopAtElse = false) { while (!_tokens.IsEndOfStream()) { if (ShouldAppendTextBeforeToken()) { AppendTextBeforeToken(); // If scope is simple (no curly braces) then stopAtLineBreak is true. // If there is a line break before start of the simple scope content // as in 'if(true)\nx<-1' we need to add indent if (stopAtLineBreak && _tb.IsAtNewLine) { _tb.AppendPreformattedText(IndentBuilder.GetIndentString(_options.IndentSize, _options.IndentType, _options.TabSize)); } } AppendNextToken(); if (stopAtLineBreak && _tokens.IsLineBreakAfter(_textProvider, _tokens.Position)) { break; } if (_tokens.PreviousToken.TokenType == RTokenType.CloseCurlyBrace) { break; } if (stopAtElse && _tokens.CurrentToken.IsKeywordText(_textProvider, "else")) { break; } } }
public static int OuterIndentSizeFromLine(ITextSnapshotLine line, RFormatOptions options) { string lineText = line.GetText(); string leadingWhitespace = lineText.Substring(0, lineText.Length - lineText.TrimStart().Length); return(IndentBuilder.TextIndentInSpaces(leadingWhitespace, options.TabSize)); }
public static int InnerIndentSizeFromLine(IEditorLine line, RFormatOptions options) { var lineText = line.GetText(); var leadingWhitespace = lineText.Substring(0, lineText.Length - lineText.TrimStart().Length); var indentbuilder = new IndentBuilder(options.IndentType, options.IndentSize, options.TabSize); return(IndentBuilder.TextIndentInSpaces(leadingWhitespace + indentbuilder.SingleIndentString, options.TabSize)); }
private void AppendTextBeforeToken() { int start = _tokens.Position > 0 ? _tokens.PreviousToken.End : 0; int end = _tokens.IsEndOfStream() ? _textProvider.Length : _tokens.CurrentToken.Start; string text = _textProvider.GetText(TextRange.FromBounds(start, end)); if (string.IsNullOrWhiteSpace(text)) { // Append any user-entered whitespace. We preserve // line breaks but trim unnecessary spaces such as // on empty lines. We must, however, preserve // user indentation in long argument lists and // in expresions split into multiple lines. // We preserve user indentation of last token was // open brace, square bracket, comma or an operator bool preserveUserIndent = false; if (_tokens.Position > 0) { switch (_tokens.PreviousToken.TokenType) { case RTokenType.OpenBrace: case RTokenType.OpenSquareBracket: case RTokenType.OpenDoubleSquareBracket: case RTokenType.Comma: case RTokenType.Operator: preserveUserIndent = true; break; } } _tb.CopyPrecedingLineBreaks(_textProvider, end); if (preserveUserIndent) { int lastLineBreakIndex = text.LastIndexOfAny(CharExtensions.LineBreakChars); if (lastLineBreakIndex >= 0) { text = text.Substring(lastLineBreakIndex + 1); int textIndentInSpaces = IndentBuilder.TextIndentInSpaces(text, _options.TabSize); text = IndentBuilder.GetIndentString(textIndentInSpaces, _indentBuilder.IndentType, _indentBuilder.TabSize); _tb.AppendPreformattedText(text); } } else { _tb.SoftIndent(); } } else { // If there is unrecognized text between tokens, append it verbatim _tb.AppendPreformattedText(text); } }
private int CurrentLineIndent(TextBuilder tb) { for (int i = tb.Length - 1; i >= 0; i--) { if (CharExtensions.IsLineBreak(tb.Text[i])) { return(IndentBuilder.TextIndentInSpaces(tb.Text.Substring(i + 1), _options.TabSize)); } } return(0); }
/// <summary> /// Given opening curly brace tries to find keyword that is associated /// with the scope and calculate indentation based on the keyword line. /// </summary> public int GetOpenCurlyBraceIndentSize(RToken openCurlyBraceToken, TextBuilder tb, RFormatOptions options) { Debug.Assert(openCurlyBraceToken.TokenType == RTokenType.OpenCurlyBrace); int keywordPosition = -1; if (_bracetoKeywordPositionMap.TryGetValue(openCurlyBraceToken, out keywordPosition)) { return(IndentBuilder.GetLineIndentSize(tb, keywordPosition, options.TabSize)); } return(0); }
private static int?IndentFromFunctionCall(IFunction fc, IEditorLine prevLine, IEditorLine currentLine, IREditorSettings settings, bool formatting, int originalIndentSizeInSpaces) { var snapshot = currentLine.Snapshot; if (fc?.Arguments == null || fc.OpenBrace == null) { // No arguments or somehow open brace is missing return(null); } if (fc.CloseBrace == null || fc.CloseBrace.End > prevLine.End) { // We only want to indent here if position is in arguments and not in the function scope. if (currentLine.Start >= fc.OpenBrace.End && !(fc.CloseBrace != null && currentLine.Start >= fc.CloseBrace.End)) { if (originalIndentSizeInSpaces >= 0) { // Preserve user indentation return(originalIndentSizeInSpaces); } // Indent one level deeper from the function definition line. var fcLine = snapshot.GetLineFromPosition(fc.Start); if (fcLine.LineNumber == prevLine.LineNumber) { // Determine current base indent of the line (leading whitespace) var fcIndentSize = IndentBuilder.TextIndentInSpaces(fcLine.GetText(), settings.TabSize); if (fc.CloseBrace == null || fc.CloseBrace.End >= (formatting ? currentLine.Start : currentLine.End)) { // Depending on options indent a) one level deeper or b) by first argument or c) by opening brace + 1 if (settings.SmartIndentByArgument) { var indent = GetIndentFromArguments(fc, prevLine, settings); fcIndentSize = indent.HasValue ? IndentBuilder.GetIndentString(indent.Value, settings.IndentType, settings.TabSize).Length : fcIndentSize + settings.IndentSize; } else { // Default indent is one level deeper fcIndentSize += settings.IndentSize; } } return(fcIndentSize); } // If all fails, indent based on the previous line return(GetBlockIndent(currentLine, settings)); } } return(null); }
private static int?GetIndentFromArguments(IFunction fc, IEditorLine prevLine, IREditorSettings settings) { // Fetch first argument on the previous line or first artument of the function // x < function(a, // | // x < function(a, // b, c // | var snapshot = prevLine.Snapshot; var offset = 0; // If previous line contains start of the function call, format it // so whitespace is correct and we can determine proper indentation // based on the argument or the opening brace if (prevLine.Contains(fc.Start)) { var start = snapshot.GetLineFromPosition(fc.Start).Start; var end = snapshot.GetLineFromPosition(fc.End).End; var fcText = snapshot.GetText(TextRange.FromBounds(start, end)); // Remember current indentation since formatter will remove it var currentIndent = IndentBuilder.TextIndentInSpaces(fcText, settings.TabSize); var formattedLineText = new RFormatter().Format(fcText); // Restore leading indent formattedLineText = IndentBuilder.GetIndentString(currentIndent, settings.IndentType, settings.TabSize) + formattedLineText; var ast = RParser.Parse(formattedLineText); var newFc = ast.FindFirstElement(n => n is IFunction) as IFunction; if (newFc != null) { offset = prevLine.Start; } fc = newFc; } if (fc != null) { var arg = fc.Arguments.FirstOrDefault(a => !(a is StubArgument) && prevLine.Contains(a.Start + offset)); if (arg != null) { var argPosition = arg.Start + offset; return(argPosition - snapshot.GetLineFromPosition(argPosition).Start); } var bracePosition = fc.OpenBrace.Start + offset; return(bracePosition - snapshot.GetLineFromPosition(bracePosition).Start + 1); } return(null); }
public static bool FormatRangeExact(ITextView textView, ITextBuffer textBuffer, ITextRange formatRange, RFormatOptions options) { ITextSnapshot snapshot = textBuffer.CurrentSnapshot; Span spanToFormat = new Span(formatRange.Start, formatRange.Length); string spanText = snapshot.GetText(spanToFormat.Start, spanToFormat.Length); string trimmedSpanText = spanText.Trim(); RFormatter formatter = new RFormatter(options); string formattedText = formatter.Format(trimmedSpanText); formattedText = formattedText.Trim(); // There may be inserted line breaks after { // Apply formatted text without indentation. We then will update the parse tree // so we can calculate proper line indents from the AST via the smart indenter. if (!spanText.Equals(formattedText, StringComparison.Ordinal)) { // Extract existing indent before applying changes. Existing indent // may be used by the smart indenter for function argument lists. var startLine = snapshot.GetLineFromPosition(spanToFormat.Start); var originalIndentSizeInSpaces = IndentBuilder.TextIndentInSpaces(startLine.GetText(), options.IndentSize); var selectionTracker = new RSelectionTracker(textView, textBuffer, formatRange); RTokenizer tokenizer = new RTokenizer(); IReadOnlyTextRangeCollection <RToken> oldTokens = tokenizer.Tokenize(spanText); IReadOnlyTextRangeCollection <RToken> newTokens = tokenizer.Tokenize(formattedText); IncrementalTextChangeApplication.ApplyChangeByTokens( textBuffer, new TextStream(spanText), new TextStream(formattedText), oldTokens, newTokens, formatRange, Resources.AutoFormat, selectionTracker, () => { var ast = UpdateAst(textBuffer); // Apply indentation IndentLines(textView, textBuffer, new TextRange(formatRange.Start, formattedText.Length), ast, options, originalIndentSizeInSpaces); }); return(true); } return(false); }
private bool FormatRangeExact(IEditorView editorView, IEditorBuffer editorBuffer, ITextRange formatRange) { var snapshot = editorBuffer.CurrentSnapshot; var spanText = snapshot.GetText(formatRange); var trimmedSpanText = spanText.Trim(); var formatter = new RFormatter(_settings.FormatOptions); var formattedText = formatter.Format(trimmedSpanText); formattedText = formattedText.Trim(); // There may be inserted line breaks after { // Apply formatted text without indentation. We then will update the parse tree // so we can calculate proper line indents from the AST via the smart indenter. if (!spanText.Equals(formattedText, StringComparison.Ordinal)) { // Extract existing indent before applying changes. Existing indent // may be used by the smart indenter for function argument lists. var startLine = snapshot.GetLineFromPosition(formatRange.Start); var originalIndentSizeInSpaces = IndentBuilder.TextIndentInSpaces(startLine.GetText(), _settings.IndentSize); var selectionTracker = GetSelectionTracker(editorView, editorBuffer, formatRange); var tokenizer = new RTokenizer(); var oldTokens = tokenizer.Tokenize(spanText); var newTokens = tokenizer.Tokenize(formattedText); var wsChangeHandler = _services.GetService <IIncrementalWhitespaceChangeHandler>(); wsChangeHandler.ApplyChange( editorBuffer, new TextStream(spanText), new TextStream(formattedText), oldTokens, newTokens, formatRange, Microsoft.R.Editor.Resources.AutoFormat, selectionTracker, () => { var ast = UpdateAst(editorBuffer); // Apply indentation IndentLines(editorBuffer, new TextRange(formatRange.Start, formattedText.Length), ast, originalIndentSizeInSpaces); }); return(true); } return(false); }
private string GetUserIndentString(ITextProvider textProvider, int position, RFormatOptions options) { for (int i = position - 1; i >= 0; i--) { char ch = textProvider[i]; if (!char.IsWhiteSpace(ch)) { break; } if (ch.IsLineBreak()) { string userIndentString = textProvider.GetText(TextRange.FromBounds(i + 1, position)); int indentSize = IndentBuilder.TextIndentInSpaces(userIndentString, options.TabSize); return(IndentBuilder.GetIndentString(indentSize, options.IndentType, options.IndentSize)); } } return(string.Empty); }
public RFormatter(RFormatOptions options) { _options = options; _indentBuilder = new IndentBuilder(_options.IndentType, _options.IndentSize, _options.TabSize); _tb = new TextBuilder(_indentBuilder); }
/// <summary> /// Appends indentation to each line so formatted text appears properly /// indented inside the host document (script block in HTML page). /// </summary> private static string IndentLines(ITextBuffer textBuffer, int rangeStartPosition, AstRoot ast, string formattedText, RFormatOptions options, int scopeStatementPosition, bool respectUserIndent = true) { ITextSnapshotLine firstLine = textBuffer.CurrentSnapshot.GetLineFromPosition(rangeStartPosition); string firstLineText = firstLine.GetText(); int baseIndentInSpaces; if (scopeStatementPosition >= 0) { // If parent statement position is provided, use it to determine indentation ITextSnapshotLine statementLine = textBuffer.CurrentSnapshot.GetLineFromPosition(scopeStatementPosition); baseIndentInSpaces = SmartIndenter.GetSmartIndent(statementLine, ast); } else if (respectUserIndent && RespectUserIndent(textBuffer, ast, rangeStartPosition)) { // Determine indent from fist line in multiline constructs // such as when function argument list spans multiple lines baseIndentInSpaces = IndentBuilder.TextIndentInSpaces(firstLineText, options.TabSize); } else { baseIndentInSpaces = SmartIndenter.GetSmartIndent(firstLine, ast); } // There are three major cases with range formatting: // 1. Formatting of a scope when } closes. // 2. Formatting of a single line on Enter or ; // 3. Formatting of a user-selected range. // // Indentation in (1) is relatively easy since complete scope is known. // (2) Is the most difficult is to figure out proper indent of a single }. // Normally we get statementPosition of the statement that define the scope // (3) Theoretically may end up with odd indents but users rarely intentionally // select strange ranges string indentString = IndentBuilder.GetIndentString(baseIndentInSpaces, options.IndentType, options.TabSize); var sb = new StringBuilder(); IList <string> lines = TextHelper.SplitTextIntoLines(formattedText); for (int i = 0; i < lines.Count; i++) { string lineText = lines[i]; if (i == 0 && lineText.Trim() == "{") { if (options.BracesOnNewLine && !LineBreakBeforePosition(textBuffer, rangeStartPosition)) { sb.Append("\r\n"); } if (scopeStatementPosition < 0 || options.BracesOnNewLine) { sb.Append(indentString); } sb.Append('{'); if (i < lines.Count - 1) { sb.Append("\r\n"); } continue; } if (i == lines.Count - 1 && lineText.Trim() == "}") { sb.Append(indentString); sb.Append('}'); break; } // Leave empty lines alone if (!string.IsNullOrWhiteSpace(lineText)) { sb.Append(indentString); } sb.Append(lineText); if (i < lines.Count - 1) { sb.Append("\r\n"); } } return(sb.ToString()); }
public FormattingScope(IndentBuilder indentBuilder) { _indentBuilder = indentBuilder; }
/// <summary> /// Determines level of indentation in the line from AST and surrounding context. /// Called when user hits ENTER and editor needs to know level of indentation in /// the new line as well as when code is being auto-formatted and range formatter /// needs to know how to indent freshly formatted lines of code. /// </summary> /// <param name="line">Line to find the indent for</param> /// <param name="ast">Optional AST</param> /// <param name="formatting"> /// Indicates if current call is from formatter or /// from the core editor for indentation when user typed Enter. /// </param> /// <returns>Level of indent in spaces</returns> public static int GetSmartIndent(ITextSnapshotLine line, AstRoot ast = null, int originalIndentSizeInSpaces = -1, bool formatting = false) { ITextBuffer textBuffer = line.Snapshot.TextBuffer; ITextSnapshotLine prevLine = null; if (line.LineNumber == 0) { // Nothing to indent at the first line return(0); } if (ast == null) { IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer); if (document == null) { return(0); } ast = document.EditorTree.AstRoot; } // The challenge here is to find scope to base the indent on. // The scope may or may not have braces and may or may not be closed. // Current line is normally empty so we use previous line data assuming // it is not empty. If previous line is empty, we do not look up // to the nearest non-empty. This is the same as C# behavior. // So we need to locate nearest node that implements IAstNodeWithScope // or the scope (implemeting IScope) itself is scope is just '{ }'. // First try based on the previous line. We will try start of the line // like in 'if(...)' { in order to locate 'if' and then, if nothing is found, // try end of the line as in 'x <- function(...) {' prevLine = line.Snapshot.GetLineFromLineNumber(line.LineNumber - 1); string prevLineText = prevLine.GetText(); if (prevLineText.Trim().Equals("else", StringComparison.Ordinal)) { // Quick short circuit for new 'else' since it is not in the ASt yet. return(GetBlockIndent(line) + REditorSettings.IndentSize); } // First, let's see if we are in a function argument list and then indent based on // the opening brace position. This needs to be done before looking for scopes // since function definition is a scope-defining statement. // Examples: 'call(a,\n<Enter>' or 'x <- function(a,<Enter>' if (prevLine.Length > 0) { var fc1 = ast.GetNodeOfTypeFromPosition <IFunction>(prevLine.End - 1); var fc2 = ast.GetNodeOfTypeFromPosition <IFunction>(line.Start); // Pick narrowest function. This happens when function definition appears // inside the argument list such as list(a = function(...)). var fc = fc2 ?? fc1; if (fc != null && fc.Arguments != null && fc.OpenBrace != null) { if (fc.CloseBrace == null || fc.CloseBrace.End > prevLine.End) { // We only want to indent here if position is in arguments and not in the function scope. if (line.Start >= fc.OpenBrace.End && !(fc.CloseBrace != null && line.Start >= fc.CloseBrace.End)) { if (originalIndentSizeInSpaces < 0) { // Indent one level deeper from the function definition line. var fcLine = line.Snapshot.GetLineFromPosition(fc.Start); if (fcLine.LineNumber == prevLine.LineNumber) { int fcIndentSize = IndentBuilder.TextIndentInSpaces(fcLine.GetText(), REditorSettings.TabSize); if (fc.CloseBrace == null || fc.CloseBrace.End >= (formatting ? line.Start : line.End)) { fcIndentSize += REditorSettings.IndentSize; } return(fcIndentSize); } else { return(GetBlockIndent(line)); } } else { return(originalIndentSizeInSpaces); } } } } } // Candidate position #1 is first non-whitespace character // in the the previous line int startOfNoWsOnPreviousLine = prevLine.Start + (prevLineText.Length - prevLineText.TrimStart().Length) + 1; // Try current new line so in case of 'if () { } else { | }' we find // the 'else' which defines the scope and not the parent 'if'. var scopeStatement1 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(line.Start); if (scopeStatement1 == null) { // If not found, try previous line that may define the indent scopeStatement1 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(startOfNoWsOnPreviousLine); if (scopeStatement1 == null) { // Line start position works for typical scope-defining statements like if() or while() // but it won't find function definition in 'x <- function(a) {' // Try end of the line instead var lastNonWsOnPreviousLine = Math.Max(0, prevLineText.TrimEnd().Length - 1); scopeStatement1 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(lastNonWsOnPreviousLine); // Verify that line we are asked to provide the smart indent for is actually inside // this scope since we could technically find end of x <- function(a) {} // when we went up one line. if (scopeStatement1?.Scope?.CloseCurlyBrace != null && !scopeStatement1.Contains(line.Start)) { scopeStatement1 = null; // line is outside of this scope. } } } IAstNodeWithScope scopeStatement; var scopeStatement2 = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(startOfNoWsOnPreviousLine); // Locate standalone scope which is not a statement, if any var scope = ast.GetNodeOfTypeFromPosition <IScope>(prevLine.End); // Pick the narrowest item // so in case of // x <- function() { // if(...)<Enter> // } // we will use the 'if' and not the function definition var scopeCandidates = new List <IAstNode>() { scopeStatement1, scopeStatement2, scope }; var smallestScope = scopeCandidates.OrderBy(x => x != null ? x.Length : Int32.MaxValue).FirstOrDefault(); scopeStatement = smallestScope as IAstNodeWithScope; scope = smallestScope as IScope; // If IScope is a scope defined by the parent statement, use // the parent statement so in // x <- function(...) { // | // } // the indent in scope is defined by the function and not by the opening { if (scope != null) { var parentStarement = scope.Parent as IAstNodeWithScope; if (parentStarement != null && parentStarement.Scope == scope) { scopeStatement = parentStarement; scope = null; } } if (scopeStatement != null) { if (scopeStatement.Scope == null) { // There is nothing after statement that allows simple scope // such as in 'if(...)EOF' return(GetBlockIndent(line) + REditorSettings.IndentSize); } if (scopeStatement.Scope is SimpleScope) { // There is statement with a simple scope above such as 'if' without { }. // We need to check if the line that is being formatted is part of this scope. if (line.Start < scopeStatement.Scope.End) { // Indent line one level deeper that the statement return(GetBlockIndent(line) + REditorSettings.IndentSize); } // Line is not part of the scope, provide regular indent return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions)); } // Check if line is the last line in a real scope (i.e. scope with { }) and only consists // of the closing }, it should be indented at the outer indent so closing scope aligns with // the beginning of the statement. if (scopeStatement.Scope.CloseCurlyBrace != null) { int endOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.CloseCurlyBrace.Start); if (endOfScopeLine <= line.LineNumber) { return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions)); } } if (scopeStatement.Scope.OpenCurlyBrace != null && REditorSettings.FormatOptions.BracesOnNewLine) { int startOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.OpenCurlyBrace.Start); if (startOfScopeLine == line.LineNumber) { return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions)); } } // We are inside a scope so provide inner indent return(InnerIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions)); } // Try locate the scope itself, if any if (scope != null && scope.OpenCurlyBrace != null) { if (scope.CloseCurlyBrace != null) { int endOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scope.CloseCurlyBrace.Start); if (endOfScopeLine == line.LineNumber) { return(OuterIndentSizeFromNode(textBuffer, scope, REditorSettings.FormatOptions)); } } return(InnerIndentSizeFromNode(textBuffer, scope, REditorSettings.FormatOptions)); } return(0); }
private void CloseFormattingScope() { Debug.Assert(_tokens.CurrentToken.TokenType == RTokenType.CloseCurlyBrace); // if it is not single line scope like { } add linke break before } if (!SingleLineScope) { _tb.SoftLineBreak(); } var leadingSpace = SingleLineScope && _tokens.PreviousToken.TokenType != RTokenType.CloseCurlyBrace; // Close formatting scope and remember if it was based on the user-supplied indent. _formattingScopes.TryCloseScope(_tokens.Position); // Closing curly indentation is defined by the line that either holds the opening curly brace // or the line that holds keyword that defines the expression that the curly belongs to. // Examples: // // x <- // function(a) { // } // // x <- function(a) { // } // if (!SingleLineScope) { int lineIndentSize = _braceHandler.GetCloseCurlyBraceIndentSize(_tokens.CurrentToken, _tb, _options); if (lineIndentSize > 0) { var indentString = IndentBuilder.GetIndentString(lineIndentSize, _options.IndentType, _options.TabSize); _tb.AppendPreformattedText(indentString); leadingSpace = false; } else { _tb.SoftIndent(); } } AppendToken(leadingSpace: leadingSpace, trailingSpace: false); bool singleLineScopeJustClosed = false; if (_tokens.CurrentToken.Start >= _singleLineScopeEnd) { _singleLineScopeEnd = -1; singleLineScopeJustClosed = true; } if (_formattingScopes.SuppressLineBreakCount == 0 && !_tokens.IsEndOfStream()) { // We insert line break after } unless // a) Next token is comma (scope is in the argument list) or // b) Next token is a closing brace (last parameter in a function or indexer) or // c) Next token is by 'else' (so 'else' does not get separated from 'if') or // d) We are in a single-line scope sequence like if() {{ }} if (!KeepCurlyAndElseTogether()) { if (singleLineScopeJustClosed && !IsClosingToken(_tokens.CurrentToken) && !_braceHandler.IsInArguments()) { SoftLineBreak(); } } } }
private void AppendTextBeforeToken(bool preserveUserIndent = false) { int start = _tokens.Position > 0 ? _tokens.PreviousToken.End : 0; int end = _tokens.IsEndOfStream() ? _textProvider.Length : _tokens.CurrentToken.Start; string text = _textProvider.GetText(TextRange.FromBounds(start, end)); bool whitespaceOnly = string.IsNullOrWhiteSpace(text); if (!preserveUserIndent && whitespaceOnly) { // Append any user-entered whitespace. We preserve line breaks but trim // unnecessary spaces such as on empty lines. We must, however, preserve // user indentation in long argument lists and in expresions split // into multiple lines. // We preserve user indentation of last token was // open brace, square bracket, comma or an operator if (_tokens.Position > 0) { switch (_tokens.PreviousToken.TokenType) { case RTokenType.OpenBrace: case RTokenType.OpenSquareBracket: case RTokenType.OpenDoubleSquareBracket: case RTokenType.Comma: case RTokenType.Operator: preserveUserIndent = true; break; case RTokenType.Comment: // Preserve user indent in argument lists preserveUserIndent = _braceHandler.IsInArguments(); break; default: // If expression between scope start and the current point is incomplete, // (i.e. we are in a middle of multiline expression) we want to preserve // user-supplied indentation. preserveUserIndent = !_expressionHelper.IsCompleteExpression(_tokens.Position); break; } // Also preserve indent before the tokens below. This matter in long lists // of arguments in functions or indexers and also before comments. if (!preserveUserIndent) { switch (_tokens.CurrentToken.TokenType) { case RTokenType.CloseBrace: case RTokenType.CloseSquareBracket: case RTokenType.CloseDoubleSquareBracket: case RTokenType.Comma: case RTokenType.Operator: case RTokenType.Comment: preserveUserIndent = true; break; } } } // Preserve line breaks user has entered. _tb.CopyPrecedingLineBreaks(_textProvider, end); if (preserveUserIndent) { // Construct indentation line based on the size of the user indent // but using tabs or spaces per formatting options. int lastLineBreakIndex = text.LastIndexOfAny(CharExtensions.LineBreakChars); if (lastLineBreakIndex >= 0) { text = text.Substring(lastLineBreakIndex + 1); int textIndentInSpaces = IndentBuilder.TextIndentInSpaces(text, _options.TabSize); text = IndentBuilder.GetIndentString(textIndentInSpaces, _indentBuilder.IndentType, _indentBuilder.TabSize); _tb.AppendPreformattedText(text); } } else { _tb.SoftIndent(); } } else { // If there is unrecognized text between tokens, append it verbatim. _tb.AppendPreformattedText(text); } }
private static bool Fits( PrintCommand nextCommand, Stack <PrintCommand> remainingCommands, int remainingWidth, PrinterOptions printerOptions, bool mustBeFlat = false ) { var returnFalseIfMoreStringsFound = false; var newCommands = new Stack <PrintCommand>(); newCommands.Push(nextCommand); void Push(Doc doc, PrintMode printMode, Indent indent) { newCommands.Push(new PrintCommand(indent, printMode, doc)); } var output = new StringBuilder(); for (var x = 0; x < remainingCommands.Count || newCommands.Count > 0;) { if (remainingWidth < 0) { return(false); } PrintCommand command; if (newCommands.Count > 0) { command = newCommands.Pop(); } else { command = remainingCommands.ElementAt(x); x++; } var(currentIndent, currentMode, currentDoc) = command; if (currentDoc is StringDoc stringDoc) { if (stringDoc.Value == null) { continue; } if (returnFalseIfMoreStringsFound) { return(false); } output.Append(stringDoc.Value); remainingWidth -= GetStringWidth(stringDoc.Value); } else if (currentDoc != Doc.Null) { switch (currentDoc) { case LeadingComment: case TrailingComment: if (output.Length > 0) { returnFalseIfMoreStringsFound = true; } break; case Concat concat: for (var i = concat.Contents.Count - 1; i >= 0; i--) { Push(concat.Contents[i], currentMode, currentIndent); } break; case IndentDoc indent: Push( indent.Contents, currentMode, IndentBuilder.Make(currentIndent, printerOptions) ); break; case Trim: remainingWidth += TrimOutput(output); break; case Group group: if (mustBeFlat && group.Break) { return(false); } var groupMode = group.Break ? PrintMode.MODE_BREAK : currentMode; Push(group.Contents, groupMode, currentIndent); if (group.GroupId != null) { groupModeMap ![group.GroupId] = groupMode;