public static IFunctionDefinition FindFunctionDefinition(this AstRoot ast, int position, out IVariable v)
        {
            v = null;
            var exp = ast.GetNodeOfTypeFromPosition <IExpressionStatement>(position);

            return(exp?.GetVariableOrFunctionDefinition(out v));
        }
예제 #2
0
        public IEnumerable <SuggestedActionSet> GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return(Enumerable.Empty <SuggestedActionSet>());
            }

            List <SuggestedActionSet> actionSets = new List <SuggestedActionSet>();
            int           caretPosition          = _textView.Caret.Position.BufferPosition;
            SnapshotPoint?bufferPoint            = _textView.MapDownToR(caretPosition);

            if (bufferPoint.HasValue)
            {
                AstRoot ast            = _document?.EditorTree.AstRoot;
                int     bufferPosition = bufferPoint.Value.Position;
                _lastNode = ast?.GetNodeOfTypeFromPosition <TokenNode>(bufferPosition);
                if (_lastNode != null)
                {
                    foreach (IRSuggestedActionProvider actionProvider in _suggestedActionProviders)
                    {
                        if (actionProvider.HasSuggestedActions(_textView, _textBuffer, bufferPosition))
                        {
                            IEnumerable <ISuggestedAction> actions = actionProvider.GetSuggestedActions(_textView, _textBuffer, bufferPosition);
                            Span applicableSpan          = new Span(_lastNode.Start, _lastNode.Length);
                            SuggestedActionSet actionSet = new SuggestedActionSet(actions, applicableToSpan: applicableSpan);
                            actionSets.Add(actionSet);
                        }
                    }
                }
            }
            return(actionSets);
        }
예제 #3
0
 /// <summary>
 /// Called after character is typed. Gives language-specific completion
 /// controller has a chance to dismiss or initiate completion and paramenter
 /// help sessions depending on the current context.
 /// </summary>
 public override void OnPostTypeChar(char typedCharacter)
 {
     if (typedCharacter == '(' || typedCharacter == ',')
     {
         // Check if caret moved into a different functions such as when
         // user types a sequence of nested function calls. If so,
         // dismiss current signature session and start a new one.
         if (!SignatureHelper.IsSameSignatureContext(TextView, _textBuffer, SignatureBroker))
         {
             DismissAllSessions();
             TriggerSignatureHelp();
         }
     }
     else if (HasActiveSignatureSession(TextView) && typedCharacter == ')')
     {
         // Typing closing ) closes signature and completion sessions.
         DismissAllSessions();
         // However, when user types closing brace is an expression inside
         // function argument like in x = y * (z + 1) we need to re-trigger
         // signature session
         AstRoot      ast = REditorDocument.FromTextBuffer(TextView.TextBuffer).EditorTree.AstRoot;
         FunctionCall f   = ast.GetNodeOfTypeFromPosition <FunctionCall>(TextView.Caret.Position.BufferPosition);
         if (f != null)
         {
             TriggerSignatureHelp();
         }
     }
     else if (HasActiveSignatureSession(TextView) && typedCharacter == '\n')
     {
         // Upon ENTER we need to dismiss all sessions and re-trigger
         // signature help. Triggering signature help outside of
         // a function definition or call is a no-op so it is safe.
         DismissAllSessions();
         TriggerSignatureHelp();
     }
     else if (this.HasActiveCompletionSession)
     {
         if (typedCharacter == '\'' || typedCharacter == '\"')
         {
             // First handle completion of a string.
             base.OnPostTypeChar(typedCharacter);
             // Then re-trigger completion.
             DismissAllSessions();
             ShowCompletion(autoShownCompletion: true);
             return;
         }
         else
         {
             // Backspace does not dismiss completion. Characters that may be an identifier
             // name do not dismiss completion either allowing correction of typing.
             if (typedCharacter != '\b' && !RTokenizer.IsIdentifierCharacter(typedCharacter))
             {
                 DismissCompletionSession();
             }
         }
     }
     base.OnPostTypeChar(typedCharacter);
 }
예제 #4
0
        public static IFunctionInfo GetUserFunctionInfo(this AstRoot ast, string functionName, int position)
        {
            var scope = ast.GetNodeOfTypeFromPosition <IScope>(position);
            var v     = scope?.FindFunctionDefinitionByName(functionName, position);
            var rf    = v?.Value as RFunction;
            var fd    = rf?.Value as IFunctionDefinition;

            return(fd?.MakeFunctionInfo(functionName));
        }
