browser_object_follow: do not give $opener for following non-element/window in new...
[conkeror.git] / modules / hints.js
blobc985b7021dc5a1ebb61eba732ccff31a996571b7
1 /**
2  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2009-2010 John J. Foerch
4  *
5  * Portions of this file are derived from Vimperator,
6  * (C) Copyright 2006-2007 Martin Stubenschrott.
7  *
8  * Use, modification, and distribution are subject to the terms specified in the
9  * COPYING file.
10 **/
12 in_module(null);
14 define_variable("active_img_hint_background_color", "#88FF00",
15     "Color for the active image hint background.");
17 define_variable("img_hint_background_color", "yellow",
18     "Color for inactive image hint backgrounds.");
20 define_variable("active_hint_background_color", "#88FF00",
21     "Color for the active hint background.");
23 define_variable("hint_background_color", "yellow",
24     "Color for the inactive hint.");
27 define_variable("hint_digits", null,
28     "Null or a string of the digits to use as the counting base "+
29     "for hint numbers, starting with the digit that represents zero "+
30     "and ascending.  If null, base 10 will be used with the normal "+
31     "hindu-arabic numerals.");
34 /**
35  * hints_enumerate is a generator of natural numbers in the base defined
36  * by hint_digits.
37  */
38 function hints_enumerate () {
39     var base = hint_digits.length;
40     var n = [1];
41     var p = 1;
42     while (true) {
43         yield n.map(function (x) hint_digits[x]).join("");
44         var i = p-1;
45         n[i]++;
46         while (n[i] >= base && i > 0) {
47             n[i] = 0;
48             n[--i]++;
49         }
50         if (n[0] >= base) {
51             n[0] = 0;
52             n.unshift(1);
53             p++;
54         }
55     }
58 /**
59  * hints_parse converts a string that represents a natural number to an
60  * int.  When hint_digits is non-null, it defines the base for conversion.
61  */
62 function hints_parse (str) {
63     if (hint_digits) {
64         var base = hint_digits.length;
65         var n = 0;
66         for (var i = 0, p = str.length - 1; p >= 0; i++, p--) {
67             n += hint_digits.indexOf(str[i]) * Math.pow(base, p);
68         }
69         return n;
70     } else
71         return parseInt(str);
74 /**
75  * Register hints style sheet
76  */
77 const hints_stylesheet = "chrome://conkeror-gui/content/hints.css";
78 register_user_stylesheet(hints_stylesheet);
81 function hints_simple_text_match (text, pattern) {
82     var pos = text.indexOf(pattern);
83     if (pos == -1)
84         return false;
85     return [pos, pos + pattern.length];
88 define_variable('hints_text_match', hints_simple_text_match,
89     "A function which takes a string and a pattern (another string) "+
90     "and returns an array of [start, end] indices if the pattern was "+
91     "found in the string, or false if it was not.");
94 /**
95  *   In the hints interaction, a node can be selected either by typing
96  * the number of its associated hint, or by typing substrings of the
97  * text content of the node.  In the case of selecting by text
98  * content, multiple substrings can be given by separating them with
99  * spaces.
100  */
101 function hint_manager (window, xpath_expr, focused_frame, focused_element) {
102     this.window = window;
103     this.hints = [];
104     this.valid_hints = [];
105     this.xpath_expr = xpath_expr;
106     this.focused_frame = focused_frame;
107     this.focused_element = focused_element;
108     this.last_selected_hint = null;
110     // Generate
111     this.generate_hints();
113 hint_manager.prototype = {
114     constructor: hint_manager,
115     current_hint_string: "",
116     current_hint_number: -1,
118     /**
119      * Create an initially hidden hint span element absolutely
120      * positioned over each element that matches
121      * hint_xpath_expression.  This is done recursively for all frames
122      * and iframes.  Information about the resulting hints are also
123      * stored in the hints array.
124      */
125     generate_hints: function () {
126         var topwin = this.window;
127         var top_height = topwin.innerHeight;
128         var top_width = topwin.innerWidth;
129         var hints = this.hints;
130         var xpath_expr = this.xpath_expr;
131         var focused_frame_hint = null, focused_element_hint = null;
132         var focused_frame = this.focused_frame;
133         var focused_element = this.focused_element;
135         function helper (window, offsetX, offsetY) {
136             var win_height = window.height;
137             var win_width = window.width;
139             // Bounds
140             var minX = offsetX < 0 ? -offsetX : 0;
141             var minY = offsetY < 0 ? -offsetY : 0;
142             var maxX = offsetX + win_width > top_width ? top_width - offsetX : top_width;
143             var maxY = offsetY + win_height > top_height ? top_height - offsetY : top_height;
145             var scrollX = window.scrollX;
146             var scrollY = window.scrollY;
148             var doc = window.document;
149             if (! doc.documentElement)
150                 return;
151             var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
152                                    Ci.nsIDOMXPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
153                                    null /* existing results */);
155             var base_node = doc.createElementNS(XHTML_NS, "span");
156             base_node.className = "__conkeror_hint";
158             var fragment = doc.createDocumentFragment();
159             var rect, elem, text, node, show_text;
160             for (var j = 0; j < res.snapshotLength; j++) {
161                 elem = res.snapshotItem(j);
162                 rect = elem.getBoundingClientRect();
163                 if (elem instanceof Ci.nsIDOMHTMLAreaElement) {
164                     rect = { top: rect.top,
165                              left: rect.left,
166                              bottom: rect.bottom,
167                              right: rect.right };
168                     try {
169                         var coords = elem.getAttribute("coords")
170                             .match(/^\D*(-?\d+)\D+(-?\d+)/);
171                         if (coords.length == 3) {
172                             rect.left += parseInt(coords[1]);
173                             rect.top += parseInt(coords[2]);
174                         }
175                     } catch (e) {}
176                 }
177                 if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
178                     continue;
179                 let style = topwin.getComputedStyle(elem, "");
180                 if (style.display == "none" || style.visibility == "hidden")
181                     continue;
182                 if (! (elem instanceof Ci.nsIDOMHTMLAreaElement))
183                     rect = elem.getClientRects()[0];
184                 if (!rect)
185                     continue;
186                 var nchildren = elem.childNodes.length;
187                 if (elem instanceof Ci.nsIDOMHTMLAnchorElement &&
188                     rect.width == 0 && rect.height == 0)
189                 {
190                     for (var c = 0; c < nchildren; ++c) {
191                         var cc = elem.childNodes.item(c);
192                         if (cc.getBoundingClientRect) {
193                             rect = cc.getBoundingClientRect();
194                             break;
195                         }
196                     }
197                 }
198                 show_text = false;
199                 if (elem instanceof Ci.nsIDOMHTMLInputElement || elem instanceof Ci.nsIDOMHTMLTextAreaElement)
200                     text = elem.value;
201                 else if (elem instanceof Ci.nsIDOMHTMLSelectElement) {
202                     if (elem.selectedIndex >= 0)
203                         text = elem.item(elem.selectedIndex).text;
204                     else
205                         text = "";
206                 } else if (elem instanceof Ci.nsIDOMHTMLFrameElement) {
207                     text = elem.name ? elem.name : "";
208                 } else if (/^\s*$/.test(elem.textContent) &&
209                            nchildren == 1 &&
210                            elem.childNodes.item(0) instanceof Ci.nsIDOMHTMLImageElement) {
211                     text = elem.childNodes.item(0).alt;
212                     show_text = true;
213                 } else
214                     text = elem.textContent;
216                 node = base_node.cloneNode(true);
217                 node.style.left = (rect.left + scrollX) + "px";
218                 node.style.top = (rect.top + scrollY) + "px";
219                 fragment.appendChild(node);
221                 let hint = { text: text,
222                              ltext: text.toLowerCase(),
223                              elem: elem,
224                              hint: node,
225                              img_hint: null,
226                              visible: false,
227                              show_text: show_text };
228                 if (elem.style) {
229                     hint.saved_color = elem.style.color;
230                     hint.saved_bgcolor = elem.style.backgroundColor;
231                 }
232                 hints.push(hint);
234                 if (elem == focused_element)
235                     focused_element_hint = hint;
236                 else if ((elem instanceof Ci.nsIDOMHTMLFrameElement ||
237                           elem instanceof Ci.nsIDOMHTMLIFrameElement) &&
238                          elem.contentWindow == focused_frame)
239                     focused_frame_hint = hint;
240             }
241             doc.documentElement.appendChild(fragment);
243             /* Recurse into any IFRAME or FRAME elements */
244             var frametag = "frame";
245             while (true) {
246                 var frames = doc.getElementsByTagName(frametag);
247                 for (var i = 0, nframes = frames.length; i < nframes; ++i) {
248                     elem = frames[i];
249                     rect = elem.getBoundingClientRect();
250                     if (!rect || rect.left > maxX || rect.right < minX || rect.top > maxY || rect.bottom < minY)
251                         continue;
252                     helper(elem.contentWindow, offsetX + rect.left, offsetY + rect.top);
253                 }
254                 if (frametag == "frame") frametag = "iframe"; else break;
255             }
256         }
257         helper(topwin, 0, 0);
258         this.last_selected_hint = focused_element_hint || focused_frame_hint;
259     },
261     /* Updates valid_hints and also re-numbers and re-displays all hints. */
262     update_valid_hints: function () {
263         this.valid_hints = [];
264         var cur_number = 1;
265         if (hint_digits)
266             var number_generator = hints_enumerate();
267         var active_number = this.current_hint_number;
268         var tokens = this.current_hint_string.split(" ");
269         var case_sensitive = (this.current_hint_string !=
270                               this.current_hint_string.toLowerCase());
271         var rect, text, img_hint, doc, scrollX, scrollY;
272     outer:
273         for (var i = 0, h; (h = this.hints[i]); ++i) {
274             if (case_sensitive)
275                 text = h.text;
276             else
277                 text = h.ltext;
278             for (var j = 0, ntokens = tokens.length; j < ntokens; ++j) {
279                 if (! hints_text_match(text, tokens[j])) {
280                     if (h.visible) {
281                         h.visible = false;
282                         h.hint.style.display = "none";
283                         if (h.img_hint)
284                             h.img_hint.style.display = "none";
285                         if (h.saved_color != null) {
286                             h.elem.style.backgroundColor = h.saved_bgcolor;
287                             h.elem.style.color = h.saved_color;
288                         }
289                     }
290                     continue outer;
291                 }
292             }
294             h.visible = true;
296             if (h == this.last_selected_hint && active_number == -1)
297                 this.current_hint_number = active_number = cur_number;
299             var img_elem = null;
301             if (text == "" && h.elem.firstChild &&
302                 h.elem.firstChild instanceof Ci.nsIDOMHTMLImageElement)
303                 img_elem = h.elem.firstChild;
304             else if (h.elem instanceof Ci.nsIDOMHTMLImageElement)
305                 img_elem = h.elem;
307             if (img_elem) {
308                 if (!h.img_hint) {
309                     rect = img_elem.getBoundingClientRect();
310                     if (rect) {
311                         doc = h.elem.ownerDocument;
312                         scrollX = doc.defaultView.scrollX;
313                         scrollY = doc.defaultView.scrollY;
314                         img_hint = doc.createElementNS(XHTML_NS, "span");
315                         img_hint.className = "__conkeror_img_hint";
316                         img_hint.style.left = (rect.left + scrollX) + "px";
317                         img_hint.style.top = (rect.top + scrollY) + "px";
318                         img_hint.style.width = (rect.right - rect.left) + "px";
319                         img_hint.style.height = (rect.bottom - rect.top) + "px";
320                         h.img_hint = img_hint;
321                         doc.documentElement.appendChild(img_hint);
322                     } else
323                         img_elem = null;
324                 }
325                 if (img_elem) {
326                     var bgcolor = (active_number == cur_number) ?
327                         active_img_hint_background_color : img_hint_background_color;
328                     h.img_hint.style.backgroundColor = bgcolor;
329                     h.img_hint.style.display = "inline";
330                 }
331             }
333             if (!h.img_hint && h.elem.style)
334                 h.elem.style.backgroundColor = (active_number == cur_number) ?
335                     active_hint_background_color : hint_background_color;
337             if (h.elem.style)
338                 h.elem.style.color = "black";
340             var label = "";
341             if (hint_digits)
342                 label = number_generator.next();
343             else
344                 label += cur_number;
345             if (h.elem instanceof Ci.nsIDOMHTMLFrameElement) {
346                 label +=  " " + text;
347             } else if (h.show_text && !/^\s*$/.test(text)) {
348                 let substrs = [[0,4]];
349                 for (j = 0; j < ntokens; ++j) {
350                     let m = hints_text_match(text, tokens[j]);
351                     if (m == false) continue;
352                     splice_range(substrs, m[0], m[1] + 2);
353                 }
354                 label += " " + substrs.map(function (x) {
355                     return text.substring(x[0],Math.min(x[1], text.length));
356                 }).join("..") + "..";
357             }
358             h.hint.textContent = label;
359             h.hint.style.display = "inline";
360             this.valid_hints.push(h);
361             cur_number++;
362         }
364         if (active_number == -1)
365             this.select_hint(1);
366     },
368     select_hint: function (index) {
369         var old_index = this.current_hint_number;
370         if (index == old_index)
371             return;
372         var vh = this.valid_hints;
373         var vl = this.valid_hints.length;
374         if (old_index >= 1 && old_index <= vl) {
375             var h = vh[old_index - 1];
376             if (h.img_hint)
377                 h.img_hint.style.backgroundColor = img_hint_background_color;
378             if (h.elem.style)
379                 h.elem.style.backgroundColor = hint_background_color;
380         }
381         this.current_hint_number = index;
382         this.last_selected_hint = null;
383         if (index >= 1 && index <= vl) {
384             h = vh[index - 1];
385             if (h.img_hint)
386                 h.img_hint.style.backgroundColor = active_img_hint_background_color;
387             if (h.elem.style)
388                 h.elem.style.backgroundColor = active_hint_background_color;
389             this.last_selected_hint = h;
390         }
391     },
393     hide_hints: function () {
394         for (var i = 0, h; h = this.hints[i]; ++i) {
395             if (h.visible) {
396                 h.visible = false;
397                 if (h.saved_color != null) {
398                     h.elem.style.color = h.saved_color;
399                     h.elem.style.backgroundColor = h.saved_bgcolor;
400                 }
401                 if (h.img_hint)
402                     h.img_hint.style.display = "none";
403                 h.hint.style.display = "none";
404             }
405         }
406     },
408     remove: function () {
409         for (var i = 0, h; h = this.hints[i]; ++i) {
410             if (h.visible && h.saved_color != null) {
411                 h.elem.style.color = h.saved_color;
412                 h.elem.style.backgroundColor = h.saved_bgcolor;
413             }
414             if (h.img_hint)
415                 h.img_hint.parentNode.removeChild(h.img_hint);
416             h.hint.parentNode.removeChild(h.hint);
417         }
418         this.hints = [];
419         this.valid_hints = [];
420     }
424  * Show panel with currently selected URL.
425  */
426 function hints_url_panel (hints, window) {
427     var g = new dom_generator(window.document, XUL_NS);
429     var p = g.element("hbox", "class", "panel url", "flex", "0");
430     g.element("label", p, "value", "URL:", "class", "url-panel-label");
431     var url_value = g.element("label", p, "class", "url-panel-value",
432                               "crop", "end", "flex", "1");
433     window.minibuffer.insert_before(p);
435     p.update = function () {
436         var s = [];
437         if (hints.manager && hints.manager.last_selected_hint) {
438             var elem = hints.manager.last_selected_hint.elem;
439             if (elem.hasAttribute("onmousedown") ||
440                 elem.hasAttribute("onclick"))
441             {
442                 s.push("[script]");
443             }
444             var tag = elem.localName.toLowerCase();
445             if ((tag == "input" || tag == "button") &&
446                 elem.type == "submit" && elem.form && elem.form.action)
447             {
448                 s.push((elem.form.method || "GET").toUpperCase() + ":" +
449                        elem.form.action);
450             } else {
451                 try {
452                     var spec = load_spec(elem);
453                     var uri = load_spec_uri_string(spec);
454                     if (uri)
455                         s.push(uri);
456                 } catch (e) {}
457             }
458         }
459         url_value.value = s.join(" ");
460     };
462     p.destroy = function () {
463         this.parentNode.removeChild(this);
464     };
466     return p;
469 define_variable("hints_display_url_panel", false,
470     "When selecting a hint, the URL can be displayed in a panel above "+
471     "the minibuffer.  This is useful for confirming that the correct "+
472     "link is selected and that the URL is not evil.  This option is "+
473     "most useful when hints_auto_exit_delay is long or disabled.");
476  * keyword arguments:
478  * $prompt
479  * $callback
480  * $abort_callback
481  */
482 define_keywords("$keymap", "$auto", "$hint_xpath_expression", "$multiple");
483 function hints_minibuffer_state (minibuffer, continuation, buffer) {
484     keywords(arguments, $keymap = hint_keymap, $auto);
485     basic_minibuffer_state.call(this, minibuffer, $prompt = arguments.$prompt,
486                                 $keymap = arguments.$keymap);
487     if (hints_display_url_panel)
488         this.url_panel = hints_url_panel(this, buffer.window);
489     this.original_prompt = arguments.$prompt;
490     this.continuation = continuation;
491     this.auto_exit = arguments.$auto ? true : false;
492     this.xpath_expr = arguments.$hint_xpath_expression;
493     this.auto_exit_timer_ID = null;
494     this.multiple = arguments.$multiple;
495     this.focused_element = buffer.focused_element;
496     this.focused_frame = buffer.focused_frame;
498 hints_minibuffer_state.prototype = {
499     constructor: hints_minibuffer_state,
500     __proto__: basic_minibuffer_state.prototype,
501     manager: null,
502     typed_string: "",
503     typed_number: "",
504     load: function () {
505         basic_minibuffer_state.prototype.load.call(this);
506         if (!this.manager) {
507             var buf = this.minibuffer.window.buffers.current;
508             this.manager = new hint_manager(buf.top_frame, this.xpath_expr,
509                                             this.focused_frame, this.focused_element);
510         }
511         this.manager.update_valid_hints();
512         if (this.url_panel)
513             this.url_panel.update();
514     },
515     clear_auto_exit_timer: function () {
516         var window = this.minibuffer.window;
517         if (this.auto_exit_timer_ID != null) {
518             window.clearTimeout(this.auto_exit_timer_ID);
519             this.auto_exit_timer_ID = null;
520         }
521     },
522     unload: function () {
523         this.clear_auto_exit_timer();
524         this.manager.hide_hints();
525         basic_minibuffer_state.prototype.unload.call(this);
526     },
527     destroy: function () {
528         this.clear_auto_exit_timer();
529         this.manager.remove();
530         if (this.url_panel)
531             this.url_panel.destroy();
532         basic_minibuffer_state.prototype.destroy.call(this);
533     },
534     update_minibuffer: function (m) {
535         if (this.typed_number.length > 0)
536             m.prompt = this.original_prompt + " #" + this.typed_number;
537         else
538             m.prompt = this.original_prompt;
539         if (this.url_panel)
540             this.url_panel.update();
541     },
543     handle_auto_exit: function (ambiguous) {
544         var window = this.minibuffer.window;
545         var num = this.manager.current_hint_number;
546         if (!this.auto_exit)
547             return;
548         let s = this;
549         let delay = ambiguous ? hints_ambiguous_auto_exit_delay : hints_auto_exit_delay;
550         if (delay > 0)
551             this.auto_exit_timer_ID = window.setTimeout(function () { hints_exit(window, s); },
552                                                         delay);
553     },
555     handle_input: function (m) {
556         this.clear_auto_exit_timer();
557         this.typed_number = "";
558         this.typed_string = m._input_text;
559         this.manager.current_hint_string = this.typed_string;
560         this.manager.current_hint_number = -1;
561         this.manager.update_valid_hints();
562         if (this.manager.valid_hints.length == 1)
563             this.handle_auto_exit(false /* unambiguous */);
564         else if (this.manager.valid_hints.length > 1)
565         this.handle_auto_exit(true /* ambiguous */);
566         this.update_minibuffer(m);
567     }
570 define_variable("hints_auto_exit_delay", 0,
571     "Delay (in milliseconds) after the most recent key stroke before a "+
572     "sole matching element is automatically selected.  When zero, "+
573     "automatic selection is disabled.  A value of 500 is a good "+
574     "starting point for an average-speed typist.");
576 define_variable("hints_ambiguous_auto_exit_delay", 0,
577     "Delay (in milliseconds) after the most recent key stroke before the "+
578     "first of an ambiguous match is automatically selected.  If this is "+
579     "set to 0, automatic selection in ambiguous matches is disabled.");
582 define_key_match_predicate("match_hint_digit", "hint digit",
583     function (e) {
584         if (e.type != "keypress")
585             return false;
586         if (e.charCode == 48) //0 is special
587             return true;
588         if (hint_digits) {
589             if (hint_digits.indexOf(String.fromCharCode(e.charCode)) > -1)
590                 return true;
591         } else if (e.charCode >= 49 && e.charCode <= 57)
592             return true;
593         return false;
594     });
596 interactive("hints-handle-number",
597     "This is the handler for numeric keys in hinting mode.  Normally, "+
598     "that means '1' through '9' and '0', but the numeric base (and digits) "+
599     "can be configured via the user variable 'hint_digits'.  No matter "+
600     "what numeric base is in effect, the character '0' is special, and "+
601     "will always be treated as a number 0, translated into the current "+
602     "base if necessary.",
603     function (I) {
604         let s = I.minibuffer.check_state(hints_minibuffer_state);
605         s.clear_auto_exit_timer();
606         var ch = String.fromCharCode(I.event.charCode);
607         if (hint_digits && ch == "0")
608             ch = hint_digits[0];
609         var auto_exit_ambiguous = null; // null -> no auto exit; false -> not ambiguous; true -> ambiguous
610         s.typed_number += ch;
611         s.manager.select_hint(hints_parse(s.typed_number));
612         var num = s.manager.current_hint_number;
613         if (num > 0 && num <= s.manager.valid_hints.length)
614             auto_exit_ambiguous = num * 10 > s.manager.valid_hints.length ? false : true;
615         else if (num == 0) {
616             if (!s.multiple) {
617                 hints_exit(I.window, s);
618                 return;
619             }
620             auto_exit_ambiguous = false;
621         }
622         if (auto_exit_ambiguous !== null)
623             s.handle_auto_exit(auto_exit_ambiguous);
624         s.update_minibuffer(I.minibuffer);
625     });
627 function hints_backspace (window, s) {
628     let m = window.minibuffer;
629     s.clear_auto_exit_timer();
630     var l = s.typed_number.length;
631     if (l > 0) {
632         s.typed_number = s.typed_number.substring(0, --l);
633         var num = l > 0 ? hints_parse(s.typed_number) : 1;
634         s.manager.select_hint(num);
635     } else if (s.typed_string.length > 0) {
636         call_builtin_command(window, 'cmd_deleteCharBackward');
637         s.typed_string = m._input_text;
638         //m._set_selection();
639         s.manager.current_hint_string = s.typed_string;
640         s.manager.current_hint_number = -1;
641         s.manager.update_valid_hints();
642     }
643     s.update_minibuffer(m);
645 interactive("hints-backspace", null,
646     function (I) {
647         hints_backspace(I.window, I.minibuffer.check_state(hints_minibuffer_state));
648     });
650 function hints_next (window, s, count) {
651     s.clear_auto_exit_timer();
652     s.typed_number = "";
653     var cur = s.manager.current_hint_number - 1;
654     var vh = s.manager.valid_hints;
655     var vl = s.manager.valid_hints.length;
656     if (vl > 0) {
657         cur = (cur + count) % vl;
658         if (cur < 0)
659             cur += vl;
660         s.manager.select_hint(cur + 1);
661     }
662     s.update_minibuffer(window);
664 interactive("hints-next", null,
665     function (I) {
666         hints_next(I.window, I.minibuffer.check_state(hints_minibuffer_state), I.p);
667     });
669 interactive("hints-previous", null,
670     function (I) {
671         hints_next(I.window, I.minibuffer.check_state(hints_minibuffer_state), -I.p);
672     });
674 function hints_exit (window, s) {
675     var cur = s.manager.current_hint_number;
676     var elem = null;
677     if (cur > 0 && cur <= s.manager.valid_hints.length)
678         elem = s.manager.valid_hints[cur - 1].elem;
679     else if (cur == 0)
680         elem = window.buffers.current.top_frame;
681     if (elem !== null) {
682         var c = s.continuation;
683         delete s.continuation;
684         window.minibuffer.pop_state();
685         if (c)
686             c(elem);
687     }
690 interactive("hints-exit", null,
691     function (I) {
692         hints_exit(I.window, I.minibuffer.check_state(hints_minibuffer_state));
693     });
695 interactive("hints-quote-next", null,
696     function (I) {
697         I.overlay_keymap = hint_quote_next_keymap;
698     },
699     $prefix);
702 define_keywords("$buffer");
703 minibuffer.prototype.read_hinted_element = function () {
704     keywords(arguments);
705     var buf = arguments.$buffer;
706     var s = new hints_minibuffer_state(this, (yield CONTINUATION), buf, forward_keywords(arguments));
707     this.push_state(s);
708     var result = yield SUSPEND;
709     yield co_return(result);
712 provide("hints");