Change to coroutine infrastructure
[conkeror.git] / modules / minibuffer.js
blobebb184b50e85a082da3a11f225e9a98bcf9e0a2c
2 /* This should only be used for minibuffer states where it makes
3  * sense.  In particular, it should not be used if additional cleanup
4  * must be done. */
5 function minibuffer_abort (window)
7     var m = window.minibuffer;
8     var s = m.current_state;
9     if (s == null)
10         throw "Invalid minibuffer state";
11     m.pop_state();
13 interactive("minibuffer-abort", function (I) {minibuffer_abort(I.window);});
15 function minibuffer_do_command(window, command) {
16     try {
17         var m = window.minibuffer;
18         if (m._input_mode_enabled)
19         {
20             m._ensure_input_area_showing();
21             var e = m.input_element;
22             var c = e.controllers.getControllerForCommand(command);
23             if (c && c.isCommandEnabled(command))
24                 c.doCommand(command);
25             var s = m.current_state;
26             if ((s instanceof text_entry_minibuffer_state))
27                 s.handle_input_changed();
28         }
29     } catch (e)
30     {
31         dump_error(e);
32     }
35 for each (let c_temp in builtin_commands)  {
36     let c = c_temp;
37     interactive("minibuffer-" + c, function(I) {minibuffer_do_command(I.window, c);});
40 for each (let c_temp in builtin_commands_with_count)
42     let c = c_temp;
43     if (typeof(c) == "string")
44         interactive("minibuffer-" + c, function (I) {
45             do_repeatedly_positive(minibuffer_do_command, I.p,
46                                    I.window, c);
47         });
48     else {
49         interactive("minibuffer-" + c[0], function (I) {
50             do_repeatedly(minibuffer_do_command, I.p, [I.window, c[0]], [I.window, c[1]]);
51         });
52         interactive("minibuffer-" + c[1], function (I) {
53             do_repeatedly(minibuffer_do_command, I.p, [I.window, c[1]], [I.window, c[0]]);
54         });
55     }
58 function minibuffer_insert_character(window, n, event)
60     var m = window.minibuffer;
61     var s = m.current_state;
62     if (!(s instanceof basic_minibuffer_state))
63         throw "Invalid minibuffer state";
64     m._ensure_input_area_showing();
65     var val = m._input_text;
66     var sel_start = m._selection_start;
67     var sel_end = m._selection_end;
68     var insert = String.fromCharCode(event.charCode);
69     var out = val.substr(0, sel_start);
70     for (var i = 0; i < n; ++i)
71         out += insert;
72     out += val.substr(sel_end);
73     m._input_text = out;
74     var new_sel = sel_end + n;
75     m._set_selection(new_sel, new_sel);
77     if (s instanceof text_entry_minibuffer_state)
78         s.handle_input_changed();
80 interactive("minibuffer-insert-character", function (I) {
81     minibuffer_insert_character(I.window, I.p, I.event);
82 });
84 function minibuffer_state(keymap, prompt, input, selection_start, selection_end)
86     this.keymap = keymap;
87     this.prompt = prompt;
88     if (input)
89         this.input = input;
90     else
91         this.input = "";
92     if (selection_start)
93         this.selection_start = selection_start;
94     else
95         this.selection_start = 0;
96     if (selection_end)
97         this.selection_end = selection_end;
98     else
99         this.selection_end = this.selection_start;
101 minibuffer_state.prototype.load = function () {}
102 minibuffer_state.prototype.unload = function () {}
103 minibuffer_state.prototype.destroy = function () {}
106  * The parameter `args' is an object specifying the arguments for
107  * basic_minibuffer_state.  The following properties of args must/may
108  * be set:
110  * prompt:            [required]
112  * initial_value:     [optional] specifies the initial text
114  * select:            [optional] specifies to select the initial text if set to non-null
115  */
116 define_keywords("$prompt", "$initial_value", "$select");
117 function basic_minibuffer_state()
119     keywords(arguments);
120     var initial_value = arguments.$initial_value || "";
121     var sel_start, sel_end;
122     if (arguments.$select)
123     {
124         sel_start = 0;
125         sel_end = initial_value.length;
126     } else {
127         sel_start = sel_end = initial_value.length;
128     }
129     minibuffer_state.call(this, minibuffer_base_keymap,
130                           arguments.$prompt, initial_value,
131                           sel_start, sel_end);
133 basic_minibuffer_state.prototype.__proto__ = minibuffer_state.prototype; // inherit from minibuffer_state
135 /* USER PREFERENCE */
136 var minibuffer_input_mode_show_message_timeout = 1000;
138 function minibuffer (window)
140     this.element = window.document.getElementById("minibuffer");
141     this.output_element = window.document.getElementById("minibuffer-message");
142     this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
143     this.input_element = window.document.getElementById("minibuffer-input");
144     var m = this;
145     this.input_element.inputField.addEventListener("blur", function() {
146             if (m._input_mode_enabled && !m._showing_message)
147             {
148                 window.setTimeout(
149                     function(){
150                         m.input_element.focus();
151                     }, 0);
152             }
153         }, false);
154     this.window = window;
155     this.last_message = "";
156     this.states = [];
159 minibuffer.prototype = {
160     constructor : minibuffer.constructor,
161     get _selection_start () { return this.input_element.selectionStart; },
162     get _selection_end () { return this.input_element.selectionEnd; },
163     get _input_text () { return this.input_element.value; },
164     set _input_text (text) { this.input_element.value = text; },
165     get prompt () { return this.input_prompt_element.value; },
166     set prompt (s) { this.input_prompt_element.value = s; },
168     _set_selection : function (start, end) {
169         if (start == null)
170             start = this._input_text.length;
171         if (end == null)
172             end = this._input_text.length;
173         this.input_element.setSelectionRange(start,end);
174     },
176     /* Saved focus state */
177     saved_focused_frame : null,
178     saved_focused_element : null,
180     default_message : "",
182     current_message : null,
184     /* This method will display the specified string in the
185      * minibuffer, without recording it in any log/Messages buffer. */
186     show : function (str) {
187         this.current_message = str;
188         this._show(str);
189     },
191     _show : function (str) {
192         if (this.last_message != str)
193         {
194             this.output_element.value = str;
195             this.last_message = str;
196         }
197     },
199     message : function (str) {
200         /* TODO: add the message to a *Messages* buffer, and/or
201          * possibly dump them to the console. */
202         this.show(str);
205         if (str.length > 0 && this._input_mode_enabled)
206             this._ensure_message_area_showing();
207     },
208     clear : function () {
209         this.current_message = null;
210         this._show(this.default_message);
211     },
213     set_default_message : function (str) {
214         this.default_message = str;
215         if (this.current_message == null)
216             this._show(str);
217     },
219     get current_state () {
220         if (this.states.length == 0)
221             return null;
222         return this.states[this.states.length - 1];
223     },
225     push_state : function (state) {
226         this._save_state();
227         this.states.push(state);
228         this._restore_state();
229         state.load(this.window);
230     },
232     pop_state : function (restore_focus) {
233         if (restore_focus === undefined)
234             restore_focus = true;
235         this.current_state.destroy();
236         this.states.pop();
237         this._restore_state(restore_focus);
238     },
240     pop_all : function () {
241         while (this.states.length > 0) {
242             this.current_state.destroy();
243             this.states.pop();
244         }
245     },
247     remove_state : function (state, restore_focus) {
248         if (restore_focus === undefined)
249             restore_focus = true;
250         var i = this.states.indexOf(state);
251         state.destroy();
252         this.states.splice(i, 1);
253         this._restore_state(restore_focus);
254     },
256     _input_mode_enabled : false,
258     /* If _input_mode_enabled is true, this is set to indicate that
259      * the message area is being temporarily shown instead of the
260      * input box. */
261     _showing_message : false,
263     _message_timer_ID : null,
265     /* This must only be called if _input_mode_enabled is true */
266     _ensure_input_area_showing : function () {
267         if (this._showing_message)
268         {
269             this.window.clearTimeout(this._message_timer_ID);
270             this._message_timer_ID = null;
271             this._showing_message = false;
272             this._switch_to_input_mode();
273         }
274     },
276     /* This must only be called if _input_mode_enabled is true */
277     _ensure_message_area_showing : function () {
278         if (this._showing_message)
279             this.window.clearTimeout(this._message_timer_ID);
280         else {
281             this._showing_message = true;
282             this._switch_to_message_mode();
283         }
284         var obj = this;
285         this._message_timer_ID = this.window.setTimeout(function(){
286                 obj._ensure_input_area_showing();
287             }, minibuffer_input_mode_show_message_timeout);
288     },
290     _switch_to_input_mode : function () {
291         this.element.setAttribute("minibuffermode", "input");
292         this.input_element.focus();
293     },
295     _switch_to_message_mode : function () {
296         this.element.setAttribute("minibuffermode", "message");
297     },
299     _restore_state : function (restore_focus) {
300         var s = this.current_state;
301         if (s) {
302             if (!this._input_mode_enabled)
303             {
304                 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
305                 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
306                 this._input_mode_enabled = true;
307                 this._switch_to_input_mode();
308             }
309             this.window.keyboard.set_override_keymap(s.keymap);
310             this._input_text = s.input;
311             this.prompt = s.prompt;
312             this._set_selection(s.selection_start, s.selection_end);
313         } else {
314             if (this._input_mode_enabled)
315             {
316                 this._input_mode_enabled = false;
317                 if (!this._showing_message)
318                     this._switch_to_message_mode();
319                 else {
320                     this.window.clearTimeout(this._message_timer_ID);
321                     this._message_timer_ID = null;
322                     this._showing_message = false;
323                 }
324                 if (restore_focus)
325                 {
326                     if (this.saved_focused_element)
327                         set_focus_no_scroll(this.window, this.saved_focused_element);
328                     else if (this.saved_focused_frame)
329                         set_focus_no_scroll(this.window, this.saved_focused_frame);
330                 }
331                 this.saved_focused_element = null;
332                 this.saved_focused_frame = null;
333                 this.window.keyboard.set_override_keymap(null);
334             }
335         }
336     },
338     _save_state : function () {
339         var s = this.current_state;
340         if (s)
341         {
342             s.input = this._input_text;
343             s.prompt = this.prompt;
344             s.selection_start = this._selection_start;
345             s.selection_end = this._selection_end;
346             s.unload(this.window);
347         }
348     },
350     insert_before : function (element) {
351         this.element.parentNode.insertBefore(element, this.element);
352     }
355 function minibuffer_initialize_window(window)
357     window.minibuffer = new minibuffer(window);
360 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
362 function minibuffer_window_close_handler(window) {
363     window.minibuffer.pop_all();
365 add_hook("window_close_hook", minibuffer_window_close_handler);
367 /* Note: This is concise, but doesn't seem to be useful in practice,
368  * because nothing can be done with the state alone. */
369 minibuffer.prototype.check_state = function(type) {
370     var s = this.current_state;
371     if (!(s instanceof type))
372         throw new Error("Invalid minibuffer state.");
373     return s;