web-console 2.3.0 → 4.2.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.
- checksums.yaml +5 -5
- data/CHANGELOG.markdown +133 -1
- data/MIT-LICENSE +1 -1
- data/README.markdown +48 -86
- data/Rakefile +14 -12
- data/lib/web-console.rb +3 -1
- data/lib/web_console/context.rb +45 -0
- data/lib/web_console/errors.rb +2 -0
- data/lib/web_console/evaluator.rb +14 -5
- data/lib/web_console/exception_mapper.rb +56 -0
- data/lib/web_console/extensions.rb +28 -17
- data/lib/web_console/injector.rb +32 -0
- data/lib/web_console/interceptor.rb +18 -0
- data/lib/web_console/locales/en.yml +1 -1
- data/lib/web_console/middleware.rb +31 -31
- data/lib/web_console/permissions.rb +42 -0
- data/lib/web_console/railtie.rb +38 -24
- data/lib/web_console/request.rb +8 -20
- data/lib/web_console/session.rb +32 -18
- data/lib/web_console/source_location.rb +15 -0
- data/lib/web_console/tasks/extensions.rake +15 -13
- data/lib/web_console/tasks/templates.rake +56 -0
- data/lib/web_console/template.rb +4 -3
- data/lib/web_console/templates/console.js.erb +497 -37
- data/lib/web_console/templates/error_page.js.erb +7 -8
- data/lib/web_console/templates/index.html.erb +4 -0
- data/lib/web_console/templates/layouts/inlined_string.erb +1 -1
- data/lib/web_console/templates/layouts/javascript.erb +1 -1
- data/lib/web_console/templates/regular_page.js.erb +24 -0
- data/lib/web_console/templates/style.css.erb +182 -27
- data/lib/web_console/testing/erb_precompiler.rb +5 -3
- data/lib/web_console/testing/fake_middleware.rb +7 -10
- data/lib/web_console/testing/helper.rb +3 -1
- data/lib/web_console/version.rb +3 -1
- data/lib/web_console/view.rb +24 -3
- data/lib/web_console/whiny_request.rb +8 -6
- data/lib/web_console.rb +28 -20
- metadata +28 -63
- data/lib/web_console/helper.rb +0 -22
- data/lib/web_console/integration/cruby.rb +0 -40
- data/lib/web_console/integration/jruby.rb +0 -111
- data/lib/web_console/integration/rubinius.rb +0 -67
- data/lib/web_console/integration.rb +0 -8
- data/lib/web_console/response.rb +0 -23
- data/lib/web_console/tasks/test_templates.rake +0 -50
- data/lib/web_console/whitelist.rb +0 -42
|
@@ -44,13 +44,221 @@ function CommandStorage() {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
function Autocomplete(_words, prefix) {
|
|
48
|
+
this.words = prepareWords(_words);
|
|
49
|
+
this.current = -1;
|
|
50
|
+
this.left = 0; // [left, right)
|
|
51
|
+
this.right = this.words.length;
|
|
52
|
+
this.confirmed = false;
|
|
53
|
+
|
|
54
|
+
function createSpan(label, className) {
|
|
55
|
+
var el = document.createElement('span');
|
|
56
|
+
addClass(el, className);
|
|
57
|
+
el.innerText = label;
|
|
58
|
+
return el;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function prepareWords(words) {
|
|
62
|
+
// convert into an object with priority and element
|
|
63
|
+
var res = new Array(words.length);
|
|
64
|
+
for (var i = 0, ind = 0; i < words.length; ++i) {
|
|
65
|
+
res[i] = new Array(words[i].length);
|
|
66
|
+
for (var j = 0; j < words[i].length; ++j) {
|
|
67
|
+
res[i][j] = {
|
|
68
|
+
word: words[i][j],
|
|
69
|
+
priority: i,
|
|
70
|
+
element: createSpan(words[i][j], 'trimmed keyword')
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// flatten and sort by alphabetical order to refine incrementally
|
|
75
|
+
res = flatten(res);
|
|
76
|
+
res.sort(function(a, b) { return a.word == b.word ? 0 : (a.word < b.word ? -1 : 1); });
|
|
77
|
+
for (var i = 0; i < res.length; ++i) res[i].element.dataset.index = i;
|
|
78
|
+
return res;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.view = document.createElement('pre');
|
|
82
|
+
addClass(this.view, 'auto-complete console-message');
|
|
83
|
+
this.view.appendChild(this.prefix = createSpan('...', 'trimmed keyword'));
|
|
84
|
+
this.view.appendChild(this.stage = document.createElement('span'));
|
|
85
|
+
this.elements = this.stage.children;
|
|
86
|
+
this.view.appendChild(this.suffix = createSpan('...', 'trimmed keyword'));
|
|
87
|
+
|
|
88
|
+
this.refine(prefix || '');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Autocomplete.prototype.getSelectedWord = function() {
|
|
92
|
+
return this.lastSelected && this.lastSelected.innerText;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
Autocomplete.prototype.onFinished = function(callback) {
|
|
96
|
+
this.onFinishedCallback = callback;
|
|
97
|
+
if (this.confirmed) callback(this.confirmed);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
Autocomplete.prototype.onKeyDown = function(ev) {
|
|
101
|
+
var self = this;
|
|
102
|
+
if (!this.elements.length) return;
|
|
103
|
+
|
|
104
|
+
function move(nextCurrent) {
|
|
105
|
+
if (self.lastSelected) removeClass(self.lastSelected, 'selected');
|
|
106
|
+
addClass(self.lastSelected = self.elements[nextCurrent], 'selected');
|
|
107
|
+
self.trim(self.current, true);
|
|
108
|
+
self.trim(nextCurrent, false);
|
|
109
|
+
self.current = nextCurrent;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
switch (ev.keyCode) {
|
|
113
|
+
case 69:
|
|
114
|
+
if (ev.ctrlKey) {
|
|
115
|
+
move(this.current + 1 >= this.elements.length ? 0 : this.current + 1);
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
case 9: // Tab
|
|
120
|
+
if (ev.shiftKey) { // move back
|
|
121
|
+
move(this.current - 1 < 0 ? this.elements.length - 1 : this.current - 1);
|
|
122
|
+
} else { // move next
|
|
123
|
+
move(this.current + 1 >= this.elements.length ? 0 : this.current + 1);
|
|
124
|
+
}
|
|
125
|
+
return true;
|
|
126
|
+
case 13: // Enter
|
|
127
|
+
this.finish();
|
|
128
|
+
return true;
|
|
129
|
+
case 27: // Esc
|
|
130
|
+
this.cancel();
|
|
131
|
+
return true;
|
|
132
|
+
case 37: case 38: case 39: case 40: // disable using arrow keys on completing
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return false;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
Autocomplete.prototype.trim = function(from, needToTrim) {
|
|
140
|
+
var self = this;
|
|
141
|
+
var num = 5;
|
|
142
|
+
|
|
143
|
+
if (this.elements.length > num) {
|
|
144
|
+
(0 < from ? removeClass : addClass)(this.prefix, 'trimmed');
|
|
145
|
+
(from + num < this.elements.length ? removeClass : addClass)(this.suffix, 'trimmed');
|
|
146
|
+
} else {
|
|
147
|
+
addClass(this.prefix, 'trimmed');
|
|
148
|
+
addClass(this.suffix, 'trimmed');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function iterate(x) {
|
|
152
|
+
for (var i = 0; i < num; ++i, ++x) if (0 <= x && x < self.elements.length) {
|
|
153
|
+
toggleClass(self.elements[x], 'trimmed');
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
var toggleClass = needToTrim ? addClass : removeClass;
|
|
158
|
+
if (from < 0) {
|
|
159
|
+
iterate(0);
|
|
160
|
+
} else if (from + num - 1 >= this.elements.length) {
|
|
161
|
+
iterate(this.elements.length - num);
|
|
162
|
+
} else {
|
|
163
|
+
iterate(from);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
Autocomplete.prototype.refine = function(prefix) {
|
|
168
|
+
if (this.confirmed) return;
|
|
169
|
+
var inc = !this.prev || (prefix.length >= this.prev.length);
|
|
170
|
+
this.prev = prefix;
|
|
171
|
+
var self = this;
|
|
172
|
+
|
|
173
|
+
function remove(parent, child) {
|
|
174
|
+
if (parent == child.parentNode) parent.removeChild(child);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function toggle(el) {
|
|
178
|
+
return inc ? remove(self.stage, el) : self.stage.appendChild(el);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function startsWith(str, prefix) {
|
|
182
|
+
return !prefix || str.substr(0, prefix.length) === prefix;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function moveRight(l, r) {
|
|
186
|
+
while (l < r && inc !== startsWith(self.words[l].word, prefix)) toggle(self.words[l++].element);
|
|
187
|
+
return l;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function moveLeft(l, r) {
|
|
191
|
+
while (l < r - 1 && inc !== startsWith(self.words[r-1].word, prefix)) toggle(self.words[--r].element);
|
|
192
|
+
return r;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
self.trim(self.current, true); // reset trimming
|
|
196
|
+
|
|
197
|
+
// Refine the range of words having same prefix
|
|
198
|
+
if (inc) {
|
|
199
|
+
self.left = moveRight(self.left, self.right);
|
|
200
|
+
self.right = moveLeft(self.left, self.right);
|
|
201
|
+
} else {
|
|
202
|
+
self.left = moveLeft(-1, self.left);
|
|
203
|
+
self.right = moveRight(self.right, self.words.length);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Render elements with sorting by scope groups
|
|
207
|
+
var words = this.words.slice(this.left, this.right);
|
|
208
|
+
words.sort(function(a, b) { return a.priority == b.priority ? (a.word < b.word ? -1 : 1) : (a.priority < b.priority ? -1 : 1); });
|
|
209
|
+
removeAllChildren(this.elements);
|
|
210
|
+
for (var i = 0; i < words.length; ++i) {
|
|
211
|
+
this.stage.appendChild(words[i].element);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Keep a previous selected element if the refined range includes the element
|
|
215
|
+
if (this.lastSelected && this.left <= this.lastSelected.dataset.index && this.lastSelected.dataset.index < this.right) {
|
|
216
|
+
this.current = Array.prototype.indexOf.call(this.elements, this.lastSelected);
|
|
217
|
+
this.trim(this.current, false);
|
|
218
|
+
} else {
|
|
219
|
+
if (this.lastSelected) removeClass(this.lastSelected, 'selected');
|
|
220
|
+
this.lastSelected = null;
|
|
221
|
+
this.current = -1;
|
|
222
|
+
this.trim(0, false);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (self.left + 1 == self.right) {
|
|
226
|
+
self.current = 0;
|
|
227
|
+
self.finish();
|
|
228
|
+
} else if (self.left == self.right) {
|
|
229
|
+
self.cancel();
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
Autocomplete.prototype.finish = function() {
|
|
234
|
+
if (0 <= this.current && this.current < this.elements.length) {
|
|
235
|
+
this.confirmed = this.elements[this.current].innerText;
|
|
236
|
+
if (this.onFinishedCallback) this.onFinishedCallback(this.confirmed);
|
|
237
|
+
this.removeView();
|
|
238
|
+
} else {
|
|
239
|
+
this.cancel();
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
Autocomplete.prototype.cancel = function() {
|
|
244
|
+
if (this.onFinishedCallback) this.onFinishedCallback();
|
|
245
|
+
this.removeView();
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
Autocomplete.prototype.removeView = function() {
|
|
249
|
+
if (this.view.parentNode) this.view.parentNode.removeChild(this.view);
|
|
250
|
+
removeAllChildren(this.view);
|
|
251
|
+
}
|
|
252
|
+
|
|
47
253
|
// HTML strings for dynamic elements.
|
|
48
|
-
var consoleInnerHtml = <%= render_inlined_string '_inner_console_markup
|
|
49
|
-
var promptBoxHtml = <%= render_inlined_string '_prompt_box_markup
|
|
254
|
+
var consoleInnerHtml = <%= render_inlined_string '_inner_console_markup' %>;
|
|
255
|
+
var promptBoxHtml = <%= render_inlined_string '_prompt_box_markup' %>;
|
|
50
256
|
// CSS
|
|
51
|
-
var consoleStyleCss = <%= render_inlined_string 'style
|
|
257
|
+
var consoleStyleCss = <%= render_inlined_string 'style' %>;
|
|
52
258
|
// Insert a style element with the unique ID
|
|
53
259
|
var styleElementId = 'sr02459pvbvrmhco';
|
|
260
|
+
// Nonce to use for CSP
|
|
261
|
+
var styleElementNonce = '<%= @nonce %>';
|
|
54
262
|
|
|
55
263
|
// REPLConsole Constructor
|
|
56
264
|
function REPLConsole(config) {
|
|
@@ -62,6 +270,7 @@ function REPLConsole(config) {
|
|
|
62
270
|
this.prompt = getConfig('promptLabel', ' >>');
|
|
63
271
|
this.mountPoint = getConfig('mountPoint');
|
|
64
272
|
this.sessionId = getConfig('sessionId');
|
|
273
|
+
this.autocomplete = false;
|
|
65
274
|
}
|
|
66
275
|
|
|
67
276
|
REPLConsole.prototype.getSessionUrl = function(path) {
|
|
@@ -73,6 +282,16 @@ REPLConsole.prototype.getSessionUrl = function(path) {
|
|
|
73
282
|
return parts.join('/').replace(/([^:]\/)\/+/g, '$1');
|
|
74
283
|
};
|
|
75
284
|
|
|
285
|
+
REPLConsole.prototype.contextRequest = function(keyword, callback) {
|
|
286
|
+
putRequest(this.getSessionUrl(), 'context=' + getContext(keyword), function(xhr) {
|
|
287
|
+
if (xhr.status == 200) {
|
|
288
|
+
callback(null, JSON.parse(xhr.responseText));
|
|
289
|
+
} else {
|
|
290
|
+
callback(xhr.statusText);
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
};
|
|
294
|
+
|
|
76
295
|
REPLConsole.prototype.commandHandle = function(line, callback) {
|
|
77
296
|
var self = this;
|
|
78
297
|
var params = 'input=' + encodeURIComponent(line);
|
|
@@ -92,7 +311,7 @@ REPLConsole.prototype.commandHandle = function(line, callback) {
|
|
|
92
311
|
|
|
93
312
|
function getErrorText(xhr) {
|
|
94
313
|
if (!xhr.status) {
|
|
95
|
-
return "
|
|
314
|
+
return "Connection Refused";
|
|
96
315
|
} else {
|
|
97
316
|
return xhr.status + ' ' + xhr.statusText;
|
|
98
317
|
}
|
|
@@ -163,8 +382,13 @@ REPLConsole.prototype.install = function(container) {
|
|
|
163
382
|
var clientHeightStart = consoleOuter.clientHeight;
|
|
164
383
|
|
|
165
384
|
var doDrag = function(e) {
|
|
166
|
-
|
|
385
|
+
var height = startHeight + startY - e.clientY;
|
|
167
386
|
consoleOuter.scrollTop = scrollTopStart + (clientHeightStart - consoleOuter.clientHeight);
|
|
387
|
+
if (height > document.documentElement.clientHeight) {
|
|
388
|
+
container.style.height = document.documentElement.clientHeight;
|
|
389
|
+
} else {
|
|
390
|
+
container.style.height = height + 'px';
|
|
391
|
+
}
|
|
168
392
|
shiftConsoleActions();
|
|
169
393
|
};
|
|
170
394
|
|
|
@@ -194,17 +418,26 @@ REPLConsole.prototype.install = function(container) {
|
|
|
194
418
|
}
|
|
195
419
|
}
|
|
196
420
|
|
|
421
|
+
var observer = new MutationObserver(function(mutationsList) {
|
|
422
|
+
for (let mutation of mutationsList) {
|
|
423
|
+
if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
|
|
424
|
+
shiftConsoleActions();
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
197
429
|
// Initialize
|
|
198
430
|
this.container = container;
|
|
199
431
|
this.outer = consoleOuter;
|
|
200
432
|
this.inner = findChild(this.outer, 'console-inner');
|
|
201
433
|
this.clipboard = findChild(container, 'clipboard');
|
|
434
|
+
this.suggestWait = 1500;
|
|
202
435
|
this.newPromptBox();
|
|
203
436
|
this.insertCss();
|
|
204
437
|
|
|
205
438
|
findChild(container, 'resizer').addEventListener('mousedown', resizeContainer);
|
|
206
439
|
findChild(consoleActions, 'close-button').addEventListener('click', closeContainer);
|
|
207
|
-
|
|
440
|
+
observer.observe(consoleOuter, { childList: true, subtree: true });
|
|
208
441
|
|
|
209
442
|
REPLConsole.currentSession = this;
|
|
210
443
|
};
|
|
@@ -218,6 +451,9 @@ REPLConsole.prototype.insertCss = function() {
|
|
|
218
451
|
style.type = 'text/css';
|
|
219
452
|
style.innerHTML = consoleStyleCss;
|
|
220
453
|
style.id = styleElementId;
|
|
454
|
+
if (styleElementNonce.length > 0) {
|
|
455
|
+
style.nonce = styleElementNonce;
|
|
456
|
+
}
|
|
221
457
|
document.getElementsByTagName('head')[0].appendChild(style);
|
|
222
458
|
};
|
|
223
459
|
|
|
@@ -267,10 +503,63 @@ REPLConsole.prototype.removeCaretFromPrompt = function() {
|
|
|
267
503
|
this.setInput(this._input, -1);
|
|
268
504
|
};
|
|
269
505
|
|
|
506
|
+
REPLConsole.prototype.getSuggestion = function(keyword) {
|
|
507
|
+
var self = this;
|
|
508
|
+
|
|
509
|
+
function show(found) {
|
|
510
|
+
if (!found) return;
|
|
511
|
+
var hint = self.promptDisplay.childNodes[1];
|
|
512
|
+
hint.className = 'console-hint';
|
|
513
|
+
hint.dataset.keyword = found;
|
|
514
|
+
hint.innerText = found.substr(self.suggestKeyword.length);
|
|
515
|
+
// clear hinting information after timeout in a few time
|
|
516
|
+
if (self.suggestTimeout) clearTimeout(self.suggestTimeout);
|
|
517
|
+
self.suggestTimeout = setTimeout(function() { self.renderInput() }, self.suggestWait);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
function find(context) {
|
|
521
|
+
var k = self.suggestKeyword;
|
|
522
|
+
for (var i = 0; i < context.length; ++i) if (context[i].substr(0, k.length) === k) {
|
|
523
|
+
if (context[i] === k) return;
|
|
524
|
+
return context[i];
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function request(keyword, callback) {
|
|
529
|
+
self.contextRequest(keyword, function(err, res) {
|
|
530
|
+
if (err) throw new Error(err);
|
|
531
|
+
var c = flatten(res['context']);
|
|
532
|
+
c.sort();
|
|
533
|
+
callback(c);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
self.suggestKeyword = keyword;
|
|
538
|
+
var input = getContext(keyword);
|
|
539
|
+
if (keyword.length - input.length < 3) return;
|
|
540
|
+
|
|
541
|
+
if (self.suggestInput !== input) {
|
|
542
|
+
self.suggestInput = input;
|
|
543
|
+
request(keyword, function(c) {
|
|
544
|
+
show(find(self.suggestContext = c));
|
|
545
|
+
});
|
|
546
|
+
} else if (self.suggestContext) {
|
|
547
|
+
show(find(self.suggestContext));
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
REPLConsole.prototype.getHintKeyword = function() {
|
|
552
|
+
var hint = this.promptDisplay.childNodes[1];
|
|
553
|
+
return hint.className === 'console-hint' && hint.dataset.keyword;
|
|
554
|
+
};
|
|
555
|
+
|
|
270
556
|
REPLConsole.prototype.setInput = function(input, caretPos) {
|
|
557
|
+
if (input == null) return; // keep value if input is undefined
|
|
271
558
|
this._caretPos = caretPos === undefined ? input.length : caretPos;
|
|
272
559
|
this._input = input;
|
|
560
|
+
if (this.autocomplete) this.autocomplete.refine(this.getCurrentWord());
|
|
273
561
|
this.renderInput();
|
|
562
|
+
if (!this.autocomplete && input.length == this._caretPos) this.getSuggestion(this.getCurrentWord());
|
|
274
563
|
};
|
|
275
564
|
|
|
276
565
|
/**
|
|
@@ -292,9 +581,8 @@ REPLConsole.prototype.renderInput = function() {
|
|
|
292
581
|
// Clear the current input.
|
|
293
582
|
removeAllChildren(this.promptDisplay);
|
|
294
583
|
|
|
295
|
-
var promptCursor = document.createElement('span');
|
|
296
|
-
promptCursor.className = "console-cursor";
|
|
297
584
|
var before, current, after;
|
|
585
|
+
var center = document.createElement('span');
|
|
298
586
|
|
|
299
587
|
if (this._caretPos < 0) {
|
|
300
588
|
before = this._input;
|
|
@@ -310,9 +598,12 @@ REPLConsole.prototype.renderInput = function() {
|
|
|
310
598
|
}
|
|
311
599
|
|
|
312
600
|
this.promptDisplay.appendChild(document.createTextNode(before));
|
|
313
|
-
|
|
314
|
-
this.promptDisplay.appendChild(promptCursor);
|
|
601
|
+
this.promptDisplay.appendChild(center);
|
|
315
602
|
this.promptDisplay.appendChild(document.createTextNode(after));
|
|
603
|
+
|
|
604
|
+
var hint = this.autocomplete && this.autocomplete.getSelectedWord();
|
|
605
|
+
addClass(center, hint ? 'console-hint' : 'console-cursor');
|
|
606
|
+
center.appendChild(document.createTextNode(hint ? hint.substr(this.getCurrentWord().length) : current));
|
|
316
607
|
};
|
|
317
608
|
|
|
318
609
|
REPLConsole.prototype.writeOutput = function(output) {
|
|
@@ -330,6 +621,12 @@ REPLConsole.prototype.writeError = function(output) {
|
|
|
330
621
|
return consoleMessage;
|
|
331
622
|
};
|
|
332
623
|
|
|
624
|
+
REPLConsole.prototype.writeNotification = function(output) {
|
|
625
|
+
var consoleMessage = this.writeOutput(output);
|
|
626
|
+
addClass(consoleMessage, "notification-message");
|
|
627
|
+
return consoleMessage;
|
|
628
|
+
};
|
|
629
|
+
|
|
333
630
|
REPLConsole.prototype.onEnterKey = function() {
|
|
334
631
|
var input = this._input;
|
|
335
632
|
|
|
@@ -340,6 +637,30 @@ REPLConsole.prototype.onEnterKey = function() {
|
|
|
340
637
|
this.commandHandle(input);
|
|
341
638
|
};
|
|
342
639
|
|
|
640
|
+
REPLConsole.prototype.onTabKey = function() {
|
|
641
|
+
var self = this;
|
|
642
|
+
|
|
643
|
+
var hintKeyword;
|
|
644
|
+
if (hintKeyword = self.getHintKeyword()) {
|
|
645
|
+
self.swapCurrentWord(hintKeyword);
|
|
646
|
+
return;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (self.autocomplete) return;
|
|
650
|
+
self.autocomplete = new Autocomplete([]);
|
|
651
|
+
|
|
652
|
+
self.contextRequest(self.getCurrentWord(), function(err, obj) {
|
|
653
|
+
if (err) return self.autocomplete = false;
|
|
654
|
+
self.autocomplete = new Autocomplete(obj['context'], self.getCurrentWord());
|
|
655
|
+
self.inner.appendChild(self.autocomplete.view);
|
|
656
|
+
self.autocomplete.onFinished(function(word) {
|
|
657
|
+
self.swapCurrentWord(word);
|
|
658
|
+
self.autocomplete = false;
|
|
659
|
+
});
|
|
660
|
+
self.scrollToBottom();
|
|
661
|
+
});
|
|
662
|
+
};
|
|
663
|
+
|
|
343
664
|
REPLConsole.prototype.onNavigateHistory = function(offset) {
|
|
344
665
|
var command = this.commandStorage.navigate(offset) || "";
|
|
345
666
|
this.setInput(command);
|
|
@@ -349,54 +670,102 @@ REPLConsole.prototype.onNavigateHistory = function(offset) {
|
|
|
349
670
|
* Handle control keys like up, down, left, right.
|
|
350
671
|
*/
|
|
351
672
|
REPLConsole.prototype.onKeyDown = function(ev) {
|
|
673
|
+
if (this.autocomplete && this.autocomplete.onKeyDown(ev)) {
|
|
674
|
+
this.renderInput();
|
|
675
|
+
ev.preventDefault();
|
|
676
|
+
ev.stopPropagation();
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
|
|
352
680
|
switch (ev.keyCode) {
|
|
353
|
-
case
|
|
354
|
-
|
|
681
|
+
case 65: // Ctrl-A
|
|
682
|
+
if (ev.ctrlKey) {
|
|
683
|
+
this.setInput(this._input, 0);
|
|
684
|
+
ev.preventDefault();
|
|
685
|
+
}
|
|
686
|
+
break;
|
|
687
|
+
|
|
688
|
+
case 69: // Ctrl-E
|
|
689
|
+
if (ev.ctrlKey) {
|
|
690
|
+
this.onTabKey();
|
|
691
|
+
ev.preventDefault();
|
|
692
|
+
}
|
|
693
|
+
break;
|
|
694
|
+
|
|
695
|
+
case 87: // Ctrl-W
|
|
696
|
+
if (ev.ctrlKey) {
|
|
697
|
+
this.deleteWord();
|
|
698
|
+
ev.preventDefault();
|
|
699
|
+
}
|
|
700
|
+
break;
|
|
701
|
+
|
|
702
|
+
case 85: // Ctrl-U
|
|
703
|
+
if (ev.ctrlKey) {
|
|
704
|
+
this.deleteLine();
|
|
705
|
+
ev.preventDefault();
|
|
706
|
+
}
|
|
707
|
+
break;
|
|
708
|
+
|
|
709
|
+
case 69: // Ctrl-E
|
|
710
|
+
if (ev.ctrlKey) {
|
|
711
|
+
this.onTabKey();
|
|
712
|
+
ev.preventDefault();
|
|
713
|
+
}
|
|
714
|
+
break;
|
|
715
|
+
|
|
716
|
+
case 80: // Ctrl-P
|
|
717
|
+
if (! ev.ctrlKey) break;
|
|
718
|
+
|
|
719
|
+
case 78: // Ctrl-N
|
|
720
|
+
if (! ev.ctrlKey) break;
|
|
721
|
+
|
|
722
|
+
case 9: // Tab
|
|
723
|
+
this.onTabKey();
|
|
724
|
+
ev.preventDefault();
|
|
725
|
+
break;
|
|
726
|
+
|
|
727
|
+
case 13: // Enter key
|
|
355
728
|
this.onEnterKey();
|
|
356
729
|
ev.preventDefault();
|
|
357
730
|
break;
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if (! ev.ctrlKey) break;
|
|
361
|
-
case 38:
|
|
362
|
-
// Up arrow
|
|
731
|
+
|
|
732
|
+
case 38: // Up arrow
|
|
363
733
|
this.onNavigateHistory(-1);
|
|
364
734
|
ev.preventDefault();
|
|
365
735
|
break;
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
if (! ev.ctrlKey) break;
|
|
369
|
-
case 40:
|
|
370
|
-
// Down arrow
|
|
736
|
+
|
|
737
|
+
case 40: // Down arrow
|
|
371
738
|
this.onNavigateHistory(1);
|
|
372
739
|
ev.preventDefault();
|
|
373
740
|
break;
|
|
374
|
-
|
|
375
|
-
|
|
741
|
+
|
|
742
|
+
case 37: // Left arrow
|
|
376
743
|
var caretPos = this._caretPos > 0 ? this._caretPos - 1 : this._caretPos;
|
|
377
744
|
this.setInput(this._input, caretPos);
|
|
378
745
|
ev.preventDefault();
|
|
379
746
|
break;
|
|
380
|
-
|
|
381
|
-
|
|
747
|
+
|
|
748
|
+
case 39: // Right arrow
|
|
382
749
|
var length = this._input.length;
|
|
383
750
|
var caretPos = this._caretPos < length ? this._caretPos + 1 : this._caretPos;
|
|
384
751
|
this.setInput(this._input, caretPos);
|
|
385
752
|
ev.preventDefault();
|
|
386
753
|
break;
|
|
387
|
-
|
|
388
|
-
|
|
754
|
+
|
|
755
|
+
case 8: // Delete
|
|
389
756
|
this.deleteAtCurrent();
|
|
390
757
|
ev.preventDefault();
|
|
391
758
|
break;
|
|
759
|
+
|
|
392
760
|
default:
|
|
393
761
|
break;
|
|
394
762
|
}
|
|
395
763
|
|
|
396
764
|
if (ev.ctrlKey || ev.metaKey) {
|
|
397
|
-
// Set focus to our clipboard in case they hit the "v" key
|
|
398
|
-
this.clipboard.focus();
|
|
399
765
|
if (ev.keyCode == 86) {
|
|
766
|
+
// Set focus to our clipboard when they hit the "v" key
|
|
767
|
+
this.clipboard.focus();
|
|
768
|
+
|
|
400
769
|
// Pasting to clipboard doesn't happen immediately,
|
|
401
770
|
// so we have to wait for a while to get the pasted text.
|
|
402
771
|
var _this = this;
|
|
@@ -404,7 +773,7 @@ REPLConsole.prototype.onKeyDown = function(ev) {
|
|
|
404
773
|
_this.addToInput(_this.clipboard.value);
|
|
405
774
|
_this.clipboard.value = "";
|
|
406
775
|
_this.clipboard.blur();
|
|
407
|
-
},
|
|
776
|
+
}, 100);
|
|
408
777
|
}
|
|
409
778
|
}
|
|
410
779
|
|
|
@@ -416,7 +785,7 @@ REPLConsole.prototype.onKeyDown = function(ev) {
|
|
|
416
785
|
*/
|
|
417
786
|
REPLConsole.prototype.onKeyPress = function(ev) {
|
|
418
787
|
// Only write to the console if it's a single key press.
|
|
419
|
-
if (ev.ctrlKey || ev.metaKey) { return; }
|
|
788
|
+
if (ev.ctrlKey && !ev.altKey || ev.metaKey) { return; }
|
|
420
789
|
var keyCode = ev.keyCode || ev.which;
|
|
421
790
|
this.insertAtCurrent(String.fromCharCode(keyCode));
|
|
422
791
|
ev.stopPropagation();
|
|
@@ -432,6 +801,52 @@ REPLConsole.prototype.deleteAtCurrent = function() {
|
|
|
432
801
|
var before = this._input.substring(0, caretPos);
|
|
433
802
|
var after = this._input.substring(this._caretPos, this._input.length);
|
|
434
803
|
this.setInput(before + after, caretPos);
|
|
804
|
+
|
|
805
|
+
if (!this._input) {
|
|
806
|
+
this.autocomplete && this.autocomplete.cancel();
|
|
807
|
+
this.autocomplete = false;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* Deletes the current line.
|
|
814
|
+
*/
|
|
815
|
+
REPLConsole.prototype.deleteLine = function() {
|
|
816
|
+
if (this._caretPos > 0) {
|
|
817
|
+
this.setInput("", 0);
|
|
818
|
+
|
|
819
|
+
if (!this._input) {
|
|
820
|
+
this.autocomplete && this.autocomplete.cancel();
|
|
821
|
+
this.autocomplete = false;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
};
|
|
825
|
+
|
|
826
|
+
/**
|
|
827
|
+
* Deletes the current word.
|
|
828
|
+
*/
|
|
829
|
+
REPLConsole.prototype.deleteWord = function() {
|
|
830
|
+
if (this._caretPos > 0) {
|
|
831
|
+
var i = 1, current = this._caretPos;
|
|
832
|
+
while (this._input[current - i++] == " ");
|
|
833
|
+
|
|
834
|
+
var deleteIndex = 0;
|
|
835
|
+
for (; current - i > 0; i++) {
|
|
836
|
+
if (this._input[current - i] == " ") {
|
|
837
|
+
deleteIndex = current - i;
|
|
838
|
+
break;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
var before = this._input.substring(0, deleteIndex);
|
|
843
|
+
var after = this._input.substring(current, this._input.length);
|
|
844
|
+
this.setInput(before + after, deleteIndex);
|
|
845
|
+
|
|
846
|
+
if (!this._input) {
|
|
847
|
+
this.autocomplete && this.autocomplete.cancel();
|
|
848
|
+
this.autocomplete = false;
|
|
849
|
+
}
|
|
435
850
|
}
|
|
436
851
|
};
|
|
437
852
|
|
|
@@ -444,15 +859,49 @@ REPLConsole.prototype.insertAtCurrent = function(char) {
|
|
|
444
859
|
this.setInput(before + char + after, this._caretPos + 1);
|
|
445
860
|
};
|
|
446
861
|
|
|
862
|
+
REPLConsole.prototype.swapCurrentWord = function(next) {
|
|
863
|
+
function right(s, pos) {
|
|
864
|
+
var x = s.indexOf(' ', pos);
|
|
865
|
+
return x === -1 ? s.length : x;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function swap(s, pos) {
|
|
869
|
+
return s.substr(0, s.lastIndexOf(' ', pos) + 1) + next + s.substr(right(s, pos))
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
if (!next) return;
|
|
873
|
+
var swapped = swap(this._input, this._caretPos);
|
|
874
|
+
this.setInput(swapped, this._caretPos + swapped.length - this._input.length);
|
|
875
|
+
};
|
|
876
|
+
|
|
877
|
+
REPLConsole.prototype.getCurrentWord = function() {
|
|
878
|
+
return (function(s, pos) {
|
|
879
|
+
var left = s.lastIndexOf(' ', pos);
|
|
880
|
+
if (left === -1) left = 0;
|
|
881
|
+
var right = s.indexOf(' ', pos)
|
|
882
|
+
if (right === -1) right = s.length - 1;
|
|
883
|
+
return s.substr(left, right - left + 1).replace(/^\s+|\s+$/g,'');
|
|
884
|
+
})(this._input, this._caretPos);
|
|
885
|
+
};
|
|
886
|
+
|
|
447
887
|
REPLConsole.prototype.scrollToBottom = function() {
|
|
448
888
|
this.outer.scrollTop = this.outer.scrollHeight;
|
|
449
889
|
};
|
|
450
890
|
|
|
451
|
-
// Change the binding of the console
|
|
452
|
-
REPLConsole.prototype.switchBindingTo = function(frameId, callback) {
|
|
891
|
+
// Change the binding of the console.
|
|
892
|
+
REPLConsole.prototype.switchBindingTo = function(frameId, exceptionObjectId, callback) {
|
|
453
893
|
var url = this.getSessionUrl('trace');
|
|
454
894
|
var params = "frame_id=" + encodeURIComponent(frameId);
|
|
455
|
-
|
|
895
|
+
|
|
896
|
+
if (exceptionObjectId) {
|
|
897
|
+
params = params + "&exception_object_id=" + encodeURIComponent(exceptionObjectId);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
var _this = this;
|
|
901
|
+
postRequest(url, params, function() {
|
|
902
|
+
var text = "Context has changed to: " + callback();
|
|
903
|
+
_this.writeNotification(text);
|
|
904
|
+
});
|
|
456
905
|
};
|
|
457
906
|
|
|
458
907
|
/**
|
|
@@ -488,7 +937,6 @@ REPLConsole.request = function request(method, url, params, callback) {
|
|
|
488
937
|
xhr.open(method, url, true);
|
|
489
938
|
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
|
490
939
|
xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
|
|
491
|
-
xhr.setRequestHeader("Accept", "<%= Mime::WEB_CONSOLE_V2 %>");
|
|
492
940
|
xhr.send(params);
|
|
493
941
|
|
|
494
942
|
xhr.onreadystatechange = function() {
|
|
@@ -514,7 +962,7 @@ function addClass(el, className) {
|
|
|
514
962
|
for (var i = 0; i < el.length; ++ i) {
|
|
515
963
|
addClass(el[i], className);
|
|
516
964
|
}
|
|
517
|
-
} else {
|
|
965
|
+
} else if (!hasClass(el, className)) {
|
|
518
966
|
el.className += " " + className;
|
|
519
967
|
}
|
|
520
968
|
}
|
|
@@ -562,3 +1010,15 @@ if (typeof exports === 'object') {
|
|
|
562
1010
|
} else {
|
|
563
1011
|
window.REPLConsole = REPLConsole;
|
|
564
1012
|
}
|
|
1013
|
+
|
|
1014
|
+
// Split string by module operators of ruby
|
|
1015
|
+
function getContext(s) {
|
|
1016
|
+
var methodOp = s.lastIndexOf('.');
|
|
1017
|
+
var moduleOp = s.lastIndexOf('::');
|
|
1018
|
+
var x = methodOp > moduleOp ? methodOp : moduleOp;
|
|
1019
|
+
return x !== -1 ? s.substr(0, x) : '';
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
function flatten(arrays) {
|
|
1023
|
+
return Array.prototype.concat.apply([], arrays);
|
|
1024
|
+
}
|