in src/documents/positionContexts/TemplatePositionContext.ts [1039:1161]
private getParameterOrVariableNameReplaceInfo(tleValue: TLE.StringValue | TLE.FunctionCallValue, tleCharacterIndex: number): ITleReplaceSpanInfo | undefined {
// Note: We icnclude closing parenthesis and single quote in the replacement span and the insertion text,
// so that the cursor ends up after them once the replacement happens. This way the user can immediately start
// typing the rest of the expression after the parameters call.
// Also note that, unlike with function name replacements, we replace the entire string argument if the cursor is anywhere inside
// the existing string, not just the part of it before the cursor.
let includeSingleQuotesInCompletion: boolean;
let includeRightParenthesisInCompletion: boolean;
let replaceSpan: Span;
if (tleValue instanceof TLE.StringValue) {
// parameters(<CURSOR> 'xxyy') or parameters('xx<CURSOR>yy') - tleValue is the string literal
const stringSpan: Span = tleValue.getSpan();
const functionValue: TLE.FunctionCallValue | undefined = TLE.asFunctionCallValue(tleValue.parent);
const stringStartIndex = stringSpan.startIndex;
let tleReplaceSpan: Span;
if (tleCharacterIndex <= stringStartIndex) {
// The cursor is before the beginning of the string (or right at the open quote, which means it's not yet
// inside the string) - just insert the completion text, don't replace anything existing (the user may be
// trying to add to the expression before the cursor if this is an existing expression or string)
//
// Example: "['Microsoft.web/sites']" -> "[concat(parameters(<CURSOR>'Microsoft.web/sites']"
// Desired replacement should *not* be to replace 'Microsoft.web/sites' with the parameter name, but rather to
// insert it: "[concat(parameters('parameter1''Microsoft.web/sites']"
/// ... because the user intends to keep typing to finish the expression
// this way: "[concat(parameters('parameter1'), 'Microsoft.web/sites')]"
tleReplaceSpan = new Span(tleCharacterIndex, 0);
includeSingleQuotesInCompletion = true;
includeRightParenthesisInCompletion = false;
} else if (tleCharacterIndex > tleValue.getSpan().endIndex) {
// Cursor is after the string - no replacements
return undefined;
} else if (tleCharacterIndex - stringStartIndex === 1 && tleValue.unquotedValue.startsWith(`''`)) {
// Special case - image user has
//
// "[resourceId(<CURSOR>'Microsoft.Network/virtualNetworks', parameters('subnet2Name'))]"
//
// At the cursor they want to add "parameters('sku')" as a new first argument to resourceId, without
// deleting the existing 'Microsoft.Network/virtualNetworks' string, which will become the second argument
// after inserting.
//
// They type "parameters('" (note the initial single quote). VS Code immediately adds the closing single
// quote, so they have:
//
// "[resourceId('<CURSOR>''Microsoft.Network/virtualNetworks', parameters('subnet2Name'))]"
//
// when the completion request comes through here. Since two single quotes inside a string are an escape
// for a single quote, the entire "'''Microsoft.Network/virtualNetworks'" string is tokenized as a single
// string literal, which means we would normally replace the entire string. Instead, special case this as
// if it were two strings and only replace the first two single quotes, leaving the existing string alone.
tleReplaceSpan = new Span(stringStartIndex, 2);
includeSingleQuotesInCompletion = true;
includeRightParenthesisInCompletion = false;
} else {
// The cursor is inside the string - replace the entire parameter, including the closing single
// quote and parenthesis, so that the cursor will end up after the parameters call
includeSingleQuotesInCompletion = true;
// If the string is not properly closed with an ending single quote, the parser may pick up the
// closing parenthesis and bracket in the string. It's very unlikely these were meant to be part of
// a parameter/variable name, so cut off our replacement at this point, in order to prevent data in
// the string that the user probably wants to keep around after making the completion.
// This can happen for instance if they're turning an existing string into an expression or adding
// to the front of an existing expression.
//
// Example: "[concat(parameters('p1<CURSOR>)Microsoft.web/sites']"
const rightParenthesisIndex: number = tleValue.toString().indexOf(")");
const rightSquareBracketIndex: number = tleValue.toString().indexOf("]");
if (rightParenthesisIndex >= 0) {
// Cut off before the ending parenthesis
tleReplaceSpan = new Span(stringStartIndex, rightParenthesisIndex + 1);
includeRightParenthesisInCompletion = true;
} else if (rightSquareBracketIndex >= 0) {
// Cut off before the ending square bracket
tleReplaceSpan = new Span(stringStartIndex, rightSquareBracketIndex);
includeRightParenthesisInCompletion = true;
} else if (functionValue && functionValue.rightParenthesisToken && functionValue.argumentExpressions.length === 1) {
// The parameters or variables function includes a right parenthesis already
tleReplaceSpan = new Span(stringStartIndex, functionValue.rightParenthesisToken.span.afterEndIndex - stringStartIndex);
includeRightParenthesisInCompletion = true;
} else {
// The parameters or variables function call does not yet include a right parenthesis, just replace the string
includeRightParenthesisInCompletion = !!functionValue && functionValue.argumentExpressions.length <= 1;
tleReplaceSpan = stringSpan;
}
}
replaceSpan = tleReplaceSpan.translate(this.jsonTokenStartIndex);
} else {
// parameters(<CURSOR>) - tleValue is the parameters/variables call
includeSingleQuotesInCompletion = true;
if (tleValue.rightParenthesisToken) {
replaceSpan = new Span(
this.documentCharacterIndex,
tleValue.rightParenthesisToken.span.startIndex - tleCharacterIndex + 1);
includeRightParenthesisInCompletion = true;
} else {
replaceSpan = this.emptySpanAtDocumentCharacterIndex;
includeRightParenthesisInCompletion = true;
}
}
if (includeRightParenthesisInCompletion) {
assert(includeSingleQuotesInCompletion, "includeSingleQuotesInCompletion required if includeRightParenthesisInCompletion");
}
return {
includeRightParenthesisInCompletion,
replaceSpan: replaceSpan,
includeSingleQuotesInCompletion
};
}