content-buffer.js: fix typo in calling window_set_extra_args
[conkeror.git] / modules / minibuffer.js
blobb535b88ff4ab36033f3b64864e8a10508c8c80bf
1 /**
2  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3  *
4  * Use, modification, and distribution are subject to the terms specified in the
5  * COPYING file.
6 **/
8 /* This should only be used for minibuffer states where it makes
9  * sense.  In particular, it should not be used if additional cleanup
10  * must be done. */
11 function minibuffer_abort (window)
13     var m = window.minibuffer;
14     var s = m.current_state;
15     if (s == null)
16         throw "Invalid minibuffer state";
17     m.pop_state();
19 interactive("minibuffer-abort", null, function (I) {minibuffer_abort(I.window);});
21 define_builtin_commands(
22     "minibuffer-",
23     function (I, command) {
24         try {
25             var m = I.minibuffer;
26             if (m._input_mode_enabled)
27             {
28                 m._restore_normal_state();
29                 var e = m.input_element;
30                 var c = e.controllers.getControllerForCommand(command);
31                 try {
32                     m.ignore_input_events = true;
33                     if (c && c.isCommandEnabled(command))
34                         c.doCommand(command);
35                 } finally {
36                     m.ignore_input_events = false;
37                 }
38                 var s = m.current_state;
39                 if (s.ran_minibuffer_command)
40                     s.ran_minibuffer_command(command);
41             }
42         } catch (e)
43         {
44             /* Ignore exceptions. */
45         }
46     },
47     function (I) {
48         I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
49     },
51     function (I) I.minibuffer.current_state.mark_active,
52     false
55 function minibuffer_state(keymap, use_input_mode)
57     this.keymap = keymap;
58     this.use_input_mode = use_input_mode;
60 minibuffer_state.prototype.load = function () {}
61 minibuffer_state.prototype.unload = function () {}
62 minibuffer_state.prototype.destroy = function () {}
64 function minibuffer_message_state(keymap, message, destroy_function)
66     minibuffer_state.call(this, keymap, false);
67     this._message = message;
68     if (destroy_function)
69         this.destroy = destroy_function;
71 minibuffer_message_state.prototype = {
72     __proto__: minibuffer_state.prototype,
73     load : function (window) {
74         this.window = window;
75     },
76     unload : function (window) {
77         this.window = null;
78     },
79     get message () { return this._message; },
80     set message (x) {
81         if (this.window) {
82             this.window.minibuffer._restore_normal_state();
83             this.window.minibuffer._show(this._message);
84         }
85     }
88 function minibuffer_input_state(keymap, prompt, input, selection_start, selection_end)
90     this.prompt = prompt;
91     if (input)
92         this.input = input;
93     else
94         this.input = "";
95     if (selection_start)
96         this.selection_start = selection_start;
97     else
98         this.selection_start = 0;
99     if (selection_end)
100         this.selection_end = selection_end;
101     else
102         this.selection_end = this.selection_start;
104     minibuffer_state.call(this, keymap, true);
106 minibuffer_input_state.prototype.__proto__ = minibuffer_state.prototype;
110  * The parameter `args' is an object specifying the arguments for
111  * basic_minibuffer_state.  The following properties of args must/may
112  * be set:
114  * prompt:            [required]
116  * initial_value:     [optional] specifies the initial text
118  * select:            [optional] specifies to select the initial text if set to non-null
119  */
120 define_keywords("$prompt", "$initial_value", "$select");
121 function basic_minibuffer_state()
123     keywords(arguments);
124     var initial_value = arguments.$initial_value || "";
125     var sel_start, sel_end;
126     if (arguments.$select)
127     {
128         sel_start = 0;
129         sel_end = initial_value.length;
130     } else {
131         sel_start = sel_end = initial_value.length;
132     }
133     minibuffer_input_state.call(this, minibuffer_base_keymap,
134                                 arguments.$prompt, initial_value,
135                                 sel_start, sel_end);
137 basic_minibuffer_state.prototype.__proto__ = minibuffer_input_state.prototype; // inherit from minibuffer_state
139 define_variable("minibuffer_input_mode_show_message_timeout", 1000, "Time duration (in milliseconds) to flash minibuffer messages while in minibuffer input mode.");
141 function minibuffer (window)
143     this.element = window.document.getElementById("minibuffer");
144     this.output_element = window.document.getElementById("minibuffer-message");
145     this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
146     this.input_element = window.document.getElementById("minibuffer-input");
147     var m = this;
148     this.input_element.inputField.addEventListener("blur", function() {
149             if (m.active && m._input_mode_enabled && !m._showing_message)
150             {
151                 window.setTimeout(
152                     function(){
153                         m.input_element.inputField.focus();
154                     }, 0);
155             }
156         }, false);
157     this.input_element.addEventListener("input", function(e) {
158         if (m.ignore_input_events || !m._input_mode_enabled)
159             return;
160         var s = m.current_state;
161         if (s) {
162             if (s.handle_input)
163                 s.handle_input(m);
164         }
165     }, true);
167     // Ensure that the input area will have focus if a message is
168     // currently being flashed so that the default handler for key
169     // events will properly add text to the input area.
170     window.addEventListener("keydown", function (e) {
171         if (m._input_mode_enabled && m._showing_message)
172             m._restore_normal_state();
173     }, true);
174     this.window = window;
175     this.last_message = "";
176     this.states = [];
179 minibuffer.prototype = {
180     constructor : minibuffer.constructor,
181     get _selection_start () { return this.input_element.selectionStart; },
182     get _selection_end () { return this.input_element.selectionEnd; },
183     get _input_text () { return this.input_element.value; },
184     set _input_text (text) { this.input_element.value = text; },
185     get prompt () { return this.input_prompt_element.value; },
186     set prompt (s) { this.input_prompt_element.value = s; },
188     set_input_state : function(x) {
189         this._input_text = x[0];
190         this._set_selection(x[1], x[2]);
191     },
193     _set_selection : function (start, end) {
194         if (start == null)
195             start = this._input_text.length;
196         if (end == null)
197             end = this._input_text.length;
198         this.input_element.setSelectionRange(start,end);
199     },
201     /* Saved focus state */
202     saved_focused_frame : null,
203     saved_focused_element : null,
205     default_message : "",
207     current_message : null,
209     /* This method will display the specified string in the
210      * minibuffer, without recording it in any log/Messages buffer. */
211     show : function (str, force) {
212         if (!this.active || force) {
213             this.current_message = str;
214             this._show(str);
215         }
216     },
218     _show : function (str, force) {
219         if (this.last_message != str)
220         {
221             this.output_element.value = str;
222             this.last_message = str;
223         }
224     },
226     message : function (str) {
227         /* TODO: add the message to a *Messages* buffer, and/or
228          * possibly dump them to the console. */
229         this.show(str, true /* force */);
231         if (str.length > 0 && this.active)
232             this._flash_temporary_message();
233     },
234     clear : function () {
235         this.current_message = null;
236         if (!this.active)
237             this._show(this.default_message);
238     },
240     set_default_message : function (str) {
241         this.default_message = str;
242         if (this.current_message == null)
243             this._show(str);
244     },
246     get current_state () {
247         if (this.states.length == 0)
248             return null;
249         return this.states[this.states.length - 1];
250     },
252     push_state : function (state) {
253         this._save_state();
254         this.states.push(state);
255         this._restore_state();
256     },
258     pop_state : function () {
259         this.current_state.destroy();
260         this.states.pop();
261         this._restore_state();
262     },
264     pop_all : function () {
265         while (this.states.length > 0) {
266             this.current_state.destroy();
267             this.states.pop();
268         }
269     },
271     remove_state : function (state) {
272         var i = this.states.indexOf(state);
273         if (i == -1)
274             return;
275         var was_current = (i == (this.states.length - 1));
276         state.destroy();
277         this.states.splice(i, 1);
278         if (was_current)
279             this._restore_state();
280     },
282     _input_mode_enabled : false,
284     active : false,
286     /* If _input_mode_enabled is true, this is set to indicate that
287      * the message area is being temporarily shown instead of the
288      * input box. */
289     _showing_message : false,
291     _message_timer_ID : null,
293     /* This must only be called if _input_mode_enabled is true */
294     _restore_normal_state : function () {
295         if (this._showing_message)
296         {
297             this.window.clearTimeout(this._message_timer_ID);
298             this._message_timer_ID = null;
299             this._showing_message = false;
301             if (this._input_mode_enabled)
302                 this._switch_to_input_mode();
303             else
304                 this._show(this.current_state._message);
305         }
306     },
308     /* This must only be called if _input_mode_enabled is true */
309     _flash_temporary_message : function () {
310         if (this._showing_message)
311             this.window.clearTimeout(this._message_timer_ID);
312         else {
313             this._showing_message = true;
314             if (this._input_mode_enabled)
315                 this._switch_to_message_mode();
316         }
317         var obj = this;
318         this._message_timer_ID = this.window.setTimeout(function(){
319             obj._restore_normal_state();
320         }, minibuffer_input_mode_show_message_timeout);
321     },
323     _switch_to_input_mode : function () {
324         this.element.setAttribute("minibuffermode", "input");
325         this.input_element.inputField.focus();
326     },
328     _switch_to_message_mode : function () {
329         this.element.setAttribute("minibuffermode", "message");
330     },
332     _restore_state : function () {
333         var s = this.current_state;
334         var want_input_mode = false;
335         if (s) {
336             if (!this.active) {
337                 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
338                 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
339             }
340             if (s.use_input_mode) {
341                 want_input_mode = true;
342                 this._input_text = s.input;
343                 this.prompt = s.prompt;
344                 this._set_selection(s.selection_start, s.selection_end);
345             } else {
346                 this._show(s._message);
347             }
348             s.load(this.window);
349             this.window.keyboard.set_override_keymap(s.keymap);
350             this.active = true;
351         } else {
352             if (this.active) {
353                 this.active = false;
354                 this.window.keyboard.set_override_keymap(null);
355                 if (this.saved_focused_element)
356                     set_focus_no_scroll(this.window, this.saved_focused_element);
357                 else if (this.saved_focused_frame)
358                     set_focus_no_scroll(this.window, this.saved_focused_frame);
359                 this.saved_focused_element = null;
360                 this.saved_focused_frame = null;
361                 this._show(this.current_message || this.default_message);
362             }
363         }
364         var in_input_mode = this._input_mode_enabled && !this._showing_message;
365         if (this._showing_message) {
366             this.window.clearTimeout(this._message_timer_ID);
367             this._message_timer_ID = null;
368             this._showing_message = false;
369         }
370         if (want_input_mode && !in_input_mode)
371             this._switch_to_input_mode();
372         else if (!want_input_mode && in_input_mode)
373             this._switch_to_message_mode();
374         this._input_mode_enabled = want_input_mode;
375     },
377     _save_state : function () {
378         var s = this.current_state;
379         if (s)
380         {
381             if (s.use_input_mode) {
382                 s.input = this._input_text;
383                 s.prompt = this.prompt;
384                 s.selection_start = this._selection_start;
385                 s.selection_end = this._selection_end;
386             }
387             s.unload(this.window);
388         }
389     },
391     insert_before : function (element) {
392         this.element.parentNode.insertBefore(element, this.element);
393     }
396 function minibuffer_initialize_window(window)
398     window.minibuffer = new minibuffer(window);
401 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
403 function minibuffer_window_close_handler(window) {
404     window.minibuffer.pop_all();
406 add_hook("window_close_hook", minibuffer_window_close_handler);
408 /* Note: This is concise, but doesn't seem to be useful in practice,
409  * because nothing can be done with the state alone. */
410 minibuffer.prototype.check_state = function(type) {
411     var s = this.current_state;
412     if (!(s instanceof type))
413         throw new Error("Invalid minibuffer state.");
414     return s;
417 minibuffer.prototype.show_wait_message = function (initial_message, destroy_function) {
418     var s = new minibuffer_message_state(minibuffer_message_keymap, initial_message, destroy_function);
419     this.push_state(s);
420     return s;
423 minibuffer.prototype.wait_for = function minibuffer__wait_for(message, coroutine) {
424     var cc = yield CONTINUATION;
425     var done = false;
426     var s = this.show_wait_message(message, function () { if (!done) cc.throw(abort()); });
427     var result;
428     try {
429         result = yield coroutine;
430     }
431     finally {
432         done = true;
433         this.remove_state(s);
434     }
435     yield co_return(result);