3 var default_minibuffer_auto_complete_delay = 150;
6 var minibuffer_auto_complete_preferences = {};
8 var minibuffer_auto_complete_default = false;
11 var minibuffer_history_data = new string_hashmap();
13 /* FIXME: These should possibly be saved to disk somewhere */
15 var minibuffer_history_max_items = 100;
18 /* The parameter `args' specifies the arguments. In addition, the
19 * arguments for basic_minibuffer_state are also allowed.
21 * history: [optional] specifies a string to identify the history list to use
27 * default_completion only used if match_required is set to true
29 * $valiator [optional]
30 * specifies a function
32 define_keywords("$history", "$validator",
34 "$completer", "$match_required", "$default_completion",
35 "$auto_complete", "$auto_complete_initial", "$auto_complete_conservative",
36 "$auto_complete_delay",
38 /* FIXME: support completing in another thread */
39 function text_entry_minibuffer_state(continuation) {
42 basic_minibuffer_state.call(this, forward_keywords(arguments));
43 this.keymap = minibuffer_keymap;
45 this.continuation = continuation;
46 if (arguments.$history)
48 this.history = minibuffer_history_data.get_put_default(arguments.$history, []);
49 this.history_index = this.history.length;
52 this.validator = arguments.$validator;
54 if (arguments.$completer != null)
56 this.completer = arguments.$completer;
57 let auto = arguments.$auto_complete;
58 while (typeof(auto) == "string")
59 auto = minibuffer_auto_complete_preferences[auto];
61 auto = minibuffer_auto_complete_default;
62 this.auto_complete = auto;
63 this.auto_complete_initial = !!arguments.$auto_complete_initial;
64 this.auto_complete_conservative = !!arguments.$auto_complete_conservative;
65 let delay = arguments.$auto_complete_delay;
67 delay = default_minibuffer_auto_complete_delay;
68 this.auto_complete_delay = delay;
69 this.completions = null;
70 this.completions_valid = false;
71 this.space_completes = !!arguments.$space_completes;
72 this.completions_timer_ID = null;
73 this.completions_display_element = null;
74 this.selected_completion_index = -1;
75 this.match_required = !!arguments.$match_required;
76 if (this.match_required)
77 this.default_completion = arguments.$default_completion;
81 function completions_tree_view(minibuffer_state)
83 this.minibuffer_state = minibuffer_state;
86 var atom_service = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
88 completions_tree_view.prototype = {
90 var c = this.minibuffer_state.completions;
95 getCellText : function(row,column){
96 var c = this.minibuffer_state.completions;
99 if (column.index == 0)
100 return c.get_string(row);
101 if (c.get_description)
102 return c.get_description(row);
105 setTree : function(treebox){ this.treebox = treebox; },
106 isContainer: function(row){ return false; },
107 isSeparator: function(row){ return false; },
108 isSorted: function(){ return false; },
109 getLevel: function(row){ return 0; },
110 getImageSrc: function(row,col){ return null; },
111 getRowProperties: function(row,props){},
112 getCellProperties: function(row,col,props){
114 props.AppendElement(atom_service.getAtom("completion-string"));
116 props.AppendElement(atom_service.getAtom("completion-description"));
118 getColumnProperties: function(colid,col,props){}
121 // inherit from basic_minibuffer_state
122 text_entry_minibuffer_state.prototype = {
123 __proto__: basic_minibuffer_state.prototype,
124 load : function (window) {
125 this.window = window;
126 if (this.completer) {
127 // Create completion display element if needed
128 if (!this.completion_element)
130 /* FIXME: maybe use the dom_generator */
131 var tree = create_XUL(window, "tree");
133 tree.addEventListener("select", function () {
134 s.selected_completion_index = s.completions_display_element.currentIndex;
135 s.handle_completion_selected();
137 tree.setAttribute("class", "completions");
139 tree.setAttribute("rows", "8");
141 tree.setAttribute("collapsed", "true");
143 tree.setAttribute("hidecolumnpicker", "true");
144 tree.setAttribute("hideheader", "true");
146 var treecols = create_XUL(window, "treecols");
147 tree.appendChild(treecols);
148 var treecol = create_XUL(window, "treecol");
149 treecol.setAttribute("flex", "1");
150 treecols.appendChild(treecol);
151 treecol = create_XUL(window, "treecol");
152 treecol.setAttribute("flex", "1");
153 treecols.appendChild(treecol);
154 tree.appendChild(create_XUL(window, "treechildren"));
156 window.minibuffer.insert_before(tree);
157 tree.view = new completions_tree_view(this);
158 this.completions_display_element = tree;
160 /* This is the initial loading of this minibuffer
161 * state. If this.complete_initial is true, generate
163 if (this.auto_complete_initial)
164 this.handle_input_changed();
167 this.update_completions_display();
171 unload : function (window) {
172 if (this.completions_display_element)
173 this.completions_display_element.setAttribute("collapsed", "true");
176 destroy : function (window) {
177 if (this.completions != null && this.completions.destroy)
178 this.completions.destroy();
179 var el = this.completions_display_element;
182 el.parentNode.removeChild(el);
183 this.completions_display_element = null;
185 if (this.continuation)
186 this.continuation.throw(abort());
189 handle_input_changed : function () {
190 if (!this.completer) return;
192 this.completions_valid = false;
194 if (!this.auto_complete) return;
198 if (this.auto_complete_delay > 0) {
199 if (this.completions_timer_ID != null)
200 this.window.clearTimeout(this.completions_timer_ID);
201 this.completions_timer_ID = this.window.setTimeout(
203 s.completions_timer_ID = null;
204 s.update_completions(true /* auto */);
205 s.update_completions_display();
206 }, this.auto_complete_delay);
210 s.update_completions(true /* auto */);
211 s.update_completions_display();
214 update_completions_display : function () {
216 var m = this.window.minibuffer;
218 if (m.current_state == this)
220 if (this.completions && this.completions.count > 0)
222 this.completions_display_element.view = this.completions_display_element.view;
223 this.completions_display_element.setAttribute("collapsed", "false");
225 this.completions_display_element.currentIndex = this.selected_completion_index;
226 this.completions_display_element.treeBoxObject.scrollToRow(this.selected_completion_index);
228 this.completions_display_element.setAttribute("collapsed", "true");
233 /* If auto is true, this update is due to auto completion, rather
234 * than specifically requested. */
235 update_completions : function (auto) {
238 if (this.completions_timer_ID != null) {
239 this.window.clearTimeout(this.completions_timer_ID);
240 this.completions_timer_ID = null;
243 let m = this.window.minibuffer;
245 /* The completer should return undefined if completion was not
246 * attempted due to auto being true. Otherwise, it can return
247 * null to indicate no completions. */
248 if (this.completions != null && this.completions.destroy)
249 this.completions.destroy();
250 let c = this.completions = this.completer(m._input_text, m._selection_start,
251 auto && this.auto_complete_conservative);
252 this.completions_valid = true;
255 if (c && c.count > 0) {
256 if (this.match_required) {
259 else if (c.default_completion != null)
260 i = c.default_completion;
261 else if (this.default_completion && this.completions.index_of)
262 i = this.completions.index_of(this.default_completion);
264 this.selected_completion_index = i;
268 select_completion : function (i) {
269 this.selected_completion_index = i;
270 this.completions_display_element.currentIndex = i;
272 this.completions_display_element.treeBoxObject.ensureRowIsVisible(i);
273 this.handle_completion_selected();
276 handle_completion_selected : function () {
278 * When a completion is selected, apply it to the input text
279 * if a match is not "required"; otherwise, the completion is
282 var i = this.selected_completion_index;
283 var m = this.window.minibuffer;
284 var c = this.completions;
286 if (this.completions_valid && c && !this.match_required && i >= 0 && i < c.count)
293 function minibuffer_complete(window, count)
295 var m = window.minibuffer;
296 var s = m.current_state;
297 if (!(s instanceof text_entry_minibuffer_state))
298 throw new Error("Invalid minibuffer state");
301 var just_completed_manually = false;
302 if (!s.completions_valid || s.completions === undefined) {
303 if (s.completions_timer_ID == null)
304 just_completed_manually = true;
305 s.update_completions(false /* not auto */);
306 s.update_completions_display();
309 var c = s.completions;
311 if (!c || c.count == 0)
314 var e = s.completions_display_element;
317 if (count == 1 && c.apply_common_prefix)
319 c.apply_common_prefix(m);
320 c.apply_common_prefix = null;
321 } else if (!just_completed_manually) {
322 if (e.currentIndex != -1)
324 new_index = (e.currentIndex + count) % c.count;
326 new_index += c.count;
328 new_index = (count - 1) % c.count;
330 new_index += c.count;
335 s.select_completion(new_index);
337 interactive("minibuffer-complete", function (I) {minibuffer_complete(I.window, I.p);});
338 interactive("minibuffer-complete-previous", function (I) {minibuffer_complete(I.window, -I.p);});
340 function exit_minibuffer(window)
342 var m = window.minibuffer;
343 var s = m.current_state;
344 if (!(s instanceof text_entry_minibuffer_state))
345 throw new Error("Invalid minibuffer state");
347 var val = m._input_text;
349 if (s.validator != null && !s.validator(val, m))
354 if (s.completer && s.match_required) {
355 if (!s.completions_valid || s.completions === undefined)
356 s.update_completions(false);
358 let c = s.completions;
359 let i = s.selected_completion_index;
360 if (c != null && i >= 0 && i < c.count) {
361 if (c.get_value != null)
362 match = c.get_value(i);
364 match = c.get_string(i);
366 m.message("No match");
374 if (s.history.length > minibuffer_history_max_items)
375 s.history.splice(0, s.history.length - minibuffer_history_max_items);
377 var cont = s.continuation;
378 delete s.continuation;
381 if (s.match_required)
387 interactive("exit-minibuffer", function (I) {exit_minibuffer(I.window);});
389 function minibuffer_history_next (window, count)
391 var m = window.minibuffer;
392 var s = m.current_state;
393 if (!(s instanceof text_entry_minibuffer_state))
394 throw new Error("Invalid minibuffer state");
395 if (!s.history || s.history.length == 0)
397 m._ensure_input_area_showing();
398 var index = s.history_index + count;
401 if (index >= s.history.length)
402 index = s.history.length - 1;
403 s.history_index = index;
404 m._input_text = s.history[index];
407 interactive("minibuffer-history-next", function (I) {minibuffer_history_next(I.window, I.p);});
408 interactive("minibuffer-history-previous", function (I) {minibuffer_history_next(I.window, -I.p);});
410 // Define the asynchronous minibuffer.read function
411 minibuffer.prototype.read = function () {
412 var s = new text_entry_minibuffer_state((yield CONTINUATION), forward_keywords(arguments));
414 var result = yield SUSPEND;
415 yield co_return(result);
418 minibuffer.prototype.read_command = function () {
420 var completer = prefix_completer(
421 $completions = function (visitor) interactive_commands.for_each_value(visitor),
422 $get_string = function (x) x.name,
423 $get_description = function (x) x.shortdoc || "",
424 $get_value = function (x) x.name
427 var result = yield this.read($prompt = "Command", $history = "command",
428 forward_keywords(arguments),
429 $completer = completer,
430 $match_required = true);
431 yield co_return(result);