@@ -10,6 +10,7 @@ package org.fife.ui.rsyntaxtextarea.modes;
1010
1111import java.io.* ;
1212import javax.swing.text.Segment ;
13+ import java.util.Stack ;
1314
1415import org.fife.ui.rsyntaxtextarea.* ;
1516
@@ -164,6 +165,16 @@ import org.fife.ui.rsyntaxtextarea.*;
164165 */
165166 public static final int INTERNAL_CSS_VALUE = - 18 ;
166167
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+
167178 /**
168179 * Internal type denoting line ending in a CSS double-quote string.
169180 * The state to return to is embedded in the actual end token type.
@@ -213,6 +224,8 @@ import org.fife.ui.rsyntaxtextarea.*;
213224 */
214225 private static final int LANG_INDEX_CSS = 2 ;
215226
227+ private Stack<Boolean > varDepths;
228+
216229
217230 /**
218231 * Constructor. This must be here because JFlex does not generate a
@@ -371,6 +384,7 @@ import org.fife.ui.rsyntaxtextarea.*;
371384 * @return The first <code >Token</code> in a linked list representing
372385 * the syntax highlighted text.
373386 */
387+ @Override
374388 public Token getTokenList(Segment text, int initialTokenType, int startOffset) {
375389
376390 resetTokenList();
@@ -457,6 +471,16 @@ import org.fife.ui.rsyntaxtextarea.*;
457471 state = CSS_VALUE ;
458472 languageIndex = LANG_INDEX_CSS ;
459473 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 ;
460484 default :
461485 if (initialTokenType< - 1024 ) {
462486 int main = - (- initialTokenType & 0xffffff00 );
@@ -573,7 +597,7 @@ LetterOrUnderscoreOrDash = ({LetterOrUnderscore}|[\-])
573597
574598// JavaScript stuff.
575599EscapedSourceCharacter = ( "u" {HexDigit}{HexDigit}{HexDigit}{HexDigit} )
576- NonSeparator = ( [^\t\f\r\n \ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\' ] | "#" | "\\ " )
600+ NonSeparator = ( [^\t\f\r\n \ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\'\` ] | "#" | "\\ " )
577601IdentifierStart = ( {Letter} | "_" | "$" )
578602IdentifierPart = ( {IdentifierStart} | {Digit} |( "\\ " {EscapedSourceCharacter} ))
579603JS_MLCBegin = "/*"
@@ -599,6 +623,7 @@ JS_Identifier = ({IdentifierStart}{IdentifierPart}*)
599623JS_ErrorIdentifier = ( {NonSeparator} +)
600624JS_Regex = ( "/" ( [^ \*\\ /] | \\ .)( [^ /\\ ] | \\ .)* "/" [ gim] *)
601625
626+ JS_TemplateLiteralExprStart = ( "${" )
602627
603628// CSS stuff.
604629CSS_SelectorPiece = (( "*" | "." | {LetterOrUnderscoreOrDash} )( {LetterOrUnderscoreOrDash} | "." | {Digit} )*)
@@ -650,6 +675,8 @@ URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
650675%state CSS_STRING
651676%state CSS_CHAR_LITERAL
652677%state CSS_C_STYLE_COMMENT
678+ %state JS_TEMPLATE_LITERAL
679+ %state JS_TEMPLATE_LITERAL_EXPR
653680
654681
655682%%
@@ -1015,6 +1042,7 @@ URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
10151042 /* String/Character literals. */
10161043 [ \' ] { start = zzMarkedPos- 1 ; validJSString = true ; yybegin(JS_CHAR ); }
10171044 [ \" ] { start = zzMarkedPos- 1 ; validJSString = true ; yybegin(JS_STRING ); }
1045+ [ \` ] { start = zzMarkedPos- 1 ; validJSString = true ; yybegin(JS_TEMPLATE_LITERAL ); }
10181046
10191047 /* Comment literals. */
10201048 "/**/" { addToken(Token . COMMENT_MULTILINE ); }
@@ -1115,6 +1143,77 @@ URL = (((https?|f(tp|ile))"://"|"www.")({URLCharacters}{URLEndCharacter})?)
11151143 <<EOF>> { addToken(start,zzStartRead- 1 , Token . ERROR_CHAR ); addEndToken(INTERNAL_IN_JS ); return firstToken; }
11161144}
11171145
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+
11181217<JS_MLC> {
11191218 // JavaScript MLC's. This state is essentially Java's MLC state.
11201219 [^ hwf<\n \* ] + {}
0 commit comments