Add webjump integration with OpenSearch search engines
[conkeror.git] / modules / minibuffer.js
blobfdedf43a2ef4f573cce4e34f7383b2d5661a31da
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 define_builtin_commands(
16     "minibuffer-",
17     function (I, command) {
18         try {
19             var m = I.minibuffer;
20             if (m._input_mode_enabled)
21             {
22                 m._restore_normal_state();
23                 var e = m.input_element;
24                 var c = e.controllers.getControllerForCommand(command);
25                 try {
26                     m.ignore_input_events = true;
27                     if (c && c.isCommandEnabled(command))
28                         c.doCommand(command);
29                 } finally {
30                     m.ignore_input_events = false;
31                 }
32                 var s = m.current_state;
33                 if (s.ran_minibuffer_command)
34                     s.ran_minibuffer_command(command);
35             }
36         } catch (e)
37         {
38             /* Ignore exceptions. */
39         }
40     },
41     function (I) {
42         I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
43     },
45     function (I) I.minibuffer.current_state.mark_active
48 function minibuffer_state(keymap, use_input_mode)
50     this.keymap = keymap;
51     this.use_input_mode = use_input_mode;
53 minibuffer_state.prototype.load = function () {}
54 minibuffer_state.prototype.unload = function () {}
55 minibuffer_state.prototype.destroy = function () {}
57 function minibuffer_message_state(keymap, message, destroy_function)
59     minibuffer_state.call(this, keymap, false);
60     this._message = message;
61     if (destroy_function)
62         this.destroy = destroy_function;
64 minibuffer_message_state.prototype = {
65     __proto__: minibuffer_state.prototype,
66     load : function (window) {
67         this.window = window;
68     },
69     unload : function (window) {
70         this.window = null;
71     },
72     get message () { return this._message; },
73     set message (x) {
74         if (this.window) {
75             this.window.minibuffer._restore_normal_state();
76             this.window.minibuffer._show(this._message);
77         }
78     }
81 function minibuffer_input_state(keymap, prompt, input, selection_start, selection_end)
83     this.prompt = prompt;
84     if (input)
85         this.input = input;
86     else
87         this.input = "";
88     if (selection_start)
89         this.selection_start = selection_start;
90     else
91         this.selection_start = 0;
92     if (selection_end)
93         this.selection_end = selection_end;
94     else
95         this.selection_end = this.selection_start;
97     minibuffer_state.call(this, keymap, true);
99 minibuffer_input_state.prototype.__proto__ = minibuffer_state.prototype;
103  * The parameter `args' is an object specifying the arguments for
104  * basic_minibuffer_state.  The following properties of args must/may
105  * be set:
107  * prompt:            [required]
109  * initial_value:     [optional] specifies the initial text
111  * select:            [optional] specifies to select the initial text if set to non-null
112  */
113 define_keywords("$prompt", "$initial_value", "$select");
114 function basic_minibuffer_state()
116     keywords(arguments);
117     var initial_value = arguments.$initial_value || "";
118     var sel_start, sel_end;
119     if (arguments.$select)
120     {
121         sel_start = 0;
122         sel_end = initial_value.length;
123     } else {
124         sel_start = sel_end = initial_value.length;
125     }
126     minibuffer_input_state.call(this, minibuffer_base_keymap,
127                                 arguments.$prompt, initial_value,
128                                 sel_start, sel_end);
130 basic_minibuffer_state.prototype.__proto__ = minibuffer_input_state.prototype; // inherit from minibuffer_state
132 define_variable("minibuffer_input_mode_show_message_timeout", 1000, "Time duration (in milliseconds) to flash minibuffer messages while in minibuffer input mode.");
134 function minibuffer (window)
136     this.element = window.document.getElementById("minibuffer");
137     this.output_element = window.document.getElementById("minibuffer-message");
138     this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
139     this.input_element = window.document.getElementById("minibuffer-input");
140     var m = this;
141     this.input_element.inputField.addEventListener("blur", function() {
142             if (m.active && m._input_mode_enabled && !m._showing_message)
143             {
144                 window.setTimeout(
145                     function(){
146                         m.input_element.inputField.focus();
147                     }, 0);
148             }
149         }, false);
150     this.input_element.addEventListener("input", function(e) {
151         if (m.ignore_input_events || !m._input_mode_enabled)
152             return;
153         var s = m.current_state;
154         if (s) {
155             if (s.handle_input)
156                 s.handle_input(m);
157         }
158     }, true);
160     // Ensure that the input area will have focus if a message is
161     // currently being flashed so that the default handler for key
162     // events will properly add text to the input area.
163     window.addEventListener("keydown", function (e) {
164         if (m._input_mode_enabled && m._showing_message)
165             m._restore_normal_state();
166     }, true);
167     this.window = window;
168     this.last_message = "";
169     this.states = [];
172 minibuffer.prototype = {
173     constructor : minibuffer.constructor,
174     get _selection_start () { return this.input_element.selectionStart; },
175     get _selection_end () { return this.input_element.selectionEnd; },
176     get _input_text () { return this.input_element.value; },
177     set _input_text (text) { this.input_element.value = text; },
178     get prompt () { return this.input_prompt_element.value; },
179     set prompt (s) { this.input_prompt_element.value = s; },
181     set_input_state : function(x) {
182         this._input_text = x[0];
183         this._set_selection(x[1], x[2]);
184     },
186     _set_selection : function (start, end) {
187         if (start == null)
188             start = this._input_text.length;
189         if (end == null)
190             end = this._input_text.length;
191         this.input_element.setSelectionRange(start,end);
192     },
194     /* Saved focus state */
195     saved_focused_frame : null,
196     saved_focused_element : null,
198     default_message : "",
200     current_message : null,
202     /* This method will display the specified string in the
203      * minibuffer, without recording it in any log/Messages buffer. */
204     show : function (str, force) {
205         if (!this.active || force) {
206             this.current_message = str;
207             this._show(str);
208         }
209     },
211     _show : function (str, force) {
212         if (this.last_message != str)
213         {
214             this.output_element.value = str;
215             this.last_message = str;
216         }
217     },
219     message : function (str) {
220         /* TODO: add the message to a *Messages* buffer, and/or
221          * possibly dump them to the console. */
222         this.show(str, true /* force */);
224         if (str.length > 0 && this.active)
225             this._flash_temporary_message();
226     },
227     clear : function () {
228         this.current_message = null;
229         if (!this.active)
230             this._show(this.default_message);
231     },
233     set_default_message : function (str) {
234         this.default_message = str;
235         if (this.current_message == null)
236             this._show(str);
237     },
239     get current_state () {
240         if (this.states.length == 0)
241             return null;
242         return this.states[this.states.length - 1];
243     },
245     push_state : function (state) {
246         this._save_state();
247         this.states.push(state);
248         this._restore_state();
249         state.load(this.window);
250     },
252     pop_state : function () {
253         this.current_state.destroy();
254         this.states.pop();
255         this._restore_state();
256     },
258     pop_all : function () {
259         while (this.states.length > 0) {
260             this.current_state.destroy();
261             this.states.pop();
262         }
263     },
265     remove_state : function (state) {
266         var i = this.states.indexOf(state);
267         if (i == -1)
268             return;
269         var was_current = (i == (this.states.length - 1));
270         state.destroy();
271         this.states.splice(i, 1);
272         if (was_current)
273             this._restore_state();
274     },
276     _input_mode_enabled : false,
278     active : false,
280     /* If _input_mode_enabled is true, this is set to indicate that
281      * the message area is being temporarily shown instead of the
282      * input box. */
283     _showing_message : false,
285     _message_timer_ID : null,
287     /* This must only be called if _input_mode_enabled is true */
288     _restore_normal_state : function () {
289         if (this._showing_message)
290         {
291             this.window.clearTimeout(this._message_timer_ID);
292             this._message_timer_ID = null;
293             this._showing_message = false;
295             if (this._input_mode_enabled)
296                 this._switch_to_input_mode();
297             else
298                 this._show(this.current_state._message);
299         }
300     },
302     /* This must only be called if _input_mode_enabled is true */
303     _flash_temporary_message : function () {
304         if (this._showing_message)
305             this.window.clearTimeout(this._message_timer_ID);
306         else {
307             this._showing_message = true;
308             if (this._input_mode_enabled)
309                 this._switch_to_message_mode();
310         }
311         var obj = this;
312         this._message_timer_ID = this.window.setTimeout(function(){
313             obj._restore_normal_state();
314         }, minibuffer_input_mode_show_message_timeout);
315     },
317     _switch_to_input_mode : function () {
318         this.element.setAttribute("minibuffermode", "input");
319         this.input_element.inputField.focus();
320     },
322     _switch_to_message_mode : function () {
323         this.element.setAttribute("minibuffermode", "message");
324     },
326     _restore_state : function () {
327         var s = this.current_state;
328         var want_input_mode = false;
329         if (s) {
330             if (!this.active) {
331                 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
332                 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
333             }
334             if (s.use_input_mode) {
335                 want_input_mode = true;
336                 this._input_text = s.input;
337                 this.prompt = s.prompt;
338                 this._set_selection(s.selection_start, s.selection_end);
339             } else {
340                 this._show(s._message);
341             }
342             this.window.keyboard.set_override_keymap(s.keymap);
343             this.active = true;
344         } else {
345             if (this.active) {
346                 this.active = false;
347                 this.window.keyboard.set_override_keymap(null);
348                 if (this.saved_focused_element)
349                     set_focus_no_scroll(this.window, this.saved_focused_element);
350                 else if (this.saved_focused_frame)
351                     set_focus_no_scroll(this.window, this.saved_focused_frame);
352                 this.saved_focused_element = null;
353                 this.saved_focused_frame = null;
354                 this._show(this.current_message || this.default_message);
355             }
356         }
357         var in_input_mode = this._input_mode_enabled && !this._showing_message;
358         if (this._showing_message) {
359             this.window.clearTimeout(this._message_timer_ID);
360             this._message_timer_ID = null;
361             this._showing_message = false;
362         }
363         if (want_input_mode && !in_input_mode)
364             this._switch_to_input_mode();
365         else if (!want_input_mode && in_input_mode)
366             this._switch_to_message_mode();
367         this._input_mode_enabled = want_input_mode;
368     },
370     _save_state : function () {
371         var s = this.current_state;
372         if (s)
373         {
374             if (s.use_input_mode) {
375                 s.input = this._input_text;
376                 s.prompt = this.prompt;
377                 s.selection_start = this._selection_start;
378                 s.selection_end = this._selection_end;
379             }
380             s.unload(this.window);
381         }
382     },
384     insert_before : function (element) {
385         this.element.parentNode.insertBefore(element, this.element);
386     }
389 function minibuffer_initialize_window(window)
391     window.minibuffer = new minibuffer(window);
394 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
396 function minibuffer_window_close_handler(window) {
397     window.minibuffer.pop_all();
399 add_hook("window_close_hook", minibuffer_window_close_handler);
401 /* Note: This is concise, but doesn't seem to be useful in practice,
402  * because nothing can be done with the state alone. */
403 minibuffer.prototype.check_state = function(type) {
404     var s = this.current_state;
405     if (!(s instanceof type))
406         throw new Error("Invalid minibuffer state.");
407     return s;
410 minibuffer.prototype.show_wait_message = function (initial_message, destroy_function) {
411     var s = new minibuffer_message_state(minibuffer_message_keymap, initial_message, destroy_function);
412     this.push_state(s);
413     return s;
416 minibuffer.prototype.wait_for = function minibuffer__wait_for(message, coroutine) {
417     var cc = yield CONTINUATION;
418     var done = false;
419     var s = this.show_wait_message(message, function () { if (!done) cc.throw(abort()); });
420     var result;
421     try {
422         result = yield coroutine;
423     }
424     finally {
425         done = true;
426         this.remove_state(s);
427     }
428     yield co_return(result);