interactive_methods.url_or_webjump: fixed
[conkeror.git] / conkeror / content / interactive.js
blobe8a6816eeb7c4c6d6830d726e75905da439719f2
3 var interactive_methods = {
4 a: { func: function (spec) {
5             // -- Function name: symbol with a function definition.
6             return null;
7         }
8 },
10 active_document: { func: function (spec) {
11             // -- The document currently being browsed.
12             return this.window.content.document;
13         }
16 // b: existing buffer.
17 b: { async: function (spec, iargs, callback, callback_args, given_args) {
18             var bufs = [];
19             var existing_names = new Object();
20             this.buffers.for_each(function(b) {
21                     var base_name = b.name;
22                     var name = base_name;
23                     var index = 1;
24                     while (existing_names["i_" + name])
25                     {
26                         ++index;
27                         name = base_name + "<" + index + ">";
28                     }
29                     existing_names["i_" + name] = true;
30                     bufs.push([name, b]);
31                 });
32             var prompt = (1 in spec && spec[1] ? spec[1].call (this, callback_args) : "Buffer: ");
33             var initval = (2 in spec && spec[2] ?
34                            spec[2].call (this, callback_args) : this.buffers.current.name);
35             var frame = this;
36             this.minibuffer.read({prompt: prompt, initial_value: initval, history: "buffer",
37                         completions: bufs,
38                         callback: function (s) {
39                                          callback_args.push (s);
40                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
41                     }});
42         },
43      doc: "Name of existing buffer, defaulting to the current one.\n"+
44      "Its optional arguments are:\n"+
45      "PROMPT      A string prompt for the minibuffer read.  The default is 'Buffer: '.\n"+
46      "INITVAL     A function to get the initial value.  The default is the current URL."
49 // XXX: does it make sense to have an interactive method for non-existent buffers?
50 B: { func: function (spec) {
51             // -- Name of buffer, possibly nonexistent.
52             return null;
53         }
56 c: { func: function (spec) {
57             // -- Character
58             return null;
59         }
62 C: { func: function (spec) {
63             // -- Command name: symbol with interactive function definition.
64             return null;
65         }
68 content_charset: { func: function (spec) {
69             // -- Charset of content area of focusedWindow
70             var focusedWindow = this.document.commandDispatcher.focusedWindow;
71             if (focusedWindow == this.window)
72                 focusedWindow = this.content;
73             if (focusedWindow)
74                 return this.document.commandDispatcher.focusedWindow.document.characterSet;
75             else
76                 return null;
77         }
80 content_selection: { func: function (spec) {
81             // -- Selection of content area of focusedWindow
82             var focusedWindow = this.document.commandDispatcher.focusedWindow;
83             if (focusedWindow == this.window)
84                 focusedWindow = this.content;
85             return focusedWindow.getSelection ();
86         }
89 current_buffer_window: { func: function (spec) {
90             return this.content;
91         }
94 current_command: { func: function (spec) {
95             // -- Name of the command being evaluated right now.
96             return conkeror.current_command;
97         }
100 current_frame: { func: function (spec) {
101             // -- Frame of the current command.
102             return this;
103         }
106 current_frameset_frame: { func: function (spec) {
107             var w = this.document.commandDispatcher.focusedWindow;
108             if (w.top != this.content)
109                 w = this.content;
110             return w;
111         }
114 current_frameset_frame_url: { func: function (spec) {
115             var w = this.document.commandDispatcher.focusedWindow;
116             if (w.top != this.content)
117                 w = this.content;
118             return w.location.href;
119         }
122 current_url: { func: function (spec) {
123             return this.getWebNavigation().currentURI.spec;
124         }
127 d: { func: function (spec) {
128             // -- Value of point as number.  Does not do I/O.
129             return null;
130         }
133 D: { func: function (spec) {
134             // -- Directory name.
135             return null;
136         }
139 // e: Event that invoked this command.
140 e: { func: function (spec) { return this.keyboard_state.last_command_event; } },
142 f: { async: function (spec, iargs, callback, callback_args, given_args) {
143             // -- Exisiting file object. (nsILocalFile)
144             var prompt = (1 in spec && spec[1] ? spec[1].call (this, callback_args) : "File: ");
145             var initval = (2 in spec && spec[2] ? spec[2].call (this, callback_args) : default_directory.path);
146             var hist = (3 in spec ? spec[3] : null);
147             var frame = this;
148             this.minibuffer.read({
149                         prompt:        prompt,
150                         initial_value: initval,
151                         history: hist,
152                         callback: function (s) {
153                                          var f = Components.classes["@mozilla.org/file/local;1"]
154                                              .createInstance(Components.interfaces.nsILocalFile);
155                                          f.initWithPath (s);
156                                          callback_args.push (f);
157                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
158                     } });
159         }
162 F: { async: function (spec, iargs, callback, callback_args, given_args) {
163             // -- Possibly nonexistent file object. (nsILocalFile)
164             var prompt = (1 in spec && spec[1] ? spec[1].call (this, callback_args) : "File: ");
165             var initval = (2 in spec && spec[2] ? spec[2].call (this, callback_args) : default_directory.path);
166             var hist = (3 in spec ? spec[3] : null);
167             var frame = this;
168             this.minibuffer.read({
169                         prompt:        prompt,
170                         initial_value: initval,
171                         history: hist,
172                         callback: function (s) {
173                                          var f = Components.classes["@mozilla.org/file/local;1"]
174                                              .createInstance(Components.interfaces.nsILocalFile);
175                                          f.initWithPath (s);
176                                          callback_args.push (f);
177                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
178                     } });
179         }
182 focused_link_url: { func: function (spec) {
183             // -- Focused link element
184             ///JJF: check for errors or wrong element type.
185             return get_link_location (this.document.commandDispatcher.focusedElement);
186         }
190 // i: Ignored, i.e. always nil.  Does not do I/O.
191 i: { func: function (spec) { return null; } },
193 // image: numbered image.
194 image: { async: function (spec, iargs, callback, callback_args, given_args) {
195             // -- Number read using minibuffer.
196             var prompt = (1 in spec ? spec[1].call (this, callback_args) : "Image Number: ");
197             var buf_state = this.getBrowser().numberedImages;
198             if (!buf_state) {
199                 // turn on image numbers
200                 gTurnOffLinksAfter = true;
201                 this.toggleNumberedImages();
202             }
203             var frame = this;
204             // Setup a context for the context-keymap system.
205             this.readFromMiniBuffer (prompt, null, null, null, null, null,
206                                      function (s) {
207                                          callback_args.push (s);
208                                          if (gTurnOffLinksAfter) {
209                                              toggleNumberedImages();
210                                              gTurnOffLinksAfter = false;
211                                          }
212                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
213                                      },
214                                      function () {
215                                          if (this.gTurnOffLinksAfter) {
216                                              this.toggleNumberedImages ();
217                                              this.gTurnOffLinksAfter = false;
218                                          }
219                                      });
220         }
224 image_url: { async: function (spec, iargs, callback, callback_args, given_args) {
226             var prompt = (1 in spec ? spec[1].call (this, callback_args) : "Image Number: ");
227             var buf_state = this.getBrowser().numberedImages;
228             if (!buf_state) {
229                 // turn on image numbers
230                 this.gTurnOffLinksAfter = true;
231                 this.toggleNumberedImages();
232             }
233             var frame = this;
234             // Setup a context for the context-keymap system.
235             this.readFromMiniBuffer (prompt, null, null, null, null, null,
236                                      function (s) {
237                                          function fail (number)
238                                          {
239                                              this.message ("'"+number+"' is not the number of any image here. ");
240                                          }
241                                          var nl = this.get_numberedlink (s);
242                                          if (! nl) { this.fail (s); return; }
243                                          var type = nl.nlnode.getAttribute("__conktype");
244                                          var loc;
245                                          if (type == "image" && nl.node.getAttribute("src")) {
246                                              loc = nl.node.getAttribute("src");
247                                              loc = makeURLAbsolute (nl.node.baseURI, loc);
248                                          } else {
249                                              fail (number);
250                                          }
251                                          callback_args.push (loc);
253                                          if (this.gTurnOffLinksAfter) {
254                                              this.toggleNumberedImages();
255                                              this.gTurnOffLinksAfter = false;
256                                          }
257                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
258                                      },
259                                      function () {
260                                          if (this.gTurnOffLinksAfter) {
261                                              this.toggleNumberedImages ();
262                                              this.gTurnOffLinksAfter = false;
263                                          }
264                                      });
265         }
269 k: { func: function (spec) {
270             // -- Key sequence (downcase the last event if needed to get a definition).
271             return null;
272         }
275 K: { func: function (spec) {
276             // -- Key sequence to be redefined (do not downcase the last event).
277             return null;
278         }
281 // link: numbered link
282 link: { async: function (spec, iargs, callback, callback_args, given_args) {
283             var prompt = (1 in spec && spec[1] ? spec[1].call (this, callback_args) : "Link Number: ");
284             var initVal = (2 in spec && spec[2] ? spec[2].call (this, callback_args) : "");
285             /* FIXME: numbered link toggling is not yet working */
286             /* 
287             var buf_state = this.getBrowser().numberedLinks;
288             if (!buf_state) {
289                 this.gTurnOffLinksAfter = true;
290                 toggleNumberedLinks();
291             } */
292             // Setup a context for the context-keymap system.
293             this.numberedlinks_minibuffer_active = true;
294             var frame = this;
295     this.minibuffer.read ({prompt: prompt, initial_value: initVal,
296                            callback: function (s) {
297                                          callback_args.push (s);
298                                          /* FIXME: numbered link toggling not yet working
299                                          if (this.gTurnOffLinksAfter) {
300                                              this.toggleNumberedLinks();
301                                              this.gTurnOffLinksAfter = false;
302                                          }*/
303                                          // unset keymap context
304                                          this.numberedlinks_minibuffer_active = false;
305                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
306                                      },
307                            abort_callback:
308                                      function () {
309                                        /* FIXME: numbered link toggling not yet working */
310                                        /*
311                                          if (this.gTurnOffLinksAfter) {
312                                              this.toggleNumberedLinks ();
313                                              this.gTurnOffLinksAfter = false;
314                                          }
315                                          */
316                                          // unset keymap context
317                                          this.numberedlinks_minibuffer_active = false;
318                                      }});
319         }
322 m: { func: function (spec) {
323             // -- Value of mark as number.  Does not do I/O.
324             return null;
325         }
328 mathml_node: { async: function (spec, iargs, callback, callback_args, given_args) {
329             // -- DOM node of a MathML
330             //TO-DO: implement mathml_node interactive spec.
331             callback_args.push (null);
332             do_interactive.call (this, iargs, callback, callback_args, given_args);
333         }
336 minibuffer_exit: { func: function (spec) {
337             // -- minibuffer.exit
338             return this.minibuffer.exit;
339         }
342 // n: Number read using minibuffer.
343 n: { async: function (spec, iargs, callback, callback_args, given_args) {
344             var prompt = "Number: ";
345             if (1 in spec)
346                 prompt = spec[1];
347             var frame = this;
348             this.minibuffer.read
349             ({  prompt: prompt,
350                 callback: function (s) {
351                     callback_args.push (s);
352                     do_interactive.call (frame, iargs, callback, callback_args, given_args);
353                 }
354             });
355         }
358 N: { func: function (spec) {
359             // -- Raw prefix arg, or if none, do like code `n'.
360             return null;
361         }
364 // p: Prefix arg converted to number.  Does not do I/O.
365 p: { func: function (spec) {
366             return univ_arg_to_number(this.gPrefixArg);
367         }
370 // P: Prefix arg in raw form.  Does not do I/O.
371 P: { func: function (spec) {
372             return this.gPrefixArg;
373         }
376 pref: { func: function (spec) {
377             var pref = spec[1];
378             var type = preferences.getPrefType (pref);
379             switch (type) {
380                 case preferences.PREF_BOOL:
381                     return preferences.getBoolPref (pref);
382                 case preferences.PREF_INT:
383                     return preferences.getIntPref (pref);
384                 case preferences.PREF_STRING:
385                     return preferences.getCharPref (pref);
386                 default:
387                     return null;
388             }
389         }
392 r: { func: function (spec) {
393             // -- Region: point and mark as 2 numeric args, smallest first.  Does no I/O.
394             return null;
395         }
398 result: { func: function (spec) {
399             // -- result of given function
400             return (1 in spec ? spec[1]() : null);
401         }
404 s: { async: function (spec, iargs, callback, callback_args, given_args) {
405             // -- Any string.
406             var prompt = "String: ";
407             if (1 in spec)
408                 prompt = spec[1];
409             var frame = this;
410             this.minibuffer.read({ prompt: prompt,
411                         callback:  function (s) {
412                                          callback_args.push (s);
413                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
414                     }});
415         }
418 S: { func: function (spec) {
419             // -- Any symbol.
420             return null;
421         }
424 //RETROJ: this may be improperly named.  it can read either an url or a
425 //        webjump from the minibuffer, but it will always return an url.
426 url_or_webjump: { async: function (spec, iargs, callback, callback_args, given_args) {
427             var prompt = (1 in spec && spec[1] ? spec[1].call (this, callback_args) : "URL: ");
428             var initval = (2 in spec && spec[2] ? spec[2].call (this, callback_args) : "");
429             var hist = (3 in spec ? spec[3] : null);
430             var completions = (4 in spec && spec[4] ? spec[4].call (this, callback_args) : []);
431             var frame = this;
432             this.minibuffer.read({prompt: prompt, initial_value : initval, history: hist,
433                         completions : completions,
434                         allow_non_matches : true,
435                         callback : function (match, s) {
436                                          if (s == "") // well-formedness check. (could be better!)
437                                              throw ("invalid url or webjump (\""+s+"\")");
438                                          callback_args.push (get_url_or_webjump (s));
439                                          do_interactive.call (frame, iargs, callback, callback_args, given_args);
440                     }});
441         }
444 v: { func: function (spec) {
445             // -- Variable name: symbol that is user-variable-p.
446             return null;
447         }
450 value: { func: function (spec) {
451             // -- given value
452             return (1 in spec ? spec[1] : null);
453         }
456 x: { func: function (spec) {
457             // -- Lisp expression read but not evaluated.
458             return null;
459         }
462 X: { func: function (spec) {
463             // -- Lisp expression read and evaluated.
464             return null;
465         }
468 z: { func: function (spec) {
469             // -- Coding system.
470             return null;
471         }
474 Z: { func: function (spec) {
475             // -- Coding system, nil if no prefix arg.
476             return null;
477         }
481 function do_interactive (iargs, callback, callback_args, given_args)
483     if (! callback_args) callback_args = Array ();
485     var iarg, method;
486     while (iargs.length > 0)
487     {
488         // process as many synchronous args as possible
489         iarg = iargs.shift ();
490         if (given_args && 0 in given_args) {
491             var got = given_args.shift ();
492             if (got) {
493                 callback_args.push (got);
494                 continue;
495             }
496         }
497         if (typeof (iarg) == "string") {
498             method = iarg;
499             iarg = Array (iarg);
500         } else {
501             method = iarg[0];
502         }
504         if (! method in interactive_methods) {
505             // prefix should get reset on failed interactive call.
506             this.gPrefixArg = null;
507             this.minibuffer.message ("Failed: invalid interactive specifier: '"+iarg+"'");
508             return;
509         }
511         if ('func' in interactive_methods[method])
512         {
513             // 'func' denotes that this method can be done synchronously.
514             try {
515                 callback_args.push (interactive_methods[method].func.call (this, iarg));
516             } catch (e) {
517                 this.minibuffer.message ('do_interactive (' + method + '): ' + e);
518             }
519             // do_interactive (iargs, callback, callback_args);
520         } else {
521             // an asynchronous call needs to be made.  break the loop and let
522             // the async handler below take over.
523             break;
524         }
525         method = null;
526     }
528     if (method) {
529         if (! 'async' in interactive_methods[method]) {
530             // fail.  improperly defined interactive method.
531             // prefix should get reset on failed interactive call.
532             this.gPrefixArg = null;
533             this.minibuffer.message ("Failed: improperly defined interactive specifer: '"+iarg+"'");
534         }
536         // go on a little trip..
537         //
538         // asynchronous methods get called with their interactive spec and
539         // all the information they need to continue the interactive
540         // process when their data has been gathered.
541         //
542         try {
543             interactive_methods[method].async.call (this, iarg, iargs, callback, callback_args, given_args);
544         } catch (e) {
545             this.minibuffer.message ('do_interactive (' + method + '): ' + e);
546         }
547     } else {
548         // always reset the prefix arg after collecting all
549         // interactive data, in case it appears multiple times in the
550         // interactive spec.
551         this.gPrefixArg = null;
552         try {
553             callback.call (this, callback_args);
554         } catch (e) {
555             this.minibuffer.message ('do_interactive <CALLBACK>: ' + e);
556             dumpln ('do_interactive <CALLBACK>: ' + e);
557         }
558     }
562 function call_interactively(frame, cmd, given_args)
564     try {
565         conkeror.current_command = cmd;
566         for (var i=0; i < conkeror.commands.length; i++) {
567             if (conkeror.commands[i][0] == cmd)
568             {
569                 // Copy the interactive args spec, because do_interactive is
570                 // destructive to its first argument.
571                 var iargs = conkeror.commands[i][2].slice (0);
572                 var given = given_args ? given_args.slice (0) : null;
573                 do_interactive.call (frame,
574                                      iargs,
575                                      function (args) {
576                                          conkeror.commands[i][1].apply (conkeror, args);
577                                      },
578                                      null,
579                                      given);
580                 return;
581             }
582         }
583         frame.minibuffer.message("No such command '" + cmd + "'");
584     } catch(e) {
585         frame.minibuffer.message ("call_interactively: " + e);
586     }
590 function interactive(name, fn, args)
592     for (var i=0; i < conkeror.commands.length; i++) {
593         if (conkeror.commands[i][0] == name) {
594             conkeror.commands[i] = [name,fn,args];
595             return;
596         }
597     }
598     conkeror.commands.push([name,fn,args]);