예제 #5
0
        public static IAstNode GetIndentDefiningNode(AstRoot ast, int position)
        {
            IScope scope = ast.GetNodeOfTypeFromPosition <IScope>(position);
            // Scope indentation is defined by its parent statement.
            IAstNodeWithScope parentStatement = scope.Parent as IAstNodeWithScope;

            if (parentStatement != null && parentStatement.Scope == scope)
            {
                return(parentStatement);
            }
            return(scope);
        }
예제 #6
0
        private static bool GetFunction(AstRoot astRoot, ref int position, out FunctionCall functionCall, out Variable functionVariable)
        {
            // Note that we do not want just the deepest call since in abc(def())
            // when position is over 'def' we actually want signature help for 'abc'
            // while simple depth search will find 'def'.
            functionVariable = null;
            int p = position;

            functionCall = astRoot.GetSpecificNodeFromPosition <FunctionCall>(p, (x) => {
                var fc = x as FunctionCall;
                // Take into account incompleted argument lists line in 'func(a|'
                return(fc != null && (fc.Arguments.Contains(p) || (fc.CloseBrace == null && fc.Arguments.End == p)));
            });

            if (functionCall == null && position > 0)
            {
                // Retry in case caret is at the very end of function signature
                // that does not have final close brace yet.
                functionCall = astRoot.GetNodeOfTypeFromPosition <FunctionCall>(position - 1, includeEnd: true);
                if (functionCall != null)
                {
                    // But if signature does have closing brace and caret
                    // is beyond it, we are really otuside of the signature.
                    if (functionCall.CloseBrace != null && position >= functionCall.CloseBrace.End)
                    {
                        return(false);
                    }

                    if (position > functionCall.SignatureEnd)
                    {
                        position = functionCall.SignatureEnd;
                    }
                }
            }

            if (functionCall != null && functionCall.Children.Count > 0)
            {
                functionVariable = functionCall.Children[0] as Variable;
                if (functionVariable == null)
                {
                    // Might be in a namespace
                    var op = functionCall.Children[0] as IOperator;
                    if (op != null && op.OperatorType == OperatorType.Namespace)
                    {
                        functionVariable = op.RightOperand as Variable;
                    }
                }
                return(functionVariable != null);
            }

            return(false);
        }
예제 #7
0
        public static void FormatScope(ITextView textView, ITextBuffer textBuffer, int position, bool indentCaret)
        {
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                document.EditorTree.EnsureTreeReady();

                int           baseIndentPosition = -1;
                ITextSnapshot snapshot           = textBuffer.CurrentSnapshot;
                AstRoot       ast   = document.EditorTree.AstRoot;
                IScope        scope = ast.GetNodeOfTypeFromPosition <IScope>(position);

                // Scope indentation is defined by its parent statement.
                IAstNodeWithScope parentStatement = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(position);
                if (parentStatement != null && parentStatement.Scope == scope)
                {
                    ITextSnapshotLine baseLine = snapshot.GetLineFromPosition(parentStatement.Start);
                    baseIndentPosition = baseLine.Start;
                }
                FormatScope(textView, textBuffer, ast, scope, baseIndentPosition, indentCaret);
            }
        }
예제 #8
0
        /// <summary>
        /// Determines if position is in the argument name. Typically used to
        ///     a) suppress general intellisense when typing function arguments
        ///         in a function/ definition such as in 'x &lt;- function(a|'
        ///     b) determine if completion list should contain argumet names
        ///        when user types inside function call.
        /// </summary>
        internal static bool IsInFunctionArgumentName <T>(AstRoot ast, int position) where T : class, IFunction
        {
            T funcDef = ast.GetNodeOfTypeFromPosition <T>(position);

            if (funcDef == null || funcDef.OpenBrace == null || funcDef.Arguments == null)
            {
                return(false);
            }

            if (position < funcDef.OpenBrace.End || position >= funcDef.SignatureEnd)
            {
                return(false);
            }

            int start = funcDef.OpenBrace.End;
            int end   = funcDef.SignatureEnd;

            if (funcDef.Arguments.Count == 0 && position >= start && position <= end)
            {
                return(true);
            }

            for (int i = 0; i < funcDef.Arguments.Count; i++)
            {
                CommaSeparatedItem csi = funcDef.Arguments[i];
                NamedArgument      na  = csi as NamedArgument;

                if (position < csi.Start)
                {
                    break;
                }

                end = csi.End;
                if (position >= start && position <= end)
                {
                    if (na == null)
                    {
                        return(true);
                    }

                    if (position <= na.EqualsSign.Start)
                    {
                        return(true);
                    }
                }
            }

            return(false);
        }
