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