Properly fix frame argument handling to work with both Mozilla 1.9 and Mozilla 1.8
[conkeror.git] / modules / minibuffer.js
blobbd29460dbf5dcd65b0353236f80cb34fe5443f0b
2 function find_complete_match(matches, val)
4     for (var i=0; i<matches.length; i++) {
5         if (matches[i][0] == val)
6             return matches[i][1];
7     }
8     return null;
11 function exit_minibuffer(frame)
13     var m = frame.minibuffer;
14     var s = m.current_state;
15     if (!(s instanceof text_entry_minibuffer_state))
16         throw "Invalid minibuffer state";
18     var match = null;
19     var val = trim_whitespace(m._input_text);
20     if (s instanceof completion_minibuffer_state) {
21         if (val.length == 0 && s.default_match != null)
22             val = s.default_match;
23         match = find_complete_match(s.completions, val);
24     }
26     // Check if we are allowed to exit here
27     if ((s instanceof completion_minibuffer_state) && !match && !s.allow_non_matches)
28         return;
30     if (s.history)
31     {
32         s.history.push(val);
33         if (s.history.length > minibuffer_history_max_items)
34             s.history.splice(0, s.history.length - minibuffer_history_max_items);
35     }
36     m.pop_state();
37     if (s.callback) {
38         if (s instanceof completion_minibuffer_state) {
39             if (s.allow_non_matches)
40                 s.callback(match, val);
41             else // match must be non-null because of previous check
42                 s.callback(match);
43         }
44         else
45             s.callback(val);
46     }
48 interactive("exit-minibuffer", exit_minibuffer, I.current_frame);
50 function minibuffer_history_next (frame)
52     var m = frame.minibuffer;
53     var s = m.current_state;
54     if (!(s instanceof text_entry_minibuffer_state))
55         throw "Invalid minibuffer state";
56     if (!s.history)
57         return;
58     m._ensure_input_area_showing();
59     s.history_index = Math.max(s.history_index + 1, s.history.length - 1);
60     m._input_text = s.history[s.history_index];
61     m._set_selection();
63 interactive("minibuffer-history-next", minibuffer_history_next, I.current_frame);
65 function minibuffer_history_previous (frame)
67     var m = frame.minibuffer;
68     var s = m.current_state;
69     if (!(s instanceof text_entry_minibuffer_state))
70         throw "Invalid minibuffer state";
71     if (!s.history)
72         return;
73     m._ensure_input_area_showing();
74     s.history_index = Math.min(s.history_index - 1, 0);
75     m._input_text = s.history[s.history_index];
76     m._set_selection();
78 interactive("minibuffer-history-previous", minibuffer_history_previous, I.current_frame);
80 function minibuffer_abort (frame)
82     var m = frame.minibuffer;
83     var s = m.current_state;
84     if (!(s instanceof text_entry_minibuffer_state))
85         throw "Invalid minibuffer state";
86     if (s.abort_callback)
87         s.abort_callback();
88     m.pop_state();
90 interactive("minibuffer-abort", minibuffer_abort, I.current_frame);
92 /* FIXME: Change this to use a binary search */
93 function get_completions(str, matches)
95     if (str.length == 0)
96         return matches;
97     var ret = [];
98     for (var i=0; i<matches.length; i++)
99         {
100             if (str == matches[i][0].substr(0, str.length))
101             ret.push(matches[i]);
102         }
103     return ret;
106 function find_longest_common_prefix(matches)
108     var a = matches[0][0];
109     var b = matches[matches.length - 1][0];
110     for (var i = 0; i < a.length && i < b.length; i++)
111         if (a.charAt(i) != b.charAt(i))
112             return i;
113     return Math.min(a.length,b.length);
116 function minibuffer_complete (frame)
118     var m = frame.minibuffer;
119     var s = m.current_state;
120     if (!(s instanceof completion_minibuffer_state))
121         throw "Invalid minibuffer state";
122     var str = m._input_text;
123     var entered_text = str.substring(0, m._selection_start);
124     var matches = get_completions(entered_text, s.completions);
126     // If no completions, then nothing to do
127     if (matches.length == 0)
128     {
129         m._set_selection(); // moves cursor to end
130         return;
131     }
133     /* Find longest common prefix; since s.completions is sorted, this
134      * is easy */
135     var lcp = find_longest_common_prefix(matches);
137     if (lcp == entered_text.length)
138     {
139         /* Cycle: find current match */
140         var current_index = -1;
141         for (var i = 0; i < matches.length; ++i)
142             if (matches[i][0] == str)
143             {
144                 current_index = i;
145                 break;
146             }
147         current_index = (current_index + 1) % matches.length;
148         m._input_text = matches[current_index][0];
149         m._set_selection(lcp); // select after lcp
150     } else {
151         m._input_text = matches[0][0];
152         m._set_selection(lcp);
153     }
155 interactive("minibuffer-complete", minibuffer_complete, I.current_frame);
158 function minibuffer_accept_match (frame)
160     var m = frame.minibuffer;
161     var s = m.current_state;
162     if (!(s instanceof completion_minibuffer_state))
163         throw "Invalid minibuffer state";
164     var sel_start = m._selection_start;
165     var sel_end = m._selection_end;
166     var str = m._input_text;
167     if (sel_start == sel_end) {
168         m._input_text = str.substr(0, sel_start) + " " + str.substr(sel_end);
169         m._set_selection(sel_start + 1, sel_start + 1);
170     } else {
171         // When we allow non-matches it generally means the
172         // completion takes an argument. So add a space.
173         if (s.allow_non_matches && str[str.length-1] != ' ')
174             m._input_text = str + " ";
175         m._set_selection();
176     }
178 interactive("minibuffer-accept-match", minibuffer_accept_match, I.current_frame);
180 function minibuffer_do_command(frame, command) {
181     /* FIXME: Once we have the minibuffer capable of flashing a
182      * message, this should always revert to input mode immediately */
183     try {
184         var m = frame.minibuffer;
185         if (m._input_mode_enabled)
186         {
187             m._ensure_input_area_showing();
188             var e = m.input_element;
189             var c = e.controllers.getControllerForCommand(command);
190             if (c && c.isCommandEnabled(command))
191                 c.doCommand(command);
192         }
193     } catch (e)
194     {
195         dumpln("minibuffer_do_command: " + e);
196     }
199 /* FIXME: These should all be defined more compactly using a loop */
200 interactive("minibuffer-cmd_beginLine", minibuffer_do_command, I.current_frame, 'cmd_beginLine');
201 interactive("minibuffer-cmd_copy", minibuffer_do_command, I.current_frame, 'cmd_copy');
202 interactive("minibuffer-cmd_copyOrDelete", minibuffer_do_command, I.current_frame, 'cmd_copyOrDelete');
203 interactive("minibuffer-cmd_cut", minibuffer_do_command, I.current_frame, 'cmd_cut');
204 interactive("minibuffer-cmd_cutOrDelete", minibuffer_do_command, I.current_frame, 'cmd_cutOrDelete');
205 interactive("minibuffer-cmd_deleteToBeginningOfLine", minibuffer_do_command, I.current_frame, 'cmd_deleteToBeginningOfLine');
206 interactive("minibuffer-cmd_deleteToEndOfLine", minibuffer_do_command, I.current_frame, 'cmd_deleteToEndOfLine');
207 interactive("minibuffer-cmd_endLine", minibuffer_do_command, I.current_frame, 'cmd_endLine');
208 interactive("minibuffer-cmd_moveTop", minibuffer_do_command, I.current_frame, 'cmd_moveTop');
209 interactive("minibuffer-cmd_moveBottom", minibuffer_do_command, I.current_frame, 'cmd_moveBottom');
210 interactive("minibuffer-cmd_selectAll", minibuffer_do_command, I.current_frame, 'cmd_selectAll');
211 interactive("minibuffer-cmd_selectBeginLine", minibuffer_do_command, I.current_frame, 'cmd_selectBeginLine');
212 interactive("minibuffer-cmd_selectBottom", minibuffer_do_command, I.current_frame, 'cmd_selectBottom');
213 interactive("minibuffer-cmd_selectEndLine", minibuffer_do_command, I.current_frame, 'cmd_selectEndLine');
214 interactive("minibuffer-cmd_selectTop", minibuffer_do_command, I.current_frame, 'cmd_selectTop');
215 interactive("minibuffer-cmd_scrollBeginLine", minibuffer_do_command, I.current_frame, 'cmd_scrollBeginLine');
216 interactive("minibuffer-cmd_scrollEndLine", minibuffer_do_command, I.current_frame, 'cmd_scrollEndLine');
217 interactive("minibuffer-cmd_scrollTop", minibuffer_do_command, I.current_frame, 'cmd_scrollTop');
218 interactive("minibuffer-cmd_scrollBottom", minibuffer_do_command, I.current_frame, 'cmd_scrollBottom');
220 interactive("minibuffer-cmd_charNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_charNext');
221 interactive("minibuffer-cmd_charPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_charPrevious');
222 interactive("minibuffer-cmd_deleteCharBackward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteCharBackward');
223 interactive("minibuffer-cmd_deleteCharForward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteCharForward');
224 interactive("minibuffer-cmd_deleteWordBackward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteWordBackward');
225 interactive("minibuffer-cmd_deleteWordForward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteWordForward');
226 interactive("minibuffer-cmd_lineNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_lineNext');
227 interactive("minibuffer-cmd_linePrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_linePrevious');
228 interactive("minibuffer-cmd_movePageDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_movePageDown');
229 interactive("minibuffer-cmd_movePageUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_movePageUp');
230 interactive("minibuffer-cmd_redo", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_redo');
231 interactive("minibuffer-cmd_selectCharNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectCharNext');
232 interactive("minibuffer-cmd_selectCharPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectCharPrevious');
233 interactive("minibuffer-cmd_selectLineNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectLineNext');
234 interactive("minibuffer-cmd_selectLinePrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectLinePrevious');
235 interactive("minibuffer-cmd_selectPageDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectPageDown');
236 interactive("minibuffer-cmd_selectPageUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectPageUp');
237 interactive("minibuffer-cmd_selectWordNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectWordNext');
238 interactive("minibuffer-cmd_selectWordPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectWordPrevious');
239 interactive("minibuffer-cmd_undo", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_undo');
240 interactive("minibuffer-cmd_wordNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_wordNext');
241 interactive("minibuffer-cmd_wordPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_wordPrevious');
242 interactive("minibuffer-cmd_scrollPageUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollPageUp');
243 interactive("minibuffer-cmd_scrollPageDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollPageDown');
244 interactive("minibuffer-cmd_scrollLineUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollLineUp');
245 interactive("minibuffer-cmd_scrollLineDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollLineDown');
246 interactive("minibuffer-cmd_scrollLeft", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollLeft');
247 interactive("minibuffer-cmd_scrollRight", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollRight');
248 interactive("minibuffer-cmd_paste", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_paste');
250 function minibuffer_insert_character(frame, n, event)
252     var m = frame.minibuffer;
253     var s = m.current_state;
254     if (!(s instanceof basic_minibuffer_state))
255         throw "Invalid minibuffer state";
256     m._ensure_input_area_showing();
257     var val = m._input_text;
258     var sel_start = m._selection_start;
259     var sel_end = m._selection_end;
260     var insert = String.fromCharCode(event.charCode);
261     var out = val.substr(0, sel_start);
262     for (var i = 0; i < n; ++i)
263         out += insert;
264     out += val.substr(sel_end);
265     m._input_text = out;
266     var new_sel = sel_end + n;
267     m._set_selection(new_sel, new_sel);
269 interactive("minibuffer-insert-character", minibuffer_insert_character,
270             I.current_frame, I.p, I.e);
272 function minibuffer_insert_character_complete(frame, n, event)
274     var m = frame.minibuffer;
275     var s = m.current_state;
276     if (!(s instanceof completion_minibuffer_state))
277         throw "Invalid minibuffer state";
279     minibuffer_insert_character(frame, n, event);
281     // Check for completions
283     var entered_text = m._input_text.substring(0, m._selection_start);
284     var matches = get_completions(entered_text, s.completions);
285     if (matches.length == 0)
286         return;
287     m._input_text = matches[0][0];
288     m._set_selection(entered_text.length);
290 interactive("minibuffer-insert-character-complete", minibuffer_insert_character_complete,
291             I.current_frame, I.p, I.e);
293 var minibuffer_history_data = new string_hashmap();
295 /* FIXME: These should possibly be saved to disk somewhere */
296 /* USER PREFERENCE */
297 var minibuffer_history_max_items = 100;
300 function minibuffer_state(keymap, prompt, input, selection_start, selection_end)
302     this.keymap = keymap;
303     this.prompt = prompt;
304     if (input)
305         this.input = input;
306     else
307         this.input = "";
308     if (selection_start)
309         this.selection_start = selection_start;
310     else
311         this.selection_start = 0;
312     if (selection_end)
313         this.selection_end = selection_end;
314     else
315         this.selection_end = this.selection_start;
321  * The parameter `args' is an object specifying the arguments for
322  * basic_minibuffer_state.  The following properties of args must/may
323  * be set:
325  * prompt:            [required]
327  * initial_value:     [optional] specifies the initial text
329  * select:            [optional] specifies to select the initial text if set to non-null
330  */
331 define_keywords("$prompt", "$initial_value", "$select");
332 function basic_minibuffer_state()
334     keywords(arguments, $initial_value = "");
335     var sel_start, sel_end;
336     if (arguments.$select)
337     {
338         sel_start = 0;
339         sel_end = arguments.$initial_value.length;
340     } else {
341         sel_start = sel_end = arguments.$initial_value.length;
342     }
343     minibuffer_state.call(this, minibuffer_base_kmap,
344                           arguments.$prompt, arguments.$initial_value,
345                           sel_start, sel_end);
347 basic_minibuffer_state.prototype.__proto__ = minibuffer_state.prototype; // inherit from minibuffer_state
349 /* The parameter `args' specifies the arguments.  In addition, the
350  * arguments for basic_minibuffer_state are also allowed.
352  * history:           [optional] specifies a string to identify the history list to use
354  * callback:          [optional] function called once the user successfully enters a value; it is 
355  *                               called with the value entered by the user.
357  * abort_callback:    [optional] called if the operaion is aborted
358  */
359 define_keywords("$callback", "$abort_callback", "$history");
360 function text_entry_minibuffer_state() {
361     keywords(arguments);
363     basic_minibuffer_state.call(this, forward_keywords(arguments));
364     this.keymap = minibuffer_kmap;
365     
366     this.callback = arguments.$callback;
367     this.abort_callback = arguments.$abort_callback;
368     if (arguments.$history)
369     {
370         this.history = minibuffer_history_data.get_put_default(arguments.$history, []);
371         this.history_index = this.history.length;
372     }
374 // inherit from basic_minibuffer_state
375 text_entry_minibuffer_state.prototype.__proto__ = basic_minibuffer_state.prototype;
378  * The parameter `args' specifies the arguments.  In addition, the
379  * arguments for text_entry_minibuffer_state are also allowed.
381  * completions:       [required] specifies an array of possible completions
383  * allow_non_matches: [optional] if completions is non-null, setting
384  *                               this allows a non-match to be successfully entered; the callback
385  *                               with be called with the match as the first argument, and the true
386  *                               value as the second argument.
388  * default_match:     [optional] if completions is non-null, specifies a default
389  *                               match to use if the user entered a blank string.
391  * callback:          [optional] Called with the match as the first argument, and possibly with
392  *                               the true value as the second argument, depending on allow_non_matches.
394  */
395 define_keywords("$completions", "$allow_non_matches", "$default_match");
396 function completion_minibuffer_state() {
397     text_entry_minibuffer_state.call(this, forward_keywords(arguments));
398     keywords(arguments, $allow_non_matches = false);
399     this.keymap = minibuffer_completion_kmap;
400     this.completions = arguments.$completions.slice().sort(function (a,b) {
401             if (a[0] < b[0]) return -1;
402             else if (a[0] == b[0]) return 0;
403             else return 1;
404         });
405     this.allow_non_matches = arguments.$allow_non_matches;
406     this.default_match = arguments.$default_match;
408 // inherit from text_entry_minibuffer_state
409 completion_minibuffer_state.prototype.__proto__ = text_entry_minibuffer_state.prototype;
411 /* USER PREFERENCE */
412 var minibuffer_input_mode_show_message_timeout = 1000;
414 function minibuffer (frame)
416     this.output_element = frame.document.getElementById("minibuffer-output");
417     this.input_prompt_element = frame.document.getElementById("input-prompt");
418     this.input_element = frame.document.getElementById("input-field");
419     var m = this;
420     this.input_element.inputField.addEventListener("blur", function() {
421             if (m._input_mode_enabled && !m._showing_message)
422             {
423                 frame.setTimeout(
424                     function(){
425                         m.input_element.focus();
426                     }, 0);
427             }
428         }, false);
429     this.frame = frame;
430     this.last_message = "";
431     this.states = [];
432     this._keymap_set = new keymap_set();
435 minibuffer.prototype = {
436     constructor : minibuffer.constructor,
437     get _selection_start () { return this.input_element.selectionStart; },
438     get _selection_end () { return this.input_element.selectionEnd; },
439     get _input_text () { return this.input_element.value; },
440     set _input_text (text) { this.input_element.value = text; },
441     get prompt () { return this.input_prompt_element.value; },
442     set prompt (s) { this.input_prompt_element.value = s; },
444     _set_selection : function (start, end) {
445         if (start == null)
446             start = this._input_text.length;
447         if (end == null)
448             end = this._input_text.length;
449         this.input_element.setSelectionRange(start,end);
450     },
452     /* Saved focus state */
453     saved_focused_window : null,
454     saved_focused_element : null,
456     /* This method will display the specified string in the
457      * minibuffer, without recording it in any log/Messages buffer. */
458     show : function (str) {
459         if (this.last_message != str)
460         {
461             this.output_element.value = str;
462             this.last_message = str;
463             /*
464             if (str.length > 0)
465                 dumpln("MINIBUFFER: " + str);
466             */
467         }
469         if (str.length > 0 && this._input_mode_enabled)
470             this._ensure_message_area_showing();
471     },
472     message : function (str) {
473         /* TODO: add the message to a *Messages* buffer, and/or
474          * possibly dump them to the console. */
475         this.show(str);
476     },
477     clear : function () {
478         this.show("");
479     },
481     get current_state () {
482         if (this.states.length == 0)
483             return null;
484         return this.states[this.states.length - 1];
485     },
487     push_state : function (state) {
488         this._save_state();
489         this.states.push(state);
490         this._restore_state();
491     },
493     pop_state : function (restore_focus) {
494         if (restore_focus === undefined)
495             restore_focus = true;
496         this.states.pop();
497         this._restore_state(restore_focus);
498     },
500     _input_mode_enabled : false,
502     /* If _input_mode_enabled is true, this is set to indicate that
503      * the message area is being temporarily shown instead of the
504      * input box. */
505     _showing_message : false,
507     _message_timer_ID : null,
509     /* This must only be called if _input_mode_enabled is true */
510     _ensure_input_area_showing : function () {
511         if (this._showing_message)
512         {
513             this.frame.clearTimeout(this._message_timer_ID);
514             this._message_timer_ID = null;
515             this._showing_message = false;
516             this._switch_to_input_mode();
517         }
518     },
520     /* This must only be called if _input_mode_enabled is true */
521     _ensure_message_area_showing : function () {
522         if (this._showing_message)
523             this.frame.clearTimeout(this._message_timer_ID);
524         else {
525             this._showing_message = true;
526             this._switch_to_message_mode();
527         }
528         var obj = this;
529         this._message_timer_ID = this.frame.setTimeout(function(){
530                 obj._ensure_input_area_showing();
531             }, minibuffer_input_mode_show_message_timeout);
532     },
534     _switch_to_input_mode : function () {
535         this.output_element.collapsed = true;
536         this.input_prompt_element.collapsed = false;
537         this.input_element.collapsed = false;
538         this.input_element.focus();
539     },
541     _switch_to_message_mode : function () {
542         this.output_element.collapsed = false;
543         this.input_prompt_element.collapsed = true;
544         this.input_element.collapsed = true;
545     },
547     _restore_state : function (restore_focus) {
548         var s = this.current_state;
549         if (s) {
550             if (!this._input_mode_enabled)
551             {
552                 this.saved_focused_window = this.frame.document.commandDispatcher.focusedWindow;
553                 this.saved_focused_element = this.frame.document.commandDispatcher.focusedElement;
554                 this._input_mode_enabled = true;
555                 this._switch_to_input_mode();
556             }
557             this._keymap_set.default_keymap = s.keymap;
558             this.frame.keyboard_state.override_keymap_set = this._keymap_set;
559             this._input_text = s.input;
560             this.prompt = s.prompt;
561             this._set_selection(s.selection_start, s.selection_end);
562         } else {
563             if (this._input_mode_enabled)
564             {
565                 this._input_mode_enabled = false;
566                 if (!this._showing_message)
567                     this._switch_to_message_mode();
568                 else {
569                     this.frame.clearTimeout(this._message_timer_ID);
570                     this._message_timer_ID = null;
571                     this._showing_message = false;
572                 }
573                 if (restore_focus)
574                 {
575                     if (this.saved_focused_element)
576                         set_focus_no_scroll(this.frame, this.saved_focused_element);
577                     else if (this.saved_focused_window)
578                         set_focus_no_scroll(this.frame, this.saved_focused_window);
579                 }
580                 this.saved_focused_element = null;
581                 this.saved_focused_window = null;
582                 this.frame.keyboard_state.override_keymap_set = null;
583                 this._keymap_set.default_keymap = null;
584             }
585         }
586     },
588     _save_state : function () {
589         var s = this.current_state;
590         if (s)
591         {
592             s.input = this._input_text;
593             s.prompt = this.prompt;
594             s.selection_start = this._selection_start;
595             s.selection_end = this._selection_end;
596         }
597     },
599     /*  See basic_minibuffer_state for a description of arguments */
600     read : function () {
601         /* FIXME: have policy for deciding whether to refuse a
602          * recursive minibuffer operation */
603         this.push_state(new text_entry_minibuffer_state(forward_keywords(arguments)));
604     },
606     read_with_completion : function () {
607         this.push_state(new completion_minibuffer_state(forward_keywords(arguments)));
608     }
611 function minibuffer_initialize_frame(frame)
613     frame.minibuffer = new minibuffer(frame);
616 add_hook("frame_initialize_early_hook", minibuffer_initialize_frame);
618 /* Note: This is concise, but doesn't seem to be useful in practice,
619  * because nothing can be done with the state alone.
621 I.minibuffer_state = interactive_method(
622     $doc = "Topmost minibuffer state",
623     $sync = function (ctx, type) {
624         var s = ctx.frame.minibuffer.current_state;
625         if (!s || (type && !(s instanceof type)))
626             throw new Error("Invalid minibuffer state.");
627         return s;
628     });