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);
}