Esempio n. 1
0
        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);
        }
Esempio n. 2
0
        /// <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);
                        }
                    }
                }
            }
        }
Esempio n. 3
0
 public RFormatter(RFormatOptions options)
 {
     _options       = options;
     _indentBuilder = new IndentBuilder(_options.IndentType, _options.IndentSize, _options.TabSize);
     _tb            = new TextBuilder(_indentBuilder);
     _formattingScopes.Push(new FormattingScope(_indentBuilder));
 }
Esempio n. 4
0
        /// <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);
                        }
                    }
                }
            }
        }
Esempio n. 5
0
        /// <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;
                }
            }
        }
Esempio n. 6
0
        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));
        }
Esempio n. 7
0
        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));
        }
Esempio n. 8
0
        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);
            }
        }
Esempio n. 9
0
 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);
 }
Esempio n. 10
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);
        }
Esempio n. 11
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);
        }
Esempio n. 12
0
        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);
        }
Esempio n. 13
0
        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);
        }
Esempio n. 14
0
        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);
        }
Esempio n. 15
0
        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);
        }
Esempio n. 16
0
 public RFormatter(RFormatOptions options)
 {
     _options       = options;
     _indentBuilder = new IndentBuilder(_options.IndentType, _options.IndentSize, _options.TabSize);
     _tb            = new TextBuilder(_indentBuilder);
 }
Esempio n. 17
0
        /// <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());
        }
Esempio n. 18
0
 public FormattingScope(IndentBuilder indentBuilder)
 {
     _indentBuilder = indentBuilder;
 }
Esempio n. 19
0
        /// <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);
        }
Esempio n. 20
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();
                    }
                }
            }
        }
Esempio n. 21
0
        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);
            }
        }
Esempio n. 22
0
        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;