Skip to content

Commit c5ef4eb

Browse files
authored
Merge pull request #3987 from benkobalog/fix-#3814-2
Fix #3814 Correct highlighting issues in REPL
2 parents d49a656 + 05c0901 commit c5ef4eb

File tree

3 files changed

+122
-68
lines changed

3 files changed

+122
-68
lines changed

compiler/src/dotty/tools/dotc/printing/SyntaxHighlighting.scala

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,19 @@ object SyntaxHighlighting {
4242
'q' :: 'r' :: 's' :: 't' :: 'u' :: 'v' :: 'w' :: 'x' :: 'y' :: 'z' :: Nil
4343

4444
private val typeEnders =
45-
'{' :: '}' :: ')' :: '(' :: '[' :: ']' :: '=' :: ' ' :: ',' :: '.' ::
46-
'\n' :: Nil
45+
'{' :: '}' :: ')' :: '(' :: '[' :: ']' :: '=' :: ' ' :: ',' :: '.' :: '|' ::
46+
'&' :: '\n' :: Nil
4747

4848
def apply(chars: Iterable[Char]): Iterable[Char] = {
4949
var prev: Char = 0
5050
var remaining = chars.toStream
5151
val newBuf = new StringBuilder
52-
var lastToken = ""
52+
var lastValDefToken = ""
5353

5454
@inline def keywordStart =
5555
prev == 0 || prev == ' ' || prev == '{' || prev == '(' ||
56-
prev == '\n' || prev == '[' || prev == ','
56+
prev == '\n' || prev == '[' || prev == ',' || prev == ':' ||
57+
prev == '|' || prev == '&'
5758

5859
@inline def numberStart(c: Char) =
5960
c.isDigit && (!prev.isLetter || prev == '.' || prev == ' ' || prev == '(' || prev == '\u0000')
@@ -289,6 +290,23 @@ object SyntaxHighlighting {
289290
case _ => false
290291
}
291292

293+
val valDefStarterTokens = "var" :: "val" :: "def" :: "case" :: Nil
294+
295+
/** lastValDefToken is used to check whether we want to show something
296+
* in valDef color or not. There are only a few cases when lastValDefToken
297+
* should be updated, that way we can avoid stopping coloring too early.
298+
* eg.: case A(x, y, z) => ???
299+
* Without this function only x would be colored.
300+
*/
301+
def updateLastToken(currentToken: String): String =
302+
(lastValDefToken, currentToken) match {
303+
case _ if valDefStarterTokens.contains(currentToken) => currentToken
304+
case (("val" | "var"), "=") => currentToken
305+
case ("case", ("=>" | "class" | "object")) => currentToken
306+
case ("def", _) => currentToken
307+
case _ => lastValDefToken
308+
}
309+
292310
while (remaining.nonEmpty && !delim(curr)) {
293311
curr = takeChar()
294312
if (!delim(curr)) sb += curr
@@ -298,12 +316,12 @@ object SyntaxHighlighting {
298316
val toAdd =
299317
if (shouldHL(str))
300318
highlight(str)
301-
else if (("var" :: "val" :: "def" :: "case" :: Nil).contains(lastToken))
319+
else if (valDefStarterTokens.contains(lastValDefToken) && !List("=", "=>").contains(str))
302320
valDef(str)
303321
else str
304322
val suffix = if (delim(curr)) s"$curr" else ""
305323
newBuf append (toAdd + suffix)
306-
lastToken = str
324+
lastValDefToken = updateLastToken(str)
307325
prev = curr
308326
}
309327

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package dotty.tools.dotc.printing
2+
3+
import org.junit.Assert._
4+
import org.junit.Test
5+
6+
/** Adapted from Ammonite HighlightTests
7+
*/
8+
class SyntaxHighlightingTests {
9+
import SyntaxHighlighting._
10+
11+
private def test(source: String, expected: String): Unit = {
12+
val highlighted = SyntaxHighlighting.apply(source)
13+
.mkString
14+
.replace(NoColor, ">")
15+
.replace(CommentColor, "<C|")
16+
.replace(KeywordColor, "<K|")
17+
.replace(ValDefColor, "<V|")
18+
.replace(LiteralColor, "<L|")
19+
.replace(StringColor, "<S|")
20+
.replace(TypeColor, "<T|")
21+
// .replace(AnnotationColor, "<A|") // is the same color as type color
22+
23+
if (expected != highlighted) {
24+
// assertEquals produces weird expected/found message
25+
fail(s"expected: $expected but was: $highlighted")
26+
}
27+
}
28+
29+
@Test
30+
def comments = {
31+
test("//a", "<C|//a>")
32+
test("/** a */", "<C|/** a */>")
33+
test("/* a */", "<C|/* a */>")
34+
}
35+
36+
@Test
37+
def types = {
38+
test("type Foo = Int", "<K|type> <T|Foo> = <T|Int>")
39+
}
40+
41+
@Test
42+
def literals = {
43+
test("1", "<L|1>")
44+
// test("1L", "<L|1L>")
45+
}
46+
47+
@Test
48+
def strings = {
49+
// For some reason we currently use literal color for string
50+
test("\"Hello\"", "<L|\"Hello\">")
51+
}
52+
53+
@Test
54+
def annotations = {
55+
test("@tailrec", "<T|@tailrec>")
56+
}
57+
58+
@Test
59+
def expressions = {
60+
test("val x = 1 + 2 + 3", "<K|val> <V|x> = <L|1> + <L|2> + <L|3>")
61+
}
62+
63+
@Test
64+
def valDef = {
65+
test("val a = 123", "<K|val> <V|a> = <L|123>")
66+
test("var b = 123 /*Int*/", "<K|var> <V|b> = <L|123> <C|/*Int*/>")
67+
test("""var c = "123" // String""", """<K|var> <V|c> = <L|"123"> <C|// String>""")
68+
test("var e:Int = 123;e", "<K|var> <V|e>:<T|Int> = <L|123>;e")
69+
test("def f = 123", "<K|def> <V|f> = <L|123>")
70+
test("def f1(x: Int) = 123", "<K|def> <V|f1>(x: <T|Int>) = <L|123>")
71+
test("def f2[T](x: T) = { 123 }", "<K|def> <V|f2>[<T|T>](x: <T|T>) = { <L|123> }")
72+
}
73+
74+
@Test
75+
def patternMatching = {
76+
test("""val aFruit: Fruit = Apple("red", 123)""",
77+
"""<K|val> <V|aFruit>: <T|Fruit> = <T|Apple>(<L|"red">, <L|123>)""")
78+
test("""val Apple(color, weight) = aFruit""",
79+
"""<K|val> <T|Apple>(<V|color>, <V|weight>) = aFruit""")
80+
test("""case Apple(_, weight) => println(s"apple: $weight kgs")""",
81+
"""<K|case> <T|Apple>(<V|_>, <V|weight>) <T|=>> println(s<L|"apple: <V|$weight <L|kgs">)""")
82+
test("""case o: Orange => println(s"orange ${o.weight} kgs")""",
83+
"""<K|case> <V|o>: <T|Orange> <T|=>> println(s<L|"orange <V|${o.weight}<L| kgs">)""")
84+
test("""case m @ Melon(weight) => println(s"melon: ${m.weight} kgs")""",
85+
"""<K|case> <V|m> @ <T|Melon>(<V|weight>) <T|=>> println(s<L|"melon: <V|${m.weight}<L| kgs">)""")
86+
}
87+
88+
@Test
89+
def unionTypes = {
90+
test("type A = String|Int| Long", "<K|type> <T|A> = <T|String>|<T|Int>| <T|Long>")
91+
test("type B = String |Int| Long", "<K|type> <T|B> = <T|String> |<T|Int>| <T|Long>")
92+
test("type C = String | Int | Long", "<K|type> <T|C> = <T|String> | <T|Int> | <T|Long>")
93+
test("type D = String&Int& Long", "<K|type> <T|D> = <T|String>&<T|Int>& <T|Long>")
94+
test("type E = String &Int& Long", "<K|type> <T|E> = <T|String> &<T|Int>& <T|Long>")
95+
test("type F = String & Int & Long", "<K|type> <T|F> = <T|String> & <T|Int> & <T|Long>")
96+
test("fn[String|Char](input)", "fn[<T|String>|<T|Char>](input)")
97+
}
98+
}

compiler/test/dotty/tools/dotc/reporting/SyntaxHighlightingTests.scala

Lines changed: 0 additions & 62 deletions
This file was deleted.

0 commit comments

Comments
 (0)