opal-irb 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +113 -0
- data/Guardfile +5 -0
- data/LICENSE +21 -0
- data/README.md +175 -0
- data/Rakefile +65 -0
- data/Roadmap.org +17 -0
- data/app/assets/stylesheets/opal-irb/jqconsole.css +263 -0
- data/compiled/app-embeddable.js +39765 -0
- data/compiled/app-jqconsole.js +39767 -0
- data/compiled/application.js +27399 -0
- data/css/ansi.css +172 -0
- data/css/opal_irb_jqconsole.css +79 -0
- data/css/show-hint.css +38 -0
- data/doc/presentations/opal_irb_overview.html +678 -0
- data/doc/presentations/opal_irb_overview.org +448 -0
- data/examples/app-embeddable.rb +8 -0
- data/examples/app-jqconsole.rb +10 -0
- data/examples/application.rb +8 -0
- data/index-embeddable.html +29 -0
- data/index-homebrew.html +115 -0
- data/index-jq.html +80 -0
- data/js/anyword-hint.js +44 -0
- data/js/jqconsole.js +1583 -0
- data/js/nodeutil.js +546 -0
- data/js/ruby.js +285 -0
- data/js/show-hint.js +383 -0
- data/lib/opal-irb/rails_engine.rb +3 -0
- data/lib/opal-irb/version.rb +3 -0
- data/lib/opal-irb-rails.rb +2 -0
- data/lib/opal-irb.rb +44 -0
- data/opal/object_extensions.rb +20 -0
- data/opal/opal_irb/completion_engine.rb +202 -0
- data/opal/opal_irb/completion_formatter.rb +49 -0
- data/opal/opal_irb/completion_results.rb +88 -0
- data/opal/opal_irb.rb +88 -0
- data/opal/opal_irb_homebrew_console.rb +398 -0
- data/opal/opal_irb_jqconsole.rb +517 -0
- data/opal/opal_irb_jqconsole_css.rb +259 -0
- data/opal/opal_irb_log_redirector.rb +32 -0
- data/opal/opal_phantomjs.rb +49 -0
- data/opal-irb.gemspec +20 -0
- data/spec/code_link_handler_spec.rb +30 -0
- data/spec/jquery.js +5 -0
- data/spec/object_extensions_spec.rb +32 -0
- data/spec/opal_irb/completion_engine_spec.rb +204 -0
- data/spec/opal_irb/completion_results_spec.rb +32 -0
- data/spec/opal_irb_log_director_spec.rb +19 -0
- data/spec/opal_irb_spec.rb +19 -0
- data/spec/spec_helper.rb +1 -0
- metadata +151 -0
data/js/ruby.js
ADDED
@@ -0,0 +1,285 @@
|
|
1
|
+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
2
|
+
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
3
|
+
|
4
|
+
(function(mod) {
|
5
|
+
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
6
|
+
mod(require("../../lib/codemirror"));
|
7
|
+
else if (typeof define == "function" && define.amd) // AMD
|
8
|
+
define(["../../lib/codemirror"], mod);
|
9
|
+
else // Plain browser env
|
10
|
+
mod(CodeMirror);
|
11
|
+
})(function(CodeMirror) {
|
12
|
+
"use strict";
|
13
|
+
|
14
|
+
CodeMirror.defineMode("ruby", function(config) {
|
15
|
+
function wordObj(words) {
|
16
|
+
var o = {};
|
17
|
+
for (var i = 0, e = words.length; i < e; ++i) o[words[i]] = true;
|
18
|
+
return o;
|
19
|
+
}
|
20
|
+
var keywords = wordObj([
|
21
|
+
"alias", "and", "BEGIN", "begin", "break", "case", "class", "def", "defined?", "do", "else",
|
22
|
+
"elsif", "END", "end", "ensure", "false", "for", "if", "in", "module", "next", "not", "or",
|
23
|
+
"redo", "rescue", "retry", "return", "self", "super", "then", "true", "undef", "unless",
|
24
|
+
"until", "when", "while", "yield", "nil", "raise", "throw", "catch", "fail", "loop", "callcc",
|
25
|
+
"caller", "lambda", "proc", "public", "protected", "private", "require", "load",
|
26
|
+
"require_relative", "extend", "autoload", "__END__", "__FILE__", "__LINE__", "__dir__"
|
27
|
+
]);
|
28
|
+
var indentWords = wordObj(["def", "class", "case", "for", "while", "module", "then",
|
29
|
+
"catch", "loop", "proc", "begin"]);
|
30
|
+
var dedentWords = wordObj(["end", "until"]);
|
31
|
+
var matching = {"[": "]", "{": "}", "(": ")"};
|
32
|
+
var curPunc;
|
33
|
+
|
34
|
+
function chain(newtok, stream, state) {
|
35
|
+
state.tokenize.push(newtok);
|
36
|
+
return newtok(stream, state);
|
37
|
+
}
|
38
|
+
|
39
|
+
function tokenBase(stream, state) {
|
40
|
+
curPunc = null;
|
41
|
+
if (stream.sol() && stream.match("=begin") && stream.eol()) {
|
42
|
+
state.tokenize.push(readBlockComment);
|
43
|
+
return "comment";
|
44
|
+
}
|
45
|
+
if (stream.eatSpace()) return null;
|
46
|
+
var ch = stream.next(), m;
|
47
|
+
if (ch == "`" || ch == "'" || ch == '"') {
|
48
|
+
return chain(readQuoted(ch, "string", ch == '"' || ch == "`"), stream, state);
|
49
|
+
} else if (ch == "/") {
|
50
|
+
var currentIndex = stream.current().length;
|
51
|
+
if (stream.skipTo("/")) {
|
52
|
+
var search_till = stream.current().length;
|
53
|
+
stream.backUp(stream.current().length - currentIndex);
|
54
|
+
var balance = 0; // balance brackets
|
55
|
+
while (stream.current().length < search_till) {
|
56
|
+
var chchr = stream.next();
|
57
|
+
if (chchr == "(") balance += 1;
|
58
|
+
else if (chchr == ")") balance -= 1;
|
59
|
+
if (balance < 0) break;
|
60
|
+
}
|
61
|
+
stream.backUp(stream.current().length - currentIndex);
|
62
|
+
if (balance == 0)
|
63
|
+
return chain(readQuoted(ch, "string-2", true), stream, state);
|
64
|
+
}
|
65
|
+
return "operator";
|
66
|
+
} else if (ch == "%") {
|
67
|
+
var style = "string", embed = true;
|
68
|
+
if (stream.eat("s")) style = "atom";
|
69
|
+
else if (stream.eat(/[WQ]/)) style = "string";
|
70
|
+
else if (stream.eat(/[r]/)) style = "string-2";
|
71
|
+
else if (stream.eat(/[wxq]/)) { style = "string"; embed = false; }
|
72
|
+
var delim = stream.eat(/[^\w\s=]/);
|
73
|
+
if (!delim) return "operator";
|
74
|
+
if (matching.propertyIsEnumerable(delim)) delim = matching[delim];
|
75
|
+
return chain(readQuoted(delim, style, embed, true), stream, state);
|
76
|
+
} else if (ch == "#") {
|
77
|
+
stream.skipToEnd();
|
78
|
+
return "comment";
|
79
|
+
} else if (ch == "<" && (m = stream.match(/^<-?[\`\"\']?([a-zA-Z_?]\w*)[\`\"\']?(?:;|$)/))) {
|
80
|
+
return chain(readHereDoc(m[1]), stream, state);
|
81
|
+
} else if (ch == "0") {
|
82
|
+
if (stream.eat("x")) stream.eatWhile(/[\da-fA-F]/);
|
83
|
+
else if (stream.eat("b")) stream.eatWhile(/[01]/);
|
84
|
+
else stream.eatWhile(/[0-7]/);
|
85
|
+
return "number";
|
86
|
+
} else if (/\d/.test(ch)) {
|
87
|
+
stream.match(/^[\d_]*(?:\.[\d_]+)?(?:[eE][+\-]?[\d_]+)?/);
|
88
|
+
return "number";
|
89
|
+
} else if (ch == "?") {
|
90
|
+
while (stream.match(/^\\[CM]-/)) {}
|
91
|
+
if (stream.eat("\\")) stream.eatWhile(/\w/);
|
92
|
+
else stream.next();
|
93
|
+
return "string";
|
94
|
+
} else if (ch == ":") {
|
95
|
+
if (stream.eat("'")) return chain(readQuoted("'", "atom", false), stream, state);
|
96
|
+
if (stream.eat('"')) return chain(readQuoted('"', "atom", true), stream, state);
|
97
|
+
|
98
|
+
// :> :>> :< :<< are valid symbols
|
99
|
+
if (stream.eat(/[\<\>]/)) {
|
100
|
+
stream.eat(/[\<\>]/);
|
101
|
+
return "atom";
|
102
|
+
}
|
103
|
+
|
104
|
+
// :+ :- :/ :* :| :& :! are valid symbols
|
105
|
+
if (stream.eat(/[\+\-\*\/\&\|\:\!]/)) {
|
106
|
+
return "atom";
|
107
|
+
}
|
108
|
+
|
109
|
+
// Symbols can't start by a digit
|
110
|
+
if (stream.eat(/[a-zA-Z$@_\xa1-\uffff]/)) {
|
111
|
+
stream.eatWhile(/[\w$\xa1-\uffff]/);
|
112
|
+
// Only one ? ! = is allowed and only as the last character
|
113
|
+
stream.eat(/[\?\!\=]/);
|
114
|
+
return "atom";
|
115
|
+
}
|
116
|
+
return "operator";
|
117
|
+
} else if (ch == "@" && stream.match(/^@?[a-zA-Z_\xa1-\uffff]/)) {
|
118
|
+
stream.eat("@");
|
119
|
+
stream.eatWhile(/[\w\xa1-\uffff]/);
|
120
|
+
return "variable-2";
|
121
|
+
} else if (ch == "$") {
|
122
|
+
if (stream.eat(/[a-zA-Z_]/)) {
|
123
|
+
stream.eatWhile(/[\w]/);
|
124
|
+
} else if (stream.eat(/\d/)) {
|
125
|
+
stream.eat(/\d/);
|
126
|
+
} else {
|
127
|
+
stream.next(); // Must be a special global like $: or $!
|
128
|
+
}
|
129
|
+
return "variable-3";
|
130
|
+
} else if (/[a-zA-Z_\xa1-\uffff]/.test(ch)) {
|
131
|
+
stream.eatWhile(/[\w\xa1-\uffff]/);
|
132
|
+
stream.eat(/[\?\!]/);
|
133
|
+
if (stream.eat(":")) return "atom";
|
134
|
+
return "ident";
|
135
|
+
} else if (ch == "|" && (state.varList || state.lastTok == "{" || state.lastTok == "do")) {
|
136
|
+
curPunc = "|";
|
137
|
+
return null;
|
138
|
+
} else if (/[\(\)\[\]{}\\;]/.test(ch)) {
|
139
|
+
curPunc = ch;
|
140
|
+
return null;
|
141
|
+
} else if (ch == "-" && stream.eat(">")) {
|
142
|
+
return "arrow";
|
143
|
+
} else if (/[=+\-\/*:\.^%<>~|]/.test(ch)) {
|
144
|
+
var more = stream.eatWhile(/[=+\-\/*:\.^%<>~|]/);
|
145
|
+
if (ch == "." && !more) curPunc = ".";
|
146
|
+
return "operator";
|
147
|
+
} else {
|
148
|
+
return null;
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
function tokenBaseUntilBrace(depth) {
|
153
|
+
if (!depth) depth = 1;
|
154
|
+
return function(stream, state) {
|
155
|
+
if (stream.peek() == "}") {
|
156
|
+
if (depth == 1) {
|
157
|
+
state.tokenize.pop();
|
158
|
+
return state.tokenize[state.tokenize.length-1](stream, state);
|
159
|
+
} else {
|
160
|
+
state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth - 1);
|
161
|
+
}
|
162
|
+
} else if (stream.peek() == "{") {
|
163
|
+
state.tokenize[state.tokenize.length - 1] = tokenBaseUntilBrace(depth + 1);
|
164
|
+
}
|
165
|
+
return tokenBase(stream, state);
|
166
|
+
};
|
167
|
+
}
|
168
|
+
function tokenBaseOnce() {
|
169
|
+
var alreadyCalled = false;
|
170
|
+
return function(stream, state) {
|
171
|
+
if (alreadyCalled) {
|
172
|
+
state.tokenize.pop();
|
173
|
+
return state.tokenize[state.tokenize.length-1](stream, state);
|
174
|
+
}
|
175
|
+
alreadyCalled = true;
|
176
|
+
return tokenBase(stream, state);
|
177
|
+
};
|
178
|
+
}
|
179
|
+
function readQuoted(quote, style, embed, unescaped) {
|
180
|
+
return function(stream, state) {
|
181
|
+
var escaped = false, ch;
|
182
|
+
|
183
|
+
if (state.context.type === 'read-quoted-paused') {
|
184
|
+
state.context = state.context.prev;
|
185
|
+
stream.eat("}");
|
186
|
+
}
|
187
|
+
|
188
|
+
while ((ch = stream.next()) != null) {
|
189
|
+
if (ch == quote && (unescaped || !escaped)) {
|
190
|
+
state.tokenize.pop();
|
191
|
+
break;
|
192
|
+
}
|
193
|
+
if (embed && ch == "#" && !escaped) {
|
194
|
+
if (stream.eat("{")) {
|
195
|
+
if (quote == "}") {
|
196
|
+
state.context = {prev: state.context, type: 'read-quoted-paused'};
|
197
|
+
}
|
198
|
+
state.tokenize.push(tokenBaseUntilBrace());
|
199
|
+
break;
|
200
|
+
} else if (/[@\$]/.test(stream.peek())) {
|
201
|
+
state.tokenize.push(tokenBaseOnce());
|
202
|
+
break;
|
203
|
+
}
|
204
|
+
}
|
205
|
+
escaped = !escaped && ch == "\\";
|
206
|
+
}
|
207
|
+
return style;
|
208
|
+
};
|
209
|
+
}
|
210
|
+
function readHereDoc(phrase) {
|
211
|
+
return function(stream, state) {
|
212
|
+
if (stream.match(phrase)) state.tokenize.pop();
|
213
|
+
else stream.skipToEnd();
|
214
|
+
return "string";
|
215
|
+
};
|
216
|
+
}
|
217
|
+
function readBlockComment(stream, state) {
|
218
|
+
if (stream.sol() && stream.match("=end") && stream.eol())
|
219
|
+
state.tokenize.pop();
|
220
|
+
stream.skipToEnd();
|
221
|
+
return "comment";
|
222
|
+
}
|
223
|
+
|
224
|
+
return {
|
225
|
+
startState: function() {
|
226
|
+
return {tokenize: [tokenBase],
|
227
|
+
indented: 0,
|
228
|
+
context: {type: "top", indented: -config.indentUnit},
|
229
|
+
continuedLine: false,
|
230
|
+
lastTok: null,
|
231
|
+
varList: false};
|
232
|
+
},
|
233
|
+
|
234
|
+
token: function(stream, state) {
|
235
|
+
if (stream.sol()) state.indented = stream.indentation();
|
236
|
+
var style = state.tokenize[state.tokenize.length-1](stream, state), kwtype;
|
237
|
+
var thisTok = curPunc;
|
238
|
+
if (style == "ident") {
|
239
|
+
var word = stream.current();
|
240
|
+
style = state.lastTok == "." ? "property"
|
241
|
+
: keywords.propertyIsEnumerable(stream.current()) ? "keyword"
|
242
|
+
: /^[A-Z]/.test(word) ? "tag"
|
243
|
+
: (state.lastTok == "def" || state.lastTok == "class" || state.varList) ? "def"
|
244
|
+
: "variable";
|
245
|
+
if (style == "keyword") {
|
246
|
+
thisTok = word;
|
247
|
+
if (indentWords.propertyIsEnumerable(word)) kwtype = "indent";
|
248
|
+
else if (dedentWords.propertyIsEnumerable(word)) kwtype = "dedent";
|
249
|
+
else if ((word == "if" || word == "unless") && stream.column() == stream.indentation())
|
250
|
+
kwtype = "indent";
|
251
|
+
else if (word == "do" && state.context.indented < state.indented)
|
252
|
+
kwtype = "indent";
|
253
|
+
}
|
254
|
+
}
|
255
|
+
if (curPunc || (style && style != "comment")) state.lastTok = thisTok;
|
256
|
+
if (curPunc == "|") state.varList = !state.varList;
|
257
|
+
|
258
|
+
if (kwtype == "indent" || /[\(\[\{]/.test(curPunc))
|
259
|
+
state.context = {prev: state.context, type: curPunc || style, indented: state.indented};
|
260
|
+
else if ((kwtype == "dedent" || /[\)\]\}]/.test(curPunc)) && state.context.prev)
|
261
|
+
state.context = state.context.prev;
|
262
|
+
|
263
|
+
if (stream.eol())
|
264
|
+
state.continuedLine = (curPunc == "\\" || style == "operator");
|
265
|
+
return style;
|
266
|
+
},
|
267
|
+
|
268
|
+
indent: function(state, textAfter) {
|
269
|
+
if (state.tokenize[state.tokenize.length-1] != tokenBase) return 0;
|
270
|
+
var firstChar = textAfter && textAfter.charAt(0);
|
271
|
+
var ct = state.context;
|
272
|
+
var closing = ct.type == matching[firstChar] ||
|
273
|
+
ct.type == "keyword" && /^(?:end|until|else|elsif|when|rescue)\b/.test(textAfter);
|
274
|
+
return ct.indented + (closing ? 0 : config.indentUnit) +
|
275
|
+
(state.continuedLine ? config.indentUnit : 0);
|
276
|
+
},
|
277
|
+
|
278
|
+
electricChars: "}de", // enD and rescuE
|
279
|
+
lineComment: "#"
|
280
|
+
};
|
281
|
+
});
|
282
|
+
|
283
|
+
CodeMirror.defineMIME("text/x-ruby", "ruby");
|
284
|
+
|
285
|
+
});
|
data/js/show-hint.js
ADDED
@@ -0,0 +1,383 @@
|
|
1
|
+
// CodeMirror, copyright (c) by Marijn Haverbeke and others
|
2
|
+
// Distributed under an MIT license: http://codemirror.net/LICENSE
|
3
|
+
|
4
|
+
(function(mod) {
|
5
|
+
if (typeof exports == "object" && typeof module == "object") // CommonJS
|
6
|
+
mod(require("../../lib/codemirror"));
|
7
|
+
else if (typeof define == "function" && define.amd) // AMD
|
8
|
+
define(["../../lib/codemirror"], mod);
|
9
|
+
else // Plain browser env
|
10
|
+
mod(CodeMirror);
|
11
|
+
})(function(CodeMirror) {
|
12
|
+
"use strict";
|
13
|
+
|
14
|
+
var HINT_ELEMENT_CLASS = "CodeMirror-hint";
|
15
|
+
var ACTIVE_HINT_ELEMENT_CLASS = "CodeMirror-hint-active";
|
16
|
+
|
17
|
+
// This is the old interface, kept around for now to stay
|
18
|
+
// backwards-compatible.
|
19
|
+
CodeMirror.showHint = function(cm, getHints, options) {
|
20
|
+
if (!getHints) return cm.showHint(options);
|
21
|
+
if (options && options.async) getHints.async = true;
|
22
|
+
var newOpts = {hint: getHints};
|
23
|
+
if (options) for (var prop in options) newOpts[prop] = options[prop];
|
24
|
+
return cm.showHint(newOpts);
|
25
|
+
};
|
26
|
+
|
27
|
+
CodeMirror.defineExtension("showHint", function(options) {
|
28
|
+
// We want a single cursor position.
|
29
|
+
if (this.listSelections().length > 1 || this.somethingSelected()) return;
|
30
|
+
|
31
|
+
if (this.state.completionActive) this.state.completionActive.close();
|
32
|
+
var completion = this.state.completionActive = new Completion(this, options);
|
33
|
+
if (!completion.options.hint) return;
|
34
|
+
|
35
|
+
CodeMirror.signal(this, "startCompletion", this);
|
36
|
+
completion.update(true);
|
37
|
+
});
|
38
|
+
|
39
|
+
function Completion(cm, options) {
|
40
|
+
this.cm = cm;
|
41
|
+
this.options = this.buildOptions(options);
|
42
|
+
this.widget = null;
|
43
|
+
this.debounce = 0;
|
44
|
+
this.tick = 0;
|
45
|
+
this.startPos = this.cm.getCursor();
|
46
|
+
this.startLen = this.cm.getLine(this.startPos.line).length;
|
47
|
+
|
48
|
+
var self = this;
|
49
|
+
cm.on("cursorActivity", this.activityFunc = function() { self.cursorActivity(); });
|
50
|
+
}
|
51
|
+
|
52
|
+
var requestAnimationFrame = window.requestAnimationFrame || function(fn) {
|
53
|
+
return setTimeout(fn, 1000/60);
|
54
|
+
};
|
55
|
+
var cancelAnimationFrame = window.cancelAnimationFrame || clearTimeout;
|
56
|
+
|
57
|
+
Completion.prototype = {
|
58
|
+
close: function() {
|
59
|
+
if (!this.active()) return;
|
60
|
+
this.cm.state.completionActive = null;
|
61
|
+
this.tick = null;
|
62
|
+
this.cm.off("cursorActivity", this.activityFunc);
|
63
|
+
|
64
|
+
if (this.widget && this.data) CodeMirror.signal(this.data, "close");
|
65
|
+
if (this.widget) this.widget.close();
|
66
|
+
CodeMirror.signal(this.cm, "endCompletion", this.cm);
|
67
|
+
},
|
68
|
+
|
69
|
+
active: function() {
|
70
|
+
return this.cm.state.completionActive == this;
|
71
|
+
},
|
72
|
+
|
73
|
+
pick: function(data, i) {
|
74
|
+
var completion = data.list[i];
|
75
|
+
if (completion.hint) completion.hint(this.cm, data, completion);
|
76
|
+
else this.cm.replaceRange(getText(completion), completion.from || data.from,
|
77
|
+
completion.to || data.to, "complete");
|
78
|
+
CodeMirror.signal(data, "pick", completion);
|
79
|
+
this.close();
|
80
|
+
},
|
81
|
+
|
82
|
+
cursorActivity: function() {
|
83
|
+
if (this.debounce) {
|
84
|
+
cancelAnimationFrame(this.debounce);
|
85
|
+
this.debounce = 0;
|
86
|
+
}
|
87
|
+
|
88
|
+
var pos = this.cm.getCursor(), line = this.cm.getLine(pos.line);
|
89
|
+
if (pos.line != this.startPos.line || line.length - pos.ch != this.startLen - this.startPos.ch ||
|
90
|
+
pos.ch < this.startPos.ch || this.cm.somethingSelected() ||
|
91
|
+
(pos.ch && this.options.closeCharacters.test(line.charAt(pos.ch - 1)))) {
|
92
|
+
this.close();
|
93
|
+
} else {
|
94
|
+
var self = this;
|
95
|
+
this.debounce = requestAnimationFrame(function() {self.update();});
|
96
|
+
if (this.widget) this.widget.disable();
|
97
|
+
}
|
98
|
+
},
|
99
|
+
|
100
|
+
update: function(first) {
|
101
|
+
if (this.tick == null) return;
|
102
|
+
if (this.data) CodeMirror.signal(this.data, "update");
|
103
|
+
if (!this.options.hint.async) {
|
104
|
+
this.finishUpdate(this.options.hint(this.cm, this.options), first);
|
105
|
+
} else {
|
106
|
+
var myTick = ++this.tick, self = this;
|
107
|
+
this.options.hint(this.cm, function(data) {
|
108
|
+
if (self.tick == myTick) self.finishUpdate(data, first);
|
109
|
+
}, this.options);
|
110
|
+
}
|
111
|
+
},
|
112
|
+
|
113
|
+
finishUpdate: function(data, first) {
|
114
|
+
this.data = data;
|
115
|
+
|
116
|
+
var picked = (this.widget && this.widget.picked) || (first && this.options.completeSingle);
|
117
|
+
if (this.widget) this.widget.close();
|
118
|
+
if (data && data.list.length) {
|
119
|
+
if (picked && data.list.length == 1) {
|
120
|
+
this.pick(data, 0);
|
121
|
+
} else {
|
122
|
+
this.widget = new Widget(this, data);
|
123
|
+
CodeMirror.signal(data, "shown");
|
124
|
+
}
|
125
|
+
}
|
126
|
+
},
|
127
|
+
|
128
|
+
buildOptions: function(options) {
|
129
|
+
var editor = this.cm.options.hintOptions;
|
130
|
+
var out = {};
|
131
|
+
for (var prop in defaultOptions) out[prop] = defaultOptions[prop];
|
132
|
+
if (editor) for (var prop in editor)
|
133
|
+
if (editor[prop] !== undefined) out[prop] = editor[prop];
|
134
|
+
if (options) for (var prop in options)
|
135
|
+
if (options[prop] !== undefined) out[prop] = options[prop];
|
136
|
+
return out;
|
137
|
+
}
|
138
|
+
};
|
139
|
+
|
140
|
+
function getText(completion) {
|
141
|
+
if (typeof completion == "string") return completion;
|
142
|
+
else return completion.text;
|
143
|
+
}
|
144
|
+
|
145
|
+
function buildKeyMap(completion, handle) {
|
146
|
+
var baseMap = {
|
147
|
+
Up: function() {handle.moveFocus(-1);},
|
148
|
+
Down: function() {handle.moveFocus(1);},
|
149
|
+
PageUp: function() {handle.moveFocus(-handle.menuSize() + 1, true);},
|
150
|
+
PageDown: function() {handle.moveFocus(handle.menuSize() - 1, true);},
|
151
|
+
Home: function() {handle.setFocus(0);},
|
152
|
+
End: function() {handle.setFocus(handle.length - 1);},
|
153
|
+
Enter: handle.pick,
|
154
|
+
Tab: handle.pick,
|
155
|
+
Esc: handle.close
|
156
|
+
};
|
157
|
+
var custom = completion.options.customKeys;
|
158
|
+
var ourMap = custom ? {} : baseMap;
|
159
|
+
function addBinding(key, val) {
|
160
|
+
var bound;
|
161
|
+
if (typeof val != "string")
|
162
|
+
bound = function(cm) { return val(cm, handle); };
|
163
|
+
// This mechanism is deprecated
|
164
|
+
else if (baseMap.hasOwnProperty(val))
|
165
|
+
bound = baseMap[val];
|
166
|
+
else
|
167
|
+
bound = val;
|
168
|
+
ourMap[key] = bound;
|
169
|
+
}
|
170
|
+
if (custom)
|
171
|
+
for (var key in custom) if (custom.hasOwnProperty(key))
|
172
|
+
addBinding(key, custom[key]);
|
173
|
+
var extra = completion.options.extraKeys;
|
174
|
+
if (extra)
|
175
|
+
for (var key in extra) if (extra.hasOwnProperty(key))
|
176
|
+
addBinding(key, extra[key]);
|
177
|
+
return ourMap;
|
178
|
+
}
|
179
|
+
|
180
|
+
function getHintElement(hintsElement, el) {
|
181
|
+
while (el && el != hintsElement) {
|
182
|
+
if (el.nodeName.toUpperCase() === "LI" && el.parentNode == hintsElement) return el;
|
183
|
+
el = el.parentNode;
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
function Widget(completion, data) {
|
188
|
+
this.completion = completion;
|
189
|
+
this.data = data;
|
190
|
+
this.picked = false;
|
191
|
+
var widget = this, cm = completion.cm;
|
192
|
+
|
193
|
+
var hints = this.hints = document.createElement("ul");
|
194
|
+
hints.className = "CodeMirror-hints";
|
195
|
+
this.selectedHint = data.selectedHint || 0;
|
196
|
+
|
197
|
+
var completions = data.list;
|
198
|
+
for (var i = 0; i < completions.length; ++i) {
|
199
|
+
var elt = hints.appendChild(document.createElement("li")), cur = completions[i];
|
200
|
+
var className = HINT_ELEMENT_CLASS + (i != this.selectedHint ? "" : " " + ACTIVE_HINT_ELEMENT_CLASS);
|
201
|
+
if (cur.className != null) className = cur.className + " " + className;
|
202
|
+
elt.className = className;
|
203
|
+
if (cur.render) cur.render(elt, data, cur);
|
204
|
+
else elt.appendChild(document.createTextNode(cur.displayText || getText(cur)));
|
205
|
+
elt.hintId = i;
|
206
|
+
}
|
207
|
+
|
208
|
+
var pos = cm.cursorCoords(completion.options.alignWithWord ? data.from : null);
|
209
|
+
var left = pos.left, top = pos.bottom, below = true;
|
210
|
+
hints.style.left = left + "px";
|
211
|
+
hints.style.top = top + "px";
|
212
|
+
// If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
|
213
|
+
var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
|
214
|
+
var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
|
215
|
+
(completion.options.container || document.body).appendChild(hints);
|
216
|
+
var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
|
217
|
+
if (overlapY > 0) {
|
218
|
+
var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
|
219
|
+
if (curTop - height > 0) { // Fits above cursor
|
220
|
+
hints.style.top = (top = pos.top - height) + "px";
|
221
|
+
below = false;
|
222
|
+
} else if (height > winH) {
|
223
|
+
hints.style.height = (winH - 5) + "px";
|
224
|
+
hints.style.top = (top = pos.bottom - box.top) + "px";
|
225
|
+
var cursor = cm.getCursor();
|
226
|
+
if (data.from.ch != cursor.ch) {
|
227
|
+
pos = cm.cursorCoords(cursor);
|
228
|
+
hints.style.left = (left = pos.left) + "px";
|
229
|
+
box = hints.getBoundingClientRect();
|
230
|
+
}
|
231
|
+
}
|
232
|
+
}
|
233
|
+
var overlapX = box.right - winW;
|
234
|
+
if (overlapX > 0) {
|
235
|
+
if (box.right - box.left > winW) {
|
236
|
+
hints.style.width = (winW - 5) + "px";
|
237
|
+
overlapX -= (box.right - box.left) - winW;
|
238
|
+
}
|
239
|
+
hints.style.left = (left = pos.left - overlapX) + "px";
|
240
|
+
}
|
241
|
+
|
242
|
+
cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
|
243
|
+
moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
|
244
|
+
setFocus: function(n) { widget.changeActive(n); },
|
245
|
+
menuSize: function() { return widget.screenAmount(); },
|
246
|
+
length: completions.length,
|
247
|
+
close: function() { completion.close(); },
|
248
|
+
pick: function() { widget.pick(); },
|
249
|
+
data: data
|
250
|
+
}));
|
251
|
+
|
252
|
+
if (completion.options.closeOnUnfocus) {
|
253
|
+
var closingOnBlur;
|
254
|
+
cm.on("blur", this.onBlur = function() { closingOnBlur = setTimeout(function() { completion.close(); }, 100); });
|
255
|
+
cm.on("focus", this.onFocus = function() { clearTimeout(closingOnBlur); });
|
256
|
+
}
|
257
|
+
|
258
|
+
var startScroll = cm.getScrollInfo();
|
259
|
+
cm.on("scroll", this.onScroll = function() {
|
260
|
+
var curScroll = cm.getScrollInfo(), editor = cm.getWrapperElement().getBoundingClientRect();
|
261
|
+
var newTop = top + startScroll.top - curScroll.top;
|
262
|
+
var point = newTop - (window.pageYOffset || (document.documentElement || document.body).scrollTop);
|
263
|
+
if (!below) point += hints.offsetHeight;
|
264
|
+
if (point <= editor.top || point >= editor.bottom) return completion.close();
|
265
|
+
hints.style.top = newTop + "px";
|
266
|
+
hints.style.left = (left + startScroll.left - curScroll.left) + "px";
|
267
|
+
});
|
268
|
+
|
269
|
+
CodeMirror.on(hints, "dblclick", function(e) {
|
270
|
+
var t = getHintElement(hints, e.target || e.srcElement);
|
271
|
+
if (t && t.hintId != null) {widget.changeActive(t.hintId); widget.pick();}
|
272
|
+
});
|
273
|
+
|
274
|
+
CodeMirror.on(hints, "click", function(e) {
|
275
|
+
var t = getHintElement(hints, e.target || e.srcElement);
|
276
|
+
if (t && t.hintId != null) {
|
277
|
+
widget.changeActive(t.hintId);
|
278
|
+
if (completion.options.completeOnSingleClick) widget.pick();
|
279
|
+
}
|
280
|
+
});
|
281
|
+
|
282
|
+
CodeMirror.on(hints, "mousedown", function() {
|
283
|
+
setTimeout(function(){cm.focus();}, 20);
|
284
|
+
});
|
285
|
+
|
286
|
+
CodeMirror.signal(data, "select", completions[0], hints.firstChild);
|
287
|
+
return true;
|
288
|
+
}
|
289
|
+
|
290
|
+
Widget.prototype = {
|
291
|
+
close: function() {
|
292
|
+
if (this.completion.widget != this) return;
|
293
|
+
this.completion.widget = null;
|
294
|
+
this.hints.parentNode.removeChild(this.hints);
|
295
|
+
this.completion.cm.removeKeyMap(this.keyMap);
|
296
|
+
|
297
|
+
var cm = this.completion.cm;
|
298
|
+
if (this.completion.options.closeOnUnfocus) {
|
299
|
+
cm.off("blur", this.onBlur);
|
300
|
+
cm.off("focus", this.onFocus);
|
301
|
+
}
|
302
|
+
cm.off("scroll", this.onScroll);
|
303
|
+
},
|
304
|
+
|
305
|
+
disable: function() {
|
306
|
+
this.completion.cm.removeKeyMap(this.keyMap);
|
307
|
+
var widget = this;
|
308
|
+
this.keyMap = {Enter: function() { widget.picked = true; }};
|
309
|
+
this.completion.cm.addKeyMap(this.keyMap);
|
310
|
+
},
|
311
|
+
|
312
|
+
pick: function() {
|
313
|
+
this.completion.pick(this.data, this.selectedHint);
|
314
|
+
},
|
315
|
+
|
316
|
+
changeActive: function(i, avoidWrap) {
|
317
|
+
if (i >= this.data.list.length)
|
318
|
+
i = avoidWrap ? this.data.list.length - 1 : 0;
|
319
|
+
else if (i < 0)
|
320
|
+
i = avoidWrap ? 0 : this.data.list.length - 1;
|
321
|
+
if (this.selectedHint == i) return;
|
322
|
+
var node = this.hints.childNodes[this.selectedHint];
|
323
|
+
node.className = node.className.replace(" " + ACTIVE_HINT_ELEMENT_CLASS, "");
|
324
|
+
node = this.hints.childNodes[this.selectedHint = i];
|
325
|
+
node.className += " " + ACTIVE_HINT_ELEMENT_CLASS;
|
326
|
+
if (node.offsetTop < this.hints.scrollTop)
|
327
|
+
this.hints.scrollTop = node.offsetTop - 3;
|
328
|
+
else if (node.offsetTop + node.offsetHeight > this.hints.scrollTop + this.hints.clientHeight)
|
329
|
+
this.hints.scrollTop = node.offsetTop + node.offsetHeight - this.hints.clientHeight + 3;
|
330
|
+
CodeMirror.signal(this.data, "select", this.data.list[this.selectedHint], node);
|
331
|
+
},
|
332
|
+
|
333
|
+
screenAmount: function() {
|
334
|
+
return Math.floor(this.hints.clientHeight / this.hints.firstChild.offsetHeight) || 1;
|
335
|
+
}
|
336
|
+
};
|
337
|
+
|
338
|
+
CodeMirror.registerHelper("hint", "auto", function(cm, options) {
|
339
|
+
var helpers = cm.getHelpers(cm.getCursor(), "hint"), words;
|
340
|
+
if (helpers.length) {
|
341
|
+
for (var i = 0; i < helpers.length; i++) {
|
342
|
+
var cur = helpers[i](cm, options);
|
343
|
+
if (cur && cur.list.length) return cur;
|
344
|
+
}
|
345
|
+
} else if (words = cm.getHelper(cm.getCursor(), "hintWords")) {
|
346
|
+
if (words) return CodeMirror.hint.fromList(cm, {words: words});
|
347
|
+
} else if (CodeMirror.hint.anyword) {
|
348
|
+
return CodeMirror.hint.anyword(cm, options);
|
349
|
+
}
|
350
|
+
});
|
351
|
+
|
352
|
+
CodeMirror.registerHelper("hint", "fromList", function(cm, options) {
|
353
|
+
var cur = cm.getCursor(), token = cm.getTokenAt(cur);
|
354
|
+
var found = [];
|
355
|
+
for (var i = 0; i < options.words.length; i++) {
|
356
|
+
var word = options.words[i];
|
357
|
+
if (word.slice(0, token.string.length) == token.string)
|
358
|
+
found.push(word);
|
359
|
+
}
|
360
|
+
|
361
|
+
if (found.length) return {
|
362
|
+
list: found,
|
363
|
+
from: CodeMirror.Pos(cur.line, token.start),
|
364
|
+
to: CodeMirror.Pos(cur.line, token.end)
|
365
|
+
};
|
366
|
+
});
|
367
|
+
|
368
|
+
CodeMirror.commands.autocomplete = CodeMirror.showHint;
|
369
|
+
|
370
|
+
var defaultOptions = {
|
371
|
+
hint: CodeMirror.hint.auto,
|
372
|
+
completeSingle: true,
|
373
|
+
alignWithWord: true,
|
374
|
+
closeCharacters: /[\s()\[\]{};:>,]/,
|
375
|
+
closeOnUnfocus: true,
|
376
|
+
completeOnSingleClick: false,
|
377
|
+
container: null,
|
378
|
+
customKeys: null,
|
379
|
+
extraKeys: null
|
380
|
+
};
|
381
|
+
|
382
|
+
CodeMirror.defineOption("hintOptions", null);
|
383
|
+
});
|