예제 #9
0
        /// <summary>
        /// Formats node at position
        /// </summary>
        public static void FormatNode <T>(ITextView textView, ITextBuffer textBuffer, int position) where T : class
        {
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
                AstRoot       ast      = document.EditorTree.AstRoot;
                IAstNode      node     = ast.GetNodeOfTypeFromPosition <T>(position) as IAstNode;
                if (node != null)
                {
                    UndoableFormatRange(textView, textBuffer, ast, node);
                }
            }
        }
예제 #10
0
 private IKeywordScopeStatement GetFormatScope(int position, AstRoot ast)
 {
     try {
         var snapshot   = EditorBuffer.CurrentSnapshot;
         var lineNumber = snapshot.GetLineNumberFromPosition(position);
         var line       = snapshot.GetLineFromLineNumber(lineNumber);
         var lineText   = line.GetText();
         if (lineText.TrimEnd().EndsWithOrdinal("}"))
         {
             var scopeStatement = ast.GetNodeOfTypeFromPosition <IKeywordScopeStatement>(position);
             return(scopeStatement);
         }
     } catch (ArgumentException) { }
     return(null);
 }
예제 #11
0
        /// <summary>
        /// Determines if a given position is in area where user
        /// specified indentation must be respected. For example,
        /// in multi-line list of function arguments,
        /// multi-line expressions and so on.
        /// </summary>
        private static bool RespectUserIndent(ITextBuffer textBuffer, AstRoot ast, int position)
        {
            // Look up nearest expression
            IAstNode node = ast.GetNodeOfTypeFromPosition <Expression>(position);

            if (IsMultilineNode(textBuffer, node))
            {
                return(true);
            }

            node = ast.GetNodeOfTypeFromPosition <FunctionDefinition>(position);
            if (IsMultilineNode(textBuffer, node))
            {
                return(true);
            }

            node = ast.GetNodeOfTypeFromPosition <FunctionCall>(position);
            if (IsMultilineNode(textBuffer, node))
            {
                return(true);
            }

            return(false);
        }
예제 #12
0
        private static bool DetermineFunction(AstRoot ast, int position, out IFunctionDefinition fd, out IVariable v, out FunctionCall fc)
        {
            fd = ast.FindFunctionDefinition(position, out v);
            fc = null;

            if (fd == null)
            {
                fc = ast.GetNodeOfTypeFromPosition <FunctionCall>(position);
                var name = fc.GetFunctionName();
                if (string.IsNullOrEmpty(name) || !_s4FunctionNames.Contains(name))
                {
                    fc = null;
                }
            }
            return(fd != null || fc != null);
        }
예제 #13
0
        /// <summary>
        /// Formats statement that the caret is at
        /// </summary>
        public static void FormatCurrentStatement(ITextView textView, ITextBuffer textBuffer, bool limitAtCaret = false, int caretOffset = 0)
        {
            SnapshotPoint?caretPoint = REditorDocument.MapCaretPositionFromView(textView);

            if (!caretPoint.HasValue)
            {
                return;
            }
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
                AstRoot       ast      = document.EditorTree.AstRoot;
                IAstNode      node     = ast.GetNodeOfTypeFromPosition <IStatement>(Math.Max(0, caretPoint.Value + caretOffset)) as IAstNode;
                FormatNode(textView, textBuffer, node, limit: caretPoint.Value);
            }
        }
예제 #14
0
        public static bool CanShowFileCompletion(AstRoot ast, int position, out string directory)
        {
            var node = ast.GetNodeOfTypeFromPosition <TokenNode>(position);

            directory = null;
            if (node != null && node.Token.TokenType == RTokenType.String)
            {
                var text = node.Root.TextProvider.GetText(node);
                // Bring file/folder completion when either string is empty or ends with /
                // assuming that / specifies directory where files are.
                if (text.Length == 2 || text.EndsWith("/\"", StringComparison.Ordinal) || text.EndsWith("/\'", StringComparison.Ordinal))
                {
                    directory = text;
                    return(true);
                }
            }
            return(false);
        }
