├── editor.css
├── demo
├── treeEditor.js
├── number.js
├── demo1.html
├── demo2.html
├── demo.css
└── twoDeeEditor.js
├── README.md
├── tests.html
├── 3rdparty
├── keymaster.js
└── ohm.min.js
└── editor.js
/editor.css:
--------------------------------------------------------------------------------
1 | editor {
2 | position: relative;
3 | white-space: pre;
4 | overflow: auto;
5 | user-select: none;
6 | -webkit-user-select: none;
7 | -moz-user-select: none;
8 | }
9 |
10 | editor > editorCursor {
11 | display: none;
12 | position: absolute;
13 | border-left: 2px solid slateblue;
14 | opacity: .75;
15 | }
16 |
17 | editor:focus editorCursor {
18 | display: inline-block;
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/demo/treeEditor.js:
--------------------------------------------------------------------------------
1 | function createTreeEditor() {
2 | var self = editor.create();
3 | self.setAttribute('id', 'tree');
4 |
5 | var g = ohm.namespace('demo').getGrammar('Arithmetic');
6 |
7 | var render = {
8 | _default: function(ruleName, args) {
9 | for (var idx = 0; idx < args.length; idx++) {
10 | args[idx].value;
11 | }
12 | if (ruleName.indexOf('_') === -1) {
13 | self.nest(this.interval, document.createElement(ruleName));
14 | }
15 | }
16 | };
17 |
18 | self.render = function(text) {
19 | var thunk = g.matchContents(text, 'Expr');
20 | if (thunk) {
21 | thunk(render);
22 | setTimeout(function() { self.removeAttribute('class'); }, 0);
23 | } else if (text.length > 0) {
24 | setTimeout(function() { self.setAttribute('class', 'oops'); }, 0);
25 | }
26 | };
27 |
28 | return self;
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/demo/number.js:
--------------------------------------------------------------------------------
1 | function createNumber() {
2 | var node = document.createElement('number');
3 | node.onwheel = node.onmousewheel = function(e) {
4 | var delta = e.deltaX !== undefined ? -e.deltaX : e.wheelDeltaX;
5 | if (delta !== 0) {
6 | var floatMode = e.metaKey;
7 | var text = node.innerText !== undefined ? node.innerText : node.textContent;
8 | var oldValue = parseFloat(text);
9 | var newValue = oldValue + (floatMode ? 0.01 : 1) * delta / Math.abs(delta);
10 | newValue = newValue.toFixed(floatMode ? 2 : 0);
11 | while (this.firstChild) {
12 | this.removeChild(this.firstChild);
13 | }
14 | this.appendChild(document.createTextNode(newValue));
15 | this.dispatchEvent(new CustomEvent('valuechange', {detail: {oldValue: oldValue, newValue: newValue}}));
16 | }
17 | return false;
18 | };
19 | return node;
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/demo/demo1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | one view
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
51 |
52 |
53 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/demo/demo2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | two views
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
52 |
53 |
54 |
79 |
80 |
81 |
82 |
--------------------------------------------------------------------------------
/demo/demo.css:
--------------------------------------------------------------------------------
1 | editor {
2 | display: block;
3 | padding: 8pt;
4 | border: 1px solid rgba(0, 0, 0, .1);
5 | box-shadow: 2px 2px 16px 0px rgba(0, 0, 0, .5);
6 | overflow: auto;
7 | font-family: Times, "Times New Roman", serif;
8 | font-size: 18pt;
9 | outline: none;
10 | margin-bottom: 15px;
11 | }
12 |
13 | editor:focus {
14 | border: 1px solid rgba(0, 0, 255, .2);
15 | }
16 |
17 | editor.oops, editor.oops:focus {
18 | border: 1px solid rgba(255, 0, 0, .3);
19 | }
20 |
21 | editor unrecognizedInput {
22 | color: red;
23 | }
24 |
25 | #twoDee {
26 | min-height: 150px;
27 | background: azure;
28 | }
29 |
30 | #twoDee::after {
31 | content: attr(value);
32 | color: slateblue;
33 | }
34 |
35 | #twoDee ident {
36 | font-style: italic;
37 | }
38 |
39 | #twoDee:focus paren > gray {
40 | font-size: 100%;
41 | opacity: 1;
42 | }
43 |
44 | #twoDee:not(:focus) paren > gray {
45 | font-size: 100%;
46 | opacity: 1;
47 | }
48 |
49 | #twoDee:focus power > exponent > paren > gray,
50 | #twoDee:focus fraction > numerator > paren > gray,
51 | #twoDee:focus fraction > denominator > paren > gray {
52 | opacity: .25 !important;
53 | }
54 |
55 | #twoDee:not(:focus) power > exponent > paren > gray,
56 | #twoDee:not(:focus) fraction > numerator > paren > gray,
57 | #twoDee:not(:focus) fraction > denominator > paren > gray {
58 | font-size: 0;
59 | }
60 |
61 | #twoDee fraction {
62 | display: inline-block;
63 | text-align: center;
64 | vertical-align: middle;
65 | }
66 |
67 | #twoDee numerator > gray {
68 | vertical-align: bottom;
69 | }
70 |
71 | #twoDee numerator {
72 | display: inline-block;
73 | padding: 0 2pt;
74 | }
75 |
76 | #twoDee denominator {
77 | display: block;
78 | border-top: 2px solid black;
79 | padding: 0 2pt;
80 | }
81 |
82 | #twoDee power > exponent {
83 | font-size: 65%;
84 | vertical-align: super;
85 | }
86 |
87 | #twoDee:not(:focus) gray {
88 | font-size: 0;
89 | }
90 |
91 | #twoDee:focus gray {
92 | opacity: .25;
93 | }
94 |
95 | #twoDee number:hover {
96 | text-shadow: 0px 0px 8pt rgba(255, 255, 0, 1);
97 | }
98 |
99 | #tree {
100 | min-height: 300px;
101 | background: ivory;
102 | }
103 |
104 | #tree *:not(editorCursor):not(editorSpan) {
105 | display: inline-block;
106 | font-size: 18pt;
107 | margin: 0 1pt 1pt 1pt;
108 | text-align: center;
109 | vertical-align: top;
110 | transition: .5s ease;
111 | }
112 |
113 | #tree *:not(editorCursor):not(editorSpan)::after {
114 | font-family: "PT Sans", Arial, Helvetica, sans-serif;
115 | display: block;
116 | font-size: 10pt;
117 | color: slateblue;
118 | margin-top: 2pt;
119 | border-top: 1pt solid slateblue;
120 | transition: opacity .5s ease;
121 | }
122 |
123 | #tree.oops *:not(editorCursor):not(editorSpan)::after {
124 | opacity: .25;
125 | transition: opacity .5s ease;
126 | }
127 |
128 | #tree expr::after { content: 'expr'; }
129 | #tree addExpr::after { content: 'addExpr'; }
130 | #tree plus::after { content: 'plus'; }
131 | #tree minus::after { content: 'minus'; }
132 | #tree mulExpr::after { content: 'mulExpr'; }
133 | #tree times::after { content: 'times'; }
134 | #tree divide::after { content: 'divide'; }
135 | #tree expExpr::after { content: 'expExpr'; }
136 | #tree power::after { content: 'power'; }
137 | #tree priExpr::after { content: 'priExpr'; }
138 | #tree paren::after { content: 'paren'; }
139 | #tree pos::after { content: 'pos'; }
140 | #tree neg::after { content: 'neg'; }
141 | #tree ident::after { content: 'ident'; }
142 | #tree number::after { content: 'number'; }
143 |
144 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | "Text++" Interfaces (or: Editable Views of Textual Inputs)
2 | ==========================================================
3 |
4 | [](http://alexwarth.github.io/projects/textPlusPlus/demo/demo1.html)
5 | [](http://alexwarth.github.io/projects/textPlusPlus/demo/demo2.html)
6 |
7 | [Bret Victor's Scrubbing Calculator](http://worrydream.com/ScrubbingCalculator/) lets you type mathematical expressions, and as you type, some of your text gets "super powers": it automagically turns into user interface components. E.g., you can scrub numbers to change their values, but they're still text and can be edited as such. It feels pretty fluid and magical. I spent some time thinking about how to make this kind of thing easier to implement.
8 |
9 | I also got interested in the idea of creating editable views of textual inputs. I'm not a big fan of the two-pane user interfaces that are popular these days where you type some text on the left, and see the rendered output on the right. (These are a huge step backwards considering [WYSIWYG](https://en.wikipedia.org/wiki/WYSIWYG) has been around since the 70s!)
10 |
11 | This is a little experiment I did along these lines. The idea is that the input is really just text, but as you type it gets rendered to make it look like the thing you're trying to create. You still see all of the characters that you typed, so you can edit or delete them if you want, but they're de-emphasized so that they don't obscure the view.
12 |
13 | A few things that are worth pointing out:
14 |
15 | * I only render the input when it can be parsed -- when it's not valid, all I'm doing is inserting the new text into the structure that was produced from rendering the last good input, and making the new stuff red to show that something's not right.
16 |
17 | * It's easy to add superpowers to the rendered, like in Bret's scrubbing calculator. Notice the numbers "glow" when you mouse over them, and they're also scrubbable.
18 |
19 | * Some of the characters are only displayed when the "text++" widget has focus. E.g., near the end of the video, when I click outside of the widget, the "^" and "()"s disappear b/c they're not really necessary for viewing. They're only useful for editing, so you should only see them while editing. (I got this idea from Glen Chiacchieri's [Legible Mathematics](http://glench.com/LegibleMathematics/).)
20 |
21 | * The library that I wrote to make this kind of interface (see the code in this repo) is pretty straightforward to use. The basic idea is that you get to wrap parts of the text in DOM nodes (which are styled using CSS) in a bottom-up way, while you're parsing it. The case where the input doesn't parse is handled automatically.
22 |
23 | Of course this is just a little prototype, and there are lots of open questions, things you'd have to do to make it more usable, etc. But it's a start, and I haven't seen a lot of work in this area yet. It would be great if eventually, people could create text++ interfaces that are as slick as the expression editor in the [Demos calculator](https://www.desmos.com/calculator), without doing so much work. That's a good motivating example, I think.
24 |
25 | Another application of this idea that I'd like to experiment with at some point is a hybrid of text-based and tile-based programming interfaces. Then you'd get the nice UI features from tile-based interfaces (like drop zones) without the associated "structural inertia" and lack of fluidity -- because you'd be able to enter or edit everything as though it were text, breaking tile boundaries, etc., whenever it made sense to do so.
26 |
27 | -- Alex Warth, sometime in Q1 of 2014
28 |
29 | Notes
30 | -----
31 |
32 | In this prototype, the programmer:
33 |
34 | * gives the framework a DOM tree
35 | * nodes w/ a "source" property are atomic w.r.t. to editing,
36 | the framework never looks inside them.
37 | * backspace deletes whole thing, all at once
38 | * this is useful for doing decoration stuff (source = '')
39 | * also useful for representing keywords as graphics (ST-like up-arrow image would have source = 'return')
40 | * text nodes are fine, and allow cursor and editing operations inside
41 |
42 | while the framework:
43 |
44 | * converts programmer-provided DOM to a string
45 | * keeps track of cursor position
46 | * displays cursor
47 | * processes user events (keyboard and pointer events)
48 | * mutates user-provided DOM when key press events happen
49 | (but also asks programmer for a new DOM using updated string)
50 |
--------------------------------------------------------------------------------
/demo/twoDeeEditor.js:
--------------------------------------------------------------------------------
1 | function createTwoDeeEditor() {
2 | var self = editor.create();
3 | self.setAttribute('id', 'twoDee');
4 |
5 | var g = ohm.namespace('demo').getGrammar('Arithmetic');
6 |
7 | var constants = {pi: Math.PI, e: Math.E};
8 | var interpret = {
9 | Expr: function(expr) { return expr.value; },
10 | AddExpr: function(expr) { return expr.value; },
11 | AddExpr_plus: function(x, op, y) { return x.value + y.value; },
12 | AddExpr_minus: function(x, op, y) { return x.value - y.value; },
13 | MulExpr: function(expr) { return expr.value; },
14 | MulExpr_times: function(x, op, y) { return x.value * y.value; },
15 | MulExpr_divide: function(x, op, y) { return x.value / y.value; },
16 | ExpExpr: function(expr) { return expr.value; },
17 | ExpExpr_power: function(x, op, y) { return Math.pow(x.value, y.value); },
18 | PriExpr: function(expr) { return expr.value; },
19 | PriExpr_paren: function(open, e, close) { return e.value; },
20 | ident: function() { return constants[this.interval.contents]; },
21 | number: function() { return parseFloat(this.interval.contents); }
22 | };
23 |
24 | var render = {
25 | Expr: function(expr) {
26 | expr.value;
27 | },
28 |
29 | AddExpr: function(expr) {
30 | expr.value;
31 | },
32 | AddExpr_plus: function(x, op, y) {
33 | x.value;
34 | op.value;
35 | y.value;
36 | self.nest(op.interval, document.createElement('operator'));
37 | self.nest(this.interval, document.createElement('plus'));
38 | },
39 | AddExpr_minus: function(x, op, y) {
40 | x.value;
41 | op.value;
42 | y.value;
43 | self.decorate(op.interval, document.createElement('operator'), '\u2212');
44 | self.nest(this.interval, document.createElement('minus'));
45 | },
46 | MulExpr: function(expr) {
47 | return expr.value;
48 | },
49 | MulExpr_times: function(x, op, y) {
50 | x.value;
51 | op.value;
52 | y.value;
53 | self.decorate(op.interval, document.createElement('operator'), '\u00D7');
54 | self.nest(this.interval, document.createElement('times'));
55 | },
56 | MulExpr_divide: function(x, op, y) {
57 | x.value;
58 | op.value;
59 | y.value;
60 | self.decorate(op.interval, document.createElement('span'));
61 | self.nest(x.interval.coverageWith(op.interval), document.createElement('numerator'));
62 | self.nest(y.interval, document.createElement('denominator'));
63 | self.nest(this.interval, document.createElement('fraction'));
64 | },
65 | ExpExpr: function(expr) {
66 | return expr.value;
67 | },
68 | ExpExpr_power: function(x, op, y) {
69 | x.value;
70 | op.value;
71 | y.value;
72 | self.nest(x.interval, document.createElement('theBase'));
73 | self.nest(op.interval, document.createElement('gray'));
74 | self.nest(op.interval.coverageWith(y.interval), document.createElement('exponent'));
75 | self.nest(this.interval, document.createElement('power'));
76 | },
77 | PriExpr: function(expr) {
78 | return expr.value;
79 | },
80 | PriExpr_paren: function(open, e, close) {
81 | open.value;
82 | e.value;
83 | close.value;
84 | self.nest(open.interval, document.createElement('gray'));
85 | self.nest(close.interval, document.createElement('gray'));
86 | self.nest(this.interval, document.createElement('paren'));
87 | },
88 | ident: function() {
89 | if (this.interval.contents === 'pi') {
90 | self.decorate(this.interval, document.createElement('ident'), '\u03C0');
91 | } else {
92 | self.nest(this.interval, document.createElement('ident'));
93 | }
94 | },
95 | number: function() {
96 | var n = createNumber();
97 | n.valueChangeEventListener = function(e) {
98 | var pos = self.getCursorPos(n) + self.numCharsIn(n);
99 | self.changed('', self.getText(), true);
100 | self.setCursorPos(pos);
101 | };
102 | n.addEventListener('valuechange', n.valueChangeEventListener, false);
103 | numbers.push(n);
104 | self.nest(this.interval, n);
105 | }
106 | };
107 |
108 | var numbers = [];
109 | self.render = function(text) {
110 | var thunk = g.matchContents(text, 'Expr');
111 | if (thunk) {
112 | while (numbers.length > 0) {
113 | var n = numbers.pop();
114 | n.removeEventListener('valuechange', n.valueChangeEventListener);
115 | }
116 | this.removeAttribute('class');
117 | this.setAttribute('value', ' = ' + thunk(interpret));
118 | thunk(render);
119 | } else {
120 | if (text.length > 0) {
121 | this.setAttribute('class', 'oops');
122 | }
123 | this.setAttribute('value', '');
124 | }
125 | };
126 |
127 | return self;
128 | }
129 |
130 |
--------------------------------------------------------------------------------
/tests.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
72 |
73 |
74 |
268 |
272 |
273 |
274 |
275 |
--------------------------------------------------------------------------------
/3rdparty/keymaster.js:
--------------------------------------------------------------------------------
1 | // keymaster.js
2 | // (c) 2011-2013 Thomas Fuchs
3 | // keymaster.js may be freely distributed under the MIT license.
4 |
5 | ;(function(global){
6 | var k,
7 | _handlers = {},
8 | _mods = { 16: false, 18: false, 17: false, 91: false },
9 | _scope = 'all',
10 | // modifier keys
11 | _MODIFIERS = {
12 | '⇧': 16, shift: 16,
13 | '⌥': 18, alt: 18, option: 18,
14 | '⌃': 17, ctrl: 17, control: 17,
15 | '⌘': 91, command: 91
16 | },
17 | // special keys
18 | _MAP = {
19 | backspace: 8, tab: 9, clear: 12,
20 | enter: 13, 'return': 13,
21 | esc: 27, escape: 27, space: 32,
22 | left: 37, up: 38,
23 | right: 39, down: 40,
24 | del: 46, 'delete': 46,
25 | home: 36, end: 35,
26 | pageup: 33, pagedown: 34,
27 | ',': 188, '.': 190, '/': 191,
28 | '`': 192, '-': 189, '=': 187,
29 | ';': 186, '\'': 222,
30 | '[': 219, ']': 221, '\\': 220
31 | },
32 | code = function(x){
33 | return _MAP[x] || x.toUpperCase().charCodeAt(0);
34 | },
35 | _downKeys = [];
36 |
37 | for(k=1;k<20;k++) _MAP['f'+k] = 111+k;
38 |
39 | // IE doesn't support Array#indexOf, so have a simple replacement
40 | function index(array, item){
41 | var i = array.length;
42 | while(i--) if(array[i]===item) return i;
43 | return -1;
44 | }
45 |
46 | // for comparing mods before unassignment
47 | function compareArray(a1, a2) {
48 | if (a1.length != a2.length) return false;
49 | for (var i = 0; i < a1.length; i++) {
50 | if (a1[i] !== a2[i]) return false;
51 | }
52 | return true;
53 | }
54 |
55 | var modifierMap = {
56 | 16:'shiftKey',
57 | 18:'altKey',
58 | 17:'ctrlKey',
59 | 91:'metaKey'
60 | };
61 | function updateModifierKey(event) {
62 | for(k in _mods) _mods[k] = event[modifierMap[k]];
63 | };
64 |
65 | // handle keydown event
66 | function dispatch(event) {
67 | var key, handler, k, i, modifiersMatch, scope;
68 | key = event.keyCode;
69 |
70 | if (index(_downKeys, key) == -1) {
71 | _downKeys.push(key);
72 | }
73 |
74 | // if a modifier key, set the key. property to true and return
75 | if(key == 93 || key == 224) key = 91; // right command on webkit, command on Gecko
76 | if(key in _mods) {
77 | _mods[key] = true;
78 | // 'assignKey' from inside this closure is exported to window.key
79 | for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = true;
80 | return;
81 | }
82 | updateModifierKey(event);
83 |
84 | // see if we need to ignore the keypress (filter() can can be overridden)
85 | // by default ignore key presses if a select, textarea, or input is focused
86 | if(!assignKey.filter.call(this, event)) return;
87 |
88 | // abort if no potentially matching shortcuts found
89 | if (!(key in _handlers)) return;
90 |
91 | scope = getScope();
92 |
93 | // for each potential shortcut
94 | for (i = 0; i < _handlers[key].length; i++) {
95 | handler = _handlers[key][i];
96 |
97 | // see if it's in the current scope
98 | if(handler.scope == scope || handler.scope == 'all'){
99 | // check if modifiers match if any
100 | modifiersMatch = handler.mods.length > 0;
101 | for(k in _mods)
102 | if((!_mods[k] && index(handler.mods, +k) > -1) ||
103 | (_mods[k] && index(handler.mods, +k) == -1)) modifiersMatch = false;
104 | // call the handler and stop the event if neccessary
105 | if((handler.mods.length == 0 && !_mods[16] && !_mods[18] && !_mods[17] && !_mods[91]) || modifiersMatch){
106 | if(handler.method(event, handler)===false){
107 | if(event.preventDefault) event.preventDefault();
108 | else event.returnValue = false;
109 | if(event.stopPropagation) event.stopPropagation();
110 | if(event.cancelBubble) event.cancelBubble = true;
111 | }
112 | }
113 | }
114 | }
115 | };
116 |
117 | // unset modifier keys on keyup
118 | function clearModifier(event){
119 | var key = event.keyCode, k,
120 | i = index(_downKeys, key);
121 |
122 | // remove key from _downKeys
123 | if (i >= 0) {
124 | _downKeys.splice(i, 1);
125 | }
126 |
127 | if(key == 93 || key == 224) key = 91;
128 | if(key in _mods) {
129 | _mods[key] = false;
130 | for(k in _MODIFIERS) if(_MODIFIERS[k] == key) assignKey[k] = false;
131 | }
132 | };
133 |
134 | function resetModifiers() {
135 | for(k in _mods) _mods[k] = false;
136 | for(k in _MODIFIERS) assignKey[k] = false;
137 | };
138 |
139 | // parse and assign shortcut
140 | function assignKey(key, scope, method){
141 | var keys, mods;
142 | keys = getKeys(key);
143 | if (method === undefined) {
144 | method = scope;
145 | scope = 'all';
146 | }
147 |
148 | // for each shortcut
149 | for (var i = 0; i < keys.length; i++) {
150 | // set modifier keys if any
151 | mods = [];
152 | key = keys[i].split('+');
153 | if (key.length > 1){
154 | mods = getMods(key);
155 | key = [key[key.length-1]];
156 | }
157 | // convert to keycode and...
158 | key = key[0]
159 | key = code(key);
160 | // ...store handler
161 | if (!(key in _handlers)) _handlers[key] = [];
162 | _handlers[key].push({ shortcut: keys[i], scope: scope, method: method, key: keys[i], mods: mods });
163 | }
164 | };
165 |
166 | // unbind all handlers for given key in current scope
167 | function unbindKey(key, scope) {
168 | var multipleKeys, keys,
169 | mods = [],
170 | i, j, obj;
171 |
172 | multipleKeys = getKeys(key);
173 |
174 | for (j = 0; j < multipleKeys.length; j++) {
175 | keys = multipleKeys[j].split('+');
176 |
177 | if (keys.length > 1) {
178 | mods = getMods(keys);
179 | key = keys[keys.length - 1];
180 | }
181 |
182 | key = code(key);
183 |
184 | if (scope === undefined) {
185 | scope = getScope();
186 | }
187 | if (!_handlers[key]) {
188 | return;
189 | }
190 | for (i = 0; i < _handlers[key].length; i++) {
191 | obj = _handlers[key][i];
192 | // only clear handlers if correct scope and mods match
193 | if (obj.scope === scope && compareArray(obj.mods, mods)) {
194 | _handlers[key][i] = {};
195 | }
196 | }
197 | }
198 | };
199 |
200 | // Returns true if the key with code 'keyCode' is currently down
201 | // Converts strings into key codes.
202 | function isPressed(keyCode) {
203 | if (typeof(keyCode)=='string') {
204 | keyCode = code(keyCode);
205 | }
206 | return index(_downKeys, keyCode) != -1;
207 | }
208 |
209 | function getPressedKeyCodes() {
210 | return _downKeys.slice(0);
211 | }
212 |
213 | function filter(event){
214 | var tagName = (event.target || event.srcElement).tagName;
215 | // ignore keypressed in any elements that support keyboard data input
216 | return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
217 | }
218 |
219 | // initialize key. to false
220 | for(k in _MODIFIERS) assignKey[k] = false;
221 |
222 | // set current scope (default 'all')
223 | function setScope(scope){ _scope = scope || 'all' };
224 | function getScope(){ return _scope || 'all' };
225 |
226 | // delete all handlers for a given scope
227 | function deleteScope(scope){
228 | var key, handlers, i;
229 |
230 | for (key in _handlers) {
231 | handlers = _handlers[key];
232 | for (i = 0; i < handlers.length; ) {
233 | if (handlers[i].scope === scope) handlers.splice(i, 1);
234 | else i++;
235 | }
236 | }
237 | };
238 |
239 | // abstract key logic for assign and unassign
240 | function getKeys(key) {
241 | var keys;
242 | key = key.replace(/\s/g, '');
243 | keys = key.split(',');
244 | if ((keys[keys.length - 1]) == '') {
245 | keys[keys.length - 2] += ',';
246 | }
247 | return keys;
248 | }
249 |
250 | // abstract mods logic for assign and unassign
251 | function getMods(key) {
252 | var mods = key.slice(0, key.length - 1);
253 | for (var mi = 0; mi < mods.length; mi++)
254 | mods[mi] = _MODIFIERS[mods[mi]];
255 | return mods;
256 | }
257 |
258 | // cross-browser events
259 | function addEvent(object, event, method) {
260 | if (object.addEventListener)
261 | object.addEventListener(event, method, false);
262 | else if(object.attachEvent)
263 | object.attachEvent('on'+event, function(){ method(window.event) });
264 | };
265 |
266 | // set the handlers globally on document
267 | addEvent(document, 'keydown', function(event) { dispatch(event) }); // Passing _scope to a callback to ensure it remains the same by execution. Fixes #48
268 | addEvent(document, 'keyup', clearModifier);
269 |
270 | // reset modifiers to false whenever the window is (re)focused.
271 | addEvent(window, 'focus', resetModifiers);
272 |
273 | // store previously defined key
274 | var previousKey = global.key;
275 |
276 | // restore previously defined key and return reference to our key object
277 | function noConflict() {
278 | var k = global.key;
279 | global.key = previousKey;
280 | return k;
281 | }
282 |
283 | // set window.key and window.key.set/get/deleteScope, and the default filter
284 | global.key = assignKey;
285 | global.key.setScope = setScope;
286 | global.key.getScope = getScope;
287 | global.key.deleteScope = deleteScope;
288 | global.key.filter = filter;
289 | global.key.isPressed = isPressed;
290 | global.key.getPressedKeyCodes = getPressedKeyCodes;
291 | global.key.noConflict = noConflict;
292 | global.key.unbind = unbindKey;
293 |
294 | if(typeof module !== 'undefined') module.exports = key;
295 |
296 | })(this);
297 |
298 |
--------------------------------------------------------------------------------
/editor.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | TODO:
4 |
5 | -- The cursor should always be visible
6 | * Right now I'm using node.scrollIntoView(). This is annoying b/c it always happens, even when the editor
7 | doesn't have focus.
8 | * The check to see if it's already showing doesn't seem to be right.
9 |
10 | -- up and down arrow keys
11 | -- selections
12 | -- copy and paste
13 |
14 | */
15 |
16 | var k = key.noConflict();
17 |
18 | // ---------------------------
19 |
20 | var editor = {
21 | create: function() {
22 | var ed = document.createElement('editor');
23 | for (var p in editorProto) {
24 | if (editorProto.hasOwnProperty(p)) {
25 | ed[p] = editorProto[p];
26 | }
27 | }
28 | editorInit.call(ed);
29 | return ed;
30 | }
31 | };
32 |
33 | var editorProto = {
34 | cursorLeft: function() {
35 | var info = this.getCursorInfo();
36 | if (this.cursorPos === 0) {
37 | // no-op
38 | } else if (info.offset > 0) {
39 | this.cursorPos -= info.nodeAtCursor instanceof Text ? 1 : this.numCharsIn(info.nodeAtCursor);
40 | } else {
41 | var prevNode = this.getNodeBefore(info.nodeAtCursor);
42 | this.cursorPos -= prevNode instanceof Text ? 1 : this.numCharsIn(prevNode);
43 | }
44 | this.updateCursor();
45 | this.showState();
46 | },
47 |
48 | cursorRight: function() {
49 | var info = this.getCursorInfo();
50 | var text = this.getText();
51 | if (this.cursorPos === text.length) {
52 | // no-op
53 | } else {
54 | this.cursorPos += info.nodeAtCursor instanceof Text ? 1 : this.numCharsIn(info.nodeAtCursor);
55 | }
56 | this.updateCursor();
57 | this.showState();
58 | },
59 |
60 | cursorUp: function() {
61 | // TODO
62 | },
63 |
64 | cursorDown: function() {
65 | // TODO
66 | },
67 |
68 | home: function() {
69 | var pos = this.getCursorPos();
70 | if (pos > 0) {
71 | var text = this.getText();
72 | while (pos > 0 && text.charAt(pos - 1) !== '\n') {
73 | pos--;
74 | }
75 | this.setCursorPos(pos);
76 | this.updateCursor();
77 | this.showState();
78 | }
79 | },
80 |
81 | end: function() {
82 | var pos = this.getCursorPos();
83 | var text = this.getText();
84 | if (pos < text.length) {
85 | while (pos < text.length && text.charAt(pos) !== '\n') {
86 | pos++;
87 | }
88 | this.setCursorPos(pos);
89 | this.updateCursor();
90 | this.showState();
91 | }
92 | },
93 |
94 | backspace: function() {
95 | var info = this.getCursorInfo();
96 | if (this.cursorPos === 0) {
97 | // op-op
98 | } else {
99 | var oldText = this.getText();
100 | var node, offset;
101 | if (info.offset === 0) {
102 | node = this.getNodeBefore(info.nodeAtCursor);
103 | offset = this.numCharsIn(node);
104 | } else {
105 | node = info.nodeAtCursor;
106 | offset = info.offset;
107 | }
108 | if (node instanceof Text) {
109 | node.parentNode.replaceChild(
110 | document.createTextNode(node.data.substr(0, offset - 1) + node.data.substr(offset)),
111 | node
112 | );
113 | this.cursorPos--;
114 | } else {
115 | node.parentNode.removeChild(node);
116 | this.cursorPos -= offset;
117 | }
118 | this.changed(oldText, this.getText(), true);
119 | }
120 | },
121 |
122 | getNodeBefore: function(node) {
123 | var prevNode;
124 | var done = false;
125 | this.collectText(
126 | this,
127 | function(t, n) {
128 | if (n === node) {
129 | done = true;
130 | } else if (!done) {
131 | prevNode = n;
132 | }
133 | }
134 | );
135 | return prevNode;
136 | },
137 |
138 | changed: function(oldText, text, fireEvents) {
139 | var self = this;
140 | this.nestCalled = false;
141 | this.beforeNest = function() {
142 | self.nestCalled = true;
143 | self._setText(text);
144 | self.beforeNest = function() {};
145 | };
146 | this.render(text);
147 | if (!this.nestCalled && text.length === 0) {
148 | self._setText('');
149 | }
150 | this.updateCursor();
151 | this.showState();
152 | if (fireEvents) {
153 | this.dispatchEvent(new CustomEvent('valuechange', {detail: {oldValue: oldText, newValue: text}}));
154 | }
155 | },
156 |
157 | nest: function(interval, node) {
158 | var self = this;
159 | this.beforeNest();
160 | var pos = 0;
161 | node.interval = interval;
162 | var intervalIsEmpty = interval.startIdx === interval.endIdx;
163 | this.collectText(
164 | this,
165 | function(t, n) {
166 | if (intervalIsEmpty && interval.startIdx === pos) {
167 | n.parentNode.insertBefore(node, n);
168 | } else if (interval.startIdx <= pos && pos + t.length <= interval.endIdx) {
169 | if (node.parentNode === null) {
170 | n.parentNode.replaceChild(node, n);
171 | }
172 | node.appendChild(n);
173 | } else if (n instanceof Text && (pos <= interval.startIdx && interval.startIdx < pos + t.length ||
174 | pos < interval.endIdx && interval.endIdx <= pos + t.length)) {
175 | if (node.parentNode === null) {
176 | n.parentNode.replaceChild(node, n);
177 | } else {
178 | n.parentNode.removeChild(n);
179 | }
180 | var startPos = Math.max(interval.startIdx, pos);
181 | var endPos = Math.min(interval.endIdx, pos + t.length);
182 | var beforeText = document.createTextNode(t.substr(0, startPos - pos));
183 | var nestedText = document.createTextNode(t.substr(startPos - pos, endPos - startPos));
184 | var afterText = document.createTextNode(t.substr(endPos - pos));
185 | node.appendChild(nestedText);
186 | node.parentNode.insertBefore(beforeText, node);
187 | node.parentNode.insertBefore(afterText, node.nextSibling);
188 | }
189 | pos += t.length;
190 | },
191 | true
192 | );
193 | if (intervalIsEmpty && interval.startIdx === pos) {
194 | this.appendChild(node);
195 | }
196 |
197 | if (node.parentNode === null) {
198 | throw 'uh-oh, never added node! ' + node.tagName;
199 | }
200 |
201 | return node;
202 | },
203 |
204 | decorate: function(interval, node /*, childNode1, childNode2, ... */) {
205 | for (var idx = 2; idx < arguments.length; idx++) {
206 | var childNode = arguments[idx];
207 | node.appendChild(typeof childNode === 'string' ? document.createTextNode(childNode) : childNode);
208 | }
209 | var lastChild = node.lastChild;
210 | this.nest(interval, node);
211 | while (node.lastChild !== lastChild) {
212 | node.removeChild(node.lastChild);
213 | }
214 | node.setAttribute('source', interval.contents);
215 | return node;
216 | },
217 |
218 | createInterval: function(startIdx, endIdx) {
219 | var text = this.getText();
220 | return {
221 | startIdx: startIdx,
222 | endIdx: endIdx,
223 | source: text,
224 | contents: text.substr(startIdx, endIdx - startIdx)
225 | };
226 | },
227 |
228 | render: function(text) {},
229 |
230 | setText: function(text, fireEvents) {
231 | var oldText = this.getText();
232 | this._setText(text);
233 | this.setCursorPos(text.length);
234 | this.changed(oldText, text, fireEvents);
235 | },
236 |
237 | _setText: function(text) {
238 | while (this.firstChild) {
239 | this.removeChild(this.firstChild);
240 | }
241 | this.appendChild(document.createTextNode(text));
242 | },
243 |
244 | showState: function() {
245 | console.log('[' + this.getText(true) + '], cursorPos =', this.cursorPos);
246 | },
247 |
248 | getCursorPos: function(optNode) {
249 | if (optNode) {
250 | var currPos = 0, pos;
251 | this.collectText(
252 | this,
253 | function(t, n) {
254 | if (pos === undefined && optNode.contains(n)) {
255 | pos = currPos;
256 | }
257 | currPos += t.length;
258 | }
259 | );
260 | return pos;
261 | } else {
262 | return this.cursorPos;
263 | }
264 | },
265 |
266 | setCursorPos: function(pos) {
267 | this.cursorPos = Math.max(0, Math.min(pos, this.getText().length));
268 | this.updateCursor();
269 | this.showState();
270 | },
271 |
272 | numCharsIn: function(node) {
273 | var count = 0;
274 | this.collectText(
275 | node,
276 | function(t) {
277 | count += t.length;
278 | }
279 | );
280 | return count;
281 | },
282 |
283 | getTextIn: function(node) {
284 | var ts = [];
285 | this.collectText(
286 | node,
287 | function(t) {
288 | ts.push(t);
289 | }
290 | );
291 | return ts.join('');
292 | },
293 |
294 | getText: function(showCursor) {
295 | var ts = [];
296 | var n = this.cursorPos;
297 | if (n === 0 && showCursor) {
298 | ts.push('|');
299 | }
300 | this.collectText(
301 | this,
302 | function(t) {
303 | if (0 < n && n <= t.length && showCursor) {
304 | ts.push(t.substr(0, n));
305 | ts.push('|');
306 | ts.push(t.substr(n));
307 | }
308 | else {
309 | ts.push(t);
310 | }
311 | n -= t.length;
312 | }
313 | );
314 | return ts.join('');
315 | },
316 |
317 | getCursorInfo: function() {
318 | var count = this.cursorPos;
319 | var info = {};
320 | this.collectText(
321 | this,
322 | function(t, n) {
323 | if (0 <= count && count < t.length) {
324 | info.nodeAtCursor = n;
325 | info.offset = count;
326 | } else if (count === 0) {
327 | info.nodeAtCursor = n;
328 | info.offset = t.length;
329 | }
330 | count -= t.length;
331 | if (count === 0) {
332 | info.nodeAtCursor = n;
333 | info.offset = t.length;
334 | }
335 | }
336 | );
337 | if (info.nodeAtCursor === undefined) {
338 | info.offset = 0;
339 | }
340 | return info;
341 | },
342 |
343 | updateCursor: function() {
344 | var info = this.getCursorInfo();
345 | var node = info.nodeAtCursor;
346 | var offset = info.offset;
347 | this.appendChild(this.cursor); // ensure cursor is in the DOM tree
348 |
349 | var rect;
350 | if (node === undefined) {
351 | var placeholder = document.createTextNode('|');
352 | this.insertBefore(placeholder, this.firstChild);
353 | this.scrollToShow(placeholder);
354 | rect = this.getBoundingRect(placeholder);
355 | rect.left -= rect.width;
356 | rect.right -= rect.width;
357 | this.removeChild(placeholder);
358 | } else if (node instanceof Text && 0 < offset && offset < node.data.length) {
359 | this.scrollToShow(node);
360 | rect = this.getBoundingRect(node, offset);
361 | } else if (offset === 0) {
362 | this.scrollToShow(node);
363 | rect = this.getBoundingRect(node);
364 | } else {
365 | var placeholder = document.createTextNode('|');
366 | node.parentNode.insertBefore(placeholder, node.nextSibling);
367 | this.scrollToShow(placeholder);
368 | rect = this.getBoundingRect(placeholder);
369 | rect.left -= rect.width;
370 | rect.right -= rect.width;
371 | placeholder.parentNode.removeChild(placeholder);
372 | }
373 |
374 | var editorRect = this.getBoundingClientRect();
375 | this.cursor.style.top = (rect.top - editorRect.top + this.scrollTop) + 'px';
376 | this.cursor.style.left = (rect.left - editorRect.left + this.scrollLeft - 1) + 'px';
377 | this.cursor.style.height = rect.height + 'px';
378 | },
379 |
380 | scrollToShow: function(node) {
381 | var rect = this.getBoundingRect(node);
382 | var isAlreadyShowing =
383 | rect.top >= 0 &&
384 | rect.left >= 0 &&
385 | rect.bottom <= (window.innerHeight || document.documentElement.clientHeight)
386 | rect.right <= (window.innerWidth || document.documentElement.clientWidth);
387 | if (isAlreadyShowing) {
388 | // no-op
389 | } else if (node instanceof Text) {
390 | var span = document.createElement('editorSpan');
391 | node.parentNode.insertBefore(span, node);
392 | //span.scrollIntoView();
393 | span.parentNode.replaceChild(node, span);
394 | } else {
395 | //node.scrollIntoView();
396 | }
397 | },
398 |
399 | collectText: function(node, collectFn, optTreatNestingsAsAtomic) {
400 | if (node instanceof Text) {
401 | if (node.data.length > 0) {
402 | collectFn(node.data, node);
403 | }
404 | } else if (node.hasAttribute('source')) {
405 | var source = node.getAttribute('source');
406 | if (source.length > 0) {
407 | collectFn(source, node);
408 | }
409 | } else if (optTreatNestingsAsAtomic && node.interval !== undefined) {
410 | var contents = node.interval.contents;
411 | if (contents.length > 0) {
412 | collectFn(contents, node);
413 | }
414 | } else {
415 | node = node.firstChild;
416 | while (node) {
417 | var next = node.nextSibling;
418 | this.collectText(node, collectFn, optTreatNestingsAsAtomic);
419 | node = next;
420 | }
421 | }
422 | },
423 |
424 | insert: function(thing) {
425 | var oldText = this.getText();
426 | if (typeof thing === 'string') {
427 | thing = document.createTextNode(thing);
428 | }
429 | thing = this.createNode('unrecognizedInput', thing);
430 | var info = this.getCursorInfo();
431 | if (info.nodeAtCursor === undefined) {
432 | this.appendChild(thing);
433 | } else if (info.offset === 0) {
434 | info.nodeAtCursor.parentNode.insertBefore(thing, info.nodeAtCursor);
435 | } else if (info.nodeAtCursor instanceof Text) {
436 | var text = info.nodeAtCursor.data;
437 | var newTextBefore = document.createTextNode(text.substr(0, info.offset));
438 | var newTextAfter = document.createTextNode(text.substr(info.offset));
439 | info.nodeAtCursor.parentNode.insertBefore(newTextAfter, info.nodeAtCursor);
440 | info.nodeAtCursor.parentNode.insertBefore(thing, newTextAfter);
441 | info.nodeAtCursor.parentNode.insertBefore(newTextBefore, thing);
442 | info.nodeAtCursor.parentNode.removeChild(info.nodeAtCursor);
443 | } else {
444 | info.nodeAtCursor.parentNode.insertBefore(thing, info.nodeAtCursor.nextSibling);
445 | }
446 | this.cursorPos += this.numCharsIn(thing);
447 | this.changed(oldText, this.getText(), true);
448 | },
449 |
450 | onfocus: function() {
451 | var self = this;
452 | k.setScope(this.editorID);
453 | this.appendChild(this.cursor);
454 | setTimeout(function() { self.updateCursor(); }, 0);
455 | },
456 |
457 | onblur: function() {
458 | k.setScope('');
459 | },
460 |
461 | onkeypress: function(e) {
462 | if (e.charCode > 0 && !e.metaKey && !e.ctrlKey && !e.altKey) {
463 | this.insert(String.fromCharCode(e.charCode));
464 | return false;
465 | } else {
466 | return true;
467 | }
468 | },
469 |
470 | onmousedown: function(e) {
471 | var target = this.getNodeAt(e.clientX, e.clientY);
472 | var pos = this.getCursorPosFor(target, e.clientX, e.clientY);
473 | if (pos >= 0) {
474 | this.setCursorPos(pos);
475 | }
476 | },
477 |
478 | // Helper methods (should be top-level functions once this is turned into a node module)
479 |
480 | getCursorPosFor: function(node, x, y) {
481 | var self = this;
482 | var pos = 0;
483 | var ans = -1;
484 | this.collectText(
485 | this,
486 | function(t, n) {
487 | if (ans < 0 && n === node) {
488 | ans = pos + self.indexForPoint(n, x, y);
489 | }
490 | pos += t.length;
491 | }
492 | );
493 | return ans;
494 | },
495 |
496 | indexForPoint: function(node, x, y) {
497 | if (node instanceof Text && node.data.length > 1) {
498 | for (var idx = 0; idx < node.data.length; idx++) {
499 | var rect = this.getBoundingRect(node, idx);
500 | if (this.boundingRectContains(rect, x, y)) {
501 | // TODO: do we need to take this.scrollLeft into account?
502 | return (x - rect.left <= rect.width / 2) ? idx : idx + 1;
503 | }
504 | }
505 | return node.data.length;
506 | } else {
507 | var rect = this.getBoundingRect(node);
508 | return (x - rect.left <= rect.width / 2) ? 0 : this.numCharsIn(node);
509 | }
510 | },
511 |
512 | getNodeAt: function(x, y) {
513 | var self = this;
514 | var ans;
515 | this.collectText(
516 | this,
517 | function(t, n) {
518 | if (ans === undefined) {
519 | if (self.boundingRectContains(self.getBoundingRect(n), x, y)) {
520 | ans = n;
521 | }
522 | }
523 | }
524 | );
525 | return ans;
526 | },
527 |
528 | getBoundingRect: function(node, optIndex) {
529 | var rect;
530 | if (node instanceof Text && typeof optIndex === 'number') {
531 | var text = node;
532 | var idx = optIndex;
533 | var textBefore = document.createTextNode(node.data.substr(0, idx));
534 | var currChar = document.createElement('editorSpan');
535 | currChar.appendChild(document.createTextNode(node.data.charAt(idx)));
536 | var textAfter = document.createTextNode(node.data.substr(idx + 1));
537 | var replacement = document.createElement('editorSpan');
538 | replacement.appendChild(textBefore);
539 | replacement.appendChild(currChar);
540 | replacement.appendChild(textAfter);
541 | node.parentNode.replaceChild(replacement, node);
542 | rect = currChar.getBoundingClientRect();
543 | replacement.parentNode.replaceChild(node, replacement);
544 | } else if (node instanceof Text) {
545 | var span = document.createElement('editorSpan');
546 | node.parentNode.insertBefore(span, node);
547 | span.appendChild(node);
548 | rect = span.getBoundingClientRect();
549 | span.parentNode.insertBefore(node, span);
550 | span.parentNode.removeChild(span);
551 | } else {
552 | rect = node.getBoundingClientRect();
553 | }
554 | return rect;
555 | },
556 |
557 | boundingRectContains: function(rect, x, y) {
558 | return rect.left <= x && x <= rect.right &&
559 | rect.top <= y && y <= rect.bottom;
560 | },
561 |
562 | createNode: function(tagName /* child1, child2, ... */) {
563 | var node = document.createElement(tagName);
564 | for (var idx = 1; idx < arguments.length; idx++) {
565 | var child = arguments[idx];
566 | node.appendChild(child);
567 | }
568 | return node;
569 | },
570 |
571 | showTextNodes: function() {
572 | this.collectText(
573 | this,
574 | function(t, n) {
575 | var cont = document.createElement('cont');
576 | cont.style.border = "1px solid blue";
577 | n.parentNode.insertBefore(cont, n);
578 | cont.appendChild(n);
579 | }
580 | );
581 | }
582 | };
583 |
584 | var nextIdNum = 0;
585 |
586 | function editorInit() {
587 | var self = this;
588 |
589 | this.editorID = 'editor_id_' + nextIdNum++;
590 |
591 | var cursor = document.createElement('editorCursor');
592 | this.cursor = cursor;
593 | this.cursorPos = 0;
594 | setInterval(
595 | function() {
596 | self.updateCursor();
597 | cursor.style.visibility = cursor.style.visibility == 'visible' ? 'hidden' : 'visible';
598 | },
599 | 350
600 | );
601 |
602 | this.setAttribute('tabindex', '0'); // make it focusable
603 |
604 | k('left', this.editorID, function() { self.cursorLeft(); return false; });
605 | k('right', this.editorID, function() { self.cursorRight(); return false; });
606 | k('up', this.editorID, function() { self.cursorUp(); return false; });
607 | k('down', this.editorID, function() { self.cursorDown(); return false; });
608 | k('home, command+left', this.editorID, function() { self.home(); return false; });
609 | k('end, command+right', this.editorID, function() { self.end(); return false; });
610 | k('backspace', this.editorID, function() { self.backspace(); return false; });
611 | k('enter', this.editorID, function() { self.insert('\n'); return false; });
612 | }
613 |
614 |
--------------------------------------------------------------------------------
/3rdparty/ohm.min.js:
--------------------------------------------------------------------------------
1 | !function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.ohm=e()}}(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o0)sb.nextPutAll(" ")}sb.nextPutAll("\n")});return sb.contents()},newLine:function(){this.lines.push([]);this.newColumn()},newColumn:function(){this.currentLine().push([])},currentColumn:function(){var line=this.currentLine();return line[line.length-1]},currentLine:function(){return this.lines[this.lines.length-1]}};exports.columnStringBuffer=function(){return new ColumnStringBuffer}},{}],6:[function(_dereq_,module,exports){var objectUtils=_dereq_("./objectUtils.js");var thisModule=exports;function pad(numberAsString,len){var zeros=[];for(var idx=0;idx0)ws.nextPutAll(", ");printOn(x[idx],ws)}ws.nextPutAll("]")}else if(typeof x==="string"){var hasSingleQuotes=x.indexOf("'")>=0;var hasDoubleQuotes=x.indexOf('"')>=0;var delim=hasSingleQuotes&&!hasDoubleQuotes?'"':"'";ws.nextPutAll(delim);for(var idx=0;idx0){return names}else{return body.producesValue()?["expr"]:[]}}},toRecipe:function(){var ws=makeStringBuffer();ws.nextPutAll("(function(ohm, optNamespace) {\n");ws.nextPutAll(" var b = ohm._builder();\n");ws.nextPutAll(" b.setName(");ws.nextPutAll(printString(this.name));ws.nextPutAll(");\n");if(this.superGrammar.name&&this.superGrammar.namespaceName){ws.nextPutAll(" b.setSuperGrammar(ohm.namespace(");ws.nextPutAll(printString(this.superGrammar.namespaceName));ws.nextPutAll(").getGrammar(");ws.nextPutAll(printString(this.superGrammar.name));ws.nextPutAll("));\n")}for(var idx=0;idx0?arguments:Object.keys(this.ruleDict);var rulesToBeIncluded=this.rulesThatNeedSemanticAction(entryPoints);var self=this;var buffer=makeStringBuffer();buffer.nextPutAll("{");var first=true;for(var ruleName in rulesToBeIncluded){var body=this.ruleDict[ruleName];if(first){first=false}else{buffer.nextPutAll(",")}buffer.nextPutAll("\n");buffer.nextPutAll(" ");self.addSemanticActionTemplate(ruleName,body,buffer)}buffer.nextPutAll("\n}");return buffer.contents()},addSemanticActionTemplate:function(ruleName,body,buffer){buffer.nextPutAll(ruleName);buffer.nextPutAll(": function(");buffer.nextPutAll(this.semanticActionArgNames(ruleName).join(", "));buffer.nextPutAll(") {\n");buffer.nextPutAll(" }")},rulesThatNeedSemanticAction:function(entryPoints){var self=this;function getBody(ruleName){if(self.ruleDict[ruleName]===undefined){throw new errors.UndeclaredRule(ruleName,self.name)}else{return self.ruleDict[ruleName]}}var rules={};for(var idx=0;idxthis.failuresPos){this.failures={expr:expr,next:null};this.failuresPos=pos}else if(pos===this.failuresPos){this.failures={expr:expr,next:this.failures}}},getCurrentPosInfo:function(){var currPos=this.pos;var posInfo=this.posInfos[currPos];return posInfo||(this.posInfos[currPos]=new PosInfo(currPos))},atEnd:function(){return this.pos===this.source.length},next:function(){if(this.atEnd()){return common.fail}else{return this.source[this.pos++]}},matchExactly:function(x){return this.next()===x?true:common.fail},interval:function(startIdx,endIdx){return this.source.slice(startIdx,endIdx)},getFailuresPos:function(){return this.failuresPos}};function StringInputStream(source){this.init(source)}StringInputStream.prototype=objectThatDelegatesTo(InputStream.prototype,{matchString:function(s){for(var idx=0;idx0){if(idx===expected.length-1){text.nextPutAll(expected.length>2?", or ":" or ")}else{text.nextPutAll(", ")}}text.nextPutAll(expected[idx])}return text.contents()};MatchFailure.prototype.getExpected=function(){var expected={};for(var failure=this.inputStream.failures;failure!==null;failure=failure.next){expected[failure.expr.toExpected(this.ruleDict)]=true}return Object.keys(expected).reverse()};exports.IntervalSourcesDontMatch=IntervalSourcesDontMatch;exports.UndeclaredGrammar=UndeclaredGrammar;exports.DuplicateGrammarDeclaration=DuplicateGrammarDeclaration;exports.UndeclaredRule=UndeclaredRule;exports.DuplicateRuleDeclaration=DuplicateRuleDeclaration;exports.RuleMustProduceValue=RuleMustProduceValue;exports.InconsistentBindings=InconsistentBindings;exports.DuplicateBindings=DuplicateBindings;exports.UselessBindings=UselessBindings;exports.DuplicatePropertyNames=DuplicatePropertyNames;exports.MatchFailure=MatchFailure},{"./common.js":13,awlib:2}],16:[function(_dereq_,module,exports){_dereq_("../dist/ohm-grammar.js");var Builder=_dereq_("./Builder.js");var Namespace=_dereq_("./Namespace.js");var errors=_dereq_("./errors.js");var awlib=_dereq_("awlib");var unescapeChar=awlib.stringUtils.unescapeChar;var thisModule=exports;function makeGrammarActionDict(optNamespace){var builder;return{Grammars:function(expr){return expr.value},Grammar:function(n,s,rs){builder=new Builder;builder.setName(n.value);s.value;rs.value;return builder.build(optNamespace)},SuperGrammar:function(expr){builder.setSuperGrammar(expr.value)},SuperGrammar_qualified:function(ns,n){return thisModule.namespace(ns.value).getGrammar(n.value)},SuperGrammar_unqualified:function(n){if(optNamespace){return optNamespace.getGrammar(n.value)}else{throw new errors.UndeclaredGrammar(n.value)}},Rule:function(expr){return expr.value},Rule_define:function(n,d,b){builder.currentRuleName=n.value;d.value;return builder.define(n.value,b.value)},Rule_override:function(n,b){builder.currentRuleName=n.value;return builder.override(n.value,b.value)},Rule_extend:function(n,b){builder.currentRuleName=n.value;return builder.extend(n.value,b.value)},Alt:function(expr){return expr.value},Alt_rec:function(x,y){return builder.alt(x.value,y.value)},Term:function(expr){return expr.value},Term_inline:function(x,n){return builder.inline(builder.currentRuleName+"_"+n.value,x.value)},Seq:function(expr){return builder.seq.apply(builder,expr.value)},Factor:function(expr){return expr.value},Factor_bind:function(x,n){return builder.bind(x.value,n.value)},Iter:function(expr){return expr.value},Iter_star:function(x){return builder.many(x.value,0)},Iter_plus:function(x){return builder.many(x.value,1)},Iter_opt:function(x){return builder.opt(x.value)},Pred:function(expr){return expr.value},Pred_not:function(x){return builder.not(x.value)},Pred_lookahead:function(x){return builder.la(x.value)},Base:function(expr){return expr.value},Base_application:function(ruleName){return builder.app(ruleName.value)},Base_prim:function(expr){return builder.prim(expr.value)},Base_paren:function(x){return x.value},Base_listy:function(x){return builder.listy(x.value)},Base_obj:function(lenient){return builder.obj([],lenient.value)},Base_objWithProps:function(ps,lenient){return builder.obj(ps.value,lenient.value)},Props:function(expr){return expr.value},Props_rec:function(p,ps){return[p.value].concat(ps.value)},Props_base:function(p){return[p.value]},Prop:function(n,p){return{name:n.value,pattern:p.value}},ruleDescr:function(t){builder.setRuleDescription(t.value);return t.value},ruleDescrText:function(){return this.interval.contents},caseName:function(n){return n.value},name:function(){return this.interval.contents},nameFirst:function(expr){},nameRest:function(expr){},ident:function(n){return n.value},keyword:function(expr){return expr.value},keyword_undefined:function(){return undefined},keyword_null:function(){return null},keyword_true:function(){return true},keyword_false:function(){return false},string:function(cs){return cs.value.map(function(c){return unescapeChar(c)}).join("")},sChar:function(){return this.interval.contents},regExp:function(e){return new RegExp(e.value)},reCharClass:function(){return this.interval.contents},number:function(){return parseInt(this.interval.contents)},space:function(expr){},space_multiLine:function(){},space_singleLine:function(){}}}function compileAndLoad(source,whatItIs,optNamespace){try{var thunk=thisModule.ohmGrammar.matchContents(source,whatItIs,true);return thunk(makeGrammarActionDict(optNamespace))}catch(e){if(e instanceof errors.MatchFailure){console.log("\n"+e.getMessage())}throw e}}function makeGrammar(source,optNamespace){return compileAndLoad(source,"Grammar",optNamespace)}function makeGrammars(source,optNamespace){return compileAndLoad(source,"Grammars",optNamespace)}var namespaces={};exports.namespace=function(name){if(namespaces[name]===undefined){namespaces[name]=new Namespace(name)}return namespaces[name]};exports.make=function(recipe){return recipe(thisModule)};exports.makeGrammar=makeGrammar;exports.makeGrammars=makeGrammars;exports._builder=function(){return new Builder};exports._makeGrammarActionDict=makeGrammarActionDict;var ohmGrammar;Object.defineProperty(exports,"ohmGrammar",{get:function(){if(!ohmGrammar){ohmGrammar=this._ohmGrammarFactory(this)}return ohmGrammar}})},{"../dist/ohm-grammar.js":1,"./Builder.js":7,"./Namespace.js":11,"./errors.js":15,awlib:2}],17:[function(_dereq_,module,exports){var common=_dereq_("./common.js");var pexprs=_dereq_("./pexprs.js");pexprs.PExpr.prototype.addRulesThatNeedSemanticAction=common.abstract;pexprs.anything.addRulesThatNeedSemanticAction=function(){};pexprs.end.addRulesThatNeedSemanticAction=function(){};pexprs.Prim.prototype.addRulesThatNeedSemanticAction=function(){};pexprs.Alt.prototype.addRulesThatNeedSemanticAction=function(dict,valueRequired){var ans=false;for(var idx=0;idx0){throw new errors.DuplicateBindings(ruleName,duplicates)}};pexprs.Bind.prototype.assertNoDuplicateBindings=function(ruleName){this.expr.assertNoDuplicateBindings(ruleName)};pexprs.Many.prototype.assertNoDuplicateBindings=function(ruleName){this.expr.assertNoDuplicateBindings(ruleName)};pexprs.Opt.prototype.assertNoDuplicateBindings=function(ruleName){this.expr.assertNoDuplicateBindings(ruleName)};pexprs.Not.prototype.assertNoDuplicateBindings=function(ruleName){this.expr.assertNoDuplicateBindings(ruleName)};pexprs.Lookahead.prototype.assertNoDuplicateBindings=function(ruleName){this.expr.assertNoDuplicateBindings(ruleName)};pexprs.Listy.prototype.assertNoDuplicateBindings=function(ruleName){this.expr.assertNoDuplicateBindings(ruleName)};pexprs.Obj.prototype.assertNoDuplicateBindings=function(ruleName){for(var idx=0;idx0){throw new errors.DuplicateBindings(ruleName,duplicates)}};pexprs.Apply.prototype.assertNoDuplicateBindings=function(ruleName){}},{"./common.js":13,"./errors.js":15,"./pexprs.js":26}],20:[function(_dereq_,module,exports){var common=_dereq_("./common.js");var pexprs=_dereq_("./pexprs.js");var errors=_dereq_("./errors.js");function assertNoBindings(ruleName,expr){var bindings=expr.getBindingNames();if(bindings.length>0){throw new errors.UselessBindings(ruleName,bindings)}}pexprs.PExpr.prototype.assertNoUselessBindings=common.abstract;pexprs.anything.assertNoUselessBindings=function(ruleName){};pexprs.end.assertNoUselessBindings=function(ruleName){};pexprs.Prim.prototype.assertNoUselessBindings=function(ruleName){};pexprs.Alt.prototype.assertNoUselessBindings=function(ruleName){for(var idx=0;idxorigNumBindings){bindings.length=origNumBindings}}}return common.fail};pexprs.Seq.prototype.eval=function(recordFailures,syntactic,ruleDict,inputStream,bindings){if(syntactic){skipSpaces(ruleDict,inputStream)}var origPos=inputStream.pos;for(var idx=0;idx0&&syntactic){skipSpaces(ruleDict,inputStream)}var factor=this.factors[idx];var value=factor.eval(recordFailures,syntactic,ruleDict,inputStream,bindings);if(value===common.fail){return common.fail}}return new thunks.ValueThunk(undefined,inputStream.source,origPos,inputStream.pos)};pexprs.Bind.prototype.eval=function(recordFailures,syntactic,ruleDict,inputStream,bindings){var value=this.expr.eval(recordFailures,syntactic,ruleDict,inputStream,bindings);if(value!==common.fail){bindings.push(this.name,value)}return value};pexprs.Many.prototype.eval=function(recordFailures,syntactic,ruleDict,inputStream,bindings){var numMatches=0;var matches=this.expr.producesValue()?[]:undefined;var origPos=inputStream.pos;while(true){var backtrackPos=inputStream.pos;var value=this.expr.eval(recordFailures,syntactic,ruleDict,inputStream,[]);if(value===common.fail){inputStream.pos=backtrackPos;break}else{numMatches++;if(matches){matches.push(value)}}}if(numMatchescurrentLR.pos){currentLR.value=value;currentLR.pos=inputStream.pos}else{value=currentLR.value;inputStream.pos=currentLR.pos;break}}}return value}},{"./InputStream.js":9,"./common.js":13,"./errors.js":15,"./pexprs.js":26,"./skipSpaces.js":27,"./thunks.js":28,awlib:2}],22:[function(_dereq_,module,exports){var pexprs=_dereq_("./pexprs.js");pexprs.PExpr.prototype.getBindingNames=function(){return[]};pexprs.Alt.prototype.getBindingNames=function(){return this.terms.length===0?[]:this.terms[0].getBindingNames()};pexprs.Seq.prototype.getBindingNames=function(){var names=[];for(var idx=0;idx0){ws.nextPutAll(", ")}this.terms[idx].outputRecipe(ws)}ws.nextPutAll(")")};pexprs.Seq.prototype.outputRecipe=function(ws){ws.nextPutAll("b.seq(");for(var idx=0;idx0){ws.nextPutAll(", ")}this.factors[idx].outputRecipe(ws)}ws.nextPutAll(")")};pexprs.Bind.prototype.outputRecipe=function(ws){ws.nextPutAll("b.bind(");this.expr.outputRecipe(ws);ws.nextPutAll(", ");ws.nextPutAll(printString(this.name));ws.nextPutAll(")")};pexprs.Many.prototype.outputRecipe=function(ws){ws.nextPutAll("b.many(");this.expr.outputRecipe(ws);ws.nextPutAll(", ");ws.nextPutAll(this.minNumMatches);ws.nextPutAll(")")};pexprs.Opt.prototype.outputRecipe=function(ws){ws.nextPutAll("b.opt(");this.expr.outputRecipe(ws);ws.nextPutAll(")")};pexprs.Not.prototype.outputRecipe=function(ws){ws.nextPutAll("b.not(");this.expr.outputRecipe(ws);ws.nextPutAll(")")};pexprs.Lookahead.prototype.outputRecipe=function(ws){ws.nextPutAll("b.la(");this.expr.outputRecipe(ws);ws.nextPutAll(")")};pexprs.Listy.prototype.outputRecipe=function(ws){ws.nextPutAll("b.listy(");this.expr.outputRecipe(ws);ws.nextPutAll(")")};pexprs.Obj.prototype.outputRecipe=function(ws){function outputPropertyRecipe(prop){ws.nextPutAll("{name: ");ws.nextPutAll(printString(prop.name));ws.nextPutAll(", pattern: ");prop.pattern.outputRecipe(ws);ws.nextPutAll("}")}ws.nextPutAll("b.obj([");for(var idx=0;idx0){ws.nextPutAll(", ")}outputPropertyRecipe(this.properties[idx])}ws.nextPutAll("], ");ws.nextPutAll(printString(!!this.isLenient));ws.nextPutAll(")")};pexprs.Apply.prototype.outputRecipe=function(ws){ws.nextPutAll("b.app(");ws.nextPutAll(printString(this.ruleName));ws.nextPutAll(")")}},{"./common.js":13,"./pexprs.js":26,awlib:2}],24:[function(_dereq_,module,exports){var pexprs=_dereq_("./pexprs.js");pexprs.PExpr.prototype.producesValue=function(){return true};pexprs.Alt.prototype.producesValue=function(){for(var idx=0;idx0){throw new errors.DuplicatePropertyNames(duplicates)}else{this.properties=properties;this.isLenient=isLenient}}Obj.prototype=objectThatDelegatesTo(PExpr.prototype);function Apply(ruleName){this.ruleName=ruleName}Apply.prototype=objectThatDelegatesTo(PExpr.prototype);exports.makePrim=function(obj){if(typeof obj==="string"&&obj.length!==1){return new StringPrim(obj)}else if(obj instanceof RegExp){return new RegExpPrim(obj)}else{return new Prim(obj)}};exports.PExpr=PExpr;exports.anything=anything;exports.end=end;exports.Prim=Prim;exports.StringPrim=StringPrim;exports.RegExpPrim=RegExpPrim;exports.Alt=Alt;exports.ExtendAlt=ExtendAlt;exports.Seq=Seq;exports.Bind=Bind;exports.Many=Many;exports.Opt=Opt;exports.Not=Not;exports.Lookahead=Lookahead;exports.Listy=Listy;exports.Obj=Obj;exports.Apply=Apply;_dereq_("./pexprs-addRulesThatNeedSemanticAction.js");_dereq_("./pexprs-assertNoDuplicateBindings.js");_dereq_("./pexprs-assertNoUselessBindings.js");_dereq_("./pexprs-assertChoicesHaveUniformBindings.js");_dereq_("./pexprs-getBindingNames.js");_dereq_("./pexprs-eval.js");_dereq_("./pexprs-outputRecipe.js");_dereq_("./pexprs-producesValue.js");_dereq_("./pexprs-toExpected.js")},{"./common.js":13,"./errors.js":15,"./pexprs-addRulesThatNeedSemanticAction.js":17,"./pexprs-assertChoicesHaveUniformBindings.js":18,"./pexprs-assertNoDuplicateBindings.js":19,"./pexprs-assertNoUselessBindings.js":20,"./pexprs-eval.js":21,"./pexprs-getBindingNames.js":22,"./pexprs-outputRecipe.js":23,"./pexprs-producesValue.js":24,"./pexprs-toExpected.js":25,awlib:2}],27:[function(_dereq_,module,exports){var common=_dereq_("./common.js");var pexprs=_dereq_("./pexprs.js");function skipSpaces(ruleDict,inputStream){while(true){var origPos=inputStream.pos;if(ruleDict.space.eval(false,false,ruleDict,inputStream,[])===common.fail){inputStream.pos=origPos;break}}}module.exports=skipSpaces},{"./common.js":13,"./pexprs.js":26}],28:[function(_dereq_,module,exports){var Interval=_dereq_("./Interval.js");var awlib=_dereq_("awlib");var browser=awlib.browser;var objectUtils=awlib.objectUtils;var objectThatDelegatesTo=objectUtils.objectThatDelegatesTo;function Thunk(){throw"Thunk cannot be instantiated -- it's abstract"}var nextThunkId=0;Thunk.prototype={init:function(source,startIdx,endIdx){this.id=nextThunkId++;this._source=source;this._startIdx=startIdx;this._endIdx=endIdx}};Object.defineProperty(Thunk.prototype,"interval",{get:function(){return this._interval||(this._interval=new Interval(this._source,this._startIdx,this._endIdx))}});function RuleThunk(ruleName,source,startIdx,endIdx,value,bindings){this.init(source,startIdx,endIdx);this.ruleName=ruleName;this.value=value;this.bindings=bindings}RuleThunk.prototype=objectThatDelegatesTo(Thunk.prototype,{force:function(actionDict,memo){function makeBinding(thunk){var binding={interval:thunk.interval};Object.defineProperty(binding,"value",{get:function(){return thunk.force(actionDict,memo)}});return binding}if(memo.hasOwnProperty(this.id)){return memo[this.id]}var action=this.lookupAction(actionDict);var addlInfo=this.createAddlInfo();if(this.bindings.length===0){return memo[this.id]=action.call(addlInfo,makeBinding(this.value))}else{var argDict={};for(var idx=0;idx