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)
161 this.handle_input_changed();
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 var el = this.completions_display_element;
179 el.parentNode.removeChild(el);
180 this.completions_display_element = null;
182 if (this.continuation)
183 this.continuation.throw(abort());
186 handle_input : function () {
187 if (!this.completer) return;
189 this.completions_valid = false;
191 if (!this.auto_complete) return;
195 if (this.auto_complete_delay > 0) {
196 if (this.completions_timer_ID != null)
197 this.window.clearTimeout(this.completions_timer_ID);
198 this.completions_timer_ID = this.window.setTimeout(
200 s.completions_timer_ID = null;
201 s.update_completions(true /* auto */);
202 s.update_completions_display();
203 }, this.auto_complete_delay);
207 s.update_completions(true /* auto */);
208 s.update_completions_display();
211 ran_minibuffer_command : function () {
215 update_completions_display : function () {
217 var m = this.window.minibuffer;
219 if (m.current_state == this)
221 if (this.completions && this.completions.count > 0)
223 this.completions_display_element.view = this.completions_display_element.view;
224 this.completions_display_element.setAttribute("collapsed", "false");
226 this.completions_display_element.currentIndex = this.selected_completion_index;
227 this.completions_display_element.treeBoxObject.scrollToRow(this.selected_completion_index);
229 this.completions_display_element.setAttribute("collapsed", "true");
234 /* If auto is true, this update is due to auto completion, rather
235 * than specifically requested. */
236 update_completions : function (auto) {
239 if (this.completions_timer_ID != null) {
240 this.window.clearTimeout(this.completions_timer_ID);
241 this.completions_timer_ID = null;
244 let m = this.window.minibuffer;
246 /* The completer should return undefined if completion was not
247 * attempted due to auto being true. Otherwise, it can return
248 * null to indicate no completions. */
249 if (this.completions != null && this.completions.destroy)
250 this.completions.destroy();
251 let c = this.completions = this.completer(m._input_text, m._selection_start,
252 auto && this.auto_complete_conservative);
253 this.completions_valid = true;
256 if (c && c.count > 0) {
257 if (this.match_required) {
260 else if (c.default_completion != null)
261 i = c.default_completion;
262 else if (this.default_completion && this.completions.index_of)
263 i = this.completions.index_of(this.default_completion);
265 this.selected_completion_index = i;
269 select_completion : function (i) {
270 this.selected_completion_index = i;
271 this.completions_display_element.currentIndex = i;
273 this.completions_display_element.treeBoxObject.ensureRowIsVisible(i);
274 this.handle_completion_selected();
277 handle_completion_selected : function () {
279 * When a completion is selected, apply it to the input text
280 * if a match is not "required"; otherwise, the completion is
283 var i = this.selected_completion_index;
284 var m = this.window.minibuffer;
285 var c = this.completions;
287 if (this.completions_valid && c && !this.match_required && i >= 0 && i < c.count)
294 function minibuffer_complete(window, count)
296 var m = window.minibuffer;
297 var s = m.current_state;
298 if (!(s instanceof text_entry_minibuffer_state))
299 throw new Error("Invalid minibuffer state");
302 var just_completed_manually = false;
303 if (!s.completions_valid || s.completions === undefined) {
304 if (s.completions_timer_ID == null)
305 just_completed_manually = true;
306 s.update_completions(false /* not auto */);
307 s.update_completions_display();
310 var c = s.completions;
312 if (!c || c.count == 0)
315 var e = s.completions_display_element;
318 if (count == 1 && c.apply_common_prefix)
320 c.apply_common_prefix(m);
321 c.apply_common_prefix = null;
322 } else if (!just_completed_manually) {
323 if (e.currentIndex != -1)
325 new_index = (e.currentIndex + count) % c.count;
327 new_index += c.count;
329 new_index = (count - 1) % c.count;
331 new_index += c.count;
336 s.select_completion(new_index);
338 interactive("minibuffer-complete", function (I) {minibuffer_complete(I.window, I.p);});
339 interactive("minibuffer-complete-previous", function (I) {minibuffer_complete(I.window, -I.p);});
341 function exit_minibuffer(window)
343 var m = window.minibuffer;
344 var s = m.current_state;
345 if (!(s instanceof text_entry_minibuffer_state))
346 throw new Error("Invalid minibuffer state");
348 var val = m._input_text;
350 if (s.validator != null && !s.validator(val, m))
355 if (s.completer && s.match_required) {
356 if (!s.completions_valid || s.completions === undefined)
357 s.update_completions(false);
359 let c = s.completions;
360 let i = s.selected_completion_index;
361 if (c != null && i >= 0 && i < c.count) {
362 if (c.get_value != null)
363 match = c.get_value(i);
365 match = c.get_string(i);
367 m.message("No match");
375 if (s.history.length > minibuffer_history_max_items)
376 s.history.splice(0, s.history.length - minibuffer_history_max_items);
378 var cont = s.continuation;
379 delete s.continuation;
382 if (s.match_required)
388 interactive("exit-minibuffer", function (I) {exit_minibuffer(I.window);});
390 function minibuffer_history_next (window, count)
392 var m = window.minibuffer;
393 var s = m.current_state;
394 if (!(s instanceof text_entry_minibuffer_state))
395 throw new Error("Invalid minibuffer state");
396 if (!s.history || s.history.length == 0)
398 m._restore_normal_state();
399 var index = s.history_index + count;
402 if (index >= s.history.length)
403 index = s.history.length - 1;
404 s.history_index = index;
405 m._input_text = s.history[index];
408 interactive("minibuffer-history-next", function (I) {minibuffer_history_next(I.window, I.p);});
409 interactive("minibuffer-history-previous", function (I) {minibuffer_history_next(I.window, -I.p);});
411 // Define the asynchronous minibuffer.read function
412 minibuffer.prototype.read = function () {
413 var s = new text_entry_minibuffer_state((yield CONTINUATION), forward_keywords(arguments));
415 var result = yield SUSPEND;
416 yield co_return(result);
419 minibuffer.prototype.read_command = function () {
421 var completer = prefix_completer(
422 $completions = function (visitor) interactive_commands.for_each_value(visitor),
423 $get_string = function (x) x.name,
424 $get_description = function (x) x.shortdoc || "",
425 $get_value = function (x) x.name
428 var result = yield this.read($prompt = "Command", $history = "command",
429 forward_keywords(arguments),
430 $completer = completer,
431 $match_required = true);
432 yield co_return(result);
435 minibuffer.prototype.read_user_variable = function () {
437 var completer = prefix_completer(
438 $completions = function (visitor) user_variables.for_each(visitor),
439 $get_string = function (x) x,
440 $get_description = function (x) user_variables.get(x).shortdoc || "",
441 $get_value = function (x) x
444 var result = yield this.read($prompt = "User variable", $history = "user_variable",
445 forward_keywords(arguments),
446 $completer = completer,
447 $match_required = true);
448 yield co_return(result);