예제 #15
0
        /// <summary>
        /// Formats statement that the caret is at
        /// </summary>
        public static void FormatCurrentStatement(ITextView textView, ITextBuffer textBuffer)
        {
            SnapshotPoint?caretPoint = MapCaretToBuffer(textView, textBuffer);

            if (!caretPoint.HasValue)
            {
                return;
            }
            IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);

            if (document != null)
            {
                ITextSnapshot snapshot = textBuffer.CurrentSnapshot;
                AstRoot       ast      = document.EditorTree.AstRoot;
                IAstNode      node     = ast.GetNodeOfTypeFromPosition <IStatement>(Math.Max(0, caretPoint.Value - 1)) as IAstNode;
                FormatNode(textView, textBuffer, node);
            }
        }
예제 #16
0
        private static IKeywordScopeStatement GetFormatScope(ITextView textView, ITextBuffer textBuffer, AstRoot ast)
        {
            SnapshotPoint?caret = REditorDocument.MapCaretPositionFromView(textView);

            if (caret.HasValue)
            {
                try {
                    int lineNumber             = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(caret.Value.Position);
                    ITextSnapshotLine line     = textBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber);
                    string            lineText = line.GetText();
                    if (lineText.TrimEnd().EndsWith("}", StringComparison.Ordinal))
                    {
                        IKeywordScopeStatement scopeStatement = ast.GetNodeOfTypeFromPosition <IKeywordScopeStatement>(caret.Value);
                        return(scopeStatement);
                    }
                } catch (Exception) { }
            }
            return(null);
        }
예제 #17
0
        public static bool FormatRangeExact(ITextView textView, ITextBuffer textBuffer, ITextRange formatRange,
                                            AstRoot ast, RFormatOptions options,
                                            int scopeStatementPosition, bool respectUserIndent = true)
        {
            ITextSnapshot snapshot        = textBuffer.CurrentSnapshot;
            Span          spanToFormat    = new Span(formatRange.Start, formatRange.Length);
            string        spanText        = snapshot.GetText(spanToFormat.Start, spanToFormat.Length);
            string        trimmedSpanText = spanText.Trim();

            if (trimmedSpanText == "}")
            {
                // Locate opening { and its statement
                var scopeNode = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(spanToFormat.Start);
                if (scopeNode != null)
                {
                    scopeStatementPosition = scopeNode.Start;
                }
            }

            RFormatter formatter     = new RFormatter(options);
            string     formattedText = formatter.Format(trimmedSpanText);

            formattedText = formattedText.Trim(); // there may be inserted line breaks after {
            formattedText = IndentLines(textBuffer, spanToFormat.Start, ast, formattedText, options, scopeStatementPosition, respectUserIndent);

            if (!spanText.Equals(formattedText, StringComparison.Ordinal))
            {
                var        selectionTracker = new RSelectionTracker(textView, textBuffer);
                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);
                return(true);
            }

            return(false);
        }
예제 #18
0
        private static int IndentByIncompleteStatement(AstRoot ast, IEditorLine currentLine, IEditorLine prevLine, IAstNode scope, IREditorSettings settings)
        {
            // See if [ENTER] was hit in a middle of a statement. If it was hit in the first line of the statement,
            // indent one level deeper. Otherwise use block indent based on the previous line indent.
            //  x <-[ENTER]
            //    |
            //      1
            //
            //  x <-[ENTER]
            //    |
            //
            //  x <-
            //          a +[ENTER]
            //          |
            var snapshot  = prevLine.Snapshot;
            var statement = ast.GetNodeOfTypeFromPosition <IStatement>(prevLine.End);

            if (statement != null)
            {
                return(prevLine.Contains(statement.Start)
                        ? InnerIndentSizeFromNode(snapshot.EditorBuffer, scope, settings.FormatOptions)
                        : GetBlockIndent(currentLine, settings));
            }

            // The statement may be incomplete and hence expression parser
            // failed and hence there is no statement node in the AST.
            if (LineHasContinuation(prevLine))
            {
                // We need to determine if last line was the first in the statement
                // or is it itself a continuation.
                if (prevLine.LineNumber > 0)
                {
                    var prevPrevLine = snapshot.GetLineFromLineNumber(prevLine.LineNumber - 1);
                    if (LineHasContinuation(prevPrevLine))
                    {
                        return(GetBlockIndent(currentLine, settings));
                    }
                }
                return(InnerIndentSizeFromNode(snapshot.EditorBuffer, scope, settings.FormatOptions));
            }
            return(0);
        }
