From 47a65f608f485af32d2df12be489146ad67839e1 Mon Sep 17 00:00:00 2001
From: pommicket <pommicket@gmail.com>
Date: Thu, 21 Jul 2022 23:46:56 -0400
Subject: minor fixes, syntax highlighting for java & js

---
 README.md   |   5 +-
 buffer.c    |   9 +++
 keywords.h  |  59 ++++++++++++++
 keywords.py |  63 +++++++++++++--
 syntax.c    | 255 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 ted.cfg     |   2 +
 ted.h       |  13 ++++
 test.java   |  13 ++++
 test.js     |  12 +++
 9 files changed, 421 insertions(+), 10 deletions(-)
 create mode 100644 test.java
 create mode 100644 test.js

diff --git a/README.md b/README.md
index 8bdb1d4..da61dd5 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ in other editors.
 - Multiple tabs, each with a different file
 - Split screen
 - Auto-indent
-- Syntax highlighting for C, C++, HTML, LaTeX, Markdown, Python, and Rust.
+- Syntax highlighting for C, C++, HTML, Java, JavaScript, LaTeX, Markdown, Python, and Rust.
 - Find and replace (with regular expressions!)
 - Run build command, go to errors
 - Run any shell command
@@ -145,7 +145,8 @@ Then, open windows\_installer\\ted\\ted.sln, and build.
 <tr><td>1.0</td> <td>Bugfixes, small additional features, installers</td> <td>2021 Apr 20</td></tr>
 <tr><td>1.0r1</td> <td>Windows-specific bugfixes, update to new version of PCRE2</td> <td>2022 Jan 1</td></tr>
 <tr><td>1.0r2</td> <td>Various bugfixes involving closing tabs and windows</td> <td>2022 Mar 26</td></tr>
-<tr><td>1.0r3</td> <td>Better Tex syntax highlighting, move to cursor on backspace/delete</td> <td>2022 Jul 7</td></tr>
+<tr><td>1.0r3</td> <td>Better TeX syntax highlighting, move to cursor on backspace/delete</td> <td>2022 Jul 7</td></tr>
+<tr><td>1.1</td> <td>Minor fixes, syntax highlighting for JavaScript and Java</td> <td>2022 Jul 22</td></tr>
 </table>
 
 ## License
