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:]) | "