@@ -10,6 +10,7 @@ package org.fife.ui.rsyntaxtextarea.modes;
10
10
11
11
import java.io.* ;
12
12
import javax.swing.text.Segment ;
13
+ import java.util.Stack ;
13
14
14
15
import org.fife.ui.rsyntaxtextarea.* ;
15
16
@@ -164,6 +165,16 @@ import org.fife.ui.rsyntaxtextarea.*;
164
165
*/
165
166
public static final int INTERNAL_CSS_VALUE = - 18 ;
166
167
168
+ /**
169
+ * Token type specifying we're in a valid multi-line template literal.
170
+ */
171
+ private static final int INTERNAL_IN_JS_TEMPLATE_LITERAL_VALID = - 23 ;
172
+
173
+ /**
174
+ * Token type specifying we're in an invalid multi-line template literal.
175
+ */
176
+ private static final int INTERNAL_IN_JS_TEMPLATE_LITERAL_INVALID = - 24 ;
177
+
167
178
/**
168
179
* Internal type denoting line ending in a CSS double-quote string.
169
180
* The state to return to is embedded in the actual end token type.
@@ -213,6 +224,8 @@ import org.fife.ui.rsyntaxtextarea.*;
213
224
*/
214
225
private static final int LANG_INDEX_CSS = 2 ;
215
226
227
+ private Stack<Boolean > varDepths;
228
+
216
229
217
230
/**
218
231
* Constructor. This must be here because JFlex does not generate a
@@ -371,6 +384,7 @@ import org.fife.ui.rsyntaxtextarea.*;
371
384
* @return The first <code >Token</code> in a linked list representing
372
385
* the syntax highlighted text.
373
386
*/
387
+ @Override
374
388
public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
375
389
376
390
resetTokenList();
@@ -457,6 +471,16 @@ import org.fife.ui.rsyntaxtextarea.*;
457
471
state = CSS_VALUE ;
458
472
languageIndex = LANG_INDEX_CSS ;
459
473
break ;
474
+ case INTERNAL_IN_JS_TEMPLATE_LITERAL_VALID :
475
+ state = JS_TEMPLATE_LITERAL ;
476
+ validJSString = true ;
477
+ languageIndex = LANG_INDEX_JS ;
478
+ break ;
479
+ case INTERNAL_IN_JS_TEMPLATE_LITERAL_INVALID :
480
+ state = JS_TEMPLATE_LITERAL ;
481
+ validJSString = false ;
482
+ languageIndex = LANG_INDEX_JS ;
483
+ break ;
460
484
default :
461
485
if (initialTokenType< - 1024 ) {
462
486
int main = - (- initialTokenType & 0xffffff00 );
@@ -573,7 +597,7 @@ LetterOrUnderscoreOrDash = ({LetterOrUnderscore}|[\-])
573
597
574
598
// JavaScript stuff.
575
599
EscapedSourceCharacter = ( "u" {HexDigit}{HexDigit}{HexDigit}{HexDigit} )
576
- NonSeparator = ( [^\t\f\r\n \ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\' ] | "#" | "\\ " )
600
+ NonSeparator = ( [^\t\f\r\n \ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\'\` ] | "#" | "\\ " )
577
601
IdentifierStart = ( {Letter} | "_" | "$" )
578
602
IdentifierPart = ( {IdentifierStart} | {Digit} |( "\\ " {EscapedSourceCharacter} ))
579
603
JS_MLCBegin = "/*"
@@ -599,6 +623,7 @@ JS_Identifier = ({IdentifierStart}{IdentifierPart}*)
599
623
JS_ErrorIdentifier = ( {NonSeparator} +)
600
624
JS_Regex = ( "/" ( [^ \*\\ /] | \\ .)( [^ /\\ ] | \\ .)* "/" [ gim] *)
601
625
626
+ JS_TemplateLiteralExprStart = ( "${" )
602
627
603
628
// CSS stuff.
604
629
CSS_SelectorPiece = (( "*" | "." | {LetterOrUnderscoreOrDash} )( {LetterOrUnderscoreOrDash} | "." | {Digit} )*)
@@ -650,6 +675,8 @@ URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
650
675
%state CSS_STRING
651
676
%state CSS_CHAR_LITERAL
652
677
%state CSS_C_STYLE_COMMENT
678
+ %state JS_TEMPLATE_LITERAL
679
+ %state JS_TEMPLATE_LITERAL_EXPR
653
680
654
681
655
682
%%
@@ -1015,6 +1042,7 @@ URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
1015
1042
/* String/Character literals. */
1016
1043
[ \' ] { start = zzMarkedPos- 1 ; validJSString = true ; yybegin(JS_CHAR ); }
1017
1044
[ \" ] { start = zzMarkedPos- 1 ; validJSString = true ; yybegin(JS_STRING ); }
1045
+ [ \` ] { start = zzMarkedPos- 1 ; validJSString = true ; yybegin(JS_TEMPLATE_LITERAL ); }
1018
1046
1019
1047
/* Comment literals. */
1020
1048
"/**/" { addToken(Token . COMMENT_MULTILINE ); }
@@ -1115,6 +1143,77 @@ URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
1115
1143
<<EOF>> { addToken(start,zzStartRead- 1 , Token . ERROR_CHAR ); addEndToken(INTERNAL_IN_JS ); return firstToken; }
1116
1144
}
1117
1145
1146
+ <JS_TEMPLATE_LITERAL> {
1147
+ [^\n \\\$\` ] + {}
1148
+ \\ x{HexDigit} {2} {}
1149
+ \\ x { /* Invalid latin-1 character \xXX */ validJSString = false ; }
1150
+ \\ u{HexDigit} {4} {}
1151
+ \\ u { /* Invalid Unicode character \\uXXXX */ validJSString = false ; }
1152
+ \\ . { /* Skip all escaped chars. */ }
1153
+
1154
+ {JS_TemplateLiteralExprStart} {
1155
+ addToken(start, zzStartRead - 1 , Token . LITERAL_BACKQUOTE );
1156
+ start = zzMarkedPos- 2 ;
1157
+ if (varDepths== null ) {
1158
+ varDepths = new Stack<Boolean > ();
1159
+ }
1160
+ else {
1161
+ varDepths. clear();
1162
+ }
1163
+ varDepths. push(Boolean . TRUE );
1164
+ yybegin(JS_TEMPLATE_LITERAL_EXPR );
1165
+ }
1166
+ "$" { /* Skip valid '$' that is not part of template literal expression start */ }
1167
+
1168
+ \` { int type = validJSString ? Token . LITERAL_BACKQUOTE : Token . ERROR_STRING_DOUBLE ; addToken(start,zzStartRead, type); yybegin(JAVASCRIPT ); }
1169
+
1170
+ /* Line ending in '\' => continue to next line, though not necessary in template strings. */
1171
+ \\ {
1172
+ if (validJSString) {
1173
+ addToken(start,zzStartRead, Token . LITERAL_BACKQUOTE );
1174
+ addEndToken(INTERNAL_IN_JS_TEMPLATE_LITERAL_VALID );
1175
+ }
1176
+ else {
1177
+ addToken(start,zzStartRead, Token . ERROR_STRING_DOUBLE );
1178
+ addEndToken(INTERNAL_IN_JS_TEMPLATE_LITERAL_INVALID );
1179
+ }
1180
+ return firstToken;
1181
+ }
1182
+ \n |
1183
+ <<EOF>> {
1184
+ if (validJSString) {
1185
+ addToken(start, zzStartRead - 1 , Token . LITERAL_BACKQUOTE );
1186
+ addEndToken(INTERNAL_IN_JS_TEMPLATE_LITERAL_VALID );
1187
+ }
1188
+ else {
1189
+ addToken(start,zzStartRead - 1 , Token . ERROR_STRING_DOUBLE );
1190
+ addEndToken(INTERNAL_IN_JS_TEMPLATE_LITERAL_INVALID );
1191
+ }
1192
+ return firstToken;
1193
+ }
1194
+ }
1195
+
1196
+ <JS_TEMPLATE_LITERAL_EXPR> {
1197
+ [^ \}\$ \n] + {}
1198
+ "}" {
1199
+ if (! varDepths. empty()) {
1200
+ varDepths. pop();
1201
+ if (varDepths. empty()) {
1202
+ addToken(start,zzStartRead, Token . VARIABLE );
1203
+ start = zzMarkedPos;
1204
+ yybegin(JS_TEMPLATE_LITERAL );
1205
+ }
1206
+ }
1207
+ }
1208
+ {JS_TemplateLiteralExprStart} { varDepths. push(Boolean . TRUE ); }
1209
+ "$" {}
1210
+ \n |
1211
+ <<EOF>> {
1212
+ // TODO: This isn't right. The expression and its depth should continue to the next line.
1213
+ addToken(start,zzStartRead- 1 , Token . VARIABLE ); addEndToken(INTERNAL_IN_JS_TEMPLATE_LITERAL_INVALID ); return firstToken;
1214
+ }
1215
+ }
1216
+
1118
1217
<JS_MLC> {
1119
1218
// JavaScript MLC's. This state is essentially Java's MLC state.
1120
1219
[^ hwf<\n \* ] + {}
0 commit comments