diff --git a/buffer.c b/buffer.c
index 4fa05ef..50a75fe 100644
--- a/buffer.c
+++ b/buffer.c
@@ -2183,6 +2183,13 @@ bool buffer_save(TextBuffer *buffer) {
 bool buffer_save_as(TextBuffer *buffer, char const *new_filename) {
 	char *prev_filename = buffer->filename;
 	if ((buffer->filename = buffer_strdup(buffer, new_filename))) {
+		buffer->view_only = false;
+		
+		// ensure whole file is syntax highlighted when saving with a different
+		//  file extension
+		buffer->frame_earliest_line_modified = 0;
+		buffer->frame_latest_line_modified = buffer->nlines - 1;
+		
 		if (buffer_save(buffer)) {
 			free(prev_filename);
 			return true;
@@ -2428,6 +2435,8 @@ void buffer_render(TextBuffer *buffer, Rect r) {
 	if (buffer->frame_latest_line_modified >= buffer->frame_earliest_line_modified
 		&& syntax_highlighting) {
 		// update syntax cache
+		if (buffer->frame_latest_line_modified >= buffer->nlines)
+			buffer->frame_latest_line_modified = buffer->nlines - 1;
 		Line *earliest = &buffer->lines[buffer->frame_earliest_line_modified];
 		Line *latest = &buffer->lines[buffer->frame_latest_line_modified];
 		Line *buffer_last_line = &buffer->lines[buffer->nlines - 1];
diff --git a/keywords.h b/keywords.h
index c880b77..723f8f6 100644
--- a/keywords.h
+++ b/keywords.h
@@ -131,6 +131,65 @@ static Keyword const *const syntax_all_keywords_rust[] = {
 	['A'] = syntax_keywords_rust_A, ['B'] = syntax_keywords_rust_B, ['C'] = syntax_keywords_rust_C, ['D'] = syntax_keywords_rust_D, ['E'] = syntax_keywords_rust_E, ['F'] = syntax_keywords_rust_F, ['I'] = syntax_keywords_rust_I, ['N'] = syntax_keywords_rust_N, ['O'] = syntax_keywords_rust_O, ['P'] = syntax_keywords_rust_P, ['R'] = syntax_keywords_rust_R, ['S'] = syntax_keywords_rust_S, ['T'] = syntax_keywords_rust_T, ['U'] = syntax_keywords_rust_U, ['V'] = syntax_keywords_rust_V, ['a'] = syntax_keywords_rust_a, ['b'] = syntax_keywords_rust_b, ['c'] = syntax_keywords_rust_c, ['d'] = syntax_keywords_rust_d, ['e'] = syntax_keywords_rust_e, ['f'] = syntax_keywords_rust_f, ['g'] = syntax_keywords_rust_g, ['i'] = syntax_keywords_rust_i, ['l'] = syntax_keywords_rust_l, ['m'] = syntax_keywords_rust_m, ['o'] = syntax_keywords_rust_o, ['p'] = syntax_keywords_rust_p, ['r'] = syntax_keywords_rust_r, ['s'] = syntax_keywords_rust_s, ['t'] = syntax_keywords_rust_t, ['u'] = syntax_keywords_rust_u, ['v'] = syntax_keywords_rust_v, ['w'] = syntax_keywords_rust_w, ['y'] = syntax_keywords_rust_y
 };
 
+static Keyword const syntax_keywords_javascript_A[8] = {{"AggregateError", SYNTAX_BUILTIN},{"Array", SYNTAX_BUILTIN},{"ArrayBuffer", SYNTAX_BUILTIN},{"AsyncFunction", SYNTAX_BUILTIN},{"AsyncGenerator", SYNTAX_BUILTIN},{"AsyncGeneratorFunction", SYNTAX_BUILTIN},{"Atomics", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_B[5] = {{"BigInt", SYNTAX_BUILTIN},{"BigInt64Array", SYNTAX_BUILTIN},{"BigUint64Array", SYNTAX_BUILTIN},{"Boolean", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_D[3] = {{"DataView", SYNTAX_BUILTIN},{"Date", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_E[3] = {{"Error", SYNTAX_BUILTIN},{"EvalError", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_F[5] = {{"FinalizationRegistry", SYNTAX_BUILTIN},{"Float32Array", SYNTAX_BUILTIN},{"Float64Array", SYNTAX_BUILTIN},{"Function", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_G[3] = {{"Generator", SYNTAX_BUILTIN},{"GeneratorFunction", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_I[7] = {{"Infinity", SYNTAX_BUILTIN},{"Int16Array", SYNTAX_BUILTIN},{"Int32Array", SYNTAX_BUILTIN},{"Int8Array", SYNTAX_BUILTIN},{"InternalError", SYNTAX_BUILTIN},{"Intl", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_J[2] = {{"JSON", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_M[3] = {{"Map", SYNTAX_BUILTIN},{"Math", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_N[3] = {{"NaN", SYNTAX_BUILTIN},{"Number", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_O[2] = {{"Object", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_P[3] = {{"Promise", SYNTAX_BUILTIN},{"Proxy", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_R[5] = {{"RangeError", SYNTAX_BUILTIN},{"ReferenceError", SYNTAX_BUILTIN},{"Reflect", SYNTAX_BUILTIN},{"RegExp", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_S[6] = {{"Set", SYNTAX_BUILTIN},{"SharedArrayBuffer", SYNTAX_BUILTIN},{"String", SYNTAX_BUILTIN},{"Symbol", SYNTAX_BUILTIN},{"SyntaxError", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_T[3] = {{"TypeError", SYNTAX_BUILTIN},{"TypedArray", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_U[6] = {{"URIError", SYNTAX_BUILTIN},{"Uint16Array", SYNTAX_BUILTIN},{"Uint32Array", SYNTAX_BUILTIN},{"Uint8Array", SYNTAX_BUILTIN},{"Uint8ClampedArray", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_W[5] = {{"WeakMap", SYNTAX_BUILTIN},{"WeakRef", SYNTAX_BUILTIN},{"WeakSet", SYNTAX_BUILTIN},{"WebAssembly", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_a[2] = {{"await", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_b[2] = {{"break", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_c[6] = {{"case", SYNTAX_KEYWORD},{"catch", SYNTAX_KEYWORD},{"class", SYNTAX_KEYWORD},{"const", SYNTAX_KEYWORD},{"continue", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_d[7] = {{"debugger", SYNTAX_KEYWORD},{"default", SYNTAX_KEYWORD},{"delete", SYNTAX_KEYWORD},{"do", SYNTAX_KEYWORD},{"decodeURI", SYNTAX_BUILTIN},{"decodeURIComponent", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_e[7] = {{"else", SYNTAX_KEYWORD},{"export", SYNTAX_KEYWORD},{"extends", SYNTAX_KEYWORD},{"encodeURI", SYNTAX_BUILTIN},{"encodeURIComponent", SYNTAX_BUILTIN},{"eval", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_f[5] = {{"finally", SYNTAX_KEYWORD},{"for", SYNTAX_KEYWORD},{"function", SYNTAX_KEYWORD},{"false", SYNTAX_CONSTANT}};
+static Keyword const syntax_keywords_javascript_g[2] = {{"globalThis", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_i[7] = {{"if", SYNTAX_KEYWORD},{"import", SYNTAX_KEYWORD},{"in", SYNTAX_KEYWORD},{"instanceof", SYNTAX_KEYWORD},{"isFinite", SYNTAX_BUILTIN},{"isNaN", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_l[2] = {{"let", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_n[3] = {{"new", SYNTAX_KEYWORD},{"null", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_p[3] = {{"parseFloat", SYNTAX_BUILTIN},{"parseInt", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_r[2] = {{"return", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_s[3] = {{"super", SYNTAX_KEYWORD},{"switch", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_t[6] = {{"this", SYNTAX_KEYWORD},{"throw", SYNTAX_KEYWORD},{"try", SYNTAX_KEYWORD},{"typeof", SYNTAX_KEYWORD},{"true", SYNTAX_CONSTANT}};
+static Keyword const syntax_keywords_javascript_u[2] = {{"undefined", SYNTAX_BUILTIN}};
+static Keyword const syntax_keywords_javascript_v[3] = {{"var", SYNTAX_KEYWORD},{"void", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_w[3] = {{"while", SYNTAX_KEYWORD},{"with", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_javascript_y[2] = {{"yield", SYNTAX_KEYWORD}};
+static Keyword const *const syntax_all_keywords_javascript[] = {
+	['A'] = syntax_keywords_javascript_A, ['B'] = syntax_keywords_javascript_B, ['D'] = syntax_keywords_javascript_D, ['E'] = syntax_keywords_javascript_E, ['F'] = syntax_keywords_javascript_F, ['G'] = syntax_keywords_javascript_G, ['I'] = syntax_keywords_javascript_I, ['J'] = syntax_keywords_javascript_J, ['M'] = syntax_keywords_javascript_M, ['N'] = syntax_keywords_javascript_N, ['O'] = syntax_keywords_javascript_O, ['P'] = syntax_keywords_javascript_P, ['R'] = syntax_keywords_javascript_R, ['S'] = syntax_keywords_javascript_S, ['T'] = syntax_keywords_javascript_T, ['U'] = syntax_keywords_javascript_U, ['W'] = syntax_keywords_javascript_W, ['a'] = syntax_keywords_javascript_a, ['b'] = syntax_keywords_javascript_b, ['c'] = syntax_keywords_javascript_c, ['d'] = syntax_keywords_javascript_d, ['e'] = syntax_keywords_javascript_e, ['f'] = syntax_keywords_javascript_f, ['g'] = syntax_keywords_javascript_g, ['i'] = syntax_keywords_javascript_i, ['l'] = syntax_keywords_javascript_l, ['n'] = syntax_keywords_javascript_n, ['p'] = syntax_keywords_javascript_p, ['r'] = syntax_keywords_javascript_r, ['s'] = syntax_keywords_javascript_s, ['t'] = syntax_keywords_javascript_t, ['u'] = syntax_keywords_javascript_u, ['v'] = syntax_keywords_javascript_v, ['w'] = syntax_keywords_javascript_w, ['y'] = syntax_keywords_javascript_y
+};
+
+static Keyword const syntax_keywords_java_a[3] = {{"abstract", SYNTAX_KEYWORD},{"assert", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_b[4] = {{"boolean", SYNTAX_KEYWORD},{"break", SYNTAX_KEYWORD},{"byte", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_c[7] = {{"case", SYNTAX_KEYWORD},{"catch", SYNTAX_KEYWORD},{"char", SYNTAX_KEYWORD},{"class", SYNTAX_KEYWORD},{"const", SYNTAX_KEYWORD},{"continue", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_d[4] = {{"default", SYNTAX_KEYWORD},{"do", SYNTAX_KEYWORD},{"double", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_e[4] = {{"else", SYNTAX_KEYWORD},{"enum", SYNTAX_KEYWORD},{"extends", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_f[6] = {{"final", SYNTAX_KEYWORD},{"finally", SYNTAX_KEYWORD},{"float", SYNTAX_KEYWORD},{"for", SYNTAX_KEYWORD},{"false", SYNTAX_CONSTANT}};
+static Keyword const syntax_keywords_java_g[2] = {{"goto", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_i[7] = {{"if", SYNTAX_KEYWORD},{"implements", SYNTAX_KEYWORD},{"import", SYNTAX_KEYWORD},{"instanceof", SYNTAX_KEYWORD},{"int", SYNTAX_KEYWORD},{"interface", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_l[2] = {{"long", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_n[4] = {{"native", SYNTAX_KEYWORD},{"new", SYNTAX_KEYWORD},{"null", SYNTAX_CONSTANT}};
+static Keyword const syntax_keywords_java_p[5] = {{"package", SYNTAX_KEYWORD},{"private", SYNTAX_KEYWORD},{"protected", SYNTAX_KEYWORD},{"public", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_r[2] = {{"return", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_s[7] = {{"short", SYNTAX_KEYWORD},{"static", SYNTAX_KEYWORD},{"strictfp", SYNTAX_KEYWORD},{"super", SYNTAX_KEYWORD},{"switch", SYNTAX_KEYWORD},{"synchronized", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_t[7] = {{"this", SYNTAX_KEYWORD},{"throw", SYNTAX_KEYWORD},{"throws", SYNTAX_KEYWORD},{"transient", SYNTAX_KEYWORD},{"try", SYNTAX_KEYWORD},{"true", SYNTAX_CONSTANT}};
+static Keyword const syntax_keywords_java_v[4] = {{"var", SYNTAX_KEYWORD},{"void", SYNTAX_KEYWORD},{"volatile", SYNTAX_KEYWORD}};
+static Keyword const syntax_keywords_java_w[2] = {{"while", SYNTAX_KEYWORD}};
+static Keyword const *const syntax_all_keywords_java[] = {
+	['a'] = syntax_keywords_java_a, ['b'] = syntax_keywords_java_b, ['c'] = syntax_keywords_java_c, ['d'] = syntax_keywords_java_d, ['e'] = syntax_keywords_java_e, ['f'] = syntax_keywords_java_f, ['g'] = syntax_keywords_java_g, ['i'] = syntax_keywords_java_i, ['l'] = syntax_keywords_java_l, ['n'] = syntax_keywords_java_n, ['p'] = syntax_keywords_java_p, ['r'] = syntax_keywords_java_r, ['s'] = syntax_keywords_java_s, ['t'] = syntax_keywords_java_t, ['v'] = syntax_keywords_java_v, ['w'] = syntax_keywords_java_w
+};
+
 static Keyword const syntax_keywords_python_A[4] = {{"ArithmeticError", SYNTAX_BUILTIN},{"AssertionError", SYNTAX_BUILTIN},{"AttributeError", SYNTAX_BUILTIN}};
 static Keyword const syntax_keywords_python_B[6] = {{"BaseException", SYNTAX_BUILTIN},{"BlockingIOError", SYNTAX_BUILTIN},{"BrokenPipeError", SYNTAX_BUILTIN},{"BufferError", SYNTAX_BUILTIN},{"BytesWarning", SYNTAX_BUILTIN}};
 static Keyword const syntax_keywords_python_C[6] = {{"ChildProcessError", SYNTAX_BUILTIN},{"ConnectionAbortedError", SYNTAX_BUILTIN},{"ConnectionError", SYNTAX_BUILTIN},{"ConnectionRefusedError", SYNTAX_BUILTIN},{"ConnectionResetError", SYNTAX_BUILTIN}};
diff --git a/keywords.py b/keywords.py
index 2413dd9..b7f7432 100755
--- a/keywords.py
+++ b/keywords.py
@@ -27,6 +27,13 @@ def output_keywords(file, keywords, language):
     file.write('\t'+', '.join(["['{}'] = syntax_keywords_{}_{}".format(c, language, c) for c in sorted(keywords.keys())]) + '\n')
     file.write('};\n\n')
 
+def cant_overlap(*args):
+	for i in range(len(args)):
+		for j in range(i):
+			intersection = set(args[i]).intersection(args[j])
+			if intersection:
+				raise ValueError("Argument {} intersects with {}: {}".format(i, j, intersection))
+
 constants_c = ['CHAR_BIT', 'CHAR_MAX', 'CHAR_MIN', 'DBL_DIG', 'DBL_EPSILON', 'DBL_HAS_SUBNORM', 'DBL_MANT_DIG', 'DBL_MAX', 
     'DBL_MAX_10_EXP', 'DBL_MAX_EXP', 'DBL_MIN', 'DBL_MIN_EXP', 'DBL_TRUE_MIN', 'DECIMAL_DIG', 'EXIT_FAILURE', 'EXIT_SUCCESS', 
     'FLT_DECIMAL_DIG', 'FLT_DIG', 'FLT_EVAL_METHOD', 'FLT_HAS_SUBNORM', 'FLT_MANT_DIG', 'FLT_MAX', 'FLT_MAX_10_EXP', 'FLT_MAX_EXP', 
@@ -151,12 +158,7 @@ keywords_cpp = [
     'xor', 'xor_eq',
 	'bool', 'wchar_t',
 ]
-def cant_overlap(*args):
-	for i in range(len(args)):
-		for j in range(i):
-			intersection = set(args[i]).intersection(args[j])
-			if intersection:
-				raise ValueError("Argument {} intersects with {}: {}".format(i, j, intersection))
+
 cant_overlap(keywords_c, keywords_cpp)
 
 keywords_rust = [
@@ -253,6 +255,51 @@ builtins_html = []
 for attr in attributes_html:
 	builtins_html.append(attr + '=')
 
+
+keywords_javascript = [
+	'break', 'case', 'catch', 'class', 'const',
+	'continue', 'debugger', 'default', 'delete',
+	'do', 'else', 'export', 'extends', 'finally',
+	'for', 'function', 'if', 'import', 'in',
+	'instanceof', 'new', 'return', 'super',
+	'switch', 'this', 'throw', 'try', 'typeof',
+	'var', 'void', 'while', 'with', 'yield',
+	'let', 'await'
+]
+
+constants_javascript = [
+	'true', 'false'
+]
+
+builtins_javascript = [
+	'AggregateError','Array','ArrayBuffer','AsyncFunction','AsyncGenerator','AsyncGeneratorFunction',
+	'Atomics','BigInt','BigInt64Array','BigUint64Array','Boolean','DataView','Date','decodeURI',
+	'decodeURIComponent','encodeURI','encodeURIComponent','Error','eval','EvalError','FinalizationRegistry',
+	'Float32Array','Float64Array','Function','Generator','GeneratorFunction','globalThis','Infinity',
+	'Int16Array','Int32Array','Int8Array','InternalError','Intl','isFinite','isNaN','JSON','Map','Math',
+	'NaN','Number','Object','parseFloat','parseInt','Promise','Proxy','RangeError','ReferenceError',
+	'Reflect','RegExp','Set','SharedArrayBuffer','String','Symbol','SyntaxError','TypedArray',
+	'TypeError','Uint16Array','Uint32Array','Uint8Array','Uint8ClampedArray','undefined',
+	'URIError','WeakMap','WeakRef','WeakSet','WebAssembly', 'null'
+]
+
+keywords_java = [
+	'abstract', 'continue', 'for', 'new', 'switch',
+	'assert', 'default', 'goto', 'package', 'synchronized',
+	'boolean', 'do', 'if', 'private', 'this',
+	'break', 'double', 'implements', 'protected', 'throw',
+	'byte', 'else', 'import', 'public', 'throws',
+	'case', 'enum', 'instanceof', 'return', 'transient',
+	'catch', 'extends', 'int', 'short', 'try',
+	'char', 'final', 'interface', 'static', 'var',
+	'class', 'finally', 'long', 'strictfp', 'void',
+	'const', 'float', 'native', 'super', 'volatile', 'while'
+]
+
+constants_java = [
+	'true', 'false', 'null'
+]
+
 file = open('keywords.h', 'w')
 file.write('''// keywords for all languages ted supports
 // This file was auto-generated by keywords.py
@@ -268,6 +315,7 @@ def label(kwds, l):
 cant_overlap(keywords_c, constants_c, builtins_c)
 cant_overlap(keywords_rust, builtins_rust, constants_rust)
 cant_overlap(keywords_python, builtins_python)
+cant_overlap(keywords_javascript, builtins_javascript, constants_javascript)
 c_things = label(keywords_c, SYNTAX_KEYWORD) + label(constants_c, SYNTAX_CONSTANT) + label(builtins_c, SYNTAX_BUILTIN)
 output_keywords(file, c_things, 'c')
 cpp_things = c_things + label(keywords_cpp, SYNTAX_KEYWORD)
@@ -275,6 +323,9 @@ cpp_things.remove((SYNTAX_BUILTIN, 'bool'))
 cpp_things.remove((SYNTAX_BUILTIN, 'wchar_t'))
 output_keywords(file, cpp_things, 'cpp')
 output_keywords(file, label(keywords_rust, SYNTAX_KEYWORD) + label(builtins_rust, SYNTAX_BUILTIN) + label(constants_rust, SYNTAX_CONSTANT), 'rust')
+output_keywords(file, label(keywords_javascript, SYNTAX_KEYWORD) + label(builtins_javascript, SYNTAX_BUILTIN) +
+	label(constants_javascript, SYNTAX_CONSTANT), 'javascript')
+output_keywords(file, label(keywords_java, SYNTAX_KEYWORD) + label(constants_java, SYNTAX_CONSTANT), 'java')
 output_keywords(file, label(keywords_python, SYNTAX_KEYWORD) + label(builtins_python, SYNTAX_BUILTIN), 'python')
 output_keywords(file, label(builtins_html, SYNTAX_BUILTIN), 'html')
 output_keywords(file, label(constants_config, SYNTAX_CONSTANT), 'config')
diff --git a/syntax.c b/syntax.c
index b3504bc..99aae45 100644
--- a/syntax.c
+++ b/syntax.c
@@ -20,6 +20,8 @@ char const *language_comment_start(Language l) {
 		return "/* ";
 	case LANG_RUST:
 	case LANG_CPP:
+	case LANG_JAVASCRIPT:
+	case LANG_JAVA:
 		return "// ";
 	case LANG_CONFIG:
 	case LANG_PYTHON:
@@ -497,12 +499,12 @@ static void syntax_highlight_python(SyntaxState *state, char32_t const *line, u3
 		case '\'':
 		case '"': {
 			bool dbl_quoted = c == '"';
-			bool is_triple = i < line_len - 2 &&
+			bool is_triple = i+2 < line_len &&
 				line[i+1] == c && line[i+2] == c;
 			if (in_string) {
 				if (!string_is_multiline || is_triple) {
-					// end of string
 					if (string_is_dbl_quoted == dbl_quoted && backslashes % 2 == 0) {
+						// end of string
 						in_string = false;
 						if (char_types) {
 							char_types[i] = SYNTAX_STRING;
@@ -1046,6 +1048,249 @@ static void syntax_highlight_config(SyntaxState *state, char32_t const *line, u3
 	}
 }
 
+static void syntax_highlight_javascript(SyntaxState *state, char32_t const *line, u32 line_len, SyntaxCharType *char_types) {
+	(void)state;
+	bool string_is_template = (*state & SYNTAX_STATE_JAVASCRIPT_TEMPLATE_STRING) != 0;
+	bool in_multiline_comment = (*state & SYNTAX_STATE_JAVASCRIPT_MULTILINE_COMMENT) != 0;
+	bool string_is_dbl_quoted = false;
+	bool in_number = false;
+	bool in_string = string_is_template;
+	uint backslashes = 0;
+	
+	for (u32 i = 0; i < line_len; ++i) {
+		char32_t c = line[i];
+		bool dealt_with = false;
+		switch (c) {
+		case '/':
+			if (!in_string) {
+				if (i > 0) {
+					if (line[i-1] == '*') {
+						// end of multi line comment
+						in_multiline_comment = false;
+						if (char_types) char_types[i] = SYNTAX_COMMENT;
+						dealt_with = true;
+					}
+				}
+				if (!dealt_with && i+1 < line_len) {
+					if (line[i+1] == '/') {
+						// single line comment
+						if (char_types) memset(&char_types[i], SYNTAX_COMMENT, line_len - i);
+						i = line_len - 1;
+						dealt_with = true;
+					} else if (line[i+1] == '*') {
+						// multi line comment
+						in_multiline_comment = true;
+						if (char_types) char_types[i] = SYNTAX_COMMENT;
+						dealt_with = true;
+					}
+				}
+			}
+			break;
+		case '\'':
+		case '"':
+		case '`': {
+			if (!in_multiline_comment) {
+				bool dbl_quoted = c == '"';
+				bool template = c == '`';
+				if (in_string) {
+					if (string_is_dbl_quoted == dbl_quoted && string_is_template == template && backslashes % 2 == 0) {
+						// end of string
+						in_string = false;
+						if (char_types) char_types[i] = SYNTAX_STRING;
+						dealt_with = true;
+					}
+				} else {
+					// start of string
+					string_is_dbl_quoted = dbl_quoted;
+					string_is_template = template;
+					in_string = true;
+				}
+			}
+		} break;
+		case ANY_DIGIT:
+			if (char_types && !in_string && !in_number && !in_multiline_comment) {
+				in_number = true;
+				if (i) {
+					if (line[i - 1] == '.') {
+						// support .6, for example
+						char_types[i - 1] = SYNTAX_CONSTANT;
+					} else if (is32_ident(line[i - 1])) {
+						// actually, this isn't a number. it's something like a*6* or u3*2*.
+						in_number = false;
+					}
+				}
+			}
+			break;
+		case '\\':
+			++backslashes;
+			break;
+		default:
+			if ((i && is32_ident(line[i - 1])) || !is32_ident(c))
+				break; // can't be a keyword on its own.
+			
+			if (char_types && !in_string && !in_number && !in_multiline_comment) {
+				u32 keyword_len = syntax_keyword_len(LANG_JAVASCRIPT, line, i, line_len);
+				Keyword const *keyword = syntax_keyword_lookup(syntax_all_keywords_javascript, arr_count(syntax_all_keywords_javascript),
+					&line[i], keyword_len);
+				if (keyword) {
+					SyntaxCharType type = keyword->type;
+					for (size_t j = 0; j < keyword_len; ++j) {
+						char_types[i++] = type;
+					}
+					--i; // we'll increment i from the for loop
+					dealt_with = true;
+					break;
+				}
+			}
+			break;
+		}
+		if (c != '\\') backslashes = 0;
+		if (in_number && !syntax_number_continues(line, line_len, i))
+			in_number = false;
+		
+		if (char_types && !dealt_with) {
+			SyntaxCharType type = SYNTAX_NORMAL;
+			if (in_multiline_comment)
+				type = SYNTAX_COMMENT;
+			else if (in_string)
+				type = SYNTAX_STRING;
+			else if (in_number)
+				type = SYNTAX_CONSTANT;
+			char_types[i] = type;
+		}
+	}
+	*state = 0;
+	if (in_string && string_is_template)
+		*state |= SYNTAX_STATE_JAVASCRIPT_TEMPLATE_STRING;
+	if (in_multiline_comment)
+		*state |= SYNTAX_STATE_JAVASCRIPT_MULTILINE_COMMENT;
+}
+
+static void syntax_highlight_java(SyntaxState *state_ptr, char32_t const *line, u32 line_len, SyntaxCharType *char_types) {
+	SyntaxState state = *state_ptr;
+	bool in_string = false;
+	bool in_multiline_comment = (state & SYNTAX_STATE_CPP_MULTI_LINE_COMMENT) != 0;
+	bool in_char = false;
+	bool in_number = false;
+	
+	int backslashes = 0;
+	for (u32 i = 0; i < line_len; ++i) {
+
+		// are there 1/2 characters left in the line?
+		bool has_1_char =  i + 1 < line_len;
+		
+		bool dealt_with = false;
+		
+		char32_t c = line[i];
+		
+		switch (c) {
+		case '\\':
+			++backslashes;
+			break;
+		case '/':
+			if (!in_multiline_comment && !in_string && !in_char && has_1_char) {
+				if (line[i + 1] == '/') {
+						if (char_types) memset(&char_types[i], SYNTAX_COMMENT, line_len - i);
+						i = line_len - 1;
+						dealt_with = true;
+				} else if (line[i + 1] == '*') {
+					in_multiline_comment = true; // /*
+				}
+			} else if (in_multiline_comment) {
+				if (i > 0 && line[i - 1] == '*') {
+					// */
+					in_multiline_comment = false;
+					if (char_types) {
+						dealt_with = true;
+						char_types[i] = SYNTAX_COMMENT;
+					}
+				}
+			}
+			break;
+		case '"':
+			if (in_string && backslashes % 2 == 0) {
+				in_string = false;
+				if (char_types) {
+					dealt_with = true;
+					char_types[i] = SYNTAX_STRING;
+				}
+			} else if (!in_multiline_comment && !in_char) {
+				in_string = true;
+			}
+			break;
+		case '\'':
+			if (in_char && backslashes % 2 == 0) {
+				in_char = false;
+				if (char_types) {
+					dealt_with = true;
+					char_types[i] = SYNTAX_CHARACTER;
+				}
+			} else if (!in_multiline_comment && !in_string) {
+				in_char = true;
+			}
+			break;
+		case ANY_DIGIT:
+			// a number!
+			if (char_types && !in_multiline_comment && !in_string && !in_number && !in_char) {
+				in_number = true;
+				if (i) {
+					if (line[i - 1] == '.') {
+						// support .6, for example
+						char_types[i - 1] = SYNTAX_CONSTANT;
+					} else if (is32_ident(line[i - 1])) {
+						// actually, this isn't a number. it's something like a*6* or u3*2*.
+						in_number = false;
+					}
+				}
+			}
+			break;
+		default: {
+			if ((i && is32_ident(line[i - 1])) || !is32_ident(c))
+				break; // can't be a keyword on its own.
+			
+			// keywords don't matter for advancing the state
+			if (char_types && !in_multiline_comment && !in_number && !in_string && !in_char) {
+				u32 keyword_len = syntax_keyword_len(LANG_JAVA, line, i, line_len);
+				Keyword const *keyword = syntax_keyword_lookup(syntax_all_keywords_java, arr_count(syntax_all_keywords_java),
+						&line[i], keyword_len);
+				
+				
+				if (keyword) {
+					SyntaxCharType type = keyword->type;
+					for (size_t j = 0; j < keyword_len; ++j) {
+						char_types[i++] = type;
+					}
+					--i; // we'll increment i from the for loop
+					dealt_with = true;
+					break;
+				}
+			}
+		} break;
+		}
+		if (c != '\\') backslashes = 0;
+		if (in_number && !syntax_number_continues(line, line_len, i)) {
+			in_number = false;
+		}
+
+		if (char_types && !dealt_with) {
+			SyntaxCharType type = SYNTAX_NORMAL;
+			if (in_multiline_comment)
+				type = SYNTAX_COMMENT;
+			else if (in_string)
+				type = SYNTAX_STRING;
+			else if (in_char)
+				type = SYNTAX_CHARACTER;
+			else if (in_number)
+				type = SYNTAX_CONSTANT;
+
+			char_types[i] = type;
+		}
+	}
+	*state_ptr = (SyntaxState)(
+		(in_multiline_comment * SYNTAX_STATE_JAVA_MULTILINE_COMMENT)
+	);
+}
+
 // This is the main syntax highlighting function. It will determine which colors to use for each character.
 // Rather than returning colors, it returns a character type (e.g. comment) which can be converted to a color.
 // To highlight multiple lines, start out with a zeroed SyntaxState, and pass a pointer to it each time.
@@ -1080,6 +1325,12 @@ void syntax_highlight(SyntaxState *state, Language lang, char32_t const *line, u
 	case LANG_CONFIG:
 		syntax_highlight_config(state, line, line_len, char_types);
 		break;
+	case LANG_JAVASCRIPT:
+		syntax_highlight_javascript(state, line, line_len, char_types);
+		break;
+	case LANG_JAVA:
+		syntax_highlight_java(state, line, line_len, char_types);
+		break;
 	case LANG_COUNT: assert(0); break;
 	}
 }
diff --git a/ted.cfg b/ted.cfg
index 87b1c95..27b33e5 100644
--- a/ted.cfg
+++ b/ted.cfg
@@ -227,3 +227,5 @@ Tex = .tex
 Markdown = .md 
 HTML = .html, .php, .xml, .xhtml
 Config = .cfg
+Javascript = .js
+Java = .java
diff --git a/ted.h b/ted.h
index 941508f..2400aa0 100644
--- a/ted.h
+++ b/ted.h
@@ -41,6 +41,15 @@ enum {
 	SYNTAX_STATE_HTML_COMMENT = 0x01u
 };
 
+enum {
+	SYNTAX_STATE_JAVASCRIPT_TEMPLATE_STRING = 0x01u,
+	SYNTAX_STATE_JAVASCRIPT_MULTILINE_COMMENT = 0x02u,
+};
+
+enum {
+	SYNTAX_STATE_JAVA_MULTILINE_COMMENT = 0x01u
+};
+
 typedef u8 SyntaxState;
 
 // If you are adding new languages, DO NOT change the constant values
@@ -55,6 +64,8 @@ ENUM_U16 {
 	LANG_MARKDOWN = 6,
 	LANG_HTML = 7,
 	LANG_CONFIG = 8, // .cfg files, e.g. ted.cfg
+	LANG_JAVASCRIPT = 9,
+	LANG_JAVA = 10,
 	LANG_COUNT
 } ENUM_U16_END(Language);
 
@@ -73,6 +84,8 @@ static LanguageName const language_names[] = {
 	{LANG_MARKDOWN, "Markdown"},
 	{LANG_HTML, "HTML"},
 	{LANG_CONFIG, "Config"},
+	{LANG_JAVASCRIPT, "Javascript"},
+	{LANG_JAVA, "Java"},
 };
 
 static_assert_if_possible(arr_count(language_names) == LANG_COUNT)
diff --git a/test.java b/test.java
new file mode 100644
index 0000000..7183aed
--- /dev/null
+++ b/test.java
@@ -0,0 +1,13 @@
+class Test {
+	public static void main(String[] args) {
+		/* hello!
+		everyone
+		this
+		is
+		a test*/
+		String x = "hello, world!\"";
+		System.out.println(x + 
+			"yes\n\\"+
+			x);
+	}
+}
diff --git a/test.js b/test.js
new file mode 100644
index 0000000..ad95614
--- /dev/null
+++ b/test.js
@@ -0,0 +1,12 @@
+const c = `template
+strings
+yay
+`;
+let y = 34 * parseInt('hello\\\'\\"') + "'\\";
+/*
+comm
+ent
+
+*/a//yes
+/*
+*//no
-- 
cgit v1.2.3