help_document_generator.help_text: Fix regexp looping
[conkeror.git] / modules / interactive.js
blob4825670e3a0ae408dab3d70c8e6a87167c8e8de1
1 require("utils.js");
3 function interactive_variable(name, value)
5     this.name = name;
6     this.value = value;
9 function interactive_variable_reference(name)
11     this.name = name;
14 function _get_interactive_variable_setter(name) {
15     return function (value) { return new interactive_variable(name, value); }
18 function _get_interactive_variable_getter(name) {
19     return function () { return new interactive_variable_reference(name); }
23 function define_interactive_variable_keywords()
25     for (var i = 0; i < arguments.length; ++i)
26     {
27         var name = arguments[i];
28         this.__defineSetter__(name, _get_interactive_variable_setter(name));
29         this.__defineGetter__(name, _get_interactive_variable_getter(name));
30     }
33 define_interactive_variable_keywords("$$", "$$1", "$$2", "$$3", "$$4", "$$5", "$$6", "$$7", "$$8", "$$9");
35 var interactive_commands = new string_hashmap();
37 function interactive_spec(args, sync_handler, async_handler)
39     this.args = args;
40     this.sync_handler = sync_handler;
41     this.async_handler = async_handler;
44 define_keywords("$doc", "$sync", "$async");
45 function interactive_method()
47     keywords(arguments, $doc = null, $sync = null, $async = null);
48     var sync = arguments.$sync;
49     var async = arguments.$async;
50     if ((sync == null) == (async == null))
51         throw new Error("Invalid arguments.");
52     var i = function () {
53         return new interactive_spec(arguments, sync, async);
54     };
55     i.is_interactive_method = true;
56     return i;
59 var I = {};
61 // Special interactive methods
62 function interactive_bind_spec(args)
64     this.handler = args[0];
65     this.args = Array.prototype.slice.call(args, 1);
68 I.bind = function () {
69     return new interactive_bind_spec(arguments);
72 /**
73  * First argument is the command name.
74  *
75  * This is optionally followed by a documentation string.
76  *
77  * This is followed by the command function.
78  *
79  * Remaining arguments specify interactive arguments.
80  */
81 function interactive(name)
83     var doc = null;
84     var handler;
85     var offset = 1;
86     if (typeof(arguments[1]) == "string")
87     {
88         doc = arguments[1];
89         offset = 2;
90     }
91     handler = arguments[offset++];
92     var args = Array.prototype.slice.call(arguments, offset);
93     var shortdoc = null;
94     if (doc != null) {
95         var idx = doc.indexOf("\n");
96         if (idx >= 0)
97             shortdoc = doc.substring(0,idx);
98         else
99             shortdoc = doc;
100     }
101     var cmd = { name: name,
102                 handler: handler,
103                 doc: doc,
104                 shortdoc: shortdoc,
105                 arguments: args,
106                 source_code_reference: get_caller_source_code_reference() };
108     interactive_commands.put(name, cmd);
111 function interactive_error(str) {
112     var e = new Error(str);
113     e.is_interactive_error = true;
114     return e;
117 function join_argument_lists(args1, args2)
119     if (args1.length == 0)
120         return args2;
121     var positional_args = [];
122     var keyword_args = [];
123     var keywords_seen = new Object();
125     // First process the given arguments (args1)
126     for (var i = 0; i < args1.length; ++i)
127     {
128         var arg = args1[i];
129         if (arg instanceof keyword_argument)
130         {
131             keywords_seen[arg.name] = keyword_args.length;
132             keyword_args.push(arg);
133         } else
134             positional_args[i] = arg;
135     }
137     // Process the argument list specified in the command definition (args2)
138     for (var i = 0; i < args2.length; ++i)
139     {
140         var arg = args2[i];
141         var actual_arg = arg;
142         if (arg instanceof interactive_variable)
143             actual_arg = arg.value;
144         if (actual_arg instanceof keyword_argument)
145         {
146             if (actual_arg.name in keywords_seen)
147             {
148                 if (arg != actual_arg)
149                 {
150                     var j = keywords_seen[actual_arg.name];
151                     keyword_args[j] = new interactive_variable(arg.name, keyword_args[j]);
152                 }
153             } else
154                 keyword_args.push(arg);
155         } else 
156         {
157             if (positional_args[i] === undefined)
158                 positional_args[i] = arg;
159             else if (actual_arg != arg)
160                 positional_args[i] = new interactive_variable(arg.name, positional_args[i]);
161         }
162     }
163     return positional_args.concat(keyword_args);
167 // Any additional arguments specify "given" arguments to the function.
168 function call_interactively(ctx, command)
170     var handler;
171     var top_args;
173     if (ctx.buffer == null)
174         ctx.buffer = ctx.window.buffers.current;
175     else if (ctx.window == null)
176         ctx.window = ctx.buffer.window;
178     var window = ctx.window;
180     var explicit_args = Array.prototype.slice.call(arguments, 2);
182     if (typeof(cmd) == "function") {
183         handler = command;
184         top_args = explicit_args;
185     } else {
186         var cmd = interactive_commands.get(command);
187         if (!cmd)
188         {
189             window.minibuffer.message("Invalid command: " + command);
190             return;
191         }
192         ctx.command = command;
193         top_args = join_argument_lists(explicit_args, cmd.arguments);
194         handler = cmd.handler;
195     }
197     var variable_values = new Object();
199     var state = [{args: top_args, out_args: [], handler: handler}];
200     var next_variable = null;
202     function process_next()
203     {
204         try {
205             do {
206                 var top = state[state.length - 1];
207                 /*
208                 dumpln("at level " + state.length +", args.length: " + top.args.length
209                        +", out_args.length: " + top.out_args.length);
210                 */
212                 // Check if we are done with this level
213                 if  (top.args.length == top.out_args.length)
214                 {
215                     state.pop();
216                     next_variable = top.variable;
217                     if (top.async_handler)
218                     {
219                         top.async_handler.apply(null, [ctx,cont].concat(top.out_args));
220                         return;
221                     }
222                     var result;
223                     if (top.sync_handler)
224                         result = top.sync_handler.apply(null, [ctx].concat(top.out_args));
225                     else if (top.handler)
226                     {
227                         result = top.handler.apply(null, top.out_args);
228                         if (state.length == 0)
229                             return;
230                     }
231                     push_arg(result);
232                     continue;
233                 }
235                 // Not done: we need to process the next argument at this level
236                 var arg = top.args[top.out_args.length];
237                 var variable = null;
238                 if (arg instanceof interactive_variable)
239                 {
240                     variable = arg.name;
241                     arg = arg.value;
242                 }
243                 var spec = arg;
244                 if (arg instanceof keyword_argument)
245                     spec = arg.value;
246                 if (spec instanceof interactive_bind_spec)
247                 {
248                     state.push({args: spec.args, out_args: [], handler: spec.handler, variable: variable});
249                     continue;
250                 }
251                 // Expand an interactive_method
252                 if (typeof(spec) == "function" && spec.is_interactive_method)
253                     spec = spec();
254                 if (spec instanceof interactive_spec) {
255                     state.push({args: spec.args, out_args: [],
256                                 sync_handler: spec.sync_handler,
257                                 async_handler: spec.async_handler,
258                                 variable: variable});
259                 } else {
260                     if (spec instanceof interactive_variable_reference)
261                     {
262                         if (!(spec.name in variable_values))
263                             throw new Error("Invalid interactive variable reference: " + spec.name);
264                         spec = variable_values[spec.name];
265                     }
267                     // Just a normal value
268                     if (arg instanceof keyword_argument)
269                         arg = new keyword_argument(arg.name, spec);
270                     else
271                         arg = spec;
272                     top.out_args.push(arg);
273                     if (variable)
274                         variable_values[variable] = spec;
275                 }
276             } while (true);
277         } catch (e) {
278             if (e.is_interactive_error) {
279                 window.minibuffer.message("" + e);
280             } else {
281                 window.minibuffer.message("call_interactively: "  + e);
282                 dump_error(e);
283             }
284         }
285     }
287     function push_arg(out_arg)
288     {
289         var top = state[state.length - 1];
290         var arg = top.args[top.out_args.length];
291         if (arg instanceof keyword_argument)
292             out_arg = new keyword_argument(arg.name, out_arg);
293         top.out_args.push(out_arg);
294         if (next_variable)
295             variable_values[next_variable] = out_arg;
296     }
298     function cont(out_arg)
299     {
300         push_arg(out_arg);
301         process_next();
302     }
304     process_next();
307 I.p = interactive_method(
308     $doc = "Prefix argument converted to a number",
309     $sync = function (ctx, default_value) {
310         return univ_arg_to_number(ctx.prefix_argument, default_value);
311     });
313 I.P = interactive_method(
314     $doc = "Raw prefix argument",
315     $sync = function (ctx) {
316         return ctx.prefix_argument;
317     });
319 I.current_window = interactive_method(
320     $doc = "Current window",
321     $sync = function (ctx) {
322         return ctx.window;
323     });
325 I.current_command = interactive_method(
326     $doc = "Current command",
327     $sync = function (ctx) {
328         return ctx.command;
329     });
331 I.e = interactive_method(
332     $doc = "Most recent keyboard event",
333     $sync = function (ctx) {
334         return ctx.event;
335     });
337 I.s = interactive_method(
338     $doc = "Read a string from the minibuffer",
339     $async = function (ctx, cont) {
340         keywords(arguments);
341         ctx.window.minibuffer.read($prompt = "String:", $history = "string",
342                                   forward_keywords(arguments),
343                                   $callback = cont);
344     });
346 I.n = interactive_method(
347     $doc = "Read a number from the minibuffer",
348     $async = function (ctx, cont) {
349         keywords(arguments, $prompt = "Number:", $history = "number");
350         ctx.window.minibuffer.read($prompt = "Number:", $history = "number",
351                                   $callback = cont);
352     });
354 I.pref = interactive_method(
355     $sync = function (pref) {
356             var type = preferences.getPrefType (pref);
357             switch (type) {
358                 case preferences.PREF_BOOL:
359                     return preferences.getBoolPref (pref);
360                 case preferences.PREF_INT:
361                     return preferences.getIntPref (pref);
362                 case preferences.PREF_STRING:
363                     return preferences.getCharPref (pref);
364                 default:
365                     return null;
366             }
367     });
369 I.C = interactive_method(
370     $doc = "Name of a command",
371     $async = function (ctx, cont) {
372         minibuffer_read_command(ctx.window.minibuffer,
373                                 forward_keywords(arguments),
374                                 $callback = cont);
375     });
377 I.f = interactive_method(
378     $doc = "Existing file",
379     $async = function (ctx, cont) {
380         keywords(arguments, $prompt = "File:", $initial_value = default_directory.path,
381                  $history = "file");
382         ctx.window.minibuffer.read(
383             $prompt = arguments.$prompt,
384             $initial_value = arguments.$initial_value,
385             $history = arguments.$history,
386             $callback = function(s) {
387                 var f = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
388                 f.initWithPath(s);
389                 cont(f);
390             });
391     });
393 // FIXME: eventually they will differ, when completion for files is added
394 I.F = I.f;