2 define_variable("default_minibuffer_auto_complete_delay", 150,
3 "Delay (in milliseconds) after the most recent key stroke before auto-completing.");
5 define_variable("minibuffer_auto_complete_preferences", {});
7 define_variable("minibuffer_auto_complete_default", false, "Boolean specifying whether to auto-complete by default.\nThe user variable `minibuffer_auto_complete_preferences' overrides this.");
9 var minibuffer_history_data = new string_hashmap();
11 /* FIXME: These should possibly be saved to disk somewhere */
12 define_variable("minibuffer_history_max_items", 100, "Maximum number of minibuffer history entries stored.\nOlder history entries are truncated after this limit is reached.");
15 /* The parameter `args' specifies the arguments. In addition, the
16 * arguments for basic_minibuffer_state are also allowed.
18 * history: [optional] specifies a string to identify the history list to use
24 * default_completion only used if match_required is set to true
26 * $valiator [optional]
27 * specifies a function
29 define_keywords("$history", "$validator",
31 "$completer", "$match_required", "$default_completion",
32 "$auto_complete", "$auto_complete_initial", "$auto_complete_conservative",
33 "$auto_complete_delay",
35 /* FIXME: support completing in another thread */
36 function text_entry_minibuffer_state(continuation) {
39 basic_minibuffer_state.call(this, forward_keywords(arguments));
40 this.keymap = minibuffer_keymap;
42 this.continuation = continuation;
43 if (arguments.$history)
45 this.history = minibuffer_history_data.get_put_default(arguments.$history, []);
46 this.history_index = this.history.length;
49 this.validator = arguments.$validator;
51 if (arguments.$completer != null)
53 this.completer = arguments.$completer;
54 let auto = arguments.$auto_complete;
55 while (typeof(auto) == "string")
56 auto = minibuffer_auto_complete_preferences[auto];
58 auto = minibuffer_auto_complete_default;
59 this.auto_complete = auto;
60 this.auto_complete_initial = !!arguments.$auto_complete_initial;
61 this.auto_complete_conservative = !!arguments.$auto_complete_conservative;
62 let delay = arguments.$auto_complete_delay;
64 delay = default_minibuffer_auto_complete_delay;
65 this.auto_complete_delay = delay;
66 this.completions = null;
67 this.completions_valid = false;
68 this.space_completes = !!arguments.$space_completes;
69 this.completions_timer_ID = null;
70 this.completions_display_element = null;
71 this.selected_completion_index = -1;
72 this.match_required = !!arguments.$match_required;
73 if (this.match_required)
74 this.default_completion = arguments.$default_completion;
78 function completions_tree_view(minibuffer_state)
80 this.minibuffer_state = minibuffer_state;
83 var atom_service = Cc["@mozilla.org/atom-service;1"].getService(Ci.nsIAtomService);
85 completions_tree_view.prototype = {
87 var c = this.minibuffer_state.completions;
92 getCellText : function(row,column){
93 var c = this.minibuffer_state.completions;
96 if (column.index == 0)
97 return c.get_string(row);
98 if (c.get_description)
99 return c.get_description(row);
102 setTree : function(treebox){ this.treebox = treebox; },
103 isContainer: function(row){ return false; },
104 isSeparator: function(row){ return false; },
105 isSorted: function(){ return false; },
106 getLevel: function(row){ return 0; },
107 getImageSrc: function(row,col){ return null; },
108 getRowProperties: function(row,props){},
109 getCellProperties: function(row,col,props){
111 props.AppendElement(atom_service.getAtom("completion-string"));
113 props.AppendElement(atom_service.getAtom("completion-description"));
115 getColumnProperties: function(colid,col,props){}
118 // inherit from basic_minibuffer_state
119 text_entry_minibuffer_state.prototype = {
120 __proto__: basic_minibuffer_state.prototype,
121 load : function (window) {
122 this.window = window;
123 if (this.completer) {
124 // Create completion display element if needed
125 if (!this.completion_element)
127 /* FIXME: maybe use the dom_generator */
128 var tree = create_XUL(window, "tree");
130 tree.addEventListener("select", function () {
131 s.selected_completion_index = s.completions_display_element.currentIndex;
132 s.handle_completion_selected();
134 tree.setAttribute("class", "completions");
136 tree.setAttribute("rows", "8");
138 tree.setAttribute("collapsed", "true");
140 tree.setAttribute("hidecolumnpicker", "true");
141 tree.setAttribute("hideheader", "true");
143 var treecols = create_XUL(window, "treecols");
144 tree.appendChild(treecols);
145 var treecol = create_XUL(window, "treecol");
146 treecol.setAttribute("flex", "1");
147 treecols.appendChild(treecol);
148 treecol = create_XUL(window, "treecol");
149 treecol.setAttribute("flex", "1");
150 treecols.appendChild(treecol);
151 tree.appendChild(create_XUL(window, "treechildren"));
153 window.minibuffer.insert_before(tree);
154 tree.view = new completions_tree_view(this);
155 this.completions_display_element = tree;
157 /* This is the initial loading of this minibuffer
158 * state. If this.complete_initial is true, generate
160 if (this.auto_complete_initial)
164 this.update_completions_display();
168 unload : function (window) {
169 if (this.completions_display_element)
170 this.completions_display_element.setAttribute("collapsed", "true");
173 destroy : function (window) {
174 if (this.completions != null && this.completions.destroy)
175 this.completions.destroy();
176 delete this.completions;
177 if (this.completions_cont)
178 this.completions_cont.throw(abort());
179 delete this.completions_cont;
181 var el = this.completions_display_element;
184 el.parentNode.removeChild(el);
185 this.completions_display_element = null;
187 if (this.continuation)
188 this.continuation.throw(abort());
191 handle_input : function () {
192 if (!this.completer) return;
194 this.completions_valid = false;
196 if (!this.auto_complete) return;
200 if (this.auto_complete_delay > 0) {
201 if (this.completions_timer_ID != null)
202 this.window.clearTimeout(this.completions_timer_ID);
203 this.completions_timer_ID = this.window.setTimeout(
205 s.completions_timer_ID = null;
206 s.update_completions(true /* auto */, true /* update completions display */);
207 }, this.auto_complete_delay);
211 s.update_completions(true /* auto */, true /* update completions display */);
214 ran_minibuffer_command : function () {
218 update_completions_display : function () {
220 var m = this.window.minibuffer;
222 if (m.current_state == this)
224 if (this.completions && this.completions.count > 0)
226 this.completions_display_element.view = this.completions_display_element.view;
227 this.completions_display_element.setAttribute("collapsed", "false");
229 this.completions_display_element.currentIndex = this.selected_completion_index;
230 this.completions_display_element.treeBoxObject.scrollToRow(this.selected_completion_index);
232 this.completions_display_element.setAttribute("collapsed", "true");
237 /* If auto is true, this update is due to auto completion, rather
238 * than specifically requested. */
239 update_completions : function (auto, update_display) {
241 if (this.completions_timer_ID != null) {
242 this.window.clearTimeout(this.completions_timer_ID);
243 this.completions_timer_ID = null;
246 let m = this.window.minibuffer;
248 if (this.completions_cont) {
249 this.completions_cont.throw(abort());
250 this.completions_cont = null;
253 let c = this.completer(m._input_text, m._selection_start,
254 auto && this.auto_complete_conservative);
256 if (is_coroutine(c)) {
258 let already_done = false;
259 this.completions_cont = co_call(function () {
264 s.completions_cont = null;
267 s.update_completions_done(x, update_display);
270 // In case the completer actually already finished
272 this.completions_cont = null;
275 this.update_completions_done(c, update_display);
278 update_completions_done : function update_completions_done(c, update_display) {
280 /* The completer should return undefined if completion was not
281 * attempted due to auto being true. Otherwise, it can return
282 * null to indicate no completions. */
283 if (this.completions != null && this.completions.destroy)
284 this.completions.destroy();
286 this.completions = c;
287 this.completions_valid = true;
288 this.applied_common_prefix = false;
291 if (c && c.count > 0) {
292 if (this.match_required) {
295 else if (c.default_completion != null)
296 i = c.default_completion;
297 else if (this.default_completion && this.completions.index_of)
298 i = this.completions.index_of(this.default_completion);
300 this.selected_completion_index = i;
304 this.update_completions_display();
307 select_completion : function (i) {
308 this.selected_completion_index = i;
309 this.completions_display_element.currentIndex = i;
311 this.completions_display_element.treeBoxObject.ensureRowIsVisible(i);
312 this.handle_completion_selected();
315 handle_completion_selected : function () {
317 * When a completion is selected, apply it to the input text
318 * if a match is not "required"; otherwise, the completion is
321 var i = this.selected_completion_index;
322 var m = this.window.minibuffer;
323 var c = this.completions;
325 if (this.completions_valid && c && !this.match_required && i >= 0 && i < c.count)
327 m.set_input_state(c.get_input_state(i));
332 function minibuffer_complete(window, count)
334 var m = window.minibuffer;
335 var s = m.current_state;
336 if (!(s instanceof text_entry_minibuffer_state))
337 throw new Error("Invalid minibuffer state");
340 var just_completed_manually = false;
341 if (!s.completions_valid || s.completions === undefined) {
342 if (s.completions_timer_ID == null)
343 just_completed_manually = true;
344 s.update_completions(false /* not auto */, true /* update completions display */);
346 // If the completer is a coroutine, nothing we can do here
347 if (!s.completions_valid)
351 var c = s.completions;
353 if (!c || c.count == 0)
356 var e = s.completions_display_element;
361 if (count == 1 && !s.applied_common_prefix && (common_prefix = c.common_prefix_input_state))
363 m.set_input_state(common_prefix);
364 s.applied_common_prefix = true;
365 } else if (!just_completed_manually) {
366 if (e.currentIndex != -1)
368 new_index = (e.currentIndex + count) % c.count;
370 new_index += c.count;
372 new_index = (count - 1) % c.count;
374 new_index += c.count;
379 s.select_completion(new_index);
381 interactive("minibuffer-complete", function (I) {minibuffer_complete(I.window, I.p);});
382 interactive("minibuffer-complete-previous", function (I) {minibuffer_complete(I.window, -I.p);});
384 function exit_minibuffer(window)
386 var m = window.minibuffer;
387 var s = m.current_state;
388 if (!(s instanceof text_entry_minibuffer_state))
389 throw new Error("Invalid minibuffer state");
391 var val = m._input_text;
393 if (s.validator != null && !s.validator(val, m))
398 if (s.completer && s.match_required) {
399 if (!s.completions_valid || s.completions === undefined)
400 s.update_completions(false /* not conservative */, false /* don't update */);
402 let c = s.completions;
403 let i = s.selected_completion_index;
404 if (c != null && i >= 0 && i < c.count) {
405 if (c.get_value != null)
406 match = c.get_value(i);
408 match = c.get_string(i);
410 m.message("No match");
418 if (s.history.length > minibuffer_history_max_items)
419 s.history.splice(0, s.history.length - minibuffer_history_max_items);
421 var cont = s.continuation;
422 delete s.continuation;
425 if (s.match_required)
431 interactive("exit-minibuffer", function (I) {exit_minibuffer(I.window);});
433 function minibuffer_history_next (window, count)
435 var m = window.minibuffer;
436 var s = m.current_state;
437 if (!(s instanceof text_entry_minibuffer_state))
438 throw new Error("Invalid minibuffer state");
439 if (!s.history || s.history.length == 0)
441 m._restore_normal_state();
442 var index = s.history_index + count;
445 if (index >= s.history.length)
446 index = s.history.length - 1;
447 s.history_index = index;
448 m._input_text = s.history[index];
451 interactive("minibuffer-history-next", function (I) {minibuffer_history_next(I.window, I.p);});
452 interactive("minibuffer-history-previous", function (I) {minibuffer_history_next(I.window, -I.p);});
454 // Define the asynchronous minibuffer.read function
455 minibuffer.prototype.read = function () {
456 var s = new text_entry_minibuffer_state((yield CONTINUATION), forward_keywords(arguments));
458 var result = yield SUSPEND;
459 yield co_return(result);
462 minibuffer.prototype.read_command = function () {
464 var completer = prefix_completer(
465 $completions = function (visitor) interactive_commands.for_each_value(visitor),
466 $get_string = function (x) x.name,
467 $get_description = function (x) x.shortdoc || "",
468 $get_value = function (x) x.name
471 var result = yield this.read($prompt = "Command", $history = "command",
472 forward_keywords(arguments),
473 $completer = completer,
474 $match_required = true);
475 yield co_return(result);
478 minibuffer.prototype.read_user_variable = function () {
480 var completer = prefix_completer(
481 $completions = function (visitor) user_variables.for_each(visitor),
482 $get_string = function (x) x,
483 $get_description = function (x) user_variables.get(x).shortdoc || "",
484 $get_value = function (x) x
487 var result = yield this.read($prompt = "User variable", $history = "user_variable",
488 forward_keywords(arguments),
489 $completer = completer,
490 $match_required = true);
491 yield co_return(result);