diff --git a/java/java-psi-impl/src/com/intellij/lang/java/lexer/JavaLexer.java b/java/java-psi-impl/src/com/intellij/lang/java/lexer/JavaLexer.java index 190f64dd2be6..8a4732ba9354 100644 --- a/java/java-psi-impl/src/com/intellij/lang/java/lexer/JavaLexer.java +++ b/java/java-psi-impl/src/com/intellij/lang/java/lexer/JavaLexer.java @@ -55,7 +55,8 @@ public final class JavaLexer extends LexerBase { private int myTokenEndOffset; // positioned after the last symbol of the current token private IElementType myTokenType; private short myState = 0; - private short myBraceCount = 0; + private short[] myBraceCounts = new short[1]; + private int myTemplateDepth = -1; /** The length of the last valid unicode escape (6 or greater), or 1 when no unicode escape was found. */ private int mySymbolLength = 1; @@ -76,14 +77,18 @@ public final class JavaLexer extends LexerBase { mySymbolLength = 1; if (initialState != 0) { myState = (short)initialState; - myBraceCount = (short)(initialState >> 16); + short braceCount = (short)(initialState >> 16); + if (braceCount > 0) { + myBraceCounts[0] = braceCount; + myTemplateDepth = 0; + } } myFlexLexer.reset(myBuffer, startOffset, endOffset, 0); } @Override public int getState() { - return (myBraceCount == 0) ? 0 : myBraceCount << 16 | myState; + return myTemplateDepth < 0 ? 0 : myBraceCounts[myTemplateDepth] << 16 | myState; } @Override @@ -135,16 +140,16 @@ public final class JavaLexer extends LexerBase { break; case '{': - if (myBraceCount > 0) { - myBraceCount++; + if (myTemplateDepth >= 0) { + myBraceCounts[myTemplateDepth]++; } myTokenType = JavaTokenType.LBRACE; myTokenEndOffset = myBufferIndex + mySymbolLength; break; case '}': - if (myBraceCount > 0) { - myBraceCount--; - if (myBraceCount == 0) { + if (myTemplateDepth >= 0) { + if (--myBraceCounts[myTemplateDepth] == 0) { + myTemplateDepth--; if (myState == STATE_TEXT_BLOCK_TEMPLATE) { if (locateTextBlockEnd(myBufferIndex + mySymbolLength)) { myTokenType = JavaTokenType.TEXT_BLOCK_TEMPLATE_MID; @@ -287,7 +292,11 @@ public final class JavaLexer extends LexerBase { if (pos < myBufferEndOffset) { if (locateCharAt(pos) == '{' && myStringTemplates && quoteChar == '"') { pos += mySymbolLength; - myBraceCount = 1; + myTemplateDepth++; + if (myTemplateDepth == myBraceCounts.length) { + myBraceCounts = Arrays.copyOf(myBraceCounts, myTemplateDepth * 2); + } + myBraceCounts[myTemplateDepth] = 1; myTokenEndOffset = pos; return true; } diff --git a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java index 206eb03f53bd..23b1d3df70d3 100644 --- a/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java +++ b/java/java-tests/testData/codeInsight/daemonCodeAnalyzer/advHighlightingStringTemplates/StringTemplates.java @@ -36,4 +36,8 @@ class X { System.out.println(myProcessor.""); return processor."\{}\{}\{}\{}\{}\{}"; } + + void nested() { + System.out.println(STR."\{STR."\{STR."\{STR."\{STR."\{STR."\{STR.""}"}"}"}"}"}"); + } } \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/lexer/JavaLexerTest.java b/java/java-tests/testSrc/com/intellij/java/lexer/JavaLexerTest.java index 935e8a8ddc55..0c530816177f 100644 --- a/java/java-tests/testSrc/com/intellij/java/lexer/JavaLexerTest.java +++ b/java/java-tests/testSrc/com/intellij/java/lexer/JavaLexerTest.java @@ -333,7 +333,64 @@ public class JavaLexerTest extends LexerTestCase { WHITE_SPACE (' ') TEXT_BLOCK_TEMPLATE_END ('}""\"') WHITE_SPACE (' ')"""); - //doTest("\"\\{fruit[0]}, \\{STR.\"\\{fruit[1]}, \\{fruit[2]}\"}\"",""); + doTest(""" + "\\{fruit[0]}, \\{STR."\\{fruit[1]}, \\{fruit[2]}"}" + """, + """ + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('fruit') + LBRACKET ('[') + INTEGER_LITERAL ('0') + RBRACKET (']') + STRING_TEMPLATE_MID ('}, \\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('fruit') + LBRACKET ('[') + INTEGER_LITERAL ('1') + RBRACKET (']') + STRING_TEMPLATE_MID ('}, \\{') + IDENTIFIER ('fruit') + LBRACKET ('[') + INTEGER_LITERAL ('2') + RBRACKET (']') + STRING_TEMPLATE_END ('}"') + STRING_TEMPLATE_END ('}"') + WHITE_SPACE ('\\n')"""); + doTest(""" + STR."\\{STR."\\{STR."\\{STR."\\{STR."\\{STR."\\{STR.""}"}"}"}"}"}" + """, + """ + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_TEMPLATE_BEGIN ('"\\{') + IDENTIFIER ('STR') + DOT ('.') + STRING_LITERAL ('""') + STRING_TEMPLATE_END ('}"') + STRING_TEMPLATE_END ('}"') + STRING_TEMPLATE_END ('}"') + STRING_TEMPLATE_END ('}"') + STRING_TEMPLATE_END ('}"') + STRING_TEMPLATE_END ('}"') + WHITE_SPACE ('\\n') + """); } public void testStringLiterals() { @@ -404,7 +461,8 @@ public class JavaLexerTest extends LexerTestCase { doTest("/", "DIV ('/')"); doTest("1/2", "INTEGER_LITERAL ('1')\nDIV ('/')\nINTEGER_LITERAL ('2')"); doTest("//\\\\u000A test", "END_OF_LINE_COMMENT ('//\\\\u000A test')"); // escaped backslash, not a unicode escape - doTest("//\\\\\\u000A test", "END_OF_LINE_COMMENT ('//\\\\')\nWHITE_SPACE ('\\u000A ')\nIDENTIFIER ('test')"); // escaped backslash, followed by a unicode escape + doTest("//\\\\\\u000A test", + "END_OF_LINE_COMMENT ('//\\\\')\nWHITE_SPACE ('\\u000A ')\nIDENTIFIER ('test')"); // escaped backslash, followed by a unicode escape } public void testWhitespace() {