load_count_widget: style
[conkeror/arlinius.git] / modules / buffer.js
blob4a2b2aa83a0f01b56eee633deacd7a6c0a1502a1
1 /**
2  * (C) Copyright 2004-2007 Shawn Betts
3  * (C) Copyright 2007-2012 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("buffer_icon_change_hook");
25 define_buffer_local_hook("select_buffer_hook");
26 define_buffer_local_hook("create_buffer_early_hook");
27 define_buffer_local_hook("create_buffer_late_hook");
28 define_buffer_local_hook("create_buffer_hook");
29 define_buffer_local_hook("kill_buffer_hook");
30 define_buffer_local_hook("move_buffer_hook");
31 define_buffer_local_hook("buffer_scroll_hook");
32 define_buffer_local_hook("buffer_dom_content_loaded_hook");
33 define_buffer_local_hook("buffer_loaded_hook");
34 define_buffer_local_hook("set_input_mode_hook");
35 define_buffer_local_hook("zoom_hook");
37 define_current_buffer_hook("current_buffer_title_change_hook", "buffer_title_change_hook");
38 define_current_buffer_hook("current_buffer_description_change_hook", "buffer_description_change_hook");
39 define_current_buffer_hook("current_buffer_icon_change_hook", "buffer_icon_change_hook");
40 define_current_buffer_hook("current_buffer_scroll_hook", "buffer_scroll_hook");
41 define_current_buffer_hook("current_buffer_dom_content_loaded_hook", "buffer_dom_content_loaded_hook");
42 define_current_buffer_hook("current_buffer_zoom_hook", "zoom_hook");
45 function buffer_position_before (container, b, i) {
46     return i;
49 function buffer_position_after (container, b, i) {
50     return i + 1;
53 function buffer_position_end (container, b, i) {
54     return container.count;
57 function buffer_position_end_by_type (container, b, i) {
58     // after last buffer of same type
59     var count = container.count;
60     var p = count - 1;
61     while (p >= 0 &&
62            container.get_buffer(p).constructor != b.constructor)
63     {
64         p--;
65     }
66     if (p == -1)
67         return count;
68     else
69         return p + 1;
72 define_variable("new_buffer_position", buffer_position_end,
73     "Used to compute the position in the buffer-list at which "+
74     "to insert new buffers which do not have an opener.  These "+
75     "include buffers created by typing an url or webjump, or "+
76     "buffers created via command-line remoting.  The value "+
77     "should be a number giving the index or a function of three "+
78     "arguments that returns the index at which to insert the "+
79     "new buffer.  The first argument is the buffer_container "+
80     "into which the new buffer is being inserted.  The second "+
81     "argument is the buffer to be inserted.  The third argument "+
82     "is the position of the currently selected buffer.  Several "+
83     "such functions are provided, including, buffer_position_before, "+
84     "buffer_position_after, buffer_position_end, and "+
85     "buffer_position_end_by_type.");
87 define_variable("new_buffer_with_opener_position", buffer_position_after,
88     "Used to compute the position in the buffer-list at which "+
89     "to insert new buffers which have an opener in the same "+
90     "window.  These include buffers created by following a link "+
91     "or frame, and contextual help buffers.  The allowed values "+
92     "are the same as those for `new_buffer_position', except that "+
93     "the second argument passed to the function is the index of "+
94     "the opener instead of the index of the current buffer (often "+
95     "one and the same).");
97 define_variable("bury_buffer_position", null,
98     "Used to compute the position in the buffer-list to move a "+
99     "buried buffer to.  A value of null prevents bury-buffer "+
100     "from moving the buffer at all.  Other allowed values are "+
101     "the same as those for `new_buffer_position', except that "+
102     "the second argument passed to the function is the index of "+
103     "the new buffer that will be selected after burying the "+
104     "current buffer.");
106 define_variable("allow_browser_window_close", true,
107     "If this is set to true, if a content buffer page calls " +
108     "window.close() from JavaScript and is not prevented by the " +
109     "normal Mozilla mechanism that restricts pages from closing " +
110     "a window that was not opened by a script, the buffer will be " +
111     "killed, deleting the window as well if it is the only buffer.");
113 define_keywords("$opener", "$position");
114 function buffer_creator (type) {
115     var args = forward_keywords(arguments);
116     return function (window) {
117         return new type(window, args);
118     };
121 function buffer_modality (buffer) {
122     buffer.keymaps.push(default_global_keymap);
125 function buffer (window) {
126     this.constructor_begin();
127     keywords(arguments, $position = this.default_position);
128     this.opener = arguments.$opener;
129     this.window = window;
130     var element = create_XUL(window, "vbox");
131     element.setAttribute("flex", "1");
132     var browser = create_XUL(window, "browser");
133     browser.setAttribute("type", "content");
134     browser.setAttribute("flex", "1");
135     browser.setAttribute("autocompletepopup", "popup_autocomplete");
136     element.appendChild(browser);
137     this.window.buffers.container.appendChild(element);
138     this.window.buffers.insert(this, arguments.$position, this.opener);
139     this.window.buffers.buffer_history.push(this);
140     this.element = element;
141     this.browser = element.firstChild;
142     this.element.conkeror_buffer_object = this;
144     this.local = { __proto__: conkeror };
145     this.page = null;
146     this.enabled_modes = [];
147     this.default_browser_object_classes = {};
149     var buffer = this;
151     this.browser.addEventListener("scroll", function (event) {
152             buffer_scroll_hook.run(buffer);
153         }, true /* capture */);
155     this.browser.addEventListener("DOMContentLoaded", function (event) {
156             buffer_dom_content_loaded_hook.run(buffer);
157         }, true /* capture */);
159     this.window.setTimeout(function () { create_buffer_late_hook.run(buffer); }, 0);
161     this.browser.addEventListener("load", function (event) {
162             buffer_loaded_hook.run(buffer);
163         }, true /* capture */);
165     this.browser.addEventListener("DOMWindowClose", function (event) {
166             /* This call to preventDefault is very important; without
167              * it, somehow Mozilla does something bad and as a result
168              * the window loses focus, causing keyboard commands to
169              * stop working. */
170             event.preventDefault();
172             if (allow_browser_window_close)
173                 kill_buffer(buffer, true);
174         }, true);
176     this.browser.addEventListener("focus", function (event) {
177         if (buffer.focusblocker &&
178             event.target instanceof Ci.nsIDOMHTMLElement &&
179             buffer.focusblocker(buffer, event))
180         {
181             event.target.blur();
182         } else
183             buffer.set_input_mode();
184     }, true);
186     this.browser.addEventListener("blur", function (event) {
187         buffer.set_input_mode();
188     }, true);
190     this.modalities = [buffer_modality];
192     // When create_buffer_early_hook runs, basic buffer properties
193     // will be available, but not the properties subclasses.
194     create_buffer_early_hook.run(this);
196     this.constructor_end();
198 buffer.prototype = {
199     constructor: buffer,
200     toString: function () "#<buffer>",
202     // default_position is the default value for the $position keyword to
203     // the buffer constructor.  This property can be set on the prototype
204     // of a subclass in order to override new_buffer_position and
205     // new_buffer_with_opener_position for specific types of buffers.
206     default_position: null,
208     /* Saved focus state */
209     saved_focused_frame: null,
210     saved_focused_element: null,
212     // get title ()   [must be defined by subclasses]
213     // get name ()    [must be defined by subclasses]
214     dead: false, /* This is set when the buffer is killed */
216     keymaps: null,
217     mark_active: false,
219     // The property focusblocker is available for an external module to
220     // put a function on which takes a buffer as its argument and returns
221     // true to block a focus event, or false to let normal processing
222     // occur.  Having this one property explicitly handled by the buffer
223     // class allows for otherwise modular focus-blockers.
224     focusblocker: null,
226     // icon is a string url of an icon to use for this buffer.  Setting it
227     // causes buffer_icon_change_hook to be run.
228     _icon: null,
229     get icon () this._icon,
230     set icon (x) {
231         if (this._icon != x) {
232             this._icon = x;
233             buffer_icon_change_hook.run(this);
234         }
235     },
237     default_message: "",
239     set_default_message: function (str) {
240         this.default_message = str;
241         if (this == this.window.buffers.current)
242             this.window.minibuffer.set_default_message(str);
243     },
245     constructors_running: 0,
247     constructor_begin: function () {
248         this.constructors_running++;
249     },
251     constructor_end: function () {
252         if (--this.constructors_running == 0) {
253             create_buffer_hook.run(this);
254             this.set_input_mode();
255             delete this.opener;
256         }
257     },
259     destroy: function () {
260         this.dead = true;
261         this.browser = null;
262         this.element = null;
263         this.saved_focused_frame = null;
264         this.saved_focused_element = null;
265         // prevent modalities from accessing dead browser
266         this.modalities = [];
267     },
269     set_input_mode: function () {
270         if (this != this.window.buffers.current)
271             return;
272         this.keymaps = [];
273         this.modalities.map(function (m) m(this), this);
274         set_input_mode_hook.run(this);
275     },
277     override_keymaps: function (keymaps) {
278         if (keymaps) {
279             this.keymaps = keymaps;
280             this.set_input_mode = function () {
281                 set_input_mode_hook.run(this);
282             };
283         } else
284             delete this.set_input_mode;
285         this.set_input_mode();
286     },
288     /* Browser accessors */
289     get top_frame () { return this.browser.contentWindow; },
290     get document () { return this.browser.contentDocument; },
291     get web_navigation () { return this.browser.webNavigation; },
292     get doc_shell () { return this.browser.docShell; },
293     get markup_document_viewer () { return this.browser.markupDocumentViewer; },
294     get current_uri () { return this.browser.currentURI; },
296     is_child_element: function (element) {
297         return (element && this.is_child_frame(element.ownerDocument.defaultView));
298     },
300     is_child_frame: function (frame) {
301         return (frame && frame.top == this.top_frame);
302     },
304     // This method is like focused_frame, except that if no content
305     // frame actually has focus, this returns null.
306     get focused_frame_or_null () {
307         var frame = this.window.document.commandDispatcher.focusedWindow;
308         if (this.is_child_frame(frame))
309             return frame;
310         return null;
311     },
313     get focused_frame () {
314         var frame = this.window.document.commandDispatcher.focusedWindow;
315         if (this.is_child_frame(frame))
316             return frame;
317         return this.top_frame;
318     },
320     get focused_element () {
321         var element = this.window.document.commandDispatcher.focusedElement;
322         if (this.is_child_element(element))
323             return element;
324         return null;
325     },
327     get focused_selection_controller () {
328         return this.focused_frame
329             .QueryInterface(Ci.nsIInterfaceRequestor)
330             .getInterface(Ci.nsIWebNavigation)
331             .QueryInterface(Ci.nsIInterfaceRequestor)
332             .getInterface(Ci.nsISelectionDisplay)
333             .QueryInterface(Ci.nsISelectionController);
334     },
336     do_command: function (command) {
337         function attempt_command (element, command) {
338             var controller;
339             if (element.controllers
340                 && (controller = element.controllers.getControllerForCommand(command)) != null
341                 && controller.isCommandEnabled(command))
342             {
343                 controller.doCommand(command);
344                 return true;
345             }
346             return false;
347         }
349         var element = this.focused_element;
350         if (element && attempt_command(element, command))
351             return;
352         var win = this.focused_frame;
353         while (true) {
354             if (attempt_command(win, command))
355                 return;
356             if (!win.parent || win == win.parent)
357                 break;
358             win = win.parent;
359         }
360     }
363 function with_current_buffer (buffer, callback) {
364     return callback(new interactive_context(buffer));
367 function check_buffer (obj, type) {
368     if (!(obj instanceof type))
369         throw interactive_error("Buffer has invalid type.");
370     if (obj.dead)
371         throw interactive_error("Buffer has already been killed.");
372     return obj;
375 function caret_enabled (buffer) {
376     return buffer.browser.getAttribute('showcaret');
379 function clear_selection (buffer) {
380     let sel_ctrl = buffer.focused_selection_controller;
381     if (sel_ctrl) {
382         let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
383         if (caret_enabled(buffer)) {
384             if (sel.anchorNode)
385                 sel.collapseToStart();
386         } else {
387             sel.removeAllRanges();
388         }
389     }
393 function buffer_container (window, create_initial_buffer) {
394     this.window = window;
395     this.container = window.document.getElementById("buffer-container");
396     this.buffer_list = [];
397     this.buffer_history = [];
398     window.buffers = this;
399     create_initial_buffer(window);
401 buffer_container.prototype = {
402     constructor: buffer_container,
403     toString: function () "#<buffer_container>",
405     insert: function (buffer, position, opener) {
406         var i = this.index_of(opener);
407         if (position == null) {
408             if (i == null)
409                 position = new_buffer_position;
410             else
411                 position = new_buffer_with_opener_position;
412         }
413         if (i == null)
414             i = this.selected_index || 0;
415         try {
416             if (position instanceof Function)
417                 var p = position(this, buffer, i);
418             else
419                 p = position;
420             this.buffer_list.splice(p, 0, buffer);
421         } catch (e) {
422             this.buffer_list.splice(0, 0, buffer);
423             dumpln("Error inserting buffer, inserted at 0.");
424             dump_error(e);
425         }
426     },
428     get current () {
429         return this.container.selectedPanel.conkeror_buffer_object;
430     },
432     set current (buffer) {
433         var old_value = this.current;
434         if (old_value == buffer)
435             return;
437         this.buffer_history.splice(this.buffer_history.indexOf(buffer), 1);
438         this.buffer_history.unshift(buffer);
440         this._switch_away_from(this.current);
441         this._switch_to(buffer);
443         // Run hooks
444         select_buffer_hook.run(buffer);
445     },
447     _switch_away_from: function (old_value) {
448         // Save focus state
449         old_value.saved_focused_frame = old_value.focused_frame;
450         old_value.saved_focused_element = old_value.focused_element;
452         old_value.browser.setAttribute("type", "content");
453     },
455     _switch_to: function (buffer) {
456         // Select new buffer in the XUL deck
457         this.container.selectedPanel = buffer.element;
459         buffer.browser.setAttribute("type", "content-primary");
461         /**
462          * This next focus call seems to be needed to avoid focus
463          * somehow getting lost (and the keypress handler therefore
464          * not getting called at all) when killing buffers.
465          */
466         this.window.focus();
468         // Restore focus state
469         buffer.browser.focus();
470         if (buffer.saved_focused_element)
471             set_focus_no_scroll(this.window, buffer.saved_focused_element);
472         else if (buffer.saved_focused_frame)
473             set_focus_no_scroll(this.window, buffer.saved_focused_frame);
475         buffer.saved_focused_element = null;
476         buffer.saved_focused_frame = null;
478         buffer.set_input_mode();
480         this.window.minibuffer.set_default_message(buffer.default_message);
481     },
483     get count () {
484         return this.buffer_list.length;
485     },
487     get_buffer: function (index) {
488         if (index >= 0 && index < this.count)
489             return this.buffer_list[index]
490         return null;
491     },
493     get selected_index () {
494         var nodes = this.buffer_list;
495         var count = nodes.length;
496         for (var i = 0; i < count; ++i)
497             if (nodes[i] == this.container.selectedPanel.conkeror_buffer_object)
498                 return i;
499         return null;
500     },
502     index_of: function (b) {
503         var nodes = this.buffer_list;
504         var count = nodes.length;
505         for (var i = 0; i < count; ++i)
506             if (nodes[i] == b)
507                 return i;
508         return null;
509     },
511     get unique_name_list () {
512         var existing_names = {};
513         var bufs = [];
514         this.for_each(function(b) {
515                 var base_name = b.name;
516                 var name = base_name;
517                 var index = 1;
518                 while (existing_names[name]) {
519                     ++index;
520                     name = base_name + "<" + index + ">";
521                 }
522                 existing_names[name] = true;
523                 bufs.push([name, b]);
524             });
525         return bufs;
526     },
528     kill_buffer: function (b) {
529         if (b.dead)
530             return true;
531         var count = this.count;
532         if (count <= 1)
533             return false;
534         var new_buffer = this.buffer_history[0];
535         var changed = false;
536         if (b == new_buffer) {
537             new_buffer = this.buffer_history[1];
538             changed = true;
539         }
540         this._switch_away_from(this.current);
541         // The removeChild call below may trigger events in progress
542         // listeners.  This call to `destroy' gives buffer subclasses a
543         // chance to remove such listeners, so that they cannot try to
544         // perform UI actions based upon a xul:browser that no longer
545         // exists.
546         var element = b.element;
547         b.destroy();
548         this.container.removeChild(element);
549         this.buffer_list.splice(this.buffer_list.indexOf(b), 1);
550         this.buffer_history.splice(this.buffer_history.indexOf(b), 1);
551         this._switch_to(new_buffer);
552         if (changed) {
553             select_buffer_hook.run(new_buffer);
554             this.buffer_history.splice(this.buffer_history.indexOf(new_buffer), 1);
555             this.buffer_history.unshift(new_buffer);
556         }
557         kill_buffer_hook.run(b);
558         return true;
559     },
561     bury_buffer: function (b) {
562         var new_buffer = this.buffer_history[0];
563         if (b == new_buffer)
564             new_buffer = this.buffer_history[1];
565         if (! new_buffer)
566             throw interactive_error("No other buffer");
567         if (bury_buffer_position != null) {
568             this.buffer_list.splice(this.buffer_list.indexOf(b), 1);
569             this.insert(b, bury_buffer_position, new_buffer);
570         }
571         this.buffer_history.splice(this.buffer_history.indexOf(b), 1);
572         this.buffer_history.push(b);
573         this.current = new_buffer;
574         if (bury_buffer_position != null)
575             move_buffer_hook.run(b);
576         return true;
577     },
579     for_each: function (f) {
580         var count = this.count;
581         for (var i = 0; i < count; ++i)
582             f(this.get_buffer(i));
583     }
586 function buffer_initialize_window_early (window) {
587     /**
588      * Use content_buffer by default to handle an unusual case where
589      * browser.chromeURI is used perhaps.  In general this default
590      * should not be needed.
591      */
592     var create_initial_buffer =
593         window.args.initial_buffer_creator || buffer_creator(content_buffer);
594     new buffer_container(window, create_initial_buffer);
597 add_hook("window_initialize_early_hook", buffer_initialize_window_early);
601  * initialize_first_buffer_type is a workaround for a XULRunner bug that
602  * first appeared in version 2.0, manifested as missing scrollbars in the
603  * first buffer of any window.  It only affects content-primary browsers,
604  * and the workaround is to initialize the browser as type "content" then
605  * change it to content-primary after a delay.
606  */
607 function initialize_first_buffer_type (window) {
608     window.buffers.current.browser.setAttribute("type", "content-primary");
611 add_hook("window_initialize_late_hook", initialize_first_buffer_type);
614 define_buffer_local_hook("buffer_kill_before_hook", RUN_HOOK_UNTIL_FAILURE);
615 function buffer_before_window_close (window) {
616     var bs = window.buffers;
617     var count = bs.count;
618     for (let i = 0; i < count; ++i) {
619         if (!buffer_kill_before_hook.run(bs.get_buffer(i)))
620             return false;
621     }
622     return true;
624 add_hook("window_before_close_hook", buffer_before_window_close);
626 function buffer_window_close_handler (window) {
627     var bs = window.buffers;
628     var count = bs.count;
629     for (let i = 0; i < count; ++i) {
630         let b = bs.get_buffer(i);
631         b.destroy();
632     }
634 add_hook("window_close_hook", buffer_window_close_handler);
636 /* open/follow targets */
637 const OPEN_CURRENT_BUFFER = 0; // only valid for open if the current
638                                // buffer is a content_buffer.
639 const OPEN_NEW_BUFFER = 1;
640 const OPEN_NEW_BUFFER_BACKGROUND = 2;
641 const OPEN_NEW_WINDOW = 3;
643 const FOLLOW_DEFAULT = 4; // for open, implies OPEN_CURRENT_BUFFER
644 const FOLLOW_CURRENT_FRAME = 5; // for open, implies OPEN_CURRENT_BUFFER
646 var TARGET_PROMPTS = [" in current buffer",
647                       " in new buffer",
648                       " in new buffer (background)",
649                       " in new window",
650                       "",
651                       " in current frame"];
653 var TARGET_NAMES = ["current buffer",
654                     "new buffer",
655                     "new buffer (background)",
656                     "new window",
657                     "default",
658                     "current frame"];
661 function create_buffer (window, creator, target) {
662     switch (target) {
663     case OPEN_NEW_BUFFER:
664         window.buffers.current = creator(window, null);
665         break;
666     case OPEN_NEW_BUFFER_BACKGROUND:
667         creator(window, null);
668         break;
669     case OPEN_NEW_WINDOW:
670         make_window(creator);
671         break;
672     default:
673         throw new Error("invalid target");
674     }
677 let (queued_buffer_creators = null) {
678     function create_buffer_in_current_window (creator, target, focus_existing) {
679         function process_queued_buffer_creators (window) {
680             for (var i = 0; i < queued_buffer_creators.length; ++i) {
681                 var x = queued_buffer_creators[i];
682                 create_buffer(window, x[0], x[1]);
683             }
684             queued_buffer_creators = null;
685         }
687         if (target == OPEN_NEW_WINDOW)
688             throw new Error("invalid target");
689         var window = get_recent_conkeror_window();
690         if (window) {
691             if (focus_existing)
692                 window.focus();
693             create_buffer(window, creator, target);
694         } else if (queued_buffer_creators != null) {
695             queued_buffer_creators.push([creator,target]);
696         } else {
697             queued_buffer_creators = [];
698             window = make_window(creator);
699             add_hook.call(window, "window_initialize_late_hook", process_queued_buffer_creators);
700         }
701     }
706  * Read Buffer
707  */
708 define_variable("read_buffer_show_icons", false,
709     "Boolean which says whether read_buffer should show buffer "+
710     "icons in the completions list.\nNote, setting this variable "+
711     "alone does not cause favicons or other kinds of icons to be "+
712     "fetched.  For that, load the `favicon' (or similar other) "+
713     "library.");
715 minibuffer_auto_complete_preferences["buffer"] = true;
716 define_keywords("$default");
717 minibuffer.prototype.read_buffer = function () {
718     var window = this.window;
719     var buffer = this.window.buffers.current;
720     keywords(arguments, $prompt = "Buffer:",
721              $default = buffer,
722              $history = "buffer");
723     var completer = all_word_completer(
724         $completions = function (visitor) window.buffers.for_each(visitor),
725         $get_string = function (x) x.description,
726         $get_description = function (x) x.title,
727         $get_icon = (read_buffer_show_icons ?
728                      function (x) x.icon : null));
729     var result = yield this.read(
730         $keymap = read_buffer_keymap,
731         $prompt = arguments.$prompt,
732         $history = arguments.$history,
733         $completer = completer,
734         $enable_icons = read_buffer_show_icons,
735         $match_required = true,
736         $auto_complete = "buffer",
737         $auto_complete_initial = true,
738         $auto_complete_delay = 0,
739         $default_completion = arguments.$default);
740     yield co_return(result);
744 function buffer_move_forward (window, count) {
745     var buffers = window.buffers;
746     var index = buffers.selected_index;
747     var buffer = buffers.current
748     var total = buffers.count;
749     if (total == 1)
750         return;
751     var new_index = (index + count) % total;
752     if (new_index == index)
753         return;
754     if (new_index < 0)
755         new_index += total;
756     buffers.buffer_list.splice(index, 1);
757     buffers.buffer_list.splice(new_index, 0, buffer);
758     move_buffer_hook.run(buffer);
760 interactive("buffer-move-forward",
761     "Move the current buffer forward in the buffer order.",
762     function (I) { buffer_move_forward(I.window, I.p); });
764 interactive("buffer-move-backward",
765     "Move the current buffer backward in the buffer order.",
766     function (I) { buffer_move_forward(I.window, -I.p); });
769 function buffer_next (window, count) {
770     var index = window.buffers.selected_index;
771     var total = window.buffers.count;
772     if (total == 1)
773         throw new interactive_error("No other buffer");
774     index = (index + count) % total;
775     if (index < 0)
776         index += total;
777     window.buffers.current = window.buffers.get_buffer(index);
779 interactive("buffer-next",
780     "Switch to the next buffer.",
781     function (I) { buffer_next(I.window, I.p); });
782 interactive("buffer-previous",
783     "Switch to the previous buffer.",
784     function (I) { buffer_next(I.window, -I.p); });
786 function switch_to_buffer (window, buffer) {
787     if (buffer && !buffer.dead)
788         window.buffers.current = buffer;
790 interactive("switch-to-buffer",
791     "Switch to a buffer specified in the minibuffer.",
792     function (I) {
793         switch_to_buffer(
794             I.window,
795             (yield I.minibuffer.read_buffer(
796                 $prompt = "Switch to buffer:",
797                 $default = (I.window.buffers.count > 1 ?
798                             I.window.buffers.buffer_history[1] :
799                             I.buffer))));
800     });
802 define_variable("can_kill_last_buffer", true,
803     "If this is set to true, kill-buffer can kill the last "+
804     "remaining buffer, and close the window.");
806 function kill_other_buffers (buffer) {
807     if (!buffer)
808         return;
809     var bs = buffer.window.buffers;
810     var b;
811     while ((b = bs.get_buffer(0)) != buffer)
812         bs.kill_buffer(b);
813     var count = bs.count;
814     while (--count)
815         bs.kill_buffer(bs.get_buffer(1));
817 interactive("kill-other-buffers",
818     "Kill all buffers except current one.\n",
819     function (I) { kill_other_buffers(I.buffer); });
822 function kill_buffer (buffer, force) {
823     if (!buffer)
824         return;
825     var buffers = buffer.window.buffers;
826     if (buffers.count == 1 && buffer == buffers.current) {
827         if (can_kill_last_buffer || force) {
828             delete_window(buffer.window);
829             return;
830         } else
831             throw interactive_error("Can't kill last buffer.");
832     }
833     buffers.kill_buffer(buffer);
835 interactive("kill-buffer",
836     "Kill a buffer specified in the minibuffer.\n" +
837     "If `can_kill_last_buffer' is set to true, an attempt to kill the "+
838     "last remaining buffer in a window will cause the window to be closed.",
839     function (I) {
840         kill_buffer((yield I.minibuffer.read_buffer($prompt = "Kill buffer:")));
841     });
843 interactive("kill-current-buffer",
844     "Kill the current buffer.\n" +
845     "If `can_kill_last_buffer' is set to true, an attempt to kill the "+
846     "last remaining buffer in a window will cause the window to be closed.",
847     function (I) { kill_buffer(I.buffer); });
849 interactive("read-buffer-kill-buffer",
850     "Kill the current selected buffer in the completions list "+
851     "in a read buffer minibuffer interaction.",
852     function (I) {
853         var s = I.window.minibuffer.current_state;
854         var i = s.selected_completion_index;
855         var c = s.completions;
856         if (i == -1)
857             return;
858         kill_buffer(c.get_value(i));
859         s.completer.refresh();
860         s.handle_input(I.window.minibuffer);
861     });
863 interactive("bury-buffer",
864     "Bury the current buffer.\n Put the current buffer at the end of " +
865     "the buffer list, so that it is the least likely buffer to be " +
866     "selected by `switch-to-buffer'.",
867     function (I) { I.window.buffers.bury_buffer(I.buffer); });
869 function change_directory (buffer, dir) {
870     if (buffer.page != null)
871         delete buffer.page.local.cwd;
872     buffer.local.cwd = make_file(dir);
874 interactive("change-directory",
875     "Change the current directory of the selected buffer.",
876     function (I) {
877         change_directory(
878             I.buffer,
879             (yield I.minibuffer.read_existing_directory_path(
880                 $prompt = "Change to directory:",
881                 $initial_value = make_file(I.local.cwd).path)));
882     });
884 interactive("shell-command", null,
885     function (I) {
886         var cwd = I.local.cwd;
887         var cmd = (yield I.minibuffer.read_shell_command($cwd = cwd));
888         yield shell_command(cmd, $cwd = cwd);
889     });
893  * selection_is_embed_p is used to test whether the unfocus command can
894  * unfocus an element, even though there is a selection.  This happens
895  * when the focused element is an html:embed.
896  */
897 function selection_is_embed_p (sel, focused_element) {
898     if (sel.rangeCount == 1) {
899         try {
900             var r = sel.getRangeAt(0);
901             var a = r.startContainer.childNodes[r.startOffset];
902             if ((a instanceof Ci.nsIDOMHTMLEmbedElement ||
903                  a instanceof Ci.nsIDOMHTMLObjectElement) &&
904                 a == focused_element)
905             {
906                 return true;
907             }
908         } catch (e) {}
909     }
910     return false;
914  * unfocus is a high-level command for unfocusing hyperlinks, inputs,
915  * frames, iframes, plugins, and also clearing the selection.
916  */
917 define_buffer_local_hook("unfocus_hook");
918 function unfocus (window, buffer) {
919     // 1. if there is a selection, clear it.
920     var selc = buffer.focused_selection_controller;
921     if (selc) {
922         var sel = selc.getSelection(selc.SELECTION_NORMAL);
923         var active = ! sel.isCollapsed;
924         var embed_p = selection_is_embed_p(sel, buffer.focused_element);
925         clear_selection(buffer);
926         if (active && !embed_p) {
927             window.minibuffer.message("cleared selection");
928             return;
929         }
930     }
931     // 2. if there is a focused element, unfocus it.
932     if (buffer.focused_element) {
933         buffer.focused_element.blur();
934         // if an element in a detached fragment has focus, blur() will
935         // not work, and we need to take more drastic measures.  the
936         // action taken was found through experiment, so it is possibly
937         // not the most concise way to unfocus such an element.
938         if (buffer.focused_element) {
939             buffer.element.focus();
940             buffer.top_frame.focus();
941         }
942         window.minibuffer.message("unfocused element");
943         return;
944     }
945     // 3. if an iframe has focus, we must blur it.
946     if (buffer.focused_frame_or_null &&
947         buffer.focused_frame_or_null.frameElement)
948     {
949         buffer.focused_frame_or_null.frameElement.blur();
950     }
951     // 4. return focus to top-frame from subframes and plugins.
952     buffer.top_frame.focus();
953     buffer.top_frame.focus(); // needed to get focus back from plugins
954     window.minibuffer.message("refocused top frame");
955     // give page-modes an opportunity to set focus specially
956     unfocus_hook.run(buffer);
958 interactive("unfocus",
959     "Unfocus is a high-level command for unfocusing hyperlinks, inputs, "+
960     "frames, iframes, plugins, and also for clearing the selection.\n"+
961     "The action that it takes is based on precedence.  If there is a "+
962     "focused hyperlink or input, it will unfocus that.  Otherwise, if "+
963     "there is a selection, it will clear the selection.  Otherwise, it "+
964     "will return focus to the top frame from a focused frame, iframe, "+
965     "or plugin.  In the case of plugins, since they steal keyboard "+
966     "control away from Conkeror, the normal way to unfocus them is "+
967     "to use command-line remoting externally: conkeror -batch -f "+
968     "unfocus.  Page-modes also have an opportunity to alter the default"+
969     "focus via the hook, `focus_hook'.",
970     function (I) {
971         unfocus(I.window, I.buffer);
972     });
975 function for_each_buffer (f) {
976     for_each_window(function (w) { w.buffers.for_each(f); });
981  * Buffer Modes
982  */
984 define_buffer_local_hook("buffer_mode_change_hook");
985 define_current_buffer_hook("current_buffer_mode_change_hook", "buffer_mode_change_hook");
987 define_keywords("$display_name", "$doc");
988 function buffer_mode (name, enable, disable) {
989     keywords(arguments);
990     this.name = name.replace("-","_","g");
991     this.hyphen_name = name.replace("_","-","g");
992     if (enable)
993         this._enable = enable;
994     if (disable)
995         this._disable = disable;
996     this.display_name = arguments.$display_name;
997     this.doc = arguments.$doc;
998     this.enable_hook = this.name + "_enable_hook";
999     this.disable_hook = this.name + "_disable_hook";
1001 buffer_mode.prototype = {
1002     constructor: buffer_mode,
1003     name: null,
1004     display_name: null,
1005     doc: null,
1006     enable_hook: null,
1007     disable_hook: null,
1008     _enable: null,
1009     _disable: null,
1010     enable: function (buffer) {
1011         try {
1012             if (this._enable)
1013                 this._enable(buffer);
1014         } finally {
1015             buffer.enabled_modes.push(this.name);
1016             if (conkeror[this.enable_hook])
1017                 conkeror[this.enable_hook].run(buffer);
1018             buffer_mode_change_hook.run(buffer);
1019         }
1020     },
1021     disable: function (buffer) {
1022         try {
1023             if (this._disable)
1024                 this._disable(buffer);
1025         } finally {
1026             var i = buffer.enabled_modes.indexOf(this.name);
1027             if (i > -1)
1028                 buffer.enabled_modes.splice(i, 1);
1029             if (conkeror[this.disable_hook])
1030                 conkeror[this.disable_hook].run(buffer);
1031             buffer_mode_change_hook.run(buffer);
1032         }
1033     }
1035 define_keywords("$constructor");
1036 function define_buffer_mode (name, enable, disable) {
1037     keywords(arguments, $constructor = buffer_mode, $doc = null);
1038     var constructor = arguments.$constructor;
1039     var m = new constructor(name, enable, disable, forward_keywords(arguments));
1040     name = m.name; // normalized
1041     conkeror[name] = m;
1042     define_buffer_local_hook(m.enable_hook);
1043     define_buffer_local_hook(m.disable_hook);
1044     interactive(m.hyphen_name,
1045         arguments.$doc,
1046         function (I) {
1047             var enabledp = (I.buffer.enabled_modes.indexOf(name) > -1);
1048             if (enabledp)
1049                 m.disable(I.buffer);
1050             else
1051                 m.enable(I.buffer);
1052             I.minibuffer.message(m.hyphen_name + (enabledp ? " disabled" : " enabled"));
1053         });
1055 ignore_function_for_get_caller_source_code_reference("define_buffer_mode");
1059  * Mode Display in Minibuffer
1060  */
1062 function minibuffer_mode_indicator (window) {
1063     this.window = window;
1064     var element = create_XUL(window, "label");
1065     element.setAttribute("id", "minibuffer-mode-indicator");
1066     element.setAttribute("class", "mode-text-widget");
1067     window.document.getElementById("minibuffer").appendChild(element);
1068     this.element = element;
1069     this.hook_function = method_caller(this, this.update);
1070     add_hook.call(window, "select_buffer_hook", this.hook_function);
1071     add_hook.call(window, "current_buffer_mode_change_hook", this.hook_function);
1072     this.update();
1074 minibuffer_mode_indicator.prototype = {
1075     constructor: minibuffer_mode_indicator,
1076     window: null,
1077     element: null,
1078     hook_function: null,
1079     update: function () {
1080         var buffer = this.window.buffers.current;
1081         var str = buffer.enabled_modes.map(
1082             function (x) {
1083                 return (conkeror[x].display_name || null);
1084             }).filter(function (x) x != null).join(" ");
1085         this.element.value = str;
1086     },
1087     uninstall: function () {
1088         remove_hook.call(this.window, "select_buffer_hook", this.hook_function);
1089         remove_hook.call(this.window, "current_buffer_mode_change_hook", this.hook_function);
1090         this.element.parentNode.removeChild(this.element);
1091     }
1093 define_global_window_mode("minibuffer_mode_indicator", "window_initialize_hook");
1094 minibuffer_mode_indicator_mode(true);
1098  * minibuffer-keymaps-display
1099  */
1100 function minibuffer_keymaps_display_update (buffer) {
1101     var element = buffer.window.document
1102         .getElementById("keymaps-display");
1103     if (element) {
1104         var str = buffer.keymaps.reduce(
1105             function (acc, kmap) {
1106                 if (kmap.display_name)
1107                     acc.push(kmap.display_name);
1108                 return acc;
1109             }, []).join("/");
1110         if (element.value != str)
1111             element.value = str;
1112     }
1115 function minibuffer_keymaps_display_initialize (window) {
1116     var element = create_XUL(window, "label");
1117     element.setAttribute("id", "keymaps-display");
1118     element.setAttribute("class", "mode-text-widget");
1119     element.setAttribute("value", "");
1120     var mb = window.document.getElementById("minibuffer");
1121     mb.appendChild(element);
1124 define_global_mode("minibuffer_keymaps_display_mode",
1125     function enable () {
1126         add_hook("window_initialize_hook", minibuffer_keymaps_display_initialize);
1127         add_hook("set_input_mode_hook", minibuffer_keymaps_display_update);
1128         for_each_window(minibuffer_keymaps_display_initialize);
1129     },
1130     function disable () {
1131         remove_hook("window_initialize_hook", minibuffer_keymaps_display_initialize);
1132         remove_hook("set_input_mode_hook", minibuffer_keymaps_display_update);
1133         for_each_window(function (w) {
1134             var element = w.document
1135                 .getElementById("keymaps-display");
1136             if (element)
1137                 element.parentNode.removeChild(element);
1138         });
1139     });
1141 minibuffer_keymaps_display_mode(true);
1145  * minibuffer-keymaps-highlight
1146  */
1147 function minibuffer_keymaps_highlight_update (buffer) {
1148     var mb = buffer.window.document.getElementById("minibuffer");
1149     if (buffer.keymaps.some(function (k) k.notify))
1150         dom_add_class(mb, "highlight");
1151     else
1152         dom_remove_class(mb, "highlight");
1155 define_global_mode("minibuffer_keymaps_highlight_mode",
1156     function enable () {
1157         add_hook("set_input_mode_hook", minibuffer_keymaps_highlight_update);
1158     },
1159     function disable () {
1160         remove_hook("set_input_mode_hook", minibuffer_keymaps_highlight_update);
1161         for_each_window(function (w) {
1162             var mb = w.document.getElementById("minibuffer");
1163             if (mb)
1164                 dom_remove_class("highlight");
1165         });
1166     });
1168 minibuffer_keymaps_highlight_mode(true);
1171 provide("buffer");