diff --git a/lib/ui/highlighter.coffee b/lib/ui/highlighter.coffee
deleted file mode 100644
index 12c4629a..00000000
--- a/lib/ui/highlighter.coffee
+++ /dev/null
@@ -1,66 +0,0 @@
-_ = require 'underscore-plus'
-
-# Implementation identical to https://github.com/atom/highlights/blob/master/src/highlights.coffee,
-# but uses an externally provided grammar.
-module.exports =
- # Highlights some `text` according to the specified `grammar`.
- highlight: (text, grammar, {scopePrefix, block}={}) ->
- scopePrefix ?= ''
- block ?= false
- lineTokens = grammar.tokenizeLines(text)
-
- # Remove trailing newline
- if lineTokens.length > 0
- lastLineTokens = lineTokens[lineTokens.length - 1]
-
- if lastLineTokens.length is 1 and lastLineTokens[0].value is ''
- lineTokens.pop()
-
- html = ''
- for tokens in lineTokens
- scopeStack = []
- html += "<#{if block then "div" else "span"} class=\"line\">"
- for {value, scopes} in tokens
- value = ' ' unless value
- html = @updateScopeStack(scopeStack, scopes, html, scopePrefix)
- html += "#{@escapeString(value)}"
- html = @popScope(scopeStack, html) while scopeStack.length > 0
- html += "#{if block then "div" else "span"}>"
- html += '
'
- html
-
- escapeString: (string) ->
- string.replace /[&"'<> ]/g, (match) ->
- switch match
- when '&' then '&'
- when '"' then '"'
- when "'" then '''
- when '<' then '<'
- when '>' then '>'
- when ' ' then ' '
- else match
-
- updateScopeStack: (scopeStack, desiredScopes, html, scopePrefix) ->
- excessScopes = scopeStack.length - desiredScopes.length
- if excessScopes > 0
- html = @popScope(scopeStack, html) while excessScopes--
-
- # pop until common prefix
- for i in [scopeStack.length..0]
- break if _.isEqual(scopeStack[0...i], desiredScopes[0...i])
- html = @popScope(scopeStack, html)
-
- # push on top of common prefix until scopeStack is desiredScopes
- for j in [i...desiredScopes.length]
- html = @pushScope(scopeStack, desiredScopes[j], html, scopePrefix)
-
- html
-
- pushScope: (scopeStack, scope, html, scopePrefix) ->
- scopeStack.push(scope)
- className = scopePrefix + scope.replace(/\.+/g, " #{scopePrefix}")
- html += ""
-
- popScope: (scopeStack, html) ->
- scopeStack.pop()
- html += ''
diff --git a/lib/ui/highlighter.js b/lib/ui/highlighter.js
new file mode 100644
index 00000000..b7a916ed
--- /dev/null
+++ b/lib/ui/highlighter.js
@@ -0,0 +1,81 @@
+'use babel'
+import _ from 'underscore-plus';
+
+// Implementation identical to https://github.com/atom/highlights/blob/master/src/highlights.coffee,
+// but uses an externally provided grammar.
+ // Highlights some `text` according to the specified `grammar`.
+export function highlight(text, grammar, {scopePrefix, block}={}) {
+ if (scopePrefix == null) { scopePrefix = ''; }
+ if (block == null) { block = false; }
+ const lineTokens = grammar.tokenizeLines(text);
+
+ // Remove trailing newline
+ if (lineTokens.length > 0) {
+ const lastLineTokens = lineTokens[lineTokens.length - 1];
+
+ if ((lastLineTokens.length === 1) && (lastLineTokens[0].value === '')) {
+ lineTokens.pop();
+ }
+ }
+
+ let html = '';
+ for (let tokens of lineTokens) {
+ const scopeStack = [];
+ html += `<${block ? "div" : "span"} class=\"line\">`;
+ for (let {value, scopes} of tokens) {
+ if (!value) { value = ' '; }
+ html = this.updateScopeStack(scopeStack, scopes, html, scopePrefix);
+ html += `${this.escapeString(value)}`;
+ }
+ while (scopeStack.length > 0) { html = this.popScope(scopeStack, html); }
+ html += `${block ? "div" : "span"}>`;
+ }
+ html += '
';
+ return html;
+}
+
+export function escapeString(string) {
+ string.replace(/[&"'<> ]/g, function(match) {
+ switch (match) {
+ case '&': return '&';
+ case '"': return '"';
+ case "'": return ''';
+ case '<': return '<';
+ case '>': return '>';
+ case ' ': return ' ';
+ default: return match;
+ }
+ });
+}
+
+export function updateScopeStack(scopeStack, desiredScopes, html, scopePrefix) {
+ let excessScopes = scopeStack.length - desiredScopes.length;
+ if (excessScopes > 0) {
+ while (excessScopes--) { html = this.popScope(scopeStack, html); }
+ }
+
+ // pop until common prefix
+ let i;
+ for (i = scopeStack.length; i<=0; i--) {
+ if (_.isEqual(scopeStack.slice(0, i), desiredScopes.slice(0, i))) { break; }
+ html = this.popScope(scopeStack, html);
+ }
+
+ // push on top of common prefix until scopeStack is desiredScopes
+ for (let j = i; i <= desiredScopes.length; j++ ) {
+ html = this.pushScope(scopeStack, desiredScopes[j], html, scopePrefix);
+ }
+
+ return html;
+}
+
+export function pushScope(scopeStack, scope, html, scopePrefix) {
+ scopeStack.push(scope);
+ const className = scopePrefix + scope.replace(/\.+/g, ` ${scopePrefix}`);
+ html += ``;
+}
+
+export function popScope(scopeStack, html) {
+ scopeStack.pop();
+ html += '';
+}