spawn-process: Better error information in case of timeout
[conkeror.git] / modules / completers.js
blob2511bd84b4ef7477dbe8a930d22aa0484c56e297
1 /**
2  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2008 Nelson Elhage
4  * (C) Copyright 2010,2012 John J. Foerch
5  *
6  * Portions of this file (the JavaScript completer) were derived from Vimperator,
7  * (C) Copyright 2006-2007 Martin Stubenschrott.
8  *
9  * Use, modification, and distribution are subject to the terms specified in the
10  * COPYING file.
11 **/
13 function completions (completer, data) {
14     this.completer = completer;
15     if (data) {
16         this.data = data;
17         this.count = data.length;
18     }
20 completions.prototype = {
21     constructor: completions,
22     toString: function () "#<completions>",
23     completer: null,
24     data: null,
25     count: null,
26     destroy: function () {},
27     index_of: function (x) {
28         return this.data.indexOf(x);
29     },
30     get_string: function (i) {
31         return this.completer.get_string(this.data[i]);
32     },
33     get_input_state: function (i) {
34         return [this.get_string(i)];
35     },
36     get_description: function (i) {
37         return this.completer.get_description(this.data[i]);
38     },
39     get_icon: function (i) {
40         if (this.completer.get_icon)
41             return this.completer.get_icon(this.data[i]);
42         else
43             return null;
44     },
45     get_value: function (i) {
46         if (this.completer.get_value)
47             return this.completer.get_value(this.data[i]);
48         else
49             return this.data[i];
50     }
54 define_keywords("$completions", "$get_string", "$get_description",
55                 "$get_icon", "$get_value");
56 function completer () {
57     keywords(arguments,
58              $completions = [],
59              $get_string = identity,
60              $get_description = constantly(""),
61              $get_icon = null,
62              $get_value = null);
63     this.completions_src = arguments.$completions;
64     this.get_string = arguments.$get_string;
65     this.get_description = arguments.$get_description;
66     this.get_icon = arguments.$get_icon;
67     this.get_value = arguments.$get_value;
68     this.refresh();
70 completer.prototype = {
71     constructor: completer,
72     toString: function () "#<completer>",
73     completions_src: null,
74     completions: null,
75     get_string: null,
76     get_description: null,
77     get_icon: null,
78     get_value: null,
79     complete: function (input, pos) {},
80     refresh: function () {
81         if (typeof this.completions_src == "function") {
82             var completions = [];
83             this.completions_src(function (x) { completions.push(x); });
84             this.completions = completions;
85         } else if (this.completions_src)
86             this.completions = this.completions_src.slice();
87     }
92  * All Word Completer
93  */
95 function all_word_completer () {
96     keywords(arguments);
97     completer.call(this, forward_keywords(arguments));
99 all_word_completer.prototype = {
100     constructor: all_word_completer,
101     __proto__: completer.prototype,
102     toString: function () "#<all_word_completer>",
103     complete: function (input, pos) {
104         var words = input.toLowerCase().split(" ");
105         var nwords = words.length;
106         var c = this;
107         var narrowed = this.completions.filter(function (x) {
108                 var s = c.get_string(x);
109                 var d = c.get_description(x);
110                 for (var i = 0; i < nwords; ++i) {
111                     if (s.toLowerCase().indexOf(words[i]) == -1 &&
112                         d.toLowerCase().indexOf(words[i]) == -1)
113                     {
114                         return false;
115                     }
116                 }
117                 return true;
118             });
119         return new completions(this, narrowed);
120     }
125  * Prefix Completer
126  */
128 function prefix_completions (completer, data, default_completion,
129                              offset, pos, input, common_prefix)
131     completions.call(this, completer, data);
132     this.default_completion = default_completion;
133     this.common_prefix = common_prefix;
134     this.offset = offset || 0;
135     this.pos = pos;
136     this.input = input;
138 prefix_completions.prototype = {
139     constructor: prefix_completions,
140     __proto__: completions.prototype,
141     toString: function () "#<prefix_completions>",
142     default_completion: null,
143     common_prefix: null,
144     offset: null,
145     pos: null,
146     input: null,
147     get_partial_completion_input_state: function (x, prefix_end, suffix_begin, orig_str) {
148         if (suffix_begin < orig_str.length) {
149             if (orig_str[suffix_begin] == " ")
150                 suffix_begin++;
151             var sel = x.length + prefix_end + 1;
152             return [orig_str.substring(0, prefix_end) + x + " " +
153                     orig_str.substring(suffix_begin),
154                     sel, sel];
155         } else {
156             sel = x.length + prefix_end;
157             return [orig_str.substring(0, prefix_end) + x, sel, sel];
158         }
159     },
160     get_input_state: function (i) {
161         return this.get_partial_completion_input_state(
162             this.get_string(i), 0, this.pos, this.input)
163     },
164     get common_prefix_input_state () { //used by minibuffer-read
165         return (this.common_prefix &&
166                 this.get_partial_completion_input_state(this.common_prefix,
167                                                         this.offset, this.pos,
168                                                         this.input));
169     }
172 function prefix_completer () {
173     keywords(arguments);
174     completer.call(this, forward_keywords(arguments));
176 prefix_completer.prototype = {
177     constructor: prefix_completer,
178     __proto__: completer.prototype,
179     toString: function () "#<prefix_completer>",
180     complete: function (input, pos) {
181         var common_prefix = null;
182         var input_prefix = input.substring(0, pos);
183         var default_completion = null;
184         var i = 0;
185         var c = this;
186         var narrowed = this.completions.filter(function (x) {
187                 var s = c.get_string(x);
188                 if (s == input) {
189                     default_completion = i;
190                     var retval = true;
191                 } else
192                     retval = (s.substring(0, pos) == input_prefix);
193                 if (retval)
194                     ++i;
195                 return retval;
196             });
197         var nnarrowed = narrowed.length;
198         if (nnarrowed > 0) {
199             // the completions were already sorted by 'refresh', so we can
200             // get the common prefix by comparing the first and last items.
201             var a = this.get_string(narrowed[0]);
202             var b = this.get_string(narrowed[nnarrowed - 1]);
203             let i = common_prefix_length(a, b);
204             if (i > pos) {
205                 common_prefix = a.substring(0, i);
206                 if (! default_completion) {
207                     for (var j = 0; j < nnarrowed; ++j) {
208                         if (this.get_string(narrowed[j]) == common_prefix) {
209                             default_completion = j;
210                             break;
211                         }
212                     }
213                 }
214             }
215         }
216         return new prefix_completions(this, narrowed, default_completion,
217                                       null, pos, input, common_prefix);
218     },
219     refresh: function () {
220         completer.prototype.refresh.call(this);
221         var c = this;
222         this.completions.sort(function (a, b) {
223                 a = c.get_string(a);
224                 b = c.get_string(b);
225                 if (a < b)
226                     return -1;
227                 if (a > b)
228                     return 1;
229                 return 0;
230             });
231     }
236  * Javascript Completer
237  */
239 function javascript_completer (scope) {
240     prefix_completer.call(this,
241                           $get_string = first,
242                           $get_description = second);
243     this.scope = scope;
245 javascript_completer.prototype = {
246     constructor: javascript_completer,
247     __proto__: prefix_completer.prototype,
248     toString: function () "#<javascript_completer>",
249     scope: null,
250     complete: function (input, pos) {
251         var str = input.substr(0, pos);
252         var matches = str.match(/^(.*?)(\s*\.\s*)?(\w*)$/);
253         var filter = matches[3] || "";
254         var start = matches[1].length - 1;
255         var offset = matches[1] ? matches[1].length : 0;
256         offset += matches[2] ? matches[2].length : 0;
258         if (matches[2]) {
259             let brackets = 0, parentheses = 0;
260         outer:
261             for (; start >= 0; start--) {
262                 switch (matches[1][start]) {
263                 case ";":
264                 case "{":
265                     break outer;
266                 case "]":
267                     brackets--;
268                     break;
269                 case "[":
270                     brackets++;
271                     break;
272                 case ")":
273                     parentheses--;
274                     break;
275                 case "(":
276                     parentheses++;
277                     break;
278                 }
279                 if (brackets > 0 || parentheses > 0)
280                     break outer;
281             }
282         }
284         var objects = [];
285         var narrowed = [];
286         var common_prefix_len = null;
287         var common_prefix = null;
288         var c = this;
290         function add_completion (str, desc) {
291             if (common_prefix != null)
292                 common_prefix_len = common_prefix_length(common_prefix, str, common_prefix_len);
293             else
294                 common_prefix = str;
295             narrowed.push([str, desc]);
296         }
297         if (matches[1].substr(start+1)) {
298             try {
299                 var source_obj = eval(matches[1].substr(start+1));
300             } catch (e) {}
301         } else {
302             source_obj = this.scope;
303         }
304         if (source_obj != null) {
305             try {
306                 for (let i in source_obj) {
307                     if (i.substring(0, filter.length) != filter)
308                         continue;
309                     try {
310                         var type = typeof source_obj[i];
311                     } catch (e) {
312                         type = "unknown type";
313                     }
314                     if (type == "number" || type == "string" || type == "boolean") {
315                         var description = type + ": " + source_obj[i];
316                     } else
317                         description = type;
318                     add_completion(i, description);
319                 }
320             } catch (e) {}
321         }
322         if (common_prefix != null && common_prefix_len > 0)
323             common_prefix = common_prefix.substr(0, common_prefix_len);
324         else if (common_prefix_len != null)
325             common_prefix = null;
326         return new prefix_completions(this, narrowed, null, offset, pos, input, common_prefix);
327     },
328     refresh: function () {}
333  * Merged Completer (combinator)
334  */
336 function merged_completions (results, count) {
337     completions.call(this, null);
338     this.results = results;
339     this.nresults = results.length;
340     this.count = count;
342 merged_completions.prototype = {
343     constructor: merged_completions,
344     __proto__: completions.prototype,
345     toString: function () "#<merged_completions>",
346     results: null,
347     nresults: 0,
348     forward: function (name, i) {
349         for (var j = 0; j < this.nresults; ++j) {
350             var r = this.results[j];
351             if (i < r.count)
352                 return r[name](i);
353             i -= r.count;
354         }
355         return null;
356     },
357     destroy: function () {
358         for (var j = 0; j < this.nresults; ++j) {
359             this.results[j].destroy();
360         }
361     },
362     //XXX: index_of: function (x) { },
363     get_string: function (i) {
364         return this.forward("get_string", i);
365     },
366     get_input_state: function (i) {
367         return this.forward("get_input_state", i);
368     },
369     get_description: function (i) {
370         return this.forward("get_description", i);
371     },
372     get_icon: function (i) {
373         return this.forward("get_icon", i);
374     },
375     get_value: function (i) {
376         return this.forward("get_value", i);
377     }
380 function merged_completer (completers) {
381     this.completers = completers;
382     this.ncompleters = completers.length;
383     completer.call(this);
385 merged_completer.prototype = {
386     constructor: merged_completer,
387     __proto__: completer.prototype,
388     toString: function () "#<merged_completer>",
389     completers: null,
390     ncompleters: 0,
391     complete: function (input, pos) {
392         var merged_results = [];
393         var count = 0;
394         for (let i = 0; i < this.ncompleters; ++i) {
395             let r = yield this.completers[i].complete(input, pos);
396             if (r != null && r.count > 0) {
397                 merged_results.push(r);
398                 count += r.count;
399             }
400         }
401         yield co_return(new merged_completions(merged_results, count));
402     },
403     refresh: function () {
404         for (var i = 0; i < this.ncompleters; ++i) {
405             this.completers[i].refresh();
406         }
407     },
408     get require_match () {
409         for (var i = 0; i < this.ncompleters; ++i) {
410             var r = this.completers[i];
411             if (r.require_match)
412                 return r.require_match;
413         }
414         return false;
415     }
420  * Nest Completions
422  *   Adjust input_state of a prefix_completions object for prefix and/or
423  * suffix strings.
424  */
426 function nest_completions (completions_o, prefix, suffix) {
427     if (prefix == null)
428         prefix = "";
429     if (suffix == null)
430         suffix = "";
431     function nest (x) {
432         let [s, a, b] = x;
433         if (a == null)
434             a = s.length;
435         if (b == null)
436             b = s.length;
437         return [prefix + s + suffix, a + prefix.length, b + prefix.length];
438     }
439     return {
440         __proto__: completions_o,
441         get_input_state: function (i) {
442             var x = completions_o.get_input_state(i);
443             return nest(x);
444         },
445         get common_prefix_input_state () {
446             let x = completions_o.common_prefix_input_state;
447             if (x)
448                 return nest(x);
449             return null;
450         }
451     };
455 provide("completers");