web-console 3.3.1 → 3.4.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 +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
|