isearch-backspace, isearch-done: docstrings
[conkeror.git] / modules / session.js
bloba59a32789b2be8a8af8b2a9316631749e59ccc2e
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     /**
49      * session_token is instantiated for the $opener property of buffers
50      * created by the session.  This allows the possibility of knowing,
51      * during buffer init, whether the buffer was created by a session or
52      * by other means.
53      */
54     function session_token () {}
55     session_token.prototype = {
56         constructor: session_token
57     };
59     /**
60      * session_get generates and returns a structure containing session
61      * data for the current group of open windows.
62      */
63     function session_get () {
64         let windows = {};
65         let x = 0;
66         for_each_window(function (w) {
67             let buffers = [];
68             w.buffers.for_each(function (b) {
69                 if (b instanceof content_buffer)
70                     buffers.push(b.display_uri_string);
71             });
72             if (buffers.length == 0)
73                 return;
74             windows[x] = buffers;
75             x++;
76         });
77         return windows;
78     }
80     /**
81      * session_load loads the given session (given as a data structure),
82      * with optional window as the first window to load the session into,
83      * with optional buffer_idx as the buffer index at which to begin
84      * overwriting existing buffers.
85      */
86     function session_load (session, window, buffer_idx) {
87         if (! (session[0] && session[0][0]))
88             throw new Error("Invalid 'session' argument.");
89         let s = 0;
90         var opener = new session_token();
91         if (window) {
92             let bi = buffer_idx != undefined ?
93                 buffer_idx : window.buffers.count;
95             // first kill special buffers slated for recycling.
96             let (b, i = (bi == 0 ? 1 : bi),
97                  safe2kill = bi > 0)
98             {
99                 while ((b = window.buffers.get_buffer(i))) {
100                     if (b instanceof content_buffer) {
101                         safe2kill = true;
102                         ++i;
103                     } else
104                         kill_buffer(b, true);
105                 }
106                 if (bi == 0 &&
107                     (b = window.buffers.get_buffer(0)) &&
108                     !(b instanceof content_buffer))
109                 {
110                     if (! safe2kill)
111                         create_buffer(window,
112                                       buffer_creator(content_buffer,
113                                                      $opener = opener),
114                                       OPEN_NEW_BUFFER_BACKGROUND);
115                     kill_buffer(b, true);
116                 }
117             }
119             // it is now safe to recycle the remaining buffers.
120             for (let i = 0; session[s][i] != undefined; ++i, ++bi) {
121                 let b = window.buffers.get_buffer(bi);
122                 if (b) {
123                     try {
124                         let history = b.web_navigation.sessionHistory;
125                         history.PurgeHistory(history.count);
126                     } catch (e) {}
127                     b.load(session[s][i]);
128                 } else {
129                     let c = buffer_creator(content_buffer,
130                                            $load = session[s][i],
131                                            $opener = opener,
132                                            $position = buffer_position_end);
133                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
134                 }
135             }
136             for (let b = window.buffers.get_buffer(bi); b;
137                  b = window.buffers.get_buffer(bi))
138             {
139                 kill_buffer(b, true);
140             }
141             ++s;
142         }
144         function make_init_hook (session) {
145             function init_hook (window) {
146                 for (let i = 1; session[i] != undefined; ++i) {
147                     let c = buffer_creator(content_buffer,
148                                            $load = session[i],
149                                            $opener = opener,
150                                            $position = buffer_position_end);
151                     create_buffer(window, c, OPEN_NEW_BUFFER_BACKGROUND);
152                 }
153             }
154             return init_hook;
155         }
157         for (; session[s] != undefined; ++s) {
158             let w = make_window(buffer_creator(content_buffer,
159                                                $load = session[s][0],
160                                                $opener = opener));
161             add_hook.call(w, "window_initialize_late_hook",
162                           make_init_hook(session[s]));
163         }
164     }
166     /**
167      * session_load_window_new loads the given session into new windows.
168      */
169     function session_load_window_new (session) {
170         session_load(session);
171     }
173     /**
174      * session_load_window_current loads the given session, with the
175      * session's first window being appended to window.  No existing
176      * buffers will be overwritten.
177      */
178     function session_load_window_current (session, window) {
179         let w = window ? window : get_recent_conkeror_window();
180         session_load(session, w);
181     }
183     /**
184      * session_load_window_current loads the given session, with the
185      * session's first window replacing the given window.  All buffers in
186      * the given window will be overwritten.
187      */
188     function session_load_window_current_replace (session, window) {
189         let w = window ? window : get_recent_conkeror_window();
190         session_load(session, w, 0);
191     }
193     /**
194      * session_write writes the given session to the file given by path.
195      */
196     function session_write (path, session) {
197         if (! (path instanceof Ci.nsIFile))
198             path = make_file(path);
199         if (! session)
200             session = session_get();
201         write_text_file(path, JSON.stringify(session));
202     }
204     /**
205      * session_read reads session data from the given file path,
206      * and returns a decoded session structure.
207      */
208     function session_read (path) {
209         if (! (path instanceof Ci.nsIFile))
210             path = make_file(path);
211         return JSON.parse(read_text_file(path));
212     }
214     /**
215      * session_remove deletes the given session file.
216      */
217     function session_remove (path) {
218         if (! (path instanceof Ci.nsIFile))
219             path = make_file(path);
220         path.remove(false);
221     }
223     let _session_prompt_file = function (I) {
224         yield co_return(
225             yield I.minibuffer.read_file_path(
226                 $prompt = "Session file:",
227                 $initial_value = session_dir.path,
228                 $history = "save"
229             )
230         );
231     };
233     let _session_file_not_found = function (I, file) {
234         let mb = I ? I.minibuffer : get_recent_conkeror_window().minibuffer;
235         let msg = "Session file not found: " + file.path;
236         mb.message(msg);
237         dumpln(msg);
238     }
240     interactive("session-save",
241         "Save the current session.",
242         function (I) {
243             session_write(make_file(yield _session_prompt_file(I)),
244                           session_get());
245         });
247     interactive("session-load-window-new",
248         "Load a session in a new window.", 
249         function (I) {
250             let file = make_file(yield _session_prompt_file(I));
251             if (! file.exists())
252                 _session_file_not_found(I, file);
253             else
254                 session_load_window_new(session_read(file));
255         });
257     interactive("session-load-window-current",
258         "Load a session in new buffers in the current window.",
259         function (I) {
260             let file = make_file(yield _session_prompt_file(I));
261             if (! file.exists())
262                 _session_file_not_found(I, file);
263             else
264                 session_load_window_current(session_read(file), I.window);
265         });
267     interactive("session-load-window-current-replace", 
268         "Replace all buffers in the current window with buffers "+
269         "in the saved session.",
270         function (I) {
271             let file = make_file(yield _session_prompt_file(I));
272             if (! file.exists())
273                 _session_file_not_found(I, file);
274             else
275                 session_load_window_current_replace(session_read(file),
276                                                     I.window, 0);
277         });
279     interactive("session-remove",
280         "Remove a session file.",
281         function (I) {
282             let file = make_file(yield _session_prompt_file(I));
283             if (! file.exists())
284                 _session_file_not_found(I, file);
285             else
286                 session_remove(file);
287         });
290     //// Auto-save sessions. ////
293     define_variable("session_auto_save_file", "auto-save",
294         "Default filename for the auto-save session.");
296     define_variable("session_auto_save_auto_load", false,
297         'Whether to load the auto-saved session when the browser is started. '+
298         'May be true, false, or "prompt".');
300     function session_auto_save_load_window_new () {
301         session_load_window_new(_session_auto_save_cached);
302     }
304     function session_auto_save_load_window_current (window) {
305         session_load_window_current(_session_auto_save_cached, window);
306     }
308     function session_auto_save_load_window_current_replace (window) {
309         session_load_window_current_replace(_session_auto_save_cached, window);
310     }
311     
312     define_variable("session_auto_save_auto_load_fn",
313         null,
314         "Function to be called to load the auto-saved session at start-up " +
315         "when URLs are given on the command-line. May be " +
316         "session_auto_save_load_window_new, " + 
317         "session_auto_save_load_window_current, or null. If null, the" +
318         "session will not be auto-loaded when URLs are given.");
320     // Supported values:
321     //   undefined - we have not tried to cache the auto-save.
322     //   null      - we have tried to cache the auto-save, but it didn't exist.
323     //   object    - the cached session object for the auto-save.
324     let _session_auto_save_cached = undefined;
326     let _session_auto_save_file_get = function () {
327         if (session_auto_save_file instanceof Ci.nsIFile)
328             return session_auto_save_file;
329         let f = session_dir.clone();
330         f.append(session_auto_save_file);
331         return f;
332     };
334     function session_auto_save_save () {
335         let f = _session_auto_save_file_get();
336         let s = session_get();
337         if (s[0])
338             session_write(f, s);
339         else if (f.exists())
340             f.remove(false);
341     }
343     function session_auto_save_remove () {
344         let f = _session_auto_save_file_get();
345         if (f.exists())
346             f.remove(false);
347     }
349     let _session_auto_save_auto_load = function (user_gave_urls) {
350         if (! session_auto_save_auto_load)
351             return;
352         if (! _session_auto_save_cached) {
353             _session_file_not_found(null, _session_auto_save_file_get());
354             return;
355         }
356         let do_load = false;
357         let window = get_recent_conkeror_window();
358         if (session_auto_save_auto_load == true)
359             do_load = true;
360         else if (session_auto_save_auto_load == "prompt" && !user_gave_urls) {
361             do_load = (yield window.minibuffer.read_single_character_option(
362                 $prompt = "Load auto-saved session? (y/n)",
363                 $options = ["y", "n"]
364             )) == "y";
365         } else
366             throw new Error("Invalid value for session_auto_save_auto_load: " +
367                             session_auto_save_auto_load);
368         if (! do_load)
369             return;
370         if (user_gave_urls) {
371             if (session_auto_save_auto_load_fn)
372                 session_auto_save_auto_load_fn(window);
373         } else
374             session_auto_save_load_window_current_replace(window);
375     };
377     interactive("session-auto-save-load-window-new",
378         "Load the auto-save session in a new window.",
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_new();
384         });
386     interactive("session-auto-save-load-window-current",
387         "Load the auto-save session in new buffers in the current window.",
388         function (I) {
389             if (_session_auto_save_cached == null)
390                 _session_file_not_found(I, _session_auto_save_file_get());
391             else
392                 session_auto_save_load_window_current(I.window);
393         });
395     interactive("session-auto-save-load-window-current-replace",
396         "Replace all buffers in the current window with buffers in the "+
397         "auto-saved session.",
398         function (I) {
399             if (_session_auto_save_cached == null)
400                 _session_file_not_found(I, _session_auto_save_file_get());
401             else
402                 session_auto_save_load_window_current_replace(I.window);
403         });
405     interactive("session-auto-save-remove",
406                 "Remove the auto-save session",
407                 session_auto_save_remove);
410     //// auto-save-session-mode ////
413     let _session_auto_save_mode_bootstrap = function (b) {
414         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
415         add_hook("create_buffer_hook", session_auto_save_save);
416         add_hook("kill_buffer_hook", session_auto_save_save);
417         add_hook("move_buffer_hook", session_auto_save_save);
418         add_hook("content_buffer_location_change_hook", session_auto_save_save);
419         let user_gave_urls = false;
420         for (let i = 0; i < command_line.length; ++i) {
421             if (command_line[i][0] != '-') {
422                 user_gave_urls = true;
423                 break;
424             }
425         }
426         co_call(_session_auto_save_auto_load(user_gave_urls));
427     };
429     let _session_auto_save_mode_enable = function () {
430         if (_session_auto_save_cached == undefined) {
431             let f = _session_auto_save_file_get();
432             _session_auto_save_cached = f.exists() ? session_read(f) : null;
433         }
434         if (conkeror_started) {
435             add_hook("create_buffer_hook", session_auto_save_save);
436             add_hook("kill_buffer_hook", session_auto_save_save);
437             add_hook("move_buffer_hook", session_auto_save_save);
438             add_hook("content_buffer_location_change_hook", session_auto_save_save);
439         } else
440             add_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
441     };
443     let _session_auto_save_mode_disable = function () {
444         remove_hook("create_buffer_hook", session_auto_save_save);
445         remove_hook("kill_buffer_hook", session_auto_save_save);
446         remove_hook("move_buffer_hook", session_auto_save_save);
447         remove_hook("content_buffer_location_change_hook", session_auto_save_save);
448         // Just in case.
449         remove_hook("window_initialize_late_hook", _session_auto_save_mode_bootstrap);
450     };
452     define_global_mode("session_auto_save_mode",
453                        _session_auto_save_mode_enable,
454                        _session_auto_save_mode_disable);
456     session_auto_save_mode(true);
459 provide("session");