예제 #19
0
        private static IKeywordScopeStatement GetFormatScope(ITextView textView, ITextBuffer textBuffer, AstRoot ast)
        {
            var caret = textView.GetCaretPosition(textBuffer);

            if (caret.HasValue)
            {
                try {
                    var lineNumber = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(caret.Value.Position);
                    var line       = textBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNumber);
                    var lineText   = line.GetText();
                    if (lineText.TrimEnd().EndsWith("}", StringComparison.Ordinal))
                    {
                        var scopeStatement = ast.GetNodeOfTypeFromPosition <IKeywordScopeStatement>(caret.Value);
                        return(scopeStatement);
                    }
                } catch (Exception ex) when(!ex.IsCriticalException())
                {
                }
            }
            return(null);
        }
예제 #20
0
        /// <summary>
        /// Given position over function name retrieves name range and the function call.
        /// </summary>
        public static string GetFunctionName(this AstRoot ast, int position, out FunctionCall functionCall, out Variable functionVariable)
        {
            functionVariable = null;
            functionCall     = null;

            ast.GetPositionNode(position, out var node);
            if (node == null)
            {
                return(null);
            }

            // In abc(de|f(x)) first find inner function, then outer.
            if (node is TokenNode && node.Parent is FunctionCall)
            {
                functionCall = (FunctionCall)node.Parent;
            }
            else
            {
                functionCall = ast.GetNodeOfTypeFromPosition <FunctionCall>(position);
            }
            functionVariable = functionCall?.RightOperand as Variable;
            return(functionVariable?.Name);
        }
예제 #21
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);
        }
예제 #22
0
        public static int GetSmartIndent(ITextSnapshotLine line, AstRoot ast = null)
        {
            ITextBuffer textBuffer = line.Snapshot.TextBuffer;

            if (ast == null)
            {
                IREditorDocument document = REditorDocument.TryFromTextBuffer(textBuffer);
                if (document == null)
                {
                    return(0);
                }
                ast = document.EditorTree.AstRoot;
            }

            if (line.LineNumber > 0)
            {
                ITextSnapshotLine prevLine = line.Snapshot.GetLineFromLineNumber(line.LineNumber - 1);

                string prevLineText  = prevLine.GetText();
                int    nonWsPosition = prevLine.Start + (prevLineText.Length - prevLineText.TrimStart().Length) + 1;

                IAstNodeWithScope scopeStatement = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(nonWsPosition);
                if (scopeStatement != null)
                {
                    if (scopeStatement.Scope == null)
                    {
                        // No scope of any kind, use block indent
                        return(GetBlockIndent(line) + REditorSettings.IndentSize);
                    }

                    if (scopeStatement.Scope is SimpleScope)
                    {
                        // There is statement with a simple scope above. We need to check
                        // if the line that is being formatted is actually 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, hence regular indent
                        return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                    }

                    // Check if line is the last line in scope and if so,
                    // it should be indented at the outer indent
                    if (scopeStatement.Scope.CloseCurlyBrace != null)
                    {
                        int endOfScopeLine = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(scopeStatement.Scope.CloseCurlyBrace.Start);
                        if (endOfScopeLine == line.LineNumber)
                        {
                            return(OuterIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                        }
                    }

                    return(InnerIndentSizeFromNode(textBuffer, scopeStatement, REditorSettings.FormatOptions));
                }
            }

            IAstNodeWithScope node = ast.GetNodeOfTypeFromPosition <IAstNodeWithScope>(line.Start);

            if (node != null && node.Scope != null && node.Scope.OpenCurlyBrace != null)
            {
                return(InnerIndentSizeFromNode(textBuffer, node, REditorSettings.FormatOptions));
            }

            // See if we are in function arguments and indent at the function level
            var fc = ast.GetNodeOfTypeFromPosition <FunctionCall>(line.Start);

            if (fc != null && fc.Arguments != null && fc.OpenBrace != null && line.Start >= fc.OpenBrace.End)
            {
                return(InnerIndentSizeFromNode(textBuffer, fc, REditorSettings.FormatOptions));
            }

            // If nothing is found, default to block indent
            return(GetBlockIndent(line));
        }