Skip to content

Commit 86d0fc3

Browse files
author
Nathan Hawes
committed
[SyntaxColor] Improve highligting of multiline strings
Multiline strings (and multiline tokens in general) were not well supported by the existing highlighting logic. Edits on one line can make tokens appear/disappear on previous and later lines, which broke assumptions in the existing logic, and left odd ranges of source unhighlighted or out of date. This patch accounts for these changes, and also changes unterminated multiline (and regular strings) to still be highlighted as strings, so the rest of the file doesn't look like plain text. Resolves rdar://problem/32148117.
1 parent 5bf7f23 commit 86d0fc3

File tree

9 files changed

+437
-83
lines changed

9 files changed

+437
-83
lines changed

include/swift/IDE/Formatting.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ class LineRange {
8181
else if (Line >= StartLine + Length) {
8282
Length = Line - StartLine + 1;
8383
}
84+
else if (Line < StartLine) {
85+
unsigned Delta = StartLine - Line;
86+
StartLine -= Delta;
87+
Length += Delta;
88+
}
8489
}
8590

8691
};

lib/IDE/SyntaxModel.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ SyntaxModelContext::SyntaxModelContext(SourceFile &SrcFile)
206206
break;
207207
}
208208

209+
case tok::unknown:
210+
if (Tok.getText().startswith("\"")) {
211+
// invalid string literal
212+
Kind = SyntaxNodeKind::String;
213+
break;
214+
}
215+
continue;
209216
default:
210217
continue;
211218
}

