/// <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; } } }
/// <summary> /// Appends statements inside scope that follows control block /// such as if() { } or a single statement that follows /// scope-less as in 'if() stmt' conditional. /// </summary> private void AppendStatementsInScope(string keyword) { // May or may not have curly braces if (_tokens.CurrentToken.TokenType == RTokenType.OpenCurlyBrace) { // Regular { } scope so just handle it normally AppendScopeContent(stopAtLineBreak: false); if (keyword == "if" && _tokens.CurrentToken.IsKeywordText(_textProvider, "else")) { // if (FALSE) { // x <- 1 // } else // i.e. keep 'else' at the same line. if (!_options.BracesOnNewLine && _tokens.PreviousToken.TokenType == RTokenType.CloseCurlyBrace) { while (_tb.LastCharacter.IsLineBreak()) { // Undo line break _tb.Remove(_tb.Length - 1, 1); } _tb.AppendPreformattedText(" "); } AppendKeyword(); } return; } // No curly braces: single statement block bool foundSameLineElse = false; if (keyword == "if") { // Per R language spec: // <quote> // The 'else' clause is optional. The statement if(any(x <= 0)) x <- x[x <= 0] // is valid. When the if statement is not in a block the else, if present, must // appear on the same line as the end of statement after if. Otherwise the new line // at the end of statement completes the if and yields a syntactically // complete statement that is evaluated. // </quote> // // So we have to be very careful here. If 'if' is not scoped then we need // to check if 'else' is on the same line and then keep the line as is // i.e. format but not break into multiple lines. On the other hand, // if there is no 'else' then we can insert break after the 'if(...)' foundSameLineElse = HasSameLineElse(); if (foundSameLineElse) { SuppressLineBreakCount++; AppendScopeContent(stopAtLineBreak: true); SuppressLineBreakCount--; return; } } if (SuppressLineBreakCount > 0) { AppendScopeContent(stopAtLineBreak: true); return; } bool addLineBreak = true; // Special case: preserve like break between 'else' and 'if' // if user put it there 'else if' remains on one line // if user didn't then add line break between them. if (IsInArguments() || (keyword == "else" && _tokens.CurrentToken.IsKeywordText(_textProvider, "if") && !_tokens.IsLineBreakAfter(_textProvider, _tokens.Position - 1))) { addLineBreak = false; } if (addLineBreak) { _tb.SoftLineBreak(); _tb.NewIndentLevel(); // This scope-less 'if' so we have to stop at the end // of the line as in // if (TRUE) // x <- 1 // else { // } AppendScopeContent(stopAtLineBreak: true, stopAtElse: true); // if (TRUE) // repeat { // } // else // _tb.CloseIndentLevel(); } else { _tb.AppendSpace(); } }
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(); } } } }