Skip to content
This repository was archived by the owner on Aug 10, 2022. It is now read-only.

Commit e8cff64

Browse files
committed
Fix bobbylight#215: JavaScript: Syntax highlight template literals
1 parent 9de32f4 commit e8cff64

16 files changed

+37053
-34341
lines changed

src/main/java/org/fife/ui/rsyntaxtextarea/CodeTemplateManager.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,7 @@ public synchronized int setTemplateDirectory(File dir) {
289289
new FileInputStream(files[i])));
290290
Object obj = d.readObject();
291291
if (!(obj instanceof CodeTemplate)) {
292+
d.close();
292293
throw new IOException("Not a CodeTemplate: " +
293294
files[i].getAbsolutePath());
294295
}

src/main/java/org/fife/ui/rsyntaxtextarea/modes/HTMLTokenMaker.flex

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ package org.fife.ui.rsyntaxtextarea.modes;
1010

1111
import java.io.*;
1212
import javax.swing.text.Segment;
13+
import java.util.Stack;
1314

1415
import 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.
575599
EscapedSourceCharacter = ("u"{HexDigit}{HexDigit}{HexDigit}{HexDigit})
576-
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\']|"#"|"\\")
600+
NonSeparator = ([^\t\f\r\n\ \(\)\{\}\[\]\;\,\.\=\>\<\!\~\?\:\+\-\*\/\&\|\^\%\"\'\`]|"#"|"\\")
577601
IdentifierStart = ({Letter}|"_"|"$")
578602
IdentifierPart = ({IdentifierStart}|{Digit}|("\\"{EscapedSourceCharacter}))
579603
JS_MLCBegin = "/*"
@@ -599,6 +623,7 @@ JS_Identifier = ({IdentifierStart}{IdentifierPart}*)
599623
JS_ErrorIdentifier = ({NonSeparator}+)
600624
JS_Regex = ("/"([^\*\\/]|\\.)([^/\\]|\\.)*"/"[gim]*)
601625

626+
JS_TemplateLiteralExprStart = ("${")
602627

603628
// CSS stuff.
604629
CSS_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

Comments
 (0)