Repository navigation
Expand file tree
/
Copy pathangular-codearea.js
More file actions
115 lines (99 loc) · 4.49 KB
/
Copy pathangular-codearea.js
File metadata and controls
115 lines (99 loc) · 4.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
/*
* Lightweight textarea syntax highlighter for AngularJS
* (C) 2016 Ingo van Lil <[email protected]>
*
* Based upon LDT (http://kueblc.github.io/LDT/)
* (C) 2012 Colin Kuebler <[email protected]>
*/
angular
.module('ivl.angular-codearea', ['ng'])
.directive('ivlCodearea', function() {
// directive template: transparent textarea in front of a <pre> layer with highlighted text
var template =
'<div class="codearea">' +
' <pre><span ng-repeat="token in tokens" ng-class="token.class" ng-bind="token.text"></span>' +
' </pre>' +
' <textarea ng-model="ngModel" ng-trim="false" spellcheck="false"></textarea>' +
'</div>';
// default rules: no highlighting
var DEFAULT_RULES = {
whitespace: /\s+/,
other: /\S+/
};
// example highlighting rules for Lua code
var LUA_RULES = {
whitespace: /\s+/,
comment: /--.*/,
op: /[#%\(\)\*\+,-\./:;<=>\[\]\{\}]/,
keyword: /(and|break|do|else|elseif|end|false|for|function|if|in|local|nil|not|or|repeat|require|return|then|true|until|while)(?![a-zA-Z0-9_])/,
function: /(assert|collectgarbage|error|ipairs|pcall|print|printTable|string\.find|string\.format|string\.len|string\.lower|string\.match|string\.reverse|string\.sub|string\.upper|table\.concat|table\.insert|table\.remove|table\.sort|tonumber|tostring|type|unpack)(?![a-zA-Z0-9_])/,
id: /[a-zA-Z_][a-zA-Z0-9_]*/,
number: /[0-9]+(\.[0-9]+)?|\.[0-9]+/,
string: /"[^"]*"?/,
other: /\S+/
};
// maps value of the "syntax" attribute to a set of rules
var RULES = {
lua: LUA_RULES
};
return {
restrict: 'E',
template: template,
scope: { ngModel: '=', syntax: '@' },
link: function(scope) {
scope.tokens = [];
var rules = RULES[scope.syntax] || DEFAULT_RULES;
var ruleSrcs = [];
var ruleMap = {};
for (var rule in rules) {
var src = rules[rule].source;
ruleSrcs.push(src);
ruleMap[rule] = new RegExp('^(' + src + ')$');
}
var parseRe = new RegExp(ruleSrcs.join('|'), 'g');
function identify(token) {
for (var rule in ruleMap) {
if (ruleMap[rule].test(token)) return rule;
}
return "other";
}
function update(text) {
text = text || "";
var results = text.match(parseRe) || [];
// count unchanged tokens from the front, remove from results
var firstDiff = 0;
while (firstDiff < scope.tokens.length &&
firstDiff < results.length &&
scope.tokens[firstDiff].text === results[firstDiff])
{
++firstDiff;
}
results.splice(0, firstDiff);
// count unchanged tokens from the rear, pop from results
var lastDiff = scope.tokens.length - 1;
while (lastDiff > firstDiff &&
results.length > 0 &&
scope.tokens[lastDiff].text == results[results.length - 1])
{
--lastDiff;
results.pop();
}
// replace changed tokens with new ones
var newTokens = results.map(function(token) {
return { text: token, class: identify(token) };
});
// limit number of DOM elements to be generated per digest cycle to
// avoid stalling slower browsers
var MAX_NEW_TOKENS = 50;
if (newTokens.length > MAX_NEW_TOKENS) {
newTokens.splice(MAX_NEW_TOKENS);
setTimeout(() => update(text));
}
// ES6: scope.tokens.splice(firstDiff, lastDiff - firstDiff + 1, ...newTokens);
scope.tokens.splice.apply(scope.tokens, [firstDiff, lastDiff - firstDiff + 1].concat(newTokens));
scope.$applyAsync();
}
scope.$watch('ngModel', update);
}
}
});