test/IDE/coloring.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,15 @@ func f(x: Int) -> Int {
227227
// CHECK: <str>"This is string </str>\<anchor>(</anchor>genFn({(a:<type>Int</type> -> <type>Int</type>) <kw>in</kw> a})<anchor>)</anchor><str> interpolation"</str>
228228
"This is string \(genFn({(a:Int -> Int) in a})) interpolation"
229229

230+
// CHECK: <str>"This is unterminated</str>
231+
"This is unterminated
232+
233+
// CHECK: <str>"This in unterminated with ignored \( "interpolation" ) in it</str>
234+
"This in unterminated with ignored \( "interpolation" ) in it
235+
236+
// CHECK: <str>"This is terminated with \( invalid interpolation" + "in it"</str>
237+
"This is terminated with \( invalid interpolation" + "in it"
238+
230239
// CHECK: <str>"""
231240
// CHECK-NEXT: This is a multiline string.
232241
// CHECK-NEXT: """</str>
@@ -236,9 +245,15 @@ func f(x: Int) -> Int {
236245

237246
// CHECK: <str>"""
238247
// CHECK-NEXT: This is a multiline</str>\<anchor>(</anchor> <str>"interpolated"</str> <anchor>)</anchor><str>string
248+
// CHECK-NEXT: </str>\<anchor>(</anchor>
249+
// CHECK-NEXT: <str>"inner"</str>
250+
// CHECK-NEXT: <anchor>)</anchor><str>
239251
// CHECK-NEXT: """</str>
240252
"""
241253
This is a multiline\( "interpolated" )string
254+
\(
255+
"inner"
256+
)
242257
"""
243258
}
244259

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// RUN: %target-swift-ide-test -syntax-coloring -source-filename %s | %FileCheck %s
2+
// RUN: %target-swift-ide-test -syntax-coloring -typecheck -source-filename %s | %FileCheck %s
3+
// XFAIL: broken_std_regex
4+
5+
// CHECK: <kw>let</kw> x = <str>"""
6+
// CHECK-NEXT: This is an unterminated
7+
// CHECK-NEXT: \( "multiline" )
8+
// CHECK-NEXT: string followed by code
9+
// CHECK-NEXT: ""
10+
// CHECK-NEXT: func foo() {}
11+
// CHECK-NEXT: </str>
12+
let x = """
13+
This is an unterminated
14+
\( "multiline" )
15+
string followed by code
16+
""
17+
func foo() {}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
let x = /*
2+
3+
*/
4+
func foo() {}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
let a = "value"
2+
let x = """
3+
4+
\(
5+
a
6+
)
7+
8+
9+
10+
func foo () -> String {
11+
return "foo"
12+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// RUN: %sourcekitd-test -req=open -print-raw-response %S/Inputs/syntaxmap-edit-block-comment.swift == -req=edit -print-raw-response %S/Inputs/syntaxmap-edit-block-comment.swift -pos=3:2 -replace=" " -length=1 == -req=edit -print-raw-response %S/Inputs/syntaxmap-edit-block-comment.swift -pos=3:2 -replace="/" -length=1 | %sed_clean > %t.response
2+
// RUN: %FileCheck -input-file=%t.response %s
3+
4+
// CHECK: {{^}}{
5+
// CHECK-NEXT: key.offset: 0,
6+
// CHECK-NEXT: key.length: 29,
7+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
8+
// CHECK-NEXT: key.syntaxmap: [
9+
// CHECK-NEXT: {
10+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
11+
// CHECK-NEXT: key.offset: 0,
12+
// CHECK-NEXT: key.length: 3
13+
// CHECK-NEXT: },
14+
// CHECK-NEXT: {
15+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
16+
// CHECK-NEXT: key.offset: 4,
17+
// CHECK-NEXT: key.length: 1
18+
// CHECK-NEXT: },
19+
// CHECK-NEXT: {
20+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.comment,
21+
// CHECK-NEXT: key.offset: 8,
22+
// CHECK-NEXT: key.length: 6
23+
// CHECK-NEXT: },
24+
// CHECK-NEXT: {
25+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
26+
// CHECK-NEXT: key.offset: 15,
27+
// CHECK-NEXT: key.length: 4
28+
// CHECK-NEXT: },
29+
// CHECK-NEXT: {
30+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
31+
// CHECK-NEXT: key.offset: 20,
32+
// CHECK-NEXT: key.length: 3
33+
// CHECK-NEXT: }
34+
// CHECK-NEXT: ],
35+
36+
// CHECK: {{^}}{
37+
// CHECK-NEXT: key.offset: 8,
38+
// CHECK-NEXT: key.length: 21,
39+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
40+
// CHECK-NEXT: key.syntaxmap: [
41+
// CHECK-NEXT: {
42+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.comment,
43+
// CHECK-NEXT: key.offset: 8,
44+
// CHECK-NEXT: key.length: 21
45+
// CHECK-NEXT: }
46+
// CHECK-NEXT: ],
47+
48+
// CHECK: {{^}}{
49+
// CHECK-NEXT: key.offset: 8,
50+
// CHECK-NEXT: key.length: 21,
51+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
52+
// CHECK-NEXT: key.syntaxmap: [
53+
// CHECK-NEXT: {
54+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.comment,
55+
// CHECK-NEXT: key.offset: 8,
56+
// CHECK-NEXT: key.length: 6
57+
// CHECK-NEXT: },
58+
// CHECK-NEXT: {
59+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
60+
// CHECK-NEXT: key.offset: 15,
61+
// CHECK-NEXT: key.length: 4
62+
// CHECK-NEXT: },
63+
// CHECK-NEXT: {
64+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
65+
// CHECK-NEXT: key.offset: 20,
66+
// CHECK-NEXT: key.length: 3
67+
// CHECK-NEXT: }
68+
// CHECK-NEXT: ],
69+
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// RUN: %sourcekitd-test -req=open -print-raw-response %S/Inputs/syntaxmap-edit-multiline-string.swift == -req=edit -print-raw-response %S/Inputs/syntaxmap-edit-multiline-string.swift -pos=8:1 -replace='"""' -length=3 == -req=edit -print-raw-response %S/Inputs/syntaxmap-edit-multiline-string.swift -pos=6:2 -replace=')' -length=1 == -req=edit -print-raw-response %S/Inputs/syntaxmap-edit-multiline-string.swift -pos=2:10 -replace=' ' -length=1 | %sed_clean > %t.response
2+
// RUN: %FileCheck -input-file=%t.response %s
3+
4+
// Original file contents
5+
6+
// CHECK: {{^}}{
7+
// CHECK-NEXT: key.offset: 0,
8+
// CHECK-NEXT: key.length: 84,
9+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
10+
// CHECK-NEXT: key.syntaxmap: [
11+
// CHECK-NEXT: {
12+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
13+
// CHECK-NEXT: key.offset: 0,
14+
// CHECK-NEXT: key.length: 3
15+
// CHECK-NEXT: },
16+
// CHECK-NEXT: {
17+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
18+
// CHECK-NEXT: key.offset: 4,
19+
// CHECK-NEXT: key.length: 1
20+
// CHECK-NEXT: },
21+
// CHECK-NEXT: {
22+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
23+
// CHECK-NEXT: key.offset: 8,
24+
// CHECK-NEXT: key.length: 7
25+
// CHECK-NEXT: },
26+
// CHECK-NEXT: {
27+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
28+
// CHECK-NEXT: key.offset: 16,
29+
// CHECK-NEXT: key.length: 3
30+
// CHECK-NEXT: },
31+
// CHECK-NEXT: {
32+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
33+
// CHECK-NEXT: key.offset: 20,
34+
// CHECK-NEXT: key.length: 1
35+
// CHECK-NEXT: },
36+
// CHECK-NEXT: {
37+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
38+
// CHECK-NEXT: key.offset: 24,
39+
// CHECK-NEXT: key.length: 60
40+
// CHECK-NEXT: }
41+
// CHECK-NEXT: ],
42+
43+
// After terminating the multiline string
44+
45+
// CHECK: {{^}}{
46+
// CHECK-NEXT: key.offset: 24,
47+
// CHECK-NEXT: key.length: 60,
48+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
49+
// CHECK-NEXT: key.syntaxmap: [
50+
// CHECK-NEXT: {
51+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
52+
// CHECK-NEXT: key.offset: 24,
53+
// CHECK-NEXT: key.length: 5
54+
// CHECK-NEXT: },
55+
// CHECK-NEXT: {
56+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string_interpolation_anchor,
57+
// CHECK-NEXT: key.offset: 30,
58+
// CHECK-NEXT: key.length: 1
59+
// CHECK-NEXT: },
60+
// CHECK-NEXT: {
61+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
62+
// CHECK-NEXT: key.offset: 32,
63+
// CHECK-NEXT: key.length: 1
64+
// CHECK-NEXT: },
65+
// CHECK-NEXT: {
66+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string_interpolation_anchor,
67+
// CHECK-NEXT: key.offset: 34,
68+
// CHECK-NEXT: key.length: 1
69+
// CHECK-NEXT: },
70+
// CHECK-NEXT: {
71+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
72+
// CHECK-NEXT: key.offset: 35,
73+
// CHECK-NEXT: key.length: 6
74+
// CHECK-NEXT: },
75+
// CHECK-NEXT: {
76+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
77+
// CHECK-NEXT: key.offset: 43,
78+
// CHECK-NEXT: key.length: 4
79+
// CHECK-NEXT: },
80+
// CHECK-NEXT: {
81+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
82+
// CHECK-NEXT: key.offset: 48,
83+
// CHECK-NEXT: key.length: 3
84+
// CHECK-NEXT: },
85+
// CHECK-NEXT: {
86+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.typeidentifier,
87+
// CHECK-NEXT: key.offset: 58,
88+
// CHECK-NEXT: key.length: 6
89+
// CHECK-NEXT: },
90+
// CHECK-NEXT: {
91+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
92+
// CHECK-NEXT: key.offset: 69,
93+
// CHECK-NEXT: key.length: 6
94+
// CHECK-NEXT: },
95+
// CHECK-NEXT: {
96+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
97+
// CHECK-NEXT: key.offset: 76,
98+
// CHECK-NEXT: key.length: 5
99+
// CHECK-NEXT: }
100+
// CHECK-NEXT: ],
101+
102+
// After adding a character after the interpolation
103+
// CHECK: {{^}}{
104+
// CHECK-NEXT: key.offset: 34,
105+
// CHECK-NEXT: key.length: 9,
106+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
107+
// CHECK-NEXT: key.syntaxmap: [
108+
// CHECK-NEXT: {
109+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string_interpolation_anchor,
110+
// CHECK-NEXT: key.offset: 34,
111+
// CHECK-NEXT: key.length: 1
112+
// CHECK-NEXT: },
113+
// CHECK-NEXT: {
114+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
115+
// CHECK-NEXT: key.offset: 35,
116+
// CHECK-NEXT: key.length: 6
117+
// CHECK-NEXT: }
118+
// CHECK-NEXT: ],
119+
120+
// After replacing the middle opening quote with a space
121+
122+
// CHECK: {{^}}{
123+
// CHECK-NEXT: key.offset: 16,
124+
// CHECK-NEXT: key.length: 68,
125+
// CHECK-NEXT: key.diagnostic_stage: source.diagnostic.stage.swift.parse,
126+
// CHECK-NEXT: key.syntaxmap: [
127+
// CHECK-NEXT: {
128+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.keyword,
129+
// CHECK-NEXT: key.offset: 16,
130+
// CHECK-NEXT: key.length: 3
131+
// CHECK-NEXT: },
132+
// CHECK-NEXT: {
133+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
134+
// CHECK-NEXT: key.offset: 20,
135+
// CHECK-NEXT: key.length: 1
136+
// CHECK-NEXT: },
137+
// CHECK-NEXT: {
138+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
139+
// CHECK-NEXT: key.offset: 24,
140+
// CHECK-NEXT: key.length: 3
141+
// CHECK-NEXT: },
142+
// CHECK-NEXT: {
143+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.identifier,
144+
// CHECK-NEXT: key.offset: 32,
145+
// CHECK-NEXT: key.length: 1
146+
// CHECK-NEXT: },
147+
// CHECK-NEXT: {
148+
// CHECK-NEXT: key.kind: source.lang.swift.syntaxtype.string,
149+
// CHECK-NEXT: key.offset: 38,
150+
// CHECK-NEXT: key.length: 46
151+
// CHECK-NEXT: }
152+
// CHECK-NEXT: ],
153+
154+

0 commit comments

Comments
 (0)