minibuffer-completion.js: Avoid some minor errors
[conkeror.git] / modules / minibuffer-completion.js
blobed9ce560f079ad37358c298c23592f9064da7f7c
1 require("minibuffer.js");
3 /**
4  * Completions is either a visit function or an array.
5  */
6 define_keywords("$completions", "$get_string", "$get_description", "$get_value");
7 function all_word_completer()
9     keywords(arguments);
10     var completions = arguments.$completions;
11     var get_string = arguments.$get_string ? arguments.$get_string : function (x) x;
12     var get_description = arguments.$get_description;
13     var get_value = arguments.$get_value;
14     var arr;
15     if (typeof(completions) == "function")
16     {
17         arr = [];
18         completions(function (x) { arr.push(x); });
19     } else
20         arr = completions;
21     return function (input, pos, conservative) {
22         if (input.length == 0 && conservative)
23             return undefined;
24         var words = input.toLowerCase().split(" ");
25         var data = arr.filter(function (x) {
26                 var s = get_string(x);
27                 var d = get_description(x);
28                 for (var i = 0; i < words.length; ++i)
29                 {
30                     if (s.toLowerCase().indexOf(words[i]) == -1 && d.toLowerCase().indexOf(words[i]) == -1)
31                         return false;
32                 }
33                 return true;
34             });
35         return {count: data.length,
36                 index_of:  function (x) data.indexOf(x),
37                 get_string: function (i) get_string(data[i]),
38                 get_description : function (i) get_description(data[i]),
39                 get_input_state: function (i) [get_string(data[i])],
40                 get_value : function(i) (get_value ? get_value(data[i]) : data[i])
41                };
42     }
45 function get_common_prefix_length(a, b, len) {
46     var lim;
47     if (len != null && len < a.length)
48         lim = len;
49     else
50         lim = a.length;
51     if (b < lim)
52         lim = b;
53     var i;
54     for (i = 0; i < lim && a[i] == b[i]; ++i);
55     return i;
58 function get_partial_completion_input_state(x, prefix_end, suffix_begin, orig_str) {
59     if (suffix_begin < orig_str.length)  {
60         if (orig_str[suffix_begin] == " ")
61             suffix_begin++;
62         let sel = x.length + prefix_end + 1;
63         return [orig_str.substring(0, prefix_end) + x + " " + orig_str.substring(suffix_begin),
64                 sel, sel];
65     } else {
66         let sel = x.length + prefix_end;
67         return [orig_str.substring(0, prefix_end) + x, sel, sel];
68     }
71 function prefix_completer()
73     keywords(arguments);
74     var completions = arguments.$completions;
75     var get_string = arguments.$get_string ? arguments.$get_string : function (x) x;
76     var get_description = arguments.$get_description;
77     var get_value = arguments.$get_value;
78     var arr;
79     if (typeof(completions) == "function")
80     {
81         arr = [];
82         completions(function (x) { arr.push(x); });
83     } else
84         arr = completions.slice();
85     arr.sort(function (a,b) {
86             a = get_string(a);
87             b = get_string(b);
88             if (a < b)
89                 return -1;
90             if (a > b)
91                 return 1;
92             return 0;
93         });
94     return function (input, pos, conservative) {
95         var common_prefix = null;
96         if (pos == 0 && conservative)
97             return undefined;
98         var input_prefix = input.substring(0,pos);
99         var default_completion = null;
100         var i = 0;
101         var data = arr.filter(function (x) {
102             var s = get_string(x);
103             var retval;
105             if (s == input) {
106                 default_completion = i;
107                 retval = true;
108             } else
109                 retval = (s.length >= pos && s.substring(0,pos) == input_prefix);
110             if (retval)
111                 ++i;
112             return retval;
113         });
114         if (data.length > 0)
115         {
116             let a = get_string(data[0]);
117             let b = get_string(data[data.length - 1]);
118             let i = get_common_prefix_length(a, b);
119             if (i > pos) {
120                 common_prefix = a.substring(0,i);
121                 if (!default_completion) {
122                     for (let j = 0; j < data.length; ++j) {
123                         if (get_string(data[j]) == common_prefix) {
124                             default_completion = j;
125                             break;
126                         }
127                     }
128                 }
129             }
130         }
131         return {count:data.length,
132                 index_of:  function (x) data.indexOf(x),
133                 get_string: function (i) get_string(data[i]),
134                 get_description : function (i) get_description(data[i]),
135                 get_input_state : function (i) get_partial_completion_input_state(get_string(data[i]), 0, pos, input),
136                 get_value : function(i) get_value(data[i]),
137                 get common_prefix_input_state () {
138                     return common_prefix && get_partial_completion_input_state(common_prefix, 0, pos, input);
139                 },
140                 default_completion: default_completion
141                };
142     }
145 function javascript_completer(buffer) {
146     var window = buffer.window;
148     return function (input, pos, conservative) {
149         // Derived from Vimperator JavaScript completion
150         if (pos == 0 && conservative)
151             return undefined;
152         var str = input.substr(0, pos);
153         var matches = str.match(/^(.*?)(\s*\.\s*)?(\w*)$/);
154         var filter = matches[3] || "";
155         var start = matches[1].length - 1;
156         var offset = matches[1] ? matches[1].length : 0;
157         offset += matches[2] ? matches[2].length : 0;
159         if (matches[2])
160         {
161             let brackets = 0, parentheses = 0;
162         outer:
163             for (; start >= 0; start--)
164             {
165                 switch (matches[1][start])
166                 {
167                 case ";":
168                 case "{":
169                     break outer;
171                 case "]":
172                     brackets--;
173                     break;
174                 case "[":
175                     brackets++;
176                     break;
177                 case ")":
178                     parentheses--;
179                     break;
180                 case "(":
181                     parentheses++;
182                     break;
183                 }
184                 if (brackets > 0 || parentheses > 0)
185                     break outer;
186             }
187         }
189         var objects = [];
190         var source_obj ;
191         var data = [];
192         var common_prefix_len = null;
193         var common_prefix = null;
195         function add_completion(str, desc) {
196             if (common_prefix != null)
197                 common_prefix_len = get_common_prefix_length(common_prefix, str, common_prefix_len);
198             else
199                 common_prefix = str;
200             data.push([str,desc]);
201         }
202         if (matches[1].substr(start+1))
203         {
204             try {
205                 source_obj = eval(matches[1].substr(start+1));
206             } catch (e) {}
207         }
208         else
209         {
210             source_obj = conkeror;
211             if ("window".substring(0,filter.length) == filter)
212                 add_completion("window", "object");
213             if ("buffer".substring(0,filter.length) == filter)
214                 add_completion("buffer", "object");
215         }
217         if (source_obj != null) {
218             try {
219                 for (let i in source_obj) {
220                     if (i.substring(0,filter.length) != filter)
221                         continue;
222                     let type, description;
223                     try {
224                         type = typeof(source_obj[i]);
225                     } catch (e) { type = "unknown type"; }
226                     if (type == "number" || type == "string" || type == "boolean") {
227                         description = type + ": " + source_obj[i];
228                     } else
229                         description = type;
230                     add_completion(i, description);
231                 }
232             } catch (e) {}
233         }
234         if (common_prefix != null && common_prefix_len > 0)
235             common_prefix = common_prefix.substr(0, common_prefix_len);
236         else if (common_prefix_len != null)
237             common_prefix = null;
238         return {count:data.length,
239                 get_string: function (i) data[i][0],
240                 get_description: function (i) data[i][1],
241                 get_input_state: function (i) get_partial_completion_input_state(data[i][0], offset, pos, input),
242                 get common_prefix_input_state  () {
243                     return common_prefix && get_partial_completion_input_state(common_prefix, offset, pos, input);
244                 }
245                };
246     }
250 function merge_completers(completers) {
251     if(completers.length == 0)
252         return null;
253     return function (input, pos, conservative) {
254         var results = [];
255         var count = 0;
256         for (let i = 0; i < completers.length; ++i) {
257             let r = yield completers[i](input, pos, conservative);
258             if (r != null && r.count > 0) {
259                 results.push(r);
260                 count += r.count;
261             }
262         }
263         function forward(name) {
264             return function() {
265                 var args = Array.prototype.slice.call(arguments);
266                 var i = args.shift();
267                 for(var j=0; j < results.length; j++) {
268                     var r = results[j];
269                     if(i < r.count) {
270                         if (name in r && r[name] != null) {
271                             args.unshift(i);
272                             return r[name].apply(this, args);
273                         }
274                     }
275                     i -= r.count;
276                 }
277                 return null;
278             }
279         }
280         yield co_return({count: count,
281                          get_string: forward('get_string'),
282                          get_description: forward('get_description'),
283                          get_input_state: forward('get_input_state'),
284                          destroy: forward('destroy')
285                         });
286     };
289 function nest_completions(completions, prefix, suffix) {
290     if (prefix == null)
291         prefix = "";
292     if (suffix == null)
293         suffix = "";
294     function nest(x) {
295         let [s, a, b] = x;
296         if (a == null)
297             a = s.length;
298         if (b == null)
299             b = s.length;
300         return [prefix + s + suffix, a + prefix.length, b + prefix.length];
301     }
302     return {
303         __proto__: completions,
304         get_input_state: function (i) nest(completions.get_input_state(i)),
305         get common_prefix_input_state () {
306             let x = completions.common_prefix_input_state;
307             if (x)
308                 return nest(x);
309             return null;
310         }
311     };