keywords.js: make write_keywords public and more flexible
[conkeror.git] / modules / commands.js
blobeac86a6856b97754b6005bf43048609032e726c1
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 require("content-buffer.js");
12 define_hook("quit_hook");
14 function quit ()
16     quit_hook.run();
17     var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
18         .getService(Ci.nsIAppStartup);
19     appStartup.quit(appStartup.eAttemptQuit);
21 interactive("quit",
22             "Quit Conkeror",
23             quit);
26 function show_conkeror_version (window)
28     window.minibuffer.message (conkeror.version);
30 interactive ("conkeror-version",
31              "Show version information for Conkeror.",
32              function (I) {show_conkeror_version(I.window);});
34 /* FIXME: maybe this should be supported for non-browser buffers */
35 function scroll_horiz_complete (buffer, n)
37     var w = buffer.focused_frame;
38     w.scrollTo (n > 0 ? w.scrollMaxX : 0, w.scrollY);
40 interactive("scroll-beginning-of-line",
41             "Scroll the current frame all the way to the left.",
42             function (I) {scroll_horiz_complete(I.buffer, -1);});
44 interactive("scroll-end-of-line",
45             "Scroll the current frame all the way to the right.",
46             function (I) {scroll_horiz_complete(I.buffer, 1);});
48 interactive("make-window",
49             "Make a new window.",
50             function (I) {
51                 make_window(buffer_creator(content_buffer,
52                                            $load = homepage,
53                                            $configuration = I.buffer.configuration));
54             });
56 function delete_window (window)
58     window.window.close();
60 interactive("delete-window",
61             "Delete the current window.",
62             function (I) {delete_window(I.window);});
64 interactive("jsconsole",
65             "Open the JavaScript console.",
66             function (I) {
67                 open_in_browser(I.buffer,
68                                 I.browse_target("jsconsole"),
69                                 "chrome://global/content/console.xul");
70             });
71 default_browse_targets["jsconsole"] = "find-url-new-buffer";
73 /**
74  * Given a callback func and an interactive context I, call func, passing either
75  * a focused field, or the minibuffer's input element if the minibuffer is
76  * active. Afterward, call `ensure_index_is_visible' on the field. See
77  * `paste_x_primary_selection' and `open_line' for examples.
78  */
79 function call_on_focused_field(I, func) {
80   var m = I.window.minibuffer;
81   var s = m.current_state;
82   if (m._input_mode_enabled) {
83     m._restore_normal_state();
84     var e = m.input_element;
85   } else var e = I.buffer.focused_element;
86   func(e);
87   ensure_index_is_visible (I.window, e, e.selectionStart);
88   if (s && s.handle_input) s.handle_input(m);
91 /**
92  * Replace the current region with modifier(selection). Deactivates region and
93  * sets point to the end of the inserted text, unless keep_point is true, in
94  * which case the point will be left at the beginning of the inserted text.
95  */
96 function modify_region(field, modifier, keep_point) {
97   var replacement =
98     modifier(field.value.substring(field.selectionStart, field.selectionEnd+1));
99   var point = field.selectionStart;
100   field.value =
101     field.value.substr(0, field.selectionStart) + replacement +
102     field.value.substr(field.selectionEnd);
103   if (!keep_point) point += replacement.length;
104   field.setSelectionRange(point, point);
107 function paste_x_primary_selection (field) {
108   modify_region(field, function(str) read_from_x_primary_selection());
110 interactive (
111   "paste-x-primary-selection",
112   "Insert the contents of the X primary selection into the selected field or " +
113   "minibuffer. Deactivates the region if it is active, and leaves the point " +
114   "after the inserted text.",
115   function (I) call_on_focused_field(I, paste_x_primary_selection)
118 function open_line(field) {
119   modify_region(field, function() "\n", true);
121 interactive(
122   "open-line",
123   "If there is an active region, replace is with a newline, otherwise just " +
124   "insert a newline. In both cases leave point before the inserted newline.",
125   function (I) call_on_focused_field(I, open_line)
128 function meta_x (window, prefix, command, browser_object_class)
130     call_interactively({window: window,
131                         prefix_argument: prefix,
132                         _browser_object_class: browser_object_class}, command);
134 interactive("execute-extended-command",
135             "Execute a Conkeror command specified in the minibuffer.",
136             function (I) {
137                 var prefix = I.P;
138                 var boc = I._browser_object_class;
139                 var prompt = "";
140                 if (boc)
141                     prompt += ' ['+boc+']';
142                 if (prefix !== null && prefix !== undefined) {
143                     if (typeof prefix == "object")
144                         prompt += prefix[0] == 4 ? " C-u" : " "+prefix[0];
145                     else
146                         prompt += " "+prefix;
147                 }
148                 meta_x(I.window, I.P,
149                        (yield I.minibuffer.read_command(
150                            $prompt = "M-x" + prompt)),
151                        boc);
152             });
154 /// built in commands
155 // see: http://www.xulplanet.com/tutorials/xultu/commandupdate.html
157 // Performs a command on a browser buffer content area
160 define_builtin_commands(
161     "",
162     function (I, command) {
163         var buffer = I.buffer;
164         try {
165             buffer.do_command(command);
166         } catch (e) {
167             /* Ignore exceptions */
168         }
169     },
170     function (I) {
171         I.buffer.mark_active = !I.buffer.mark_active;
172     },
173     function (I) I.buffer.mark_active,
174     false
177 define_builtin_commands(
178     "caret-",
179     function (I, command) {
180         var buffer = I.buffer;
181         try {
182             buffer.do_command(command);
183         } catch (e) {
184             /* Ignore exceptions */
185         }
186     },
187     function (I) {
188         I.buffer.mark_active = !I.buffer.mark_active;
189     },
190     function (I) I.buffer.mark_active,
191     'caret');
193 function get_link_text()
195     var e = document.commandDispatcher.focusedElement;
196     if (e && e.getAttribute("href")) {
197         return e.getAttribute("href");
198     }
199     return null;
204 function copy_email_address (loc)
206     // Copy the comma-separated list of email addresses only.
207     // There are other ways of embedding email addresses in a mailto:
208     // link, but such complex parsing is beyond us.
209     var qmark = loc.indexOf( "?" );
210     var addresses;
212     if ( qmark > 7 ) {                   // 7 == length of "mailto:"
213         addresses = loc.substring( 7, qmark );
214     } else {
215         addresses = loc.substr( 7 );
216     }
218     //XXX: the original code, which we got from firefox, unescapes the string
219     //     using the current character set.  To do this in conkeror, we
220     //     *should* use an interactive method that gives us the character set,
221     //     rather than fetching it by side-effect.
223     //     // Let's try to unescape it using a character set
224     //     // in case the address is not ASCII.
225     //     try {
226     //         var characterSet = this.target.ownerDocument.characterSet;
227     //         const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
228     //             .getService(Components.interfaces.nsITextToSubURI);
229     //         addresses = textToSubURI.unEscapeURIForUI(characterSet, addresses);
230     //     }
231     //     catch(ex) {
232     //         // Do nothing.
233     //     }
235     writeToClipboard(addresses);
236     message("Copied '" + addresses + "'");
238 interactive("copy-email-address", copy_email_address, ['focused_link_url']);
241 /* FIXME: fix this command */
243 interactive("source",
244             "Load a JavaScript file.",
245             function (fo) { load_rc (fo.path); }, [['f', function (a) { return "Source File: "; }, null, "source"]]);
247 function reinit (window, fn)
249     try {
250         load_rc (fn);
251         window.minibuffer.message ("Loaded: " + fn);
252     } catch (e) {
253         window.minibuffer.message ("Failed to load: "+fn);
254     }
257 interactive ("reinit",
258              "Reload the Conkeror rc file.",
259              function (I) {
260                  reinit(I.window, get_pref("conkeror.rcfile"));
261              });
263 interactive("help-page",
264             "Open the Conkeror help page.",
265             function (I) {
266                 open_in_browser(I.buffer, I.browse_target("find-url"),
267                                 "chrome://conkeror/content/help.html");
268             });
270 interactive("help-with-tutorial",
271             "Open the Conkeror tutorial.",
272             function (I) {
273                 open_in_browser(I.buffer, I.browse_target("find-url"),
274                                 "chrome://conkeror/content/tutorial.html");
275             });
277 function univ_arg_to_number(prefix, default_value)
279     if (prefix == null) {
280         if (default_value == null)
281             return 1;
282         else
283             return default_value;
284     }
285     if (typeof prefix == "object")
286         return prefix[0];
287     return prefix;
290 function eval_expression(window, s)
292     // eval in the global scope.
294     // In addition, the following variables are available:
295     // var window;
296     var buffer = window.buffers.current;
297     var result = eval(s);
298     if (result !== undefined) {
299         window.minibuffer.message(String(result));
300     }
302 interactive("eval-expression",
303             "Evaluate JavaScript statements.",
304             function (I) {
305                 eval_expression(
306                     I.window,
307                     (yield I.minibuffer.read($prompt = "Eval:",
308                                              $history = "eval-expression",
309                                              $completer = javascript_completer(I.buffer))));
310             });
313 function show_extension_manager () {
314     return conkeror.window_watcher.openWindow (
315         null,
316         "chrome://mozapps/content/extensions/extensions.xul?type=extensions",
317         "ExtensionsWindow",
318         "resizable=yes,dialog=no",
319         null);
321 interactive("extensions",
322             "Open the extensions manager in a new window.",
323             show_extension_manager);
325 function print_buffer(buffer)
327     buffer.top_frame.print();
329 interactive("print-buffer",
330             "Print the currently loaded page.",
331             function (I) {print_buffer(I.buffer);});
333 function view_partial_source (window, charset, selection) {
334     if (charset) { charset = "charset=" + charset; }
335     window.window.openDialog("chrome://global/content/viewPartialSource.xul",
336                             "_blank", "scrollbars,resizable,chrome,dialog=no",
337                             null, charset, selection, 'selection');
339 //interactive ('view-partial-source', view_partial_source, I.current_window, I.content_charset, I.content_selection);
342 function  view_mathml_source (window, charset, target) {
343     if (charset) { charset = "charset=" + charset; }
344     window.window.openDialog("chrome://global/content/viewPartialSource.xul",
345                             "_blank", "scrollbars,resizable,chrome,dialog=no",
346                             null, charset, target, 'mathml');
350 function send_key_as_event (window, element, key) {
351     key = kbd (key)[0];
352     var event = window.document.createEvent ("KeyboardEvent");
353     event.initKeyEvent (
354         "keypress",
355         true,
356         true,
357         null,
358         key.modifiers & MOD_CTRL, // ctrl
359         key.modifiers & MOD_META, // alt
360         key.modifiers & MOD_SHIFT, // shift
361         key.modifiers & MOD_META, // meta
362         key.keyCode,
363         null);    // charcode
364     // bit of a hack here.. we have to fake a keydown event for conkeror
365     window.keyboard.last_key_down_event = copy_event (event);
366     if (element) {
367         return element.dispatchEvent (event);
368     } else {
369         return window.dispatchEvent (event);
370     }
372 interactive (
373     "send-ret",
374     null,
375     function (I) {
376         send_key_as_event (I.window, I.buffer.focused_element, "return");
377     });
379 function ensure_content_focused(buffer) {
380     var foc = buffer.focused_frame_or_null;
381     if (!foc)
382         buffer.top_frame.focus();
384 interactive("ensure-content-focused", "Ensure that the content document has focus.",
385             function (I) { ensure_content_focused(I.buffer); });
387 function network_set_online_status (status) {
388     status = !status;
389     io_service.manageOfflineStatus = false;
390     io_service.offline = status;
393 interactive("network-go-online", "Work online.",
394             function (I) {network_set_online_status (true);});
395 interactive("network-go-offline", "Work offline.",
396             function (I) {network_set_online_status (false);});