diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 3541edd2bc4..e604ffa6306 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,13 +1,9 @@ - - - - - - + + + + \ No newline at end of file diff --git a/scala/scala-impl/src/org/jetbrains/plugins/scala/highlighter/ScalaSyntaxHighlighter.scala b/scala/scala-impl/src/org/jetbrains/plugins/scala/highlighter/ScalaSyntaxHighlighter.scala index 4a9123ca5e5..70bffb25713 100644 --- a/scala/scala-impl/src/org/jetbrains/plugins/scala/highlighter/ScalaSyntaxHighlighter.scala +++ b/scala/scala-impl/src/org/jetbrains/plugins/scala/highlighter/ScalaSyntaxHighlighter.scala @@ -157,10 +157,12 @@ object ScalaSyntaxHighlighter { private val tSTRINGS = TokenSet.create( tSTRING, tMULTILINE_STRING, + tDEDENTED_STRING, tWRONG_STRING, tCHAR, tSYMBOL, tINTERPOLATED_MULTILINE_STRING, + tINTERPOLATED_DEDENTED_STRING, tINTERPOLATED_STRING, tINTERPOLATED_STRING_ID, tINTERPOLATED_STRING_END diff --git a/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/ScalaTokenTypes.java b/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/ScalaTokenTypes.java index dc1426d1d6f..2fe4d28db6a 100644 --- a/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/ScalaTokenTypes.java +++ b/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/ScalaTokenTypes.java @@ -75,6 +75,11 @@ public interface ScalaTokenTypes { IElementType tINTERPOLATED_MULTILINE_RAW_STRING = new ScalaTokenType("interpolated multiline raw string"); IElementType tINTERPOLATED_RAW_STRING = new ScalaTokenType("interpolated raw string"); + // Dedented string literals (Scala 3) + IElementType tDEDENTED_STRING = new ScalaTokenType("dedented string"); + IElementType tINTERPOLATED_DEDENTED_STRING = new ScalaTokenType("interpolated dedented string"); + IElementType tINTERPOLATED_DEDENTED_RAW_STRING = new ScalaTokenType("interpolated dedented raw string"); + IElementType tCHAR = new ScalaTokenType("Character"); IElementType tSYMBOL = new ScalaTokenType("Symbol"); @@ -267,7 +272,9 @@ public interface ScalaTokenTypes { tMULTILINE_STRING, tINTERPOLATED_STRING, tINTERPOLATED_MULTILINE_STRING, - tINTERPOLATED_STRING_END + tINTERPOLATED_STRING_END, + tDEDENTED_STRING, + tINTERPOLATED_DEDENTED_STRING ); TokenSet VAL_VAR_TOKEN_SET = TokenSet.create(kVAL, kVAR); diff --git a/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/core/_ScalaCoreLexer.flex b/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/core/_ScalaCoreLexer.flex index fd6824a0d3e..895d21e9068 100644 --- a/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/core/_ScalaCoreLexer.flex +++ b/scala/scala-impl/src/org/jetbrains/plugins/scala/lang/lexer/core/_ScalaCoreLexer.flex @@ -86,6 +86,17 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; } } + private static class DedentedLevel extends InterpolatedStringLevel { + public final int delimiterLength; + public DedentedLevel(CharSequence interpolator, int delimiterLength) { + super(interpolator); + this.delimiterLength = delimiterLength; + } + public int getState() { + return INSIDE_DEDENTED_INTERPOLATED_STRING; + } + } + private boolean isScala3; // @@ -94,6 +105,7 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; //to get id after $ in interpolated String private boolean haveIdInString = false; private boolean haveIdInMultilineString = false; + private boolean haveIdInDedentedString = false; // Currently opened interpolated Strings. Each int represents the number of the opened left structural braces in the String private Stack nestedString = new Stack<>(); private CharSequence lastSeenInterpolator = null; @@ -105,16 +117,49 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; public void resetCustom() { haveIdInString = false; haveIdInMultilineString = false; + haveIdInDedentedString = false; nestedString.clear(); lastSeenInterpolator = null; } + private int countLeadingQuotes(CharSequence text) { + int count = 0; + for (int i = 0; i < text.length() && text.charAt(i) == '\''; i++) { + count++; + } + return count; + } + + private boolean endsWithQuotes(CharSequence text, int expectedCount) { + if (text.length() < expectedCount) return false; + int count = 0; + for (int i = text.length() - 1; i >= 0 && text.charAt(i) == '\''; i--) { + count++; + } + return count >= expectedCount; + } + + private boolean isValidDedentedString(CharSequence text) { + int leadingQuotes = countLeadingQuotes(text); + if (leadingQuotes < 3) return false; // Must have at least 3 quotes + + // Find the ending quotes + int trailingQuotes = 0; + for (int i = text.length() - 1; i >= 0 && text.charAt(i) == '\''; i--) { + trailingQuotes++; + } + + return leadingQuotes == trailingQuotes; + } + public boolean isInterpolatedStringState() { return isInsideInterpolatedString() || haveIdInString || haveIdInMultilineString || + haveIdInDedentedString || yystate() == INSIDE_INTERPOLATED_STRING || - yystate() == INSIDE_MULTI_LINE_INTERPOLATED_STRING; + yystate() == INSIDE_MULTI_LINE_INTERPOLATED_STRING || + yystate() == INSIDE_DEDENTED_INTERPOLATED_STRING; } private boolean shouldProcessBracesForInterpolated() { @@ -145,6 +190,9 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; } else if (haveIdInMultilineString) { haveIdInMultilineString = false; yybegin(INSIDE_MULTI_LINE_INTERPOLATED_STRING); + } else if (haveIdInDedentedString) { + haveIdInDedentedString = false; + yybegin(INSIDE_DEDENTED_INTERPOLATED_STRING); } } @@ -158,6 +206,8 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; typeAdjusted = tINTERPOLATED_RAW_STRING; else if (type == tINTERPOLATED_MULTILINE_STRING && isInsideRawInterpolator()) typeAdjusted = tINTERPOLATED_MULTILINE_RAW_STRING; + else if (type == tINTERPOLATED_DEDENTED_STRING && isInsideRawInterpolator()) + typeAdjusted = tINTERPOLATED_DEDENTED_RAW_STRING; else typeAdjusted = type; @@ -166,6 +216,11 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; @NotNull private IElementType processDollarInsideString(boolean isInsideMultiline) { + return processDollarInsideString(isInsideMultiline, false); + } + + @NotNull + private IElementType processDollarInsideString(boolean isInsideMultiline, boolean isInsideDedented) { final IElementType token; // TODO: remove this chech, this should always be false, cause $$ is handled by INTERPOLATED_STRING_ESCAPE pattern earlier @@ -175,7 +230,9 @@ import static org.jetbrains.plugins.scala.lang.lexer.ScalaTokenTypes.*; token = tINTERPOLATED_STRING_ESCAPE; } else { - if (isInsideMultiline) { + if (isInsideDedented) { + haveIdInDedentedString = true; + } else if (isInsideMultiline) { haveIdInMultilineString = true; } else { haveIdInString = true; @@ -271,12 +328,19 @@ hexDigit = [0-9A-Fa-f] CHAR_ESCAPE_SEQUENCE = \\[^\r\n] UNICODE_ESCAPE = \\u+ {hexDigit}{hexDigit}{hexDigit}{hexDigit} // Scala supports 1. multiple `u` chars after `\` 2. even \u000A ('\n') and \u000D (unlike Java) ESCAPE_SEQUENCE = {UNICODE_ESCAPE} | {CHAR_ESCAPE_SEQUENCE} -CHARACTER_LITERAL = "'"([^\\\'\r\n]|{ESCAPE_SEQUENCE}|{OCTAL_ESCAPE_LITERAL})("'"|\\) | \'\\u000A\' | "'''" // TODO: \'\\u000A\' is redundunt, remove +CHARACTER_LITERAL = "'"([^\\\'\r\n]|{ESCAPE_SEQUENCE}|{OCTAL_ESCAPE_LITERAL})("'"|\\) | \'\\u000A\' // TODO: \'\\u000A\' is redundunt, remove STRING_BEGIN = \"([^\\\"\r\n]|{CHAR_ESCAPE_SEQUENCE})* STRING_LITERAL={STRING_BEGIN} \" MULTI_LINE_STRING = \"\"\" ( (\"(\")?)? [^\"] )* \"\"\" (\")* // Multi-line string +// Dedented string literals (Scala 3) - modeled after MULTI_LINE_STRING pattern +DEDENTED_STRING_3 = \'\'\' ( (\'(\')?)? [^\'] )* \'\'\' (\')* +DEDENTED_STRING_4 = \'\'\'\' ( (\'(\'\'?)?)? [^\'] )* \'\'\'\' (\')* +DEDENTED_STRING_5 = \'\'\'\'\' ( (\'(\'\'\'?)?)? [^\'] )* \'\'\'\'\' (\')* +DEDENTED_STRING_6 = \'\'\'\'\'\' ( (\'(\'\'\'\'?)?)? [^\'] )* \'\'\'\'\'\' (\')* +DEDENTED_STRING = {DEDENTED_STRING_6} | {DEDENTED_STRING_5} | {DEDENTED_STRING_4} | {DEDENTED_STRING_3} + ////////String Interpolation//////// INTERPOLATED_STRING_ID = {varid} @@ -287,6 +351,9 @@ INTERPOLATED_STRING_PART_NOT_ESCAPED = [^\\\"\r\n\$] INTERPOLATED_MULTI_LINE_STRING_BEGIN = \"\"\"{INTERPOLATED_MULTI_LINE_STRING_PART}* INTERPOLATED_MULTI_LINE_STRING_PART = ((\"(\")?)? [^\"\$]) +INTERPOLATED_DEDENTED_STRING_BEGIN = \'\'\'+ {INTERPOLATED_DEDENTED_STRING_PART}* +INTERPOLATED_DEDENTED_STRING_PART = [^\'\$] | \$ [^{] | \'[^\']+ + // TODO: rename, it's missleading INTERPOLATED_STRING_ESCAPE = "$$" //INTERPOLATED_STRING_VARIABLE = "$"({identifier}) @@ -324,6 +391,7 @@ XML_BEGIN = "<" ("_" | [:jletter:]) | "