public SnapshotSpan? GetExpressionRange()

in Python/Product/PythonTools/PythonTools/Intellisense/ReverseExpressionParser.cs [178:403]


        public SnapshotSpan? GetExpressionRange(int nesting, out int paramIndex, out SnapshotPoint? sigStart, out string lastKeywordArg, out bool isParameterName, bool forCompletion = true) {
            SnapshotSpan? start = null;
            paramIndex = 0;
            sigStart = null;
            bool nestingChanged = false, lastTokenWasCommaOrOperator = true, lastTokenWasKeywordArgAssignment = false;
            int otherNesting = 0;
            bool isSigHelp = nesting != 0;
            isParameterName = false;
            lastKeywordArg = null;

            ClassificationSpan lastToken = null;
            // Walks backwards over all the lines
            var enumerator = ReverseClassificationSpanEnumerator(Classifier, _span.GetSpan(_snapshot).End);
            if (enumerator.MoveNext()) {
                if (enumerator.Current != null && enumerator.Current.ClassificationType == this.Classifier.Provider.StringLiteral) {
                    return enumerator.Current.Span;
                }

                lastToken = enumerator.Current;
                while (ShouldSkipAsLastToken(lastToken, forCompletion) && enumerator.MoveNext()) {
                    // skip trailing new line if the user is hovering at the end of the line
                    if (lastToken == null && (nesting + otherNesting == 0)) {
                        // new line out of a grouping...
                        return _span.GetSpan(_snapshot);
                    }
                    lastToken = enumerator.Current;
                }

                int currentParamAtLastColon = -1;   // used to track the current param index at this last colon, before we hit a lambda.
                SnapshotSpan? startAtLastToken = null;
                // Walk backwards over the tokens in the current line
                do {
                    var token = enumerator.Current;

                    if (token == null) {
                        // new line
                        if (nesting != 0 || otherNesting != 0 || (enumerator.MoveNext() && IsExplicitLineJoin(enumerator.Current))) {
                            // we're in a grouping, or the previous token is an explicit line join, we'll keep going.
                            continue;
                        } else {
                            break;
                        }
                    }

                    var text = token.Span.GetText();
                    if (text == "(") {
                        if (nesting != 0) {
                            nesting--;
                            nestingChanged = true;
                            if (nesting == 0) {
                                if (sigStart == null) {
                                    sigStart = token.Span.Start;
                                }
                            }
                        } else {
                            if (start == null && !forCompletion) {
                                // hovering directly over an open paren, don't provide a tooltip
                                return null;
                            }

                            // figure out if we're a parameter definition
                            isParameterName = IsParameterNameOpenParen(enumerator);
                            break;
                        }
                        lastTokenWasCommaOrOperator = true;
                        lastTokenWasKeywordArgAssignment = false;
                    } else if (token.IsOpenGrouping()) {
                        if (otherNesting != 0) {
                            otherNesting--;
                        } else {
                            if (nesting == 0) {
                                if (start == null) {
                                    return null;
                                }
                                break;
                            }
                            paramIndex = 0;
                        }
                        nestingChanged = true;
                        lastTokenWasCommaOrOperator = true;
                        lastTokenWasKeywordArgAssignment = false;
                    } else if (text == ")") {
                        nesting++;
                        nestingChanged = true;
                        lastTokenWasCommaOrOperator = true;
                        lastTokenWasKeywordArgAssignment = false;
                    } else if (token.IsCloseGrouping()) {
                        otherNesting++;
                        nestingChanged = true;
                        lastTokenWasCommaOrOperator = true;
                        lastTokenWasKeywordArgAssignment = false;
                    } else if (token.ClassificationType == Classifier.Provider.Keyword ||
                               token.ClassificationType == Classifier.Provider.Operator) {
                        lastTokenWasKeywordArgAssignment = false;

                        if (token.ClassificationType == Classifier.Provider.Keyword && text == "lambda") {
                            if (currentParamAtLastColon != -1) {
                                paramIndex = currentParamAtLastColon;
                                currentParamAtLastColon = -1;
                            } else {
                                // fabcd(lambda a, b, c[PARAMINFO]
                                // We have to be the 1st param.
                                paramIndex = 0;
                            }
                        }

                        if (text == ":") {
                            startAtLastToken = start;
                            currentParamAtLastColon = paramIndex;
                        }

                        if (nesting == 0 && otherNesting == 0) {
                            if (start == null) {
                                // http://pytools.codeplex.com/workitem/560
                                // yield_value = 42
                                // def f():
                                //     yield<ctrl-space>
                                //     yield <ctrl-space>
                                // 
                                // If we're next to the keyword, just return the keyword.
                                // If we're after the keyword, return the span of the text proceeding
                                //  the keyword so we can complete after it.
                                // 
                                // Also repros with "return <ctrl-space>" or "print <ctrl-space>" both
                                // of which we weren't reporting completions for before
                                if (forCompletion) {
                                    if (token.Span.IntersectsWith(_span.GetSpan(_snapshot))) {
                                        return token.Span;
                                    } else {
                                        return _span.GetSpan(_snapshot);
                                    }
                                }

                                // hovering directly over a keyword, don't provide a tooltip
                                return null;
                            } else if ((nestingChanged || forCompletion) && token.ClassificationType == Classifier.Provider.Keyword && (text == "def" || text == "class")) {
                                return null;
                            }
                            if (text == "*" || text == "**") {
                                if (MoveNextSkipExplicitNewLines(enumerator)) {
                                    if (enumerator.Current.ClassificationType == Classifier.Provider.CommaClassification) {
                                        isParameterName = IsParameterNameComma(enumerator);
                                    } else if (enumerator.Current.IsOpenGrouping() && enumerator.Current.Span.GetText() == "(") {
                                        isParameterName = IsParameterNameOpenParen(enumerator);
                                    }
                                }
                            }
                            break;
                        } else if ((token.ClassificationType == Classifier.Provider.Keyword &&
                            PythonKeywords.IsOnlyStatementKeyword(text)) ||
                            (token.ClassificationType == Classifier.Provider.Operator && IsAssignmentOperator(text))) {
                            if (nesting != 0 && text == "=") {
                                // keyword argument allowed in signatures
                                lastTokenWasKeywordArgAssignment = lastTokenWasCommaOrOperator = true;
                            } else if (start == null || (nestingChanged && nesting != 0)) {
                                return null;
                            } else {
                                break;
                            }
                        } else if (token.ClassificationType == Classifier.Provider.Keyword &&
                            (text == "if" || text == "else")) {
                            // if and else can be used in an expression context or a statement context
                            if (currentParamAtLastColon != -1) {
                                start = startAtLastToken;
                                if (start == null) {
                                    return null;
                                }
                                break;
                            }
                        }
                        lastTokenWasCommaOrOperator = true;
                    } else if (token.ClassificationType == Classifier.Provider.DotClassification) {
                        lastTokenWasCommaOrOperator = true;
                        lastTokenWasKeywordArgAssignment = false;
                    } else if (token.ClassificationType == Classifier.Provider.CommaClassification) {
                        lastTokenWasCommaOrOperator = true;
                        lastTokenWasKeywordArgAssignment = false;
                        if (nesting == 0 && otherNesting == 0) {
                            if (start == null && !forCompletion) {
                                return null;
                            }
                            isParameterName = IsParameterNameComma(enumerator);
                            break;
                        } else if (nesting == 1 && otherNesting == 0 && sigStart == null) {
                            paramIndex++;
                        }
                    } else if (token.ClassificationType == Classifier.Provider.Comment) {
                        return null;
                    } else if (!lastTokenWasCommaOrOperator) {
                        if (nesting == 0 && otherNesting == 0) {
                            break;
                        }
                    } else {
                        if (lastTokenWasKeywordArgAssignment &&
                            token.ClassificationType.IsOfType(PredefinedClassificationTypeNames.Identifier) &&
                            lastKeywordArg == null) {
                            if (paramIndex == 0) {
                                lastKeywordArg = text;
                            } else {
                                lastKeywordArg = "";
                            }
                        }
                        lastTokenWasCommaOrOperator = false;
                    }

                    start = token.Span;
                } while (enumerator.MoveNext());
            }

            if (start.HasValue && lastToken != null && (lastToken.Span.End.Position - start.Value.Start.Position) >= 0) {
                var spanToReturn = new SnapshotSpan(
                    Snapshot,
                    new Span(
                        start.Value.Start.Position,
                        lastToken.Span.End.Position - start.Value.Start.Position
                    )
                );
                // To handle a case where a space is returned for displaying the type signature.
                if (string.IsNullOrWhiteSpace(spanToReturn.GetText())) {
                    return null;
                }
                return spanToReturn;
            }

            return _span.GetSpan(_snapshot);
        }