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