in src/Bicep.LangServer/Completions/BicepCompletionContext.cs [1120:1272]
private static bool IsInnerExpressionContext(List<SyntaxBase> matchingNodes, int offset)
{
if (!matchingNodes.OfType<ExpressionSyntax>().Any())
{
// Fail fast.
return false;
}
var isBooleanOrNumberOrNull = SyntaxMatcher.IsTailMatch<Token>(matchingNodes, token => token.Type switch
{
TokenType.TrueKeyword => true,
TokenType.FalseKeyword => true,
TokenType.Integer => true,
TokenType.NullKeyword => true,
_ => false,
});
if (isBooleanOrNumberOrNull)
{
// Don't provide completions for a boolean, number, or null literal because it may be confusing.
return false;
}
var isInStringSegment = SyntaxMatcher.IsTailMatch<StringSyntax, Token>(matchingNodes, (_, token) => token.Type switch
{
// The cursor is immediately after the { character: '...${|...}...'.
TokenType.StringLeftPiece when IsOffsetImmediatlyAfterNode(offset, token) => false,
TokenType.StringMiddlePiece when IsOffsetImmediatlyAfterNode(offset, token) => false,
// In other cases, we are in a string segment.
TokenType.StringComplete => true,
TokenType.StringLeftPiece => true,
TokenType.StringMiddlePiece => true,
TokenType.StringRightPiece => true,
TokenType.MultilineString => true,
_ => false,
});
if (isInStringSegment)
{
return false;
}
// var foo = true ?|:
// var foo = true ?| :
// var foo = true ? |:
// var foo = true ? | :
var isInEmptyTrueExpression = SyntaxMatcher.IsTailMatch<TernaryOperationSyntax>(
matchingNodes,
ternaryOperation =>
ternaryOperation.Question.GetPosition() <= offset &&
ternaryOperation.Colon.GetPosition() >= offset &&
ternaryOperation.TrueExpression is SkippedTriviaSyntax);
if (isInEmptyTrueExpression)
{
return true;
}
// var foo = true ? : | <white spaces>
var isInEmptyFalseExpression = SyntaxMatcher.IsTailMatch<TernaryOperationSyntax>(
matchingNodes,
ternaryOperation =>
ternaryOperation.Colon.GetPosition() <= offset &&
ternaryOperation.FalseExpression.GetPosition() >= offset &&
ternaryOperation.FalseExpression is SkippedTriviaSyntax);
if (isInEmptyFalseExpression)
{
return true;
}
// It does not make sense to insert expressions at the cursor positions shown in the comments below.
return !(
//║{ ║{ ║|{| ║{ ║{
//║ foo: true | ║ | foo: true ║ foo: true ║ foo: true ║ |
//║} ║} ║} ║|}| ║}
SyntaxMatcher.IsTailMatch<ObjectSyntax>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<ObjectSyntax, Token>(matchingNodes) ||
//║[ ║[ ║|[| ║[
//║ true | ║ | true ║ true ║ true
//║] ║] ║] ║|]|
SyntaxMatcher.IsTailMatch<ArraySyntax>(matchingNodes) ||
SyntaxMatcher.IsTailMatch<ArraySyntax, Token>(
matchingNodes,
(arraySyntax, token) => token.Type != TokenType.NewLine || !CanInsertChildNodeAtOffset(arraySyntax, offset)) ||
// var test = map([], ( | ) => 'asdf')
SyntaxMatcher.IsTailMatch<VariableBlockSyntax>(matchingNodes) ||
// var test = map([], (a|) => 'asdf')
SyntaxMatcher.IsTailMatch<VariableBlockSyntax, LocalVariableSyntax, IdentifierSyntax, Token>(matchingNodes) ||
// var test = map([], (|) => 'asdf')
SyntaxMatcher.IsTailMatch<VariableBlockSyntax, Token>(matchingNodes) ||
// func foo( | ) string => 'asdf'
SyntaxMatcher.IsTailMatch<TypedVariableBlockSyntax>(matchingNodes) ||
// func foo(a|) string => 'asdf'
SyntaxMatcher.IsTailMatch<TypedVariableBlockSyntax, TypedLocalVariableSyntax, IdentifierSyntax, Token>(matchingNodes) ||
// func foo(|) string => 'asdf'
SyntaxMatcher.IsTailMatch<TypedVariableBlockSyntax, Token>(matchingNodes) ||
// var foo = ! | bar
SyntaxMatcher.IsTailMatch<UnaryOperationSyntax>(
matchingNodes,
unaryOperation => !unaryOperation.Expression.IsOverlapping(offset)) ||
// var foo = |!bar
// var foo = !| bar
SyntaxMatcher.IsTailMatch<UnaryOperationSyntax, Token>(
matchingNodes,
(unaryOperation, operatorToken) =>
operatorToken.GetPosition() == offset ||
(operatorToken.GetEndPosition() == offset && unaryOperation.Expression is not SkippedTriviaSyntax)) ||
// var foo = 1 | + ...
// var foo = 1 + | 2
SyntaxMatcher.IsTailMatch<BinaryOperationSyntax>(
matchingNodes,
binaryOperation =>
!binaryOperation.LeftExpression.IsOverlapping(offset) &&
!binaryOperation.RightExpression.IsOverlapping(offset)) ||
// var foo = 1 |+ ...
// var foo = 1 +| 2
SyntaxMatcher.IsTailMatch<BinaryOperationSyntax, Token>(
matchingNodes,
(binaryOperation, operatorToken) =>
(operatorToken.GetPosition() == offset && binaryOperation.LeftExpression is not SkippedTriviaSyntax) ||
(operatorToken.GetEndPosition() == offset && binaryOperation.RightExpression is not SkippedTriviaSyntax)) ||
// var foo = true | ? ...
// var foo = true ? | 'yes'
// var foo = true ? 'yes' | : ...
// var foo = true ? 'yes' : | 'no'
SyntaxMatcher.IsTailMatch<TernaryOperationSyntax>(
matchingNodes,
ternaryOperation =>
!ternaryOperation.ConditionExpression.IsOverlapping(offset) &&
!ternaryOperation.TrueExpression.IsOverlapping(offset) &&
!ternaryOperation.FalseExpression.IsOverlapping(offset)) ||
// var foo = true |? ...
// var foo = true ?| 'yes'
// var foo = true ? 'yes' |: ...
// var foo = true ? 'yes' :| 'no'
SyntaxMatcher.IsTailMatch<TernaryOperationSyntax, Token>(
matchingNodes,
(ternaryOperation, operatorToken) =>
(operatorToken.Type == TokenType.Question && operatorToken.GetPosition() == offset && ternaryOperation.ConditionExpression is not SkippedTriviaSyntax) ||
(operatorToken.Type == TokenType.Question && operatorToken.GetEndPosition() == offset && ternaryOperation.TrueExpression is not SkippedTriviaSyntax) ||
(operatorToken.Type == TokenType.Colon && operatorToken.GetPosition() == offset && ternaryOperation.TrueExpression is not SkippedTriviaSyntax) ||
(operatorToken.Type == TokenType.Colon && operatorToken.GetEndPosition() == offset && ternaryOperation.FalseExpression is not SkippedTriviaSyntax)));
}