input_handle_command: allow event to be a string
[conkeror/arlinius.git] / modules / session.js
blob63a7dba52856e36c6904886ce7ab614e73957864
1 /**
2  * (C) Copyright 2009 Nicholas A. Zigarovich
3  *
4  * Use, modification, and distribution are subject to the terms specified in the
5  * COPYING file.
6 **/
8 /* TODO
9  *
10  * Features:
11  *
12  * - A session-load-window-replace command which is like
13  *   window-current-replace, but which also recycles existing windows.
14  * - A session-load-window-current-flatten command which loads all buffers in a
15  *   multi-window section in the current window.
16  * - Session files should be more human readable.
17  * - Ability to store arbitrary session data, for example, a buffer's history.
18  * - The previous two features depend on a parser/generator for retroj's
19  *   structured plaintext format.
20  *
21  * Bugs, critical:
22  *
23  * - This module does not work correctly with daemon mode.
24  *
25  * Bugs, deferred:
26  *
27  * - Inhibit loading of the homepage in windows' initial buffers when auto-
28  *   loading a session.
29  * - Auto-save the session when the last conkeror window is closed by a
30  *   window manager event. Currently no session is saved.
31  * - Consider how and which errors should be handled. Too often we silently
32  *   fail and return without telling the user why we are doing so.
33  */
36     //// Manual sessions. ////
39     let _session_dir_default = Cc["@mozilla.org/file/directory_service;1"]
40         .getService(Ci.nsIProperties).get("ProfD", Ci.nsIFile);
41     _session_dir_default.append("sessions");
42     if (! _session_dir_default.exists())
43         _session_dir_default.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
45     define_variable("session_dir", _session_dir_default,
46         "Default directory for save/load interactive commands.");
48     let _json = Cc["@mozilla.org/dom/json;1"].createInstance(Ci.nsIJSON);
50     /**
51      * session_get generates and returns a structure containing session
52      * data for the current group of open windows.
53      */
54     function session_get () {
55         let windows = {};
56         let x = 0;
57         for_each_window(function (w) {
58             let buffers = {};
59             let y = 0;
60             w.buffers.for_each(function (b) {
61                 if (! b.browser || ! (b instanceof content_buffer))
62                     return;
63                 buffers[y] = b.browser.contentDocument.location.href;
64                 y++;
65             });
66             if (y == 0)
67                 return;
68             windows[x] = buffers;
69             x++;
70         });
71         return windows;
72     }
74     /**
75      * session_load loads the given session (given as a data structure),
76      * with optional window as the first window to load the session into,
77      * with optional buffer_idx as the buffer index at which to begin
78      * overwriting existing buffers.
79      */
80     function session_load (session, window, buffer_idx) {
81         if (! (session[0] && session[0][0]))
82             throw new Error("Invalid 'session' argument.");
83         let s = 0;
84         if (window) {
85             let bi = buffer_idx != undefined ?
86                 buffer_idx : window.buffers.count;
88             // first kill special buffers slated for recycling.
89             let (b, i = (bi == 0 ? 1 : bi),
90                  safe2kill = bi > 0)
91             {
92                 while ((b = window.buffers.get_buffer(i))) {
93                     if (b instanceof content_buffer) {
94                         safe2kill = true;
95                         ++i;
96                     } else
97                         kill_buffer(b, true);
98                 }
99                 if (bi == 0 &&
100                     (b = window.buffers.get_buffer(0)) &&
101                     !(b instanceof content_buffer))
102                 {
103                     if (! safe2kill)
104                         create_buffer(window,
105                                       buffer_creator(content_buffer),
106                                       OPEN_NEW_BUFFER_BACKGROUND);
107                     kill_buffer(b, true);
108                 }
109             }
111             // it is now safe to recycle the remaining buffers.
112             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
113                 let b = window.buffers.get_buffer(bi);
114                 if (b)
115                     b.load(session[s][i]);
116                 else {
117                     let c = buffer_creator(content_buffer, $load = session[s][i]);
118                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
119                 }
120             }
121             for (let b = window.buffers.get_buffer(bi); b;
122                  b = window.buffers.get_buffer(bi))
123             {
124                 kill_buffer(b, true);
125             }
126             ++s;
127         }
129         function make_init_hook (session) {
130             function init_hook (window) {
131                 for (let i = 1; session[i] != undefined; ++i) {
132                     let c = buffer_creator(content_buffer, $load = session[i]);
133                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
134                 }
135             }
136             return init_hook;
137         }
139         for (; session[s] != undefined; ++s) {
140             let w = make_window(buffer_creator(content_buffer,
141                                                $load = session[s][0]));
142             add_hook.call(w, "window_initialize_late_hook",
143                           make_init_hook(session[s]));
144         }
145     }
147     /**
148      * session_load_window_new loads the given session into new windows.
149      */
150     function session_load_window_new (session) {
151         session_load(session);
152     }
154     /**
155      * session_load_window_current loads the given session, with the
156      * session's first window being appended to window.  No existing
157      * buffers will be overwritten.
158      */
159     function session_load_window_current (session, window) {
160         let w = window ? window : get_recent_conkeror_window();
161         session_load(session, w);
162     }
164     /**
165      * session_load_window_current loads the given session, with the
166      * session's first window replacing the given window.  All buffers in
167      * the given window will be overwritten.
168      */
169     function session_load_window_current_replace (session, window) {
170         let w = window ? window : get_recent_conkeror_window();
171         session_load(session, w, 0);
172     }
174     /**
175      * session_write writes the given session to the file given by path.
176      */
177     function session_write (path, session) {
178         if (! (path instanceof Ci.nsIFile))
179             path = make_file(path);
180         if (! session)
181             session = session_get();
182         write_text_file(path, _json.encode(session));
183     }
185     /**
186      * session_read reads session data from the given file path,
187      * and returns a decoded session structure.
188      */
189     function session_read (path) {
190         if (! (path instanceof Ci.nsIFile))
191             path = make_file(path);
192         return _json.decode(read_text_file(path));
193     }
195     /**
196      * session_remove deletes the given session file.
197      */
198     function session_remove (path) {
199         if (! (path instanceof Ci.nsIFile))
200             path = make_file(path);
201         path.remove(false);
202     }
204     let _session_prompt_file = function (I) {
205         yield co_return(
206             yield I.minibuffer.read_file_path(
207                 $prompt = "Session file:",
208                 $initial_value = session_dir.path,
209                 $history = "save"
210             )
211         );
212     };
214     let _session_file_not_found = function (I, file) {
215         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
216         let msg = "Session file not found: " + file.path;
217         mb.message(msg);
218         dumpln(msg);
219     }
221     interactive("session-save",
222         "Save the current session.",
223         function (I) {
224             session_write(make_file(yield _session_prompt_file(I)),
225                           session_get());
226         });
228     interactive("session-load-window-new",
229         "Load a session in a new window.", 
230         function (I) {
231             let file = make_file(yield _session_prompt_file(I));
232             if (! file.exists())
233                 _session_file_not_found(I, file);
234             else
235                 session_load_window_new(session_read(file));
236         });
238     interactive("session-load-window-current",
239         "Load a session in new buffers in the current window.",
240         function (I) {
241             let file = make_file(yield _session_prompt_file(I));
242             if (! file.exists())
243                 _session_file_not_found(I, file);
244             else
245                 session_load_window_current(session_read(file), I.window);
246         });
248     interactive("session-load-window-current-replace", 
249         "Replace all buffers in the current window with buffers "+
250         "in the saved session.",
251         function (I) {
252             let file = make_file(yield _session_prompt_file(I));
253             if (! file.exists())
254                 _session_file_not_found(I, file);
255             else
256                 session_load_window_current_replace(session_read(file),
257                                                     I.window, 0);
258         });
260     interactive("session-remove",
261         "Remove a session file.",
262         function (I) {
263             let file = make_file(yield _session_prompt_file(I));
264             if (! file.exists())
265                 _session_file_not_found(I, file);
266             else
267                 session_remove(file);
268         });
271     //// Auto-save sessions. ////
274     define_variable("session_auto_save_file", "auto-save",
275         "Default filename for the auto-save session.");
277     define_variable("session_auto_save_auto_load", false,
278         'Whether to load the auto-saved session when the browser is started. '+
279         'May be true, false, or "prompt".');
281     function session_auto_save_load_window_new () {
282         session_load_window_new(_session_auto_save_cached);
283     }
285     function session_auto_save_load_window_current (window) {
286         session_load_window_current(_session_auto_save_cached, window);
287     }
289     function session_auto_save_load_window_current_replace (window) {
290         session_load_window_current_replace(_session_auto_save_cached, window);
291     }
292     
293     define_variable("session_auto_save_auto_load_fn",
294         null,
295         "Function to be called to load the auto-saved session at start-up " +
296         "when URLs are given on the command-line. May be " +
297         "session_auto_save_load_window_new, " + 
298         "session_auto_save_load_window_current, or null. If null, the" +
299         "session will not be auto-loaded when URLs are given.");
301     // Supported values:
302     //   undefined - we have not tried to cache the auto-save.
303     //   null      - we have tried to cache the auto-save, but it didn't exist.
304     //   object    - the cached session object for the auto-save.
305     let _session_auto_save_cached = undefined;
307     let _session_auto_save_file_get = function () {
308         if (session_auto_save_file instanceof Ci.nsIFile)
309             return session_auto_save_file;
310         let f = session_dir.clone();
311         f.append(session_auto_save_file);
312         return f;
313     };
315     function session_auto_save_save () {
316         let f = _session_auto_save_file_get();
317         let s = session_get();
318         if (s[0])
319             session_write(f, s);
320         else if (f.exists())
321             f.remove(false);
322     }
324     function session_auto_save_remove () {
325         let f = _session_auto_save_file_get();
326         if (f.exists())
327             f.remove(false);
328     }
330     let _session_auto_save_auto_load = function (user_gave_urls) {
331         if (! session_auto_save_auto_load)
332             return;
333         if (! _session_auto_save_cached) {
334             _session_file_not_found(null, _session_auto_save_file_get());
335             return;
336         }
337         let do_load = false;
338         let window = get_recent_conkeror_window();
339         if (session_auto_save_auto_load == true)
340             do_load = true;
341         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
342             do_load = (yield window.minibuffer.read_single_character_option(
343                 $prompt = "Load auto-saved session? (y/n)",
344                 $options = ["y", "n"]
345             )) == "y";
346         } else
347             throw new Error("Invalid value for session_auto_save_auto_load: " +
348                             session_auto_save_auto_load);
349         if (! do_load)
350             return;
351         if (user_gave_urls) {
352             if (session_auto_save_auto_load_fn)
353                 session_auto_save_auto_load_fn(window);
354         } else
355             session_auto_save_load_window_current_replace(window);
356     };
358     interactive("session-auto-save-load-window-new",
359         "Load the auto-save session in a new window.",
360         function (I) { 
361             if (_session_auto_save_cached == null)
362                 _session_file_not_found(I, _session_auto_save_file_get());
363             else
364                 session_auto_save_load_window_new();
365         });
367     interactive("session-auto-save-load-window-current",
368         "Load the auto-save session in new buffers in the current window.",
369         function (I) {
370             if (_session_auto_save_cached == null)
371                 _session_file_not_found(I, _session_auto_save_file_get());
372             else
373                 session_auto_save_load_window_current(I.window);
374         });
376     interactive("session-auto-save-load-window-current-replace",
377         "Replace all buffers in the current window with buffers in the "+
378         "auto-saved session.",
379         function (I) {
380             if (_session_auto_save_cached == null)
381                 _session_file_not_found(I, _session_auto_save_file_get());
382             else
383                 session_auto_save_load_window_current_replace(I.window);
384         });
386     interactive("session-auto-save-remove",
387                 "Remove the auto-save session",
388                 session_auto_save_remove);
391     //// auto-save-session-mode ////
394     let _session_auto_save_mode_bootstrap = function (b) {
395         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
396         add_hook("create_buffer_hook", session_auto_save_save);
397         add_hook("kill_buffer_hook", session_auto_save_save);
398         add_hook("content_buffer_location_change_hook", session_auto_save_save);
399         let user_gave_urls = false;
400         for (let i = 0; i < command_line.length; ++i) {
401             if (command_line[i][0] != '-') {
402                 user_gave_urls = true;
403                 break;
404             }
405         }
406         co_call(_session_auto_save_auto_load(user_gave_urls));
407     };
409     let _session_auto_save_mode_enable = function () {
410         if (_session_auto_save_cached == undefined) {
411             let f = _session_auto_save_file_get();
412             _session_auto_save_cached = f.exists() ? session_read(f) : null;
413         }
414         if (conkeror_started) {
415             add_hook("create_buffer_hook", session_auto_save_save);
416             add_hook("kill_buffer_hook", session_auto_save_save);
417             add_hook("content_buffer_location_change_hook", session_auto_save_save);
418         } else
419             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
420     };
422     let _session_auto_save_mode_disable = function () {
423         remove_hook("create_buffer_hook", session_auto_save_save);
424         remove_hook("kill_buffer_hook", session_auto_save_save);
425         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
426         // Just in case.
427         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
428     };
430     define_global_mode("session_auto_save_mode",
431                        _session_auto_save_mode_enable,
432                        _session_auto_save_mode_disable);
434     session_auto_save_mode(true);