contrib/art/conker.svg, outline.svg: better 'string'
[conkeror.git] / modules / minibuffer-completion.js
blobf1811b9e41dce255730c95ca1e4c419e45cb5e41
1 /**
2  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2008 Nelson Elhage
4  * (C) Copyright 2010 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 require("minibuffer.js");
15 /**
16  * Generic completer function factory.
17  *
18  * Keyword arguments:
19  * - $completions: Either a visit function or an array.  If a function, the
20  *   function's argument is a function which pushes argument into the
21  *   completions array.  Otherwise, it uses the provided array.
22  * - $get_value: TODO.
23  * - $get_string: TODO. Optional, default: identity function.
24  * - $get_description: TODO: Optional, default: function returning "".
25  * - $get_icon: optional. returns an icon for the completions line.
26  *
27  * TODO: Exactly what does this function return and how do you use it?
28  */
29 define_keywords("$completions", "$get_string", "$get_description",
30                 "$get_value", "$get_icon");
31 function all_word_completer () {
32     keywords(arguments,
33              $get_description = function (x) "",
34              $get_string = function (x) x,
35              $get_icon = null);
36     var completions = arguments.$completions;
37     var get_string = arguments.$get_string;
38     var get_description = arguments.$get_description;
39     var get_value = arguments.$get_value;
40     var get_icon = arguments.$get_icon;
41     var arr;
42     var completer = function (input, pos, conservative) {
43         if (input.length == 0 && conservative)
44             return undefined;
45         var words = input.toLowerCase().split(" ");
46         var data = arr.filter(function (x) {
47                 var s = get_string(x);
48                 var d = get_description(x);
49                 for (var i = 0; i < words.length; ++i) {
50                     if (s.toLowerCase().indexOf(words[i]) == -1 &&
51                         d.toLowerCase().indexOf(words[i]) == -1)
52                     {
53                         return false;
54                     }
55                 }
56                 return true;
57             });
58         return {count: data.length,
59                 index_of:  function (x) data.indexOf(x),
60                 get_string: function (i) get_string(data[i]),
61                 get_description : function (i) get_description(data[i]),
62                 get_input_state: function (i) [get_string(data[i])],
63                 get_value: function (i) (get_value ? get_value(data[i]) : data[i]),
64                 get_icon: function (i) (get_icon ? get_icon(data[i]) : null)
65                };
66     };
67     completer.refresh = function () {
68         if (typeof completions == "function") {
69             arr = [];
70             completions(function (x) { arr.push(x); });
71         } else
72             arr = completions;
73     };
74     completer.refresh();
75     return completer;
78 function get_common_prefix_length (a, b, len) {
79     var lim;
80     if (len != null && len < a.length)
81         lim = len;
82     else
83         lim = a.length;
84     if (b < lim)
85         lim = b;
86     var i;
87     for (i = 0; i < lim && a[i] == b[i]; ++i);
88     return i;
91 function get_partial_completion_input_state (x, prefix_end, suffix_begin, orig_str) {
92     if (suffix_begin < orig_str.length) {
93         if (orig_str[suffix_begin] == " ")
94             suffix_begin++;
95         let sel = x.length + prefix_end + 1;
96         return [orig_str.substring(0, prefix_end) + x + " " + orig_str.substring(suffix_begin),
97                 sel, sel];
98     } else {
99         let sel = x.length + prefix_end;
100         return [orig_str.substring(0, prefix_end) + x, sel, sel];
101     }
104 function prefix_completer () {
105     keywords(arguments,
106              $get_description = function (x) "",
107              $get_string = function (x) x,
108              $get_icon = null);
109     var completions = arguments.$completions;
110     var get_string = arguments.$get_string;
111     var get_description = arguments.$get_description;
112     var get_value = arguments.$get_value;
113     var get_icon = arguments.$get_icon;
114     var arr;
115     if (typeof completions == "function") {
116         arr = [];
117         completions(function (x) { arr.push(x); });
118     } else
119         arr = completions.slice();
120     arr.sort(function (a,b) {
121             a = get_string(a);
122             b = get_string(b);
123             if (a < b)
124                 return -1;
125             if (a > b)
126                 return 1;
127             return 0;
128         });
129     return function (input, pos, conservative) {
130         var common_prefix = null;
131         if (pos == 0 && conservative)
132             return undefined;
133         var input_prefix = input.substring(0,pos);
134         var default_completion = null;
135         var i = 0;
136         var data = arr.filter(function (x) {
137             var s = get_string(x);
138             if (s == input) {
139                 default_completion = i;
140                 var retval = true;
141             } else
142                 retval = (s.length >= pos && s.substring(0,pos) == input_prefix);
143             if (retval)
144                 ++i;
145             return retval;
146         });
147         if (data.length > 0) {
148             let a = get_string(data[0]);
149             let b = get_string(data[data.length - 1]);
150             let i = get_common_prefix_length(a, b);
151             if (i > pos) {
152                 common_prefix = a.substring(0,i);
153                 if (!default_completion) {
154                     for (let j = 0; j < data.length; ++j) {
155                         if (get_string(data[j]) == common_prefix) {
156                             default_completion = j;
157                             break;
158                         }
159                     }
160                 }
161             }
162         }
163         return {count:data.length,
164                 index_of:  function (x) data.indexOf(x),
165                 get_string: function (i) get_string(data[i]),
166                 get_description: function (i) get_description(data[i]),
167                 get_input_state: function (i) get_partial_completion_input_state(get_string(data[i]), 0, pos, input),
168                 get_value: function (i) (get_value ? get_value(data[i]) : data[i]),
169                 get_icon: function (i) (get_icon ? get_icon(data[i]) : null),
170                 get common_prefix_input_state () {
171                     return common_prefix && get_partial_completion_input_state(common_prefix, 0, pos, input);
172                 },
173                 default_completion: default_completion
174                };
175     };
178 function javascript_completer (buffer) {
179     var window = buffer.window;
181     return function (input, pos, conservative) {
182         // Derived from Vimperator JavaScript completion
183         if (pos == 0 && conservative)
184             return undefined;
185         var str = input.substr(0, pos);
186         var matches = str.match(/^(.*?)(\s*\.\s*)?(\w*)$/);
187         var filter = matches[3] || "";
188         var start = matches[1].length - 1;
189         var offset = matches[1] ? matches[1].length : 0;
190         offset += matches[2] ? matches[2].length : 0;
192         if (matches[2]) {
193             let brackets = 0, parentheses = 0;
194         outer:
195             for (; start >= 0; start--) {
196                 switch (matches[1][start]) {
197                 case ";":
198                 case "{":
199                     break outer;
201                 case "]":
202                     brackets--;
203                     break;
204                 case "[":
205                     brackets++;
206                     break;
207                 case ")":
208                     parentheses--;
209                     break;
210                 case "(":
211                     parentheses++;
212                     break;
213                 }
214                 if (brackets > 0 || parentheses > 0)
215                     break outer;
216             }
217         }
219         var objects = [];
220         var source_obj ;
221         var data = [];
222         var common_prefix_len = null;
223         var common_prefix = null;
225         function add_completion (str, desc) {
226             if (common_prefix != null)
227                 common_prefix_len = get_common_prefix_length(common_prefix, str, common_prefix_len);
228             else
229                 common_prefix = str;
230             data.push([str,desc]);
231         }
232         if (matches[1].substr(start+1)) {
233             try {
234                 source_obj = eval(matches[1].substr(start+1));
235             } catch (e) {}
236         } else {
237             source_obj = conkeror;
238             if ("window".substring(0,filter.length) == filter)
239                 add_completion("window", "object");
240             if ("buffer".substring(0,filter.length) == filter)
241                 add_completion("buffer", "object");
242         }
244         if (source_obj != null) {
245             try {
246                 for (let i in source_obj) {
247                     if (i.substring(0,filter.length) != filter)
248                         continue;
249                     let type, description;
250                     try {
251                         type = typeof(source_obj[i]);
252                     } catch (e) { type = "unknown type"; }
253                     if (type == "number" || type == "string" || type == "boolean") {
254                         description = type + ": " + source_obj[i];
255                     } else
256                         description = type;
257                     add_completion(i, description);
258                 }
259             } catch (e) {}
260         }
261         if (common_prefix != null && common_prefix_len > 0)
262             common_prefix = common_prefix.substr(0, common_prefix_len);
263         else if (common_prefix_len != null)
264             common_prefix = null;
265         return {count:data.length,
266                 get_string: function (i) data[i][0],
267                 get_description: function (i) data[i][1],
268                 get_input_state: function (i) get_partial_completion_input_state(data[i][0], offset, pos, input),
269                 get common_prefix_input_state  () {
270                     return common_prefix && get_partial_completion_input_state(common_prefix, offset, pos, input);
271                 }
272                };
273     }
277 function merge_completers (completers) {
278     if(completers.length == 0)
279         return null;
280     return function (input, pos, conservative) {
281         var results = [];
282         var count = 0;
283         for (let i = 0; i < completers.length; ++i) {
284             let r = yield completers[i](input, pos, conservative);
285             if (r != null && (r.count > 0 || "get_match_required" in r)) {
286                 results.push(r);
287                 count += r.count;
288             }
289         }
290         function forward (name) {
291             return function () {
292                 var args = Array.prototype.slice.call(arguments);
293                 var i = args.shift();
294                 for (var j=0; j < results.length; j++) {
295                     var r = results[j];
296                     if (i < r.count) {
297                         if (name in r && r[name] != null) {
298                             args.unshift(i);
299                             return r[name].apply(this, args);
300                         } else {
301                             return null;
302                         }
303                     }
304                     i -= r.count;
305                 }
306                 return null;
307             }
308         }
309         function combine_or (name) {
310             return function() {
311                 var b = false;
312                 for (var j=0; j < results.length; j++) {
313                     var r = results[j];
314                     if (name in r && r[name] != null) {
315                         b = b || r[name].apply(this, arguments);
316                     }
317                 }
318                 return b;
319             }
320         }
321         yield co_return({count: count,
322                          get_string: forward('get_string'),
323                          get_description: forward('get_description'),
324                          get_input_state: forward('get_input_state'),
325                          get_icon: forward('get_icon'),
326                          destroy: forward('destroy'),
327                          get_match_required: combine_or('get_match_required')
328                         });
329     };
332 function nest_completions (completions, prefix, suffix) {
333     if (prefix == null)
334         prefix = "";
335     if (suffix == null)
336         suffix = "";
337     function nest (x) {
338         let [s, a, b] = x;
339         if (a == null)
340             a = s.length;
341         if (b == null)
342             b = s.length;
343         return [prefix + s + suffix, a + prefix.length, b + prefix.length];
344     }
345     return {
346         __proto__: completions,
347         get_input_state: function (i) nest(completions.get_input_state(i)),
348         get common_prefix_input_state () {
349             let x = completions.common_prefix_input_state;
350             if (x)
351                 return nest(x);
352             return null;
353         }
354     };
358 provide("minibuffer-completion");