web-console 3.3.1 → 3.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.markdown +4 -0
- data/README.markdown +1 -1
- data/lib/web_console.rb +1 -0
- data/lib/web_console/context.rb +43 -0
- data/lib/web_console/locales/en.yml +1 -1
- data/lib/web_console/middleware.rb +5 -1
- data/lib/web_console/railtie.rb +3 -0
- data/lib/web_console/session.rb +7 -2
- data/lib/web_console/tasks/templates.rake +54 -0
- data/lib/web_console/templates/console.js.erb +364 -6
- data/lib/web_console/templates/style.css.erb +6 -0
- data/lib/web_console/testing/fake_middleware.rb +1 -1
- data/lib/web_console/version.rb +1 -1
- data/lib/web_console/view.rb +5 -1
- metadata +4 -3
- data/lib/web_console/tasks/test_templates.rake +0 -50
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 963c13addc1eda302c2fb5e217334b403e0f6a09
|
4
|
+
data.tar.gz: e8768504bc586024d0061ba82b0b275c65d93442
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00d6e18111d7d37a870015012dd2e590e2e4e392490a9301e883e450a95f7b87b81c767dc3cf90829496d2efecd65ffe07599ac4f09dd50d1014ea35ccb6586a
|
7
|
+
data.tar.gz: 7be81b47f363fb912e2df95c6f44a493235fed6f72aae1f0ea11006919e593668b11d4473434480d448676ef1cfa5962f7e4cd5ada857636cec518357207254f
|
data/CHANGELOG.markdown
CHANGED
data/README.markdown
CHANGED
@@ -185,7 +185,7 @@ end
|
|
185
185
|
|
186
186
|
### Why I'm getting an undefined method `web_console`?
|
187
187
|
|
188
|
-
Make sure
|
188
|
+
Make sure your configuration lives in `config/environments/development.rb`.
|
189
189
|
|
190
190
|
## Credits
|
191
191
|
|
data/lib/web_console.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
module WebConsole
|
2
|
+
# A context lets you get object names related to the current session binding.
|
3
|
+
class Context
|
4
|
+
def initialize(binding)
|
5
|
+
@binding = binding
|
6
|
+
end
|
7
|
+
|
8
|
+
# Extracts entire objects which can be called by the current session unless
|
9
|
+
# the inputs is present.
|
10
|
+
#
|
11
|
+
# Otherwise, it extracts methods and constants of the object specified by
|
12
|
+
# the input.
|
13
|
+
def extract(input = nil)
|
14
|
+
input.present? ? local(input) : global
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
GLOBAL_OBJECTS = [
|
20
|
+
'instance_variables',
|
21
|
+
'local_variables',
|
22
|
+
'methods',
|
23
|
+
'class_variables',
|
24
|
+
'Object.constants',
|
25
|
+
'global_variables'
|
26
|
+
]
|
27
|
+
|
28
|
+
def global
|
29
|
+
GLOBAL_OBJECTS.map { |cmd| eval(cmd) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def local(input)
|
33
|
+
[
|
34
|
+
eval("#{input}.methods").map { |m| "#{input}.#{m}" },
|
35
|
+
eval("#{input}.constants").map { |c| "#{input}::#{c}" },
|
36
|
+
]
|
37
|
+
end
|
38
|
+
|
39
|
+
def eval(cmd)
|
40
|
+
@binding.eval(cmd) rescue []
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
en:
|
2
2
|
errors:
|
3
3
|
unavailable_session: |
|
4
|
-
Session %{id} is
|
4
|
+
Session %{id} is no longer available in memory.
|
5
5
|
|
6
6
|
If you happen to run on a multi-process server (like Unicorn or Puma) the process
|
7
7
|
this request hit doesn't store %{id} in memory. Consider turning the number of
|
@@ -103,7 +103,11 @@ module WebConsole
|
|
103
103
|
|
104
104
|
def update_repl_session(id, request)
|
105
105
|
json_response_with_session(id, request) do |session|
|
106
|
-
|
106
|
+
if input = request.params[:input]
|
107
|
+
{ output: session.eval(input) }
|
108
|
+
elsif input = request.params[:context]
|
109
|
+
{ context: session.context(input) }
|
110
|
+
end
|
107
111
|
end
|
108
112
|
end
|
109
113
|
|
data/lib/web_console/railtie.rb
CHANGED
@@ -40,6 +40,9 @@ module WebConsole
|
|
40
40
|
if mount_point = config.web_console.mount_point
|
41
41
|
Middleware.mount_point = mount_point.chomp('/')
|
42
42
|
end
|
43
|
+
if root = Rails.application.config.relative_url_root
|
44
|
+
Middleware.mount_point = File.join(root, Middleware.mount_point)
|
45
|
+
end
|
43
46
|
end
|
44
47
|
|
45
48
|
initializer 'web_console.template_paths' do
|
data/lib/web_console/session.rb
CHANGED
@@ -43,7 +43,7 @@ module WebConsole
|
|
43
43
|
def initialize(bindings)
|
44
44
|
@id = SecureRandom.hex(16)
|
45
45
|
@bindings = bindings
|
46
|
-
@evaluator = Evaluator.new(bindings.first)
|
46
|
+
@evaluator = Evaluator.new(@current_binding = bindings.first)
|
47
47
|
|
48
48
|
store_into_memory
|
49
49
|
end
|
@@ -59,7 +59,12 @@ module WebConsole
|
|
59
59
|
#
|
60
60
|
# Returns nothing.
|
61
61
|
def switch_binding_to(index)
|
62
|
-
@evaluator = Evaluator.new(@bindings[index.to_i])
|
62
|
+
@evaluator = Evaluator.new(@current_binding = @bindings[index.to_i])
|
63
|
+
end
|
64
|
+
|
65
|
+
# Returns context of the current binding
|
66
|
+
def context(objpath)
|
67
|
+
Context.new(@current_binding).extract(objpath)
|
63
68
|
end
|
64
69
|
|
65
70
|
private
|
@@ -0,0 +1,54 @@
|
|
1
|
+
namespace :templates do
|
2
|
+
desc 'Run tests for templates'
|
3
|
+
task test: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ]
|
4
|
+
task serve: [ :npm, :rackup ]
|
5
|
+
|
6
|
+
workdir = Pathname(EXPANDED_CWD).join('test/templates')
|
7
|
+
pid = Pathname(Dir.tmpdir).join("web_console_test.pid")
|
8
|
+
runner = URI.parse("http://#{ENV['IP'] || '127.0.0.1'}:#{ENV['PORT'] || 29292}/html/test_runner.html")
|
9
|
+
rackup = "rackup --host #{runner.host} --port #{runner.port}"
|
10
|
+
result = nil
|
11
|
+
browser = 'phantomjs'
|
12
|
+
|
13
|
+
def need_to_wait?(uri)
|
14
|
+
Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) }
|
15
|
+
rescue Errno::ECONNREFUSED
|
16
|
+
retry if yield
|
17
|
+
end
|
18
|
+
|
19
|
+
task :daemonize do
|
20
|
+
rackup += " -D --pid #{pid}"
|
21
|
+
end
|
22
|
+
|
23
|
+
task :npm => [ :phantomjs ] do
|
24
|
+
Dir.chdir(workdir) { system 'npm install --silent' }
|
25
|
+
end
|
26
|
+
|
27
|
+
task :phantomjs do
|
28
|
+
unless system("which #{browser} >/dev/null")
|
29
|
+
browser = './node_modules/.bin/phantomjs'
|
30
|
+
Dir.chdir(workdir) { system("test -f #{browser} || npm install --silent phantomjs-prebuilt") }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
task :rackup do
|
35
|
+
Dir.chdir(workdir) { system rackup }
|
36
|
+
end
|
37
|
+
|
38
|
+
task :wait do
|
39
|
+
cnt = 0
|
40
|
+
need_to_wait?(runner) { sleep 1; cnt += 1; cnt < 5 }
|
41
|
+
end
|
42
|
+
|
43
|
+
task :mocha do
|
44
|
+
Dir.chdir(workdir) { result = system("#{browser} ./node_modules/mocha-phantomjs-core/mocha-phantomjs-core.js #{runner} dot") }
|
45
|
+
end
|
46
|
+
|
47
|
+
task :kill do
|
48
|
+
system "kill #{File.read pid}"
|
49
|
+
end
|
50
|
+
|
51
|
+
task :exit do
|
52
|
+
exit result
|
53
|
+
end
|
54
|
+
end
|
@@ -44,6 +44,212 @@ 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
254
|
var consoleInnerHtml = <%= render_inlined_string '_inner_console_markup.html' %>;
|
49
255
|
var promptBoxHtml = <%= render_inlined_string '_prompt_box_markup.html' %>;
|
@@ -62,6 +268,7 @@ function REPLConsole(config) {
|
|
62
268
|
this.prompt = getConfig('promptLabel', ' >>');
|
63
269
|
this.mountPoint = getConfig('mountPoint');
|
64
270
|
this.sessionId = getConfig('sessionId');
|
271
|
+
this.autocomplete = false;
|
65
272
|
}
|
66
273
|
|
67
274
|
REPLConsole.prototype.getSessionUrl = function(path) {
|
@@ -73,6 +280,16 @@ REPLConsole.prototype.getSessionUrl = function(path) {
|
|
73
280
|
return parts.join('/').replace(/([^:]\/)\/+/g, '$1');
|
74
281
|
};
|
75
282
|
|
283
|
+
REPLConsole.prototype.contextRequest = function(keyword, callback) {
|
284
|
+
putRequest(this.getSessionUrl(), 'context=' + getContext(keyword), function(xhr) {
|
285
|
+
if (xhr.status == 200) {
|
286
|
+
callback(null, JSON.parse(xhr.responseText));
|
287
|
+
} else {
|
288
|
+
callback(xhr.statusText);
|
289
|
+
}
|
290
|
+
});
|
291
|
+
};
|
292
|
+
|
76
293
|
REPLConsole.prototype.commandHandle = function(line, callback) {
|
77
294
|
var self = this;
|
78
295
|
var params = 'input=' + encodeURIComponent(line);
|
@@ -92,7 +309,7 @@ REPLConsole.prototype.commandHandle = function(line, callback) {
|
|
92
309
|
|
93
310
|
function getErrorText(xhr) {
|
94
311
|
if (!xhr.status) {
|
95
|
-
return "
|
312
|
+
return "Connection Refused";
|
96
313
|
} else {
|
97
314
|
return xhr.status + ' ' + xhr.statusText;
|
98
315
|
}
|
@@ -199,6 +416,7 @@ REPLConsole.prototype.install = function(container) {
|
|
199
416
|
this.outer = consoleOuter;
|
200
417
|
this.inner = findChild(this.outer, 'console-inner');
|
201
418
|
this.clipboard = findChild(container, 'clipboard');
|
419
|
+
this.suggestWait = 1500;
|
202
420
|
this.newPromptBox();
|
203
421
|
this.insertCss();
|
204
422
|
|
@@ -267,10 +485,63 @@ REPLConsole.prototype.removeCaretFromPrompt = function() {
|
|
267
485
|
this.setInput(this._input, -1);
|
268
486
|
};
|
269
487
|
|
488
|
+
REPLConsole.prototype.getSuggestion = function(keyword) {
|
489
|
+
var self = this;
|
490
|
+
|
491
|
+
function show(found) {
|
492
|
+
if (!found) return;
|
493
|
+
var hint = self.promptDisplay.childNodes[1];
|
494
|
+
hint.className = 'console-hint';
|
495
|
+
hint.dataset.keyword = found;
|
496
|
+
hint.innerText = found.substr(self.suggestKeyword.length);
|
497
|
+
// clear hinting information after timeout in a few time
|
498
|
+
if (self.suggestTimeout) clearTimeout(self.suggestTimeout);
|
499
|
+
self.suggestTimeout = setTimeout(function() { self.renderInput() }, self.suggestWait);
|
500
|
+
}
|
501
|
+
|
502
|
+
function find(context) {
|
503
|
+
var k = self.suggestKeyword;
|
504
|
+
for (var i = 0; i < context.length; ++i) if (context[i].substr(0, k.length) === k) {
|
505
|
+
if (context[i] === k) return;
|
506
|
+
return context[i];
|
507
|
+
}
|
508
|
+
}
|
509
|
+
|
510
|
+
function request(keyword, callback) {
|
511
|
+
self.contextRequest(keyword, function(err, res) {
|
512
|
+
if (err) throw new Error(err);
|
513
|
+
var c = flatten(res['context']);
|
514
|
+
c.sort();
|
515
|
+
callback(c);
|
516
|
+
});
|
517
|
+
}
|
518
|
+
|
519
|
+
self.suggestKeyword = keyword;
|
520
|
+
var input = getContext(keyword);
|
521
|
+
if (keyword.length - input.length < 3) return;
|
522
|
+
|
523
|
+
if (self.suggestInput !== input) {
|
524
|
+
self.suggestInput = input;
|
525
|
+
request(keyword, function(c) {
|
526
|
+
show(find(self.suggestContext = c));
|
527
|
+
});
|
528
|
+
} else if (self.suggestContext) {
|
529
|
+
show(find(self.suggestContext));
|
530
|
+
}
|
531
|
+
};
|
532
|
+
|
533
|
+
REPLConsole.prototype.getHintKeyword = function() {
|
534
|
+
var hint = this.promptDisplay.childNodes[1];
|
535
|
+
return hint.className === 'console-hint' && hint.dataset.keyword;
|
536
|
+
};
|
537
|
+
|
270
538
|
REPLConsole.prototype.setInput = function(input, caretPos) {
|
539
|
+
if (input == null) return; // keep value if input is undefined
|
271
540
|
this._caretPos = caretPos === undefined ? input.length : caretPos;
|
272
541
|
this._input = input;
|
542
|
+
if (this.autocomplete) this.autocomplete.refine(this.getCurrentWord());
|
273
543
|
this.renderInput();
|
544
|
+
if (!this.autocomplete && input.length == this._caretPos) this.getSuggestion(this.getCurrentWord());
|
274
545
|
};
|
275
546
|
|
276
547
|
/**
|
@@ -292,9 +563,8 @@ REPLConsole.prototype.renderInput = function() {
|
|
292
563
|
// Clear the current input.
|
293
564
|
removeAllChildren(this.promptDisplay);
|
294
565
|
|
295
|
-
var promptCursor = document.createElement('span');
|
296
|
-
promptCursor.className = "console-cursor";
|
297
566
|
var before, current, after;
|
567
|
+
var center = document.createElement('span');
|
298
568
|
|
299
569
|
if (this._caretPos < 0) {
|
300
570
|
before = this._input;
|
@@ -310,9 +580,12 @@ REPLConsole.prototype.renderInput = function() {
|
|
310
580
|
}
|
311
581
|
|
312
582
|
this.promptDisplay.appendChild(document.createTextNode(before));
|
313
|
-
|
314
|
-
this.promptDisplay.appendChild(promptCursor);
|
583
|
+
this.promptDisplay.appendChild(center);
|
315
584
|
this.promptDisplay.appendChild(document.createTextNode(after));
|
585
|
+
|
586
|
+
var hint = this.autocomplete && this.autocomplete.getSelectedWord();
|
587
|
+
addClass(center, hint ? 'console-hint' : 'console-cursor');
|
588
|
+
center.appendChild(document.createTextNode(hint ? hint.substr(this.getCurrentWord().length) : current));
|
316
589
|
};
|
317
590
|
|
318
591
|
REPLConsole.prototype.writeOutput = function(output) {
|
@@ -340,6 +613,30 @@ REPLConsole.prototype.onEnterKey = function() {
|
|
340
613
|
this.commandHandle(input);
|
341
614
|
};
|
342
615
|
|
616
|
+
REPLConsole.prototype.onTabKey = function() {
|
617
|
+
var self = this;
|
618
|
+
|
619
|
+
var hintKeyword;
|
620
|
+
if (hintKeyword = self.getHintKeyword()) {
|
621
|
+
self.swapCurrentWord(hintKeyword);
|
622
|
+
return;
|
623
|
+
}
|
624
|
+
|
625
|
+
if (self.autocomplete) return;
|
626
|
+
self.autocomplete = new Autocomplete([]);
|
627
|
+
|
628
|
+
self.contextRequest(self.getCurrentWord(), function(err, obj) {
|
629
|
+
if (err) return self.autocomplete = false;
|
630
|
+
self.autocomplete = new Autocomplete(obj['context'], self.getCurrentWord());
|
631
|
+
self.inner.appendChild(self.autocomplete.view);
|
632
|
+
self.autocomplete.onFinished(function(word) {
|
633
|
+
self.swapCurrentWord(word);
|
634
|
+
self.autocomplete = false;
|
635
|
+
});
|
636
|
+
self.scrollToBottom();
|
637
|
+
});
|
638
|
+
};
|
639
|
+
|
343
640
|
REPLConsole.prototype.onNavigateHistory = function(offset) {
|
344
641
|
var command = this.commandStorage.navigate(offset) || "";
|
345
642
|
this.setInput(command);
|
@@ -349,7 +646,26 @@ REPLConsole.prototype.onNavigateHistory = function(offset) {
|
|
349
646
|
* Handle control keys like up, down, left, right.
|
350
647
|
*/
|
351
648
|
REPLConsole.prototype.onKeyDown = function(ev) {
|
649
|
+
if (this.autocomplete && this.autocomplete.onKeyDown(ev)) {
|
650
|
+
this.renderInput();
|
651
|
+
ev.preventDefault();
|
652
|
+
ev.stopPropagation();
|
653
|
+
return;
|
654
|
+
}
|
655
|
+
|
352
656
|
switch (ev.keyCode) {
|
657
|
+
case 69:
|
658
|
+
// Ctrl-E
|
659
|
+
if (ev.ctrlKey) {
|
660
|
+
this.onTabKey();
|
661
|
+
ev.preventDefault();
|
662
|
+
}
|
663
|
+
break;
|
664
|
+
case 9:
|
665
|
+
// Tab
|
666
|
+
this.onTabKey();
|
667
|
+
ev.preventDefault();
|
668
|
+
break;
|
353
669
|
case 13:
|
354
670
|
// Enter key
|
355
671
|
this.onEnterKey();
|
@@ -432,6 +748,11 @@ REPLConsole.prototype.deleteAtCurrent = function() {
|
|
432
748
|
var before = this._input.substring(0, caretPos);
|
433
749
|
var after = this._input.substring(this._caretPos, this._input.length);
|
434
750
|
this.setInput(before + after, caretPos);
|
751
|
+
|
752
|
+
if (!this._input) {
|
753
|
+
this.autocomplete && this.autocomplete.cancel();
|
754
|
+
this.autocomplete = false;
|
755
|
+
}
|
435
756
|
}
|
436
757
|
};
|
437
758
|
|
@@ -444,6 +765,31 @@ REPLConsole.prototype.insertAtCurrent = function(char) {
|
|
444
765
|
this.setInput(before + char + after, this._caretPos + 1);
|
445
766
|
};
|
446
767
|
|
768
|
+
REPLConsole.prototype.swapCurrentWord = function(next) {
|
769
|
+
function right(s, pos) {
|
770
|
+
var x = s.indexOf(' ', pos);
|
771
|
+
return x === -1 ? s.length : x;
|
772
|
+
}
|
773
|
+
|
774
|
+
function swap(s, pos) {
|
775
|
+
return s.substr(0, s.lastIndexOf(' ', pos) + 1) + next + s.substr(right(s, pos))
|
776
|
+
}
|
777
|
+
|
778
|
+
if (!next) return;
|
779
|
+
var swapped = swap(this._input, this._caretPos);
|
780
|
+
this.setInput(swapped, this._caretPos + swapped.length - this._input.length);
|
781
|
+
};
|
782
|
+
|
783
|
+
REPLConsole.prototype.getCurrentWord = function() {
|
784
|
+
return (function(s, pos) {
|
785
|
+
var left = s.lastIndexOf(' ', pos);
|
786
|
+
if (left === -1) left = 0;
|
787
|
+
var right = s.indexOf(' ', pos)
|
788
|
+
if (right === -1) right = s.length - 1;
|
789
|
+
return s.substr(left, right - left + 1).replace(/^\s+|\s+$/g,'');
|
790
|
+
})(this._input, this._caretPos);
|
791
|
+
};
|
792
|
+
|
447
793
|
REPLConsole.prototype.scrollToBottom = function() {
|
448
794
|
this.outer.scrollTop = this.outer.scrollHeight;
|
449
795
|
};
|
@@ -514,7 +860,7 @@ function addClass(el, className) {
|
|
514
860
|
for (var i = 0; i < el.length; ++ i) {
|
515
861
|
addClass(el[i], className);
|
516
862
|
}
|
517
|
-
} else {
|
863
|
+
} else if (!hasClass(el, className)) {
|
518
864
|
el.className += " " + className;
|
519
865
|
}
|
520
866
|
}
|
@@ -562,3 +908,15 @@ if (typeof exports === 'object') {
|
|
562
908
|
} else {
|
563
909
|
window.REPLConsole = REPLConsole;
|
564
910
|
}
|
911
|
+
|
912
|
+
// Split string by module operators of ruby
|
913
|
+
function getContext(s) {
|
914
|
+
var methodOp = s.lastIndexOf('.');
|
915
|
+
var moduleOp = s.lastIndexOf('::');
|
916
|
+
var x = methodOp > moduleOp ? methodOp : moduleOp;
|
917
|
+
return x !== -1 ? s.substr(0, x) : '';
|
918
|
+
}
|
919
|
+
|
920
|
+
function flatten(arrays) {
|
921
|
+
return Array.prototype.concat.apply([], arrays);
|
922
|
+
}
|
@@ -11,6 +11,12 @@
|
|
11
11
|
.console .console-prompt-box { color: #FFF; }
|
12
12
|
.console .console-message { color: #1AD027; margin: 0; border: 0; white-space: pre-wrap; background-color: #333; padding: 0; }
|
13
13
|
.console .console-message.error-message { color: #fc9; }
|
14
|
+
.console .console-message.auto-complete { word-break: break-all; }
|
15
|
+
.console .console-message.auto-complete .keyword { margin-right: 11px; }
|
16
|
+
.console .console-message.auto-complete .keyword.selected { background: #FFF; color: #000; }
|
17
|
+
.console .console-message.auto-complete .hidden { display: none; }
|
18
|
+
.console .console-message.auto-complete .trimmed { display: none; }
|
19
|
+
.console .console-hint { color: #096; }
|
14
20
|
.console .console-focus .console-cursor { background: #FEFEFE; color: #333; font-weight: bold; }
|
15
21
|
.console .resizer { background: #333; width: 100%; height: 4px; cursor: ns-resize; }
|
16
22
|
.console .console-actions { padding-right: 3px; }
|
data/lib/web_console/version.rb
CHANGED
data/lib/web_console/view.rb
CHANGED
@@ -31,7 +31,11 @@ module WebConsole
|
|
31
31
|
#
|
32
32
|
# Helps to keep the Rails logs clean during errors.
|
33
33
|
def render(*)
|
34
|
-
WebConsole.logger.silence
|
34
|
+
if logger = WebConsole.logger and logger.respond_to?(:silence)
|
35
|
+
WebConsole.logger.silence { super }
|
36
|
+
else
|
37
|
+
super
|
38
|
+
end
|
35
39
|
end
|
36
40
|
|
37
41
|
# Override method for ActionView::Helpers::TranslationHelper#t.
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: web-console
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.
|
4
|
+
version: 3.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charlie Somerville
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2016-
|
14
|
+
date: 2016-10-29 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: railties
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- Rakefile
|
86
86
|
- lib/web-console.rb
|
87
87
|
- lib/web_console.rb
|
88
|
+
- lib/web_console/context.rb
|
88
89
|
- lib/web_console/errors.rb
|
89
90
|
- lib/web_console/evaluator.rb
|
90
91
|
- lib/web_console/exception_mapper.rb
|
@@ -99,7 +100,7 @@ files:
|
|
99
100
|
- lib/web_console/response.rb
|
100
101
|
- lib/web_console/session.rb
|
101
102
|
- lib/web_console/tasks/extensions.rake
|
102
|
-
- lib/web_console/tasks/
|
103
|
+
- lib/web_console/tasks/templates.rake
|
103
104
|
- lib/web_console/template.rb
|
104
105
|
- lib/web_console/templates/_inner_console_markup.html.erb
|
105
106
|
- lib/web_console/templates/_markup.html.erb
|
@@ -1,50 +0,0 @@
|
|
1
|
-
namespace :test do
|
2
|
-
desc "Run tests for templates"
|
3
|
-
task templates: "templates:all"
|
4
|
-
|
5
|
-
namespace :templates do
|
6
|
-
task all: [ :daemonize, :npm, :rackup, :wait, :mocha, :kill, :exit ]
|
7
|
-
task serve: [ :npm, :rackup ]
|
8
|
-
|
9
|
-
work_dir = Pathname(EXPANDED_CWD).join("test/templates")
|
10
|
-
pid_file = Pathname(Dir.tmpdir).join("web_console.#{SecureRandom.uuid}.pid")
|
11
|
-
runner_uri = URI.parse("http://localhost:29292/html/spec_runner.html")
|
12
|
-
rackup_opts = "-p #{runner_uri.port}"
|
13
|
-
test_result = nil
|
14
|
-
|
15
|
-
def need_to_wait?(uri)
|
16
|
-
Net::HTTP.start(uri.host, uri.port) { |http| http.get(uri.path) }
|
17
|
-
rescue Errno::ECONNREFUSED
|
18
|
-
retry if yield
|
19
|
-
end
|
20
|
-
|
21
|
-
task :daemonize do
|
22
|
-
rackup_opts += " -D -P #{pid_file}"
|
23
|
-
end
|
24
|
-
|
25
|
-
task :npm do
|
26
|
-
Dir.chdir(work_dir) { system "npm install --silent" }
|
27
|
-
end
|
28
|
-
|
29
|
-
task :rackup do
|
30
|
-
Dir.chdir(work_dir) { system "bundle exec rackup #{rackup_opts}" }
|
31
|
-
end
|
32
|
-
|
33
|
-
task :wait do
|
34
|
-
cnt = 0
|
35
|
-
need_to_wait?(runner_uri) { sleep 1; cnt += 1; cnt < 5 }
|
36
|
-
end
|
37
|
-
|
38
|
-
task :mocha do
|
39
|
-
Dir.chdir(work_dir) { test_result = system("$(npm bin)/mocha-phantomjs #{runner_uri}") }
|
40
|
-
end
|
41
|
-
|
42
|
-
task :kill do
|
43
|
-
system "kill #{File.read pid_file}"
|
44
|
-
end
|
45
|
-
|
46
|
-
task :exit do
|
47
|
-
exit test_result
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|