|
17 | 17 | var _active = false;
|
18 | 18 | var _cursor_visible = false;
|
19 | 19 | var _suggestion;
|
| 20 | + var _itemTemplate = _.template("<div><%- i %> <%- cmd %></div>"); |
20 | 21 | var _cmdHandlers = {
|
21 |
| - clear: function(cmd, args, callback) { |
22 |
| - $(_input_id).parent().empty(); |
23 |
| - callback(); |
| 22 | + clear: { |
| 23 | + exec: function(cmd, args, callback) { |
| 24 | + $(_input_id).parent().empty(); |
| 25 | + callback(); |
| 26 | + } |
24 | 27 | },
|
25 |
| - help: function(cmd, args, callback) { |
26 |
| - var content = $('<div><div><strong>Commands:</strong></div></div>'); |
27 |
| - var itemTemplate = _.template('<div> <%=command%></div>'); |
28 |
| - _.each(commands(), function(command) { |
29 |
| - content.append(itemTemplate({command: command})) |
30 |
| - }); |
31 |
| - callback(content); |
| 28 | + help: { |
| 29 | + exec: function(cmd, args, callback) { |
| 30 | + var content = $('<div><div><strong>Commands:</strong></div></div>'); |
| 31 | + var itemTemplate = _.template('<div> <%=command%></div>'); |
| 32 | + _.each(commands(), function(command) { |
| 33 | + content.append(itemTemplate({command: command})) |
| 34 | + }); |
| 35 | + callback(content); |
| 36 | + }, |
| 37 | + completion: function(cmd, arg, line, callback) { |
| 38 | + callback(self.bestMatch(arg, self.commands())) |
| 39 | + } |
32 | 40 | },
|
33 |
| - history: function(cmd, args, callback) { |
34 |
| - if(args[0] == "-c") { |
35 |
| - _history.clear(); |
36 |
| - callback(); |
37 |
| - return; |
| 41 | + history: { |
| 42 | + exec: function(cmd, args, callback) { |
| 43 | + if(args[0] == "-c") { |
| 44 | + _history.clear(); |
| 45 | + callback(); |
| 46 | + return; |
| 47 | + } |
| 48 | + var content = $('<div></div>'); |
| 49 | + _.each(_history.items(), function(cmd, i) { |
| 50 | + content.append(_itemTemplate({cmd: cmd, i: i})); |
| 51 | + }); |
| 52 | + callback(content); |
| 53 | + } |
| 54 | + }, |
| 55 | + _unknown: { |
| 56 | + exec: function(cmd, args, callback) { |
| 57 | + var content = _.template('<div><strong>Unrecognized command: </strong><%=cmd%></div>', {cmd: cmd}); |
| 58 | + callback(content); |
| 59 | + }, |
| 60 | + completion: function(cmd, arg, line, callback) { |
| 61 | + callback(self.bestMatch(arg, self.commands())) |
38 | 62 | }
|
39 |
| - var content = $('<div></div>'); |
40 |
| - var itemTemplate = _.template("<div><%- i %> <%- cmd %></div>"); |
41 |
| - _.each(_history.items(), function(cmd, i) { |
42 |
| - content.append(itemTemplate({cmd: cmd, i: i})); |
43 |
| - }); |
44 |
| - callback(content); |
45 | 63 | },
|
46 |
| - _unknown: function(cmd, args, callback) { |
47 |
| - var content = _.template('<div><strong>Unrecognized command: </strong><%=cmd%></div>', {cmd: cmd}); |
48 |
| - callback(content); |
| 64 | + _none: { |
| 65 | + completion: function(cmd, arg, line, callback) { |
| 66 | + callback(self.bestMatch(arg, self.commands())) |
| 67 | + } |
49 | 68 | }
|
50 | 69 | };
|
51 | 70 | var _line = {
|
|
84 | 103 | setCommandHandler: function(cmd, cmdHandler) {
|
85 | 104 | _cmdHandlers[cmd] = cmdHandler;
|
86 | 105 | },
|
| 106 | + getCommandHandler: function(cmd) { |
| 107 | + return _cmdHandlers[cmd]; |
| 108 | + }, |
87 | 109 | setPrompt: function(prompt) {
|
88 | 110 | _prompt = prompt;
|
89 | 111 | if(!_active) {
|
|
97 | 119 | onDeactivate: function(completionHandler) {
|
98 | 120 | _readline.onDeactivate(completionHandler);
|
99 | 121 | },
|
100 |
| - onCompletion: function(completionHandler) { |
101 |
| - _readline.onCompletion(function(line, callback) { |
102 |
| - if(_suggestion) { |
103 |
| - $(_suggest_id).remove(); |
104 |
| - _suggestion = null; |
105 |
| - } |
106 |
| - if(!line) { |
107 |
| - return; |
108 |
| - } |
109 |
| - completionHandler(line, function(completion) { |
110 |
| - console.log("completion: " + completion) |
111 |
| - if(!completion) { |
112 |
| - callback(); |
113 |
| - return; |
114 |
| - } |
115 |
| - if(completion.suggestions) { |
116 |
| - _suggestion = $(_suggest_html); |
117 |
| - for(var i = 0; i < completion.suggestions.length; i++) { |
118 |
| - console.log("suggestion: " + completion.suggestions[i]); |
119 |
| - _suggestion.append($("<div></div>").text(completion.suggestions[i])); |
120 |
| - } |
121 |
| - console.log(_suggestion); |
122 |
| - $(_input_id).after(_suggestion); |
123 |
| - } |
124 |
| - self.scrollToBottom(); |
125 |
| - callback(completion.result); |
126 |
| - }); |
127 |
| - }); |
128 |
| - }, |
129 | 122 | render: function() {
|
130 | 123 | var text = _line.text || '';
|
131 | 124 | var cursorIdx = _line.cursor || 0;
|
|
158 | 151 | scrollToBottom: function() {
|
159 | 152 | //_panel.scrollTop(_shell.height());
|
160 | 153 | _panel.animate({scrollTop: _view.height()}, 0);
|
| 154 | + }, |
| 155 | + bestMatch: function(partial, possible) { |
| 156 | + var result = { |
| 157 | + completion: null, |
| 158 | + suggestions: [] |
| 159 | + }; |
| 160 | + if(!possible || possible.length == 0) { |
| 161 | + return result; |
| 162 | + } |
| 163 | + var common = ''; |
| 164 | + if(!partial) { |
| 165 | + if(possible.length == 1) { |
| 166 | + result.completion = possible[0]; |
| 167 | + result.suggestions = possible; |
| 168 | + return result; |
| 169 | + } |
| 170 | + if(!_.every(possible, function(x) { |
| 171 | + return possible[0][0] == x[0] |
| 172 | + })) { |
| 173 | + result.suggestions = possible; |
| 174 | + return result; |
| 175 | + } |
| 176 | + common = possible[0][0]; |
| 177 | + } |
| 178 | + for(var i = 0; i < possible.length; i++) { |
| 179 | + var option = possible[i]; |
| 180 | + if(option.slice(0, partial.length) == partial) { |
| 181 | + result.suggestions.push(option); |
| 182 | + if(!common) { |
| 183 | + common = option; |
| 184 | + console.log("initial common:" + common); |
| 185 | + } else if(option.slice(0, common.length) != common) { |
| 186 | + console.log("find common stem for '" + common + "' and '" + option + "'"); |
| 187 | + var j = partial.length; |
| 188 | + while(j < common.length && j < option.length) { |
| 189 | + if(common[j] != option[j]) { |
| 190 | + common = common.substr(0, j); |
| 191 | + break; |
| 192 | + } |
| 193 | + j++; |
| 194 | + } |
| 195 | + } |
| 196 | + } |
| 197 | + } |
| 198 | + result.completion = common.substr(partial.length); |
| 199 | + return result; |
161 | 200 | }
|
162 | 201 | };
|
163 | 202 |
|
|
186 | 225 | }
|
187 | 226 |
|
188 | 227 | function split(str) {
|
189 |
| - var parts = str.split(/\s+/); |
190 |
| - if(parts.length > 0 && !parts[parts.length - 1]) { |
191 |
| - parts.pop(); |
| 228 | + return _.filter(str.split(/\s+/), function(x) { |
| 229 | + return x; |
| 230 | + }); |
| 231 | + } |
| 232 | + |
| 233 | + function getHandler(cmd) { |
| 234 | + var handler; |
| 235 | + if(!cmd) { |
| 236 | + handler = _cmdHandlers._none; |
| 237 | + } else { |
| 238 | + handler = _cmdHandlers[cmd]; |
192 | 239 | }
|
193 |
| - return parts; |
| 240 | + if(!handler) { |
| 241 | + handler = _cmdHandlers._default; |
| 242 | + if(!handler) { |
| 243 | + handler = _cmdHandlers._unknown; |
| 244 | + } |
| 245 | + } |
| 246 | + return handler; |
194 | 247 | }
|
195 | 248 |
|
196 | 249 | // init
|
197 | 250 | _readline.onChange(function(line) {
|
| 251 | + if(_suggestion) { |
| 252 | + $(_suggest_id).remove(); |
| 253 | + _suggestion = null; |
| 254 | + self.scrollToBottom(); |
| 255 | + } |
198 | 256 | _line = line;
|
199 | 257 | self.render();
|
200 | 258 | });
|
|
217 | 275 | var parts = split(cmdtext);
|
218 | 276 | var cmd = parts[0];
|
219 | 277 | var args = parts.slice(1);
|
220 |
| - var handler = _cmdHandlers[cmd]; |
221 |
| - if(!handler) { |
222 |
| - handler = _cmdHandlers['_default']; |
223 |
| - if(!handler) { |
224 |
| - handler = _cmdHandlers['_unknown']; |
225 |
| - } |
226 |
| - } |
227 |
| - return handler(cmd, args, function(output, prompt, cmdtext) { |
228 |
| - console.log("finished command " + cmd); |
| 278 | + var handler = getHandler(cmd); |
| 279 | + return handler.exec(cmd, args, function(output, prompt, cmdtext) { |
229 | 280 | if(output) {
|
230 | 281 | $(_input_id).after(output);
|
231 | 282 | }
|
|
238 | 289 | callback(cmdtext);
|
239 | 290 | });
|
240 | 291 | });
|
| 292 | + _readline.onCompletion(function(line, callback) { |
| 293 | + if(_suggestion) { |
| 294 | + $(_suggest_id).remove(); |
| 295 | + _suggestion = null; |
| 296 | + } |
| 297 | + if(!line) { |
| 298 | + return callback(); |
| 299 | + } |
| 300 | + var cmd = _.first(split(line.text)); |
| 301 | + console.log("getting completion handler for "+cmd); |
| 302 | + var handler = getHandler(cmd); |
| 303 | + if(!handler.completion) { |
| 304 | + return callback(); |
| 305 | + } |
| 306 | + console.log("calling completion handler for "+cmd); |
| 307 | + var partial = line.text.substr(0, line.cursor); |
| 308 | + var arg = _.last(split(partial)); |
| 309 | + return handler.completion(cmd, arg, line, function(match) { |
| 310 | + console.log("completion: " + JSON.stringify(match)); |
| 311 | + if(!match) { |
| 312 | + return callback(); |
| 313 | + } |
| 314 | + if(match.suggestions) { |
| 315 | + _suggestion = $(_suggest_html); |
| 316 | + for(var i = 0; i < match.suggestions.length; i++) { |
| 317 | + console.log("suggestion: " + match.suggestions[i]); |
| 318 | + _suggestion.append($("<div></div>").text(match.suggestions[i])); |
| 319 | + } |
| 320 | + $(_input_id).after(_suggestion); |
| 321 | + } |
| 322 | + self.scrollToBottom(); |
| 323 | + return callback(match.completion); |
| 324 | + }); |
| 325 | + }); |
241 | 326 | _readline.onEOT(self.deactivate);
|
242 | 327 | _readline.onCancel(self.deactivate);
|
243 | 328 | return self;
|
244 | 329 | }
|
245 | 330 | ;
|
246 |
| -}) |
247 |
| - (jQuery, _, document, window); |
| 331 | +})(jQuery, _, document, window); |
0 commit comments