page-mode keymaps reworked
[conkeror/arlinius.git] / modules / buffer.js
bloba8baa4a43416c01b15597154ff3e919ed30c34fb
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2008 John J. Foerch
4  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
5  *
6  * Use, modification, and distribution are subject to the terms specified in the
7  * COPYING file.
8 **/
10 var define_buffer_local_hook = local_hook_definer("window");
12 function define_current_buffer_hook (hook_name, existing_hook) {
13     define_buffer_local_hook(hook_name);
14     add_hook(existing_hook, function (buffer) {
15             if (!buffer.window.buffers || buffer != buffer.window.buffers.current)
16                 return;
17             var hook = conkeror[hook_name];
18             hook.run.apply(hook, Array.prototype.slice.call(arguments));
19         });
22 define_buffer_local_hook("buffer_title_change_hook");
23 define_buffer_local_hook("buffer_description_change_hook");
24 define_buffer_local_hook("select_buffer_hook");
25 define_buffer_local_hook("create_buffer_hook");
26 define_buffer_local_hook("kill_buffer_hook");
27 define_buffer_local_hook("buffer_scroll_hook");
28 define_buffer_local_hook("buffer_dom_content_loaded_hook");
29 define_buffer_local_hook("buffer_loaded_hook");
31 define_current_buffer_hook("current_buffer_title_change_hook", "buffer_title_change_hook");
32 define_current_buffer_hook("current_buffer_description_change_hook", "buffer_description_change_hook");
33 define_current_buffer_hook("current_buffer_scroll_hook", "buffer_scroll_hook");
34 define_current_buffer_hook("current_buffer_dom_content_loaded_hook", "buffer_dom_content_loaded_hook");
36 function buffer_configuration(existing_configuration) {
37     if (existing_configuration != null) {
38         this.cwd = existing_configuration.cwd;
39     }
40     else {
41         this.cwd = default_directory.path;
42     }
45 define_keywords("$configuration", "$element");
46 function buffer_creator(type) {
47     var args = forward_keywords(arguments);
48     return function (window, element) {
49         return new type(window, element, args);
50     };
53 define_variable("allow_browser_window_close", true,
54     "If this is set to true, if a content buffer page calls " +
55     "window.close() from JavaScript and is not prevented by the " +
56     "normal Mozilla mechanism that restricts pages from closing " +
57     "a window that was not opened by a script, the buffer will be " +
58     "killed, deleting the window as well if it is the only buffer.");
60 function buffer (window, element) {
61     this.constructor_begin();
62     keywords(arguments, $configuration = null);
63     this.window = window;
64     this.configuration = new buffer_configuration(arguments.$configuration);
65     if (element == null) {
66         element = create_XUL(window, "vbox");
67         element.setAttribute("flex", "1");
68         var browser = create_XUL(window, "browser");
69         browser.setAttribute("type", "content");
70         browser.setAttribute("flex", "1");
71         browser.setAttribute("autocompletepopup", "popup_autocomplete");
72         element.appendChild(browser);
73         this.window.buffers.container.appendChild(element);
74     } else {
75         /* Manually set up session history.
76          *
77          * This is needed because when constructor for the XBL binding
78          * (mozilla/toolkit/content/widgets/browser.xml#browser) for
79          * the initial browser element of the window is called, the
80          * docShell is not yet initialized and setting up the session
81          * history will fail.  To work around this problem, we do as
82          * tabbrowser.xml (Firefox) does and set the initial browser
83          * to have the disablehistory=true attribute, and then repeat
84          * the work that would normally be done in the XBL
85          * constructor.
86          */
88         // This code is taken from mozilla/browser/base/content/browser.js
89         let browser = element.firstChild;
90         browser.webNavigation.sessionHistory =
91             Cc["@mozilla.org/browser/shistory;1"].createInstance(Ci.nsISHistory);
92         observer_service.addObserver(browser, "browser:purge-session-history", false);
94         // remove the disablehistory attribute so the browser cleans up, as
95         // though it had done this work itself
96         browser.removeAttribute("disablehistory");
98         // enable global history
99         browser.docShell.QueryInterface(Ci.nsIDocShellHistory).useGlobalHistory = true;
100     }
101     this.window.buffers.buffer_list.push(this);
102     this.element = element;
103     this.browser = element.firstChild;
104     this.element.conkeror_buffer_object = this;
106     this.enabled_modes = [];
107     this.local_variables = {};
108     this.default_browser_object_classes = {};
110     var buffer = this;
112     this.browser.addEventListener("scroll", function (event) {
113             buffer_scroll_hook.run(buffer);
114         }, true /* capture */, false /* ignore untrusted events */);
116     this.browser.addEventListener("DOMContentLoaded", function (event) {
117             buffer_dom_content_loaded_hook.run(buffer);
118         }, true /* capture */, false /*ignore untrusted events */);
120     this.browser.addEventListener("load", function (event) {
121             buffer_loaded_hook.run(buffer);
122         }, true /* capture */, false /*ignore untrusted events */);
124     this.browser.addEventListener("DOMWindowClose", function (event) {
125             /* This call to preventDefault is very important; without
126              * it, somehow Mozilla does something bad and as a result
127              * the window loses focus, causing keyboard commands to
128              * stop working. */
129             event.preventDefault();
131             if (allow_browser_window_close)
132                 kill_buffer(buffer, true);
133         }, true);
135     this.constructor_end();
138 buffer.prototype = {
139     /* Saved focus state */
140     saved_focused_frame : null,
141     saved_focused_element : null,
142     on_switch_to : null,
143     on_switch_away : null,
144     // get title ()   [must be defined by subclasses]
145     // get name ()    [must be defined by subclasses]
146     dead : false, /* This is set when the buffer is killed */
148     default_message : "",
150     get : function (x) {
151         if (x in this.local_variables)
152             return this.local_variables[x];
153         return conkeror[x];
154     },
156     set_default_message : function (str) {
157         this.default_message = str;
158         if (this == this.window.buffers.current)
159             this.window.minibuffer.set_default_message(str);
160     },
162     constructors_running : 0,
164     constructor_begin : function () {
165         this.constructors_running++;
166     },
168     constructor_end : function () {
169         if (--this.constructors_running == 0)
170             create_buffer_hook.run(this);
171     },
173     /* General accessors */
174     get cwd () { return this.configuration.cwd; },
176     /* Browser accessors */
177     get top_frame () { return this.browser.contentWindow; },
178     get document () { return this.browser.contentDocument; },
179     get web_navigation () { return this.browser.webNavigation; },
180     get doc_shell () { return this.browser.docShell; },
181     get markup_document_viewer () { return this.browser.markupDocumentViewer; },
182     get current_URI () { return this.browser.currentURI; },
184     is_child_element : function (element) {
185         return (element && this.is_child_frame(element.ownerDocument.defaultView));
186     },
188     is_child_frame : function (frame) {
189         return (frame && frame.top == this.top_frame);
190     },
192     // This method is like focused_frame, except that if no content
193     // frame actually has focus, this returns null.
194     get focused_frame_or_null () {
195         var frame = this.window.document.commandDispatcher.focusedWindow;
196         var top = this.top_frame;
197         if (this.is_child_frame(frame))
198             return frame;
199         return null;
200     },
202     get focused_frame () {
203         var frame = this.window.document.commandDispatcher.focusedWindow;
204         var top = this.top_frame;
205         if (this.is_child_frame(frame))
206             return frame;
207         return this.top_frame;
208     },
210     get focused_element () {
211         var element = this.window.document.commandDispatcher.focusedElement;
212         if (this.is_child_element(element))
213             return element;
214         return null;
215     },
217     do_command : function (command) {
218         function attempt_command (element, command) {
219             var controller;
220             if (element.controllers
221                 && (controller = element.controllers.getControllerForCommand(command)) != null
222                 && controller.isCommandEnabled(command))
223             {
224                 controller.doCommand(command);
225                 return true;
226             }
227             return false;
228         }
230         var element = this.focused_element;
231         if (element && attempt_command(element, command))
232             return;
233         var win = this.focused_frame;
234         do  {
235             if (attempt_command(win, command))
236                 return;
237             if (!win.parent || win == win.parent)
238                 break;
239             win = win.parent;
240         } while (true);
241     },
243     handle_kill : function () {
244         this.dead = true;
245         this.browser = null;
246         this.element = null;
247         this.saved_focused_frame = null;
248         this.saved_focused_element = null;
249         kill_buffer_hook.run(this);
250     }
253 function check_buffer (obj, type) {
254     if (!(obj instanceof type))
255         throw interactive_error("Buffer has invalid type.");
256     if (obj.dead)
257         throw interactive_error("Buffer has already been killed.");
258     return obj;
261 function buffer_container (window, create_initial_buffer) {
262     this.window = window;
263     this.container = window.document.getElementById("buffer-container");
264     this.buffer_list = [];
265     window.buffers = this;
267     create_initial_buffer(window, this.container.firstChild);
270 buffer_container.prototype = {
271     constructor : buffer_container,
273     get current () {
274         return this.container.selectedPanel.conkeror_buffer_object;
275     },
277     set current (buffer) {
278         var old_value = this.current;
279         if (old_value == buffer)
280             return;
282         this.buffer_list.splice(this.buffer_list.indexOf(buffer), 1);
283         this.buffer_list.unshift(buffer);
285         this._switch_away_from(this.current);
286         this._switch_to(buffer);
288         // Run hooks
289         select_buffer_hook.run(buffer);
290     },
292     _switch_away_from : function (old_value) {
293         // Save focus state
294         old_value.saved_focused_frame = old_value.focused_frame;
295         old_value.saved_focused_element = old_value.focused_element;
297         old_value.browser.setAttribute("type", "content");
298     },
300     _switch_to : function (buffer) {
301         // Select new buffer in the XUL deck
302         this.container.selectedPanel = buffer.element;
304         buffer.browser.setAttribute("type", "content-primary");
306         /**
307          * This next focus call seems to be needed to avoid focus
308          * somehow getting lost (and the keypress handler therefore
309          * not getting called at all) when killing buffers.
310          */
311         this.window.focus();
313         // Restore focus state
314         if (buffer.saved_focused_element)
315             set_focus_no_scroll(this.window, buffer.saved_focused_element);
316         else if (buffer.saved_focused_frame)
317             set_focus_no_scroll(this.window, buffer.saved_focused_frame);
319         buffer.saved_focused_element = null;
320         buffer.saved_focused_frame = null;
322         this.window.minibuffer.set_default_message(buffer.default_message);
323     },
325     get count () {
326         return this.container.childNodes.length;
327     },
329     get_buffer : function (index) {
330         if (index >= 0 && index < this.count)
331             return this.container.childNodes.item(index).conkeror_buffer_object;
332         return null;
333     },
335     get selected_index () {
336         var nodes = this.container.childNodes;
337         var count = nodes.length;
338         for (var i = 0; i < count; ++i)
339             if (nodes.item(i) == this.container.selectedPanel)
340                 return i;
341         return null;
342     },
344     index_of : function (b) {
345         var nodes = this.container.childNodes;
346         var count = nodes.length;
347         for (var i = 0; i < count; ++i)
348             if (nodes.item(i) == b.element)
349                 return i;
350         return null;
351     },
353     get unique_name_list () {
354         var existing_names = new string_hashset();
355         var bufs = [];
356         this.for_each(function(b) {
357                 var base_name = b.name;
358                 var name = base_name;
359                 var index = 1;
360                 while (existing_names.contains(name))
361                 {
362                     ++index;
363                     name = base_name + "<" + index + ">";
364                 }
365                 existing_names.add(name);
366                 bufs.push([name, b]);
367             });
368         return bufs;
369     },
371     kill_buffer : function (b) {
372         if (b.dead)
373             return true;
374         var count = this.count;
375         if (count <= 1)
376             return false;
377         var new_buffer = this.buffer_list[0];
378         var changed = false;
379         if (b == new_buffer) {
380             new_buffer = this.buffer_list[1];
381             changed = true;
382         }
383         this._switch_away_from(this.current);
384         this.container.removeChild(b.element);
385         this.buffer_list.splice(this.buffer_list.indexOf(b), 1);
386         this._switch_to(new_buffer);
387         if (changed) {
388             select_buffer_hook.run(new_buffer);
389             this.buffer_list.splice(this.buffer_list.indexOf(new_buffer), 1);
390             this.buffer_list.unshift(new_buffer);
391         }
392         b.handle_kill();
393         return true;
394     },
396     bury_buffer : function(b) {
397       var new_buffer = this.buffer_list[0];
398       if (b == new_buffer) new_buffer = this.buffer_list[1];
399       this.buffer_list.splice(this.buffer_list.indexOf(b), 1);
400       this.buffer_list.push(b);
401       this.current = new_buffer;
402       return true;
403     },
405     for_each : function (f) {
406         var count = this.count;
407         for (var i = 0; i < count; ++i)
408             f(this.get_buffer(i));
409     }
412 function buffer_initialize_window_early (window) {
413     /**
414      * Use content_buffer by default to handle an unusual case where
415      * browser.chromeURI is used perhaps.  In general this default
416      * should not be needed.
417      */
419     var create_initial_buffer
420         = window.args.initial_buffer_creator || buffer_creator(content_buffer);
421     new buffer_container(window, create_initial_buffer);
424 add_hook("window_initialize_early_hook", buffer_initialize_window_early);
427 define_buffer_local_hook("buffer_kill_before_hook", RUN_HOOK_UNTIL_FAILURE);
428 function buffer_before_window_close (window) {
429     var bs = window.buffers;
430     var count = bs.count;
431     for (let i = 0; i < count; ++i) {
432         if (!buffer_kill_before_hook.run(bs.get_buffer(i)))
433             return false;
434     }
435     return true;
437 add_hook("window_before_close_hook", buffer_before_window_close);
439 function buffer_window_close_handler (window) {
440     var bs = window.buffers;
441     var count = bs.count;
442     for (let i = 0; i < count; ++i) {
443         let b = bs.get_buffer(i);
444         b.handle_kill();
445     }
447 add_hook("window_close_hook", buffer_window_close_handler);
449 /* open/follow targets */
450 const OPEN_CURRENT_BUFFER = 0; // only valid for open if the current
451                                // buffer is a content_buffer.
452 const OPEN_NEW_BUFFER = 1;
453 const OPEN_NEW_BUFFER_BACKGROUND = 2;
454 const OPEN_NEW_WINDOW = 3;
456 const FOLLOW_DEFAULT = 4; // for open, implies OPEN_CURRENT_BUFFER
457 const FOLLOW_CURRENT_FRAME = 5; // for open, implies OPEN_CURRENT_BUFFER
459 var TARGET_PROMPTS = [" in current buffer",
460                       " in new buffer",
461                       " in new buffer (background)",
462                       " in new window",
463                       "",
464                       " in current frame"];
466 var TARGET_NAMES = ["current buffer",
467                     "new buffer",
468                     "new buffer (background)",
469                     "new window",
470                     "default",
471                     "current frame"];
474 function create_buffer (window, creator, target) {
475     switch (target) {
476     case OPEN_NEW_BUFFER:
477         window.buffers.current = creator(window, null);
478         break;
479     case OPEN_NEW_BUFFER_BACKGROUND:
480         creator(window, null);
481         break;
482     case OPEN_NEW_WINDOW:
483         make_window(creator);
484         break;
485     default:
486         throw new Error("invalid target");
487     }
490 let (queued_buffer_creators = null) {
491     function create_buffer_in_current_window (creator, target, focus_existing) {
492         function process_queued_buffer_creators (window) {
493             for (var i = 0; i < queued_buffer_creators.length; ++i) {
494                 var x = queued_buffer_creators[i];
495                 create_buffer(window, x[0], x[1]);
496             }
497             queued_buffer_creators = null;
498         }
500         if (target == OPEN_NEW_WINDOW)
501             throw new Error("invalid target");
502         var window = get_recent_conkeror_window();
503         if (window) {
504             if (focus_existing)
505                 window.focus();
506             create_buffer(window, creator, target);
507         } else if (queued_buffer_creators != null) {
508             queued_buffer_creators.push([creator,target]);
509         } else {
510             queued_buffer_creators = [];
511             window = make_window(creator);
512             add_hook.call(window, "window_initialize_late_hook", process_queued_buffer_creators);
513         }
514     }
517 minibuffer_auto_complete_preferences["buffer"] = true;
518 define_keywords("$default");
519 minibuffer.prototype.read_buffer = function () {
520     var window = this.window;
521     var buffer = this.window.buffers.current;
522     keywords(arguments, $prompt = "Buffer:",
523              $default = buffer,
524              $history = "buffer");
525     var completer = all_word_completer(
526         $completions = function (visitor) window.buffers.for_each(visitor),
527         $get_string = function (x) x.description,
528         $get_description = function (x) x.title);
529     var result = yield this.read(
530         $prompt = arguments.$prompt,
531         $history = arguments.$history,
532         $completer = completer,
533         $match_required = true,
534         $auto_complete = "buffer",
535         $auto_complete_initial = true,
536         $auto_complete_delay = 0,
537         $default_completion = arguments.$default);
538     yield co_return(result);
541 interactive_context.prototype.__defineGetter__("cwd", function () this.buffer.cwd);
543 function buffer_next (window, count)
545     var index = window.buffers.selected_index;
546     var total = window.buffers.count;
547     index = (index + count) % total;
548     if (index < 0)
549         index += total;
550     window.buffers.current = window.buffers.get_buffer(index);
552 interactive("buffer-next",
553             "Switch to the next buffer.",
554             function (I) {buffer_next(I.window, I.p);});
555 interactive("buffer-previous",
556             "Switch to the previous buffer.",
557             function (I) {buffer_next(I.window, -I.p);});
559 function switch_to_buffer (window, buffer) {
560     if (buffer && !buffer.dead)
561         window.buffers.current = buffer;
563 interactive("switch-to-buffer",
564             "Switch to a buffer specified in the minibuffer.",
565             function (I) {
566                 switch_to_buffer(
567                     I.window,
568                     (yield I.minibuffer.read_buffer(
569                         $prompt = "Switch to buffer:",
570                         $default = (I.window.buffers.count > 1 ?
571                                     I.window.buffers.buffer_list[1] :
572                                     I.buffer))));
573             });
575 define_variable("can_kill_last_buffer", true,
576     "If this is set to true, kill-buffer can kill the last "+
577     "remaining buffer, and close the window.");
579 function kill_other_buffers (buffer) {
580     if (!buffer)
581         return;
582     var bs = buffer.window.buffers;
583     var b;
585     while ((b = bs.get_buffer(0)) != buffer)
586             bs.kill_buffer(b);
587     var count = bs.count;
588     while (--count)
589             bs.kill_buffer(bs.get_buffer(1));
591 interactive("kill-other-buffers",
592             "Kill all buffers except current one.\n",
593             function (I) { kill_other_buffers(I.buffer); });
596 function kill_buffer (buffer, force) {
597     if (!buffer)
598         return;
599     var buffers = buffer.window.buffers;
600     if (buffers.count == 1 && buffer == buffers.current) {
601         if (can_kill_last_buffer || force) {
602             delete_window(buffer.window);
603             return;
604         }
605         else
606             throw interactive_error("Can't kill last buffer.");
607     }
608     buffers.kill_buffer(buffer);
610 interactive("kill-buffer",
611             "Kill a buffer specified in the minibuffer.\n" +
612             "If `can_kill_last_buffer' is set to true, an attempt to kill the last remaining " +
613             "buffer in a window will cause the window to be closed.",
614             function (I) { kill_buffer((yield I.minibuffer.read_buffer($prompt = "Kill buffer:"))); });
616 interactive("kill-current-buffer",
617             "Kill the current buffer.\n" +
618             "If `can_kill_last_buffer' is set to true, an attempt to kill the last remaining " +
619             "buffer in a window will cause the window to be closed.",
620             function (I) { kill_buffer(I.buffer); });
622 interactive("bury-buffer",
623             "Bury the current buffer.\n Put the current buffer at the end of " +
624             "the buffer list, so that it is the least likely buffer to be " +
625             "selected by `switch-to-buffer'.",
626             function (I) {I.window.buffers.bury_buffer(I.buffer);});
628 function change_directory(buffer, dir) {
629     buffer.configuration.cwd = dir;
631 interactive("change-directory",
632             "Change the current directory of the selected buffer.",
633             function (I) {
634                 change_directory(
635                     I.buffer,
636                     (yield I.minibuffer.read_existing_directory_path(
637                         $prompt = "Change to directory:",
638                         $initial_value = I.cwd)));
639             });
641 interactive("shell-command", null, function (I) {
642     var cwd = I.cwd;
643     var cmd = (yield I.minibuffer.read_shell_command($cwd = cwd));
644     yield shell_command(cmd, $cwd = cwd);
647 function unfocus (window, buffer) {
648     var elem = buffer.focused_element;
649     if (elem) {
650         elem.blur();
651         return;
652     }
653     var win = buffer.focused_frame;
654     if (win != buffer.top_frame)
655         return;
656     clear_selection(buffer);
657     buffer.top_frame.focus();
659 interactive("unfocus", null, function (I) {
660     unfocus(I.window, I.buffer);
661     I.window.minibuffer.message("unfocus");
664 function for_each_buffer (f) {
665     for_each_window(function (w) { w.buffers.for_each(f); });
668 require_later("content-buffer.js");
670 var mode_functions = {};
672 var mode_display_names = {};
674 define_buffer_local_hook("buffer_mode_change_hook");
675 define_current_buffer_hook("current_buffer_mode_change_hook", "buffer_mode_change_hook");
677 define_keywords("$class", "$enable", "$disable", "$doc");
678 function define_buffer_mode (name, display_name) {
679     keywords(arguments);
681     var hyphen_name = name.replace("_","-","g");
682     var mode_class = arguments.$class;
683     var enable = arguments.$enable;
684     var disable = arguments.$disable;
686     mode_display_names[name] = display_name;
688     var can_disable;
690     if (disable == false) {
691         can_disable = false;
692         disable = null;
693     } else
694         can_disable = true;
696     var state = (mode_class != null) ? mode_class : (name + "_enabled");
697     var enable_hook_name = name + "_enable_hook";
698     var disable_hook_name = name + "_disable_hook";
699     define_buffer_local_hook(enable_hook_name);
700     define_buffer_local_hook(disable_hook_name);
702     var change_hook_name = null;
704     if (mode_class) {
705         mode_functions[name] = {enable: enable,
706                                 disable: disable,
707                                 mode_class: mode_class,
708                                 disable_hook_name: disable_hook_name};
709         change_hook_name = mode_class + "_change_hook";
710         define_buffer_local_hook(change_hook_name);
711     }
713     function func (buffer, arg) {
714         var old_state = buffer[state];
715         var cur_state = (old_state == name);
716         var new_state = (arg == null) ? !cur_state : (arg > 0);
717         if ((new_state == cur_state) || (!can_disable && !new_state))
718             // perhaps show a message if (!can_disable && !new_state)
719             // to tell the user that this mode cannot be disabled.  do
720             // we have any existing modes that would benefit by it?
721             return null;
722         if (new_state) {
723             if (mode_class && old_state != null)  {
724                 // Another buffer-mode of our same mode-class is
725                 // enabled.  Buffer-modes within a mode-class are
726                 // mutually exclusive, so turn the old one off.
727                 buffer.enabled_modes.splice(buffer.enabled_modes.indexOf(old_state), 1);
728                 let x = mode_functions[old_state];
729                 let y = x.disable;
730                 if (y) y(buffer);
731                 conkeror[x.disable_hook_name].run(buffer);
732             }
733             buffer[state] = name;
734             if (enable)
735                 enable(buffer);
736             conkeror[enable_hook_name].run(buffer);
737             buffer.enabled_modes.push(name);
738         } else {
739             buffer.enabled_modes.splice(buffer.enabled_modes.indexOf(name), 1);
740             disable(buffer);
741             conkeror[disable_hook_name].run(buffer);
742             buffer[state] = null;
743         }
744         if (change_hook_name)
745             conkeror[change_hook_name].run(buffer, buffer[state]);
746         buffer_mode_change_hook.run(buffer);
747         return new_state;
748     }
750     conkeror[name] = func;
751     interactive(hyphen_name, arguments.$doc, function (I) {
752         var arg = I.P;
753         var new_state = func(I.buffer, arg && univ_arg_to_number(arg));
754         I.minibuffer.message(hyphen_name + (new_state ? " enabled" : " disabled"));
755     });
757 ignore_function_for_get_caller_source_code_reference("define_buffer_mode");
760 function minibuffer_mode_indicator (window) {
761     this.window = window;
762     var element = create_XUL(window, "label");
763     element.setAttribute("id", "minibuffer-mode-indicator");
764     element.collapsed = true;
765     element.setAttribute("class", "minibuffer");
766     window.document.getElementById("minibuffer").appendChild(element);
767     this.element = element;
768     this.hook_func = method_caller(this, this.update);
769     add_hook.call(window, "select_buffer_hook", this.hook_func);
770     add_hook.call(window, "current_buffer_mode_change_hook", this.hook_func);
771     this.update();
773 minibuffer_mode_indicator.prototype = {
774     update : function () {
775         var buf = this.window.buffers.current;
776         var modes = buf.enabled_modes;
777         var str = modes.map( function (x) {
778             let y = mode_display_names[x];
779             if (y)
780                 return "[" + y + "]";
781             else
782                 return null;
783         } ).filter( function (x) x != null ).join(" ");
784         this.element.collapsed = (str.length == 0);
785         this.element.value = str;
786     },
787     uninstall : function () {
788         remove_hook.call(window, "select_buffer_hook", this.hook_fun);
789         remove_hook.call(window, "current_buffer_mode_change_hook", this.hook_fun);
790         this.element.parentNode.removeChild(this.element);
791     }
793 define_global_window_mode("minibuffer_mode_indicator", "window_initialize_hook");
794 minibuffer_mode_indicator_mode(true);