Add support for documenting user variables.
[conkeror.git] / modules / minibuffer.js
blob5c02819d3ba5ed26cf4db522e1f603a975a4e250
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._ensure_input_area_showing();
23                 var e = m.input_element;
24                 var c = e.controllers.getControllerForCommand(command);
25                 if (c && c.isCommandEnabled(command))
26                     c.doCommand(command);
27                 var s = m.current_state;
28                 if ((s instanceof text_entry_minibuffer_state))
29                     s.handle_input_changed();
30             }
31         } catch (e)
32         {
33             /* Ignore exceptions. */
34         }
35     },
36     function (I) {
37         I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
38     },
40     function (I) I.minibuffer.current_state.mark_active
43 function minibuffer_insert_character(window, n, event)
45     var m = window.minibuffer;
46     var s = m.current_state;
47     if (!(s instanceof basic_minibuffer_state))
48         throw "Invalid minibuffer state";
49     m._ensure_input_area_showing();
50     var val = m._input_text;
51     var sel_start = m._selection_start;
52     var sel_end = m._selection_end;
53     var insert = String.fromCharCode(event.charCode);
54     var out = val.substr(0, sel_start);
55     for (var i = 0; i < n; ++i)
56         out += insert;
57     out += val.substr(sel_end);
58     m._input_text = out;
59     var new_sel = sel_end + n;
60     m._set_selection(new_sel, new_sel);
62     if (s instanceof text_entry_minibuffer_state)
63         s.handle_input_changed();
65 interactive("minibuffer-insert-character", function (I) {
66     minibuffer_insert_character(I.window, I.p, I.event);
67 });
69 function minibuffer_state(keymap, prompt, input, selection_start, selection_end)
71     this.keymap = keymap;
72     this.prompt = prompt;
73     if (input)
74         this.input = input;
75     else
76         this.input = "";
77     if (selection_start)
78         this.selection_start = selection_start;
79     else
80         this.selection_start = 0;
81     if (selection_end)
82         this.selection_end = selection_end;
83     else
84         this.selection_end = this.selection_start;
86 minibuffer_state.prototype.load = function () {}
87 minibuffer_state.prototype.unload = function () {}
88 minibuffer_state.prototype.destroy = function () {}
90 /**
91  * The parameter `args' is an object specifying the arguments for
92  * basic_minibuffer_state.  The following properties of args must/may
93  * be set:
94  *
95  * prompt:            [required]
96  *
97  * initial_value:     [optional] specifies the initial text
98  *
99  * select:            [optional] specifies to select the initial text if set to non-null
100  */
101 define_keywords("$prompt", "$initial_value", "$select");
102 function basic_minibuffer_state()
104     keywords(arguments);
105     var initial_value = arguments.$initial_value || "";
106     var sel_start, sel_end;
107     if (arguments.$select)
108     {
109         sel_start = 0;
110         sel_end = initial_value.length;
111     } else {
112         sel_start = sel_end = initial_value.length;
113     }
114     minibuffer_state.call(this, minibuffer_base_keymap,
115                           arguments.$prompt, initial_value,
116                           sel_start, sel_end);
118 basic_minibuffer_state.prototype.__proto__ = minibuffer_state.prototype; // inherit from minibuffer_state
120 define_user_variable("minibuffer_input_mode_show_message_timeout", 1000, "Time duration (in milliseconds) to flash minibuffer messages while in minibuffer input mode.");
122 function minibuffer (window)
124     this.element = window.document.getElementById("minibuffer");
125     this.output_element = window.document.getElementById("minibuffer-message");
126     this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
127     this.input_element = window.document.getElementById("minibuffer-input");
128     var m = this;
129     this.input_element.inputField.addEventListener("blur", function() {
130             if (m._input_mode_enabled && !m._showing_message)
131             {
132                 window.setTimeout(
133                     function(){
134                         m.input_element.focus();
135                     }, 0);
136             }
137         }, false);
138     this.window = window;
139     this.last_message = "";
140     this.states = [];
143 minibuffer.prototype = {
144     constructor : minibuffer.constructor,
145     get _selection_start () { return this.input_element.selectionStart; },
146     get _selection_end () { return this.input_element.selectionEnd; },
147     get _input_text () { return this.input_element.value; },
148     set _input_text (text) { this.input_element.value = text; },
149     get prompt () { return this.input_prompt_element.value; },
150     set prompt (s) { this.input_prompt_element.value = s; },
152     _set_selection : function (start, end) {
153         if (start == null)
154             start = this._input_text.length;
155         if (end == null)
156             end = this._input_text.length;
157         this.input_element.setSelectionRange(start,end);
158     },
160     /* Saved focus state */
161     saved_focused_frame : null,
162     saved_focused_element : null,
164     default_message : "",
166     current_message : null,
168     /* This method will display the specified string in the
169      * minibuffer, without recording it in any log/Messages buffer. */
170     show : function (str) {
171         this.current_message = str;
172         this._show(str);
173     },
175     _show : function (str) {
176         if (this.last_message != str)
177         {
178             this.output_element.value = str;
179             this.last_message = str;
180         }
181     },
183     message : function (str) {
184         /* TODO: add the message to a *Messages* buffer, and/or
185          * possibly dump them to the console. */
186         this.show(str);
189         if (str.length > 0 && this._input_mode_enabled)
190             this._ensure_message_area_showing();
191     },
192     clear : function () {
193         this.current_message = null;
194         this._show(this.default_message);
195     },
197     set_default_message : function (str) {
198         this.default_message = str;
199         if (this.current_message == null)
200             this._show(str);
201     },
203     get current_state () {
204         if (this.states.length == 0)
205             return null;
206         return this.states[this.states.length - 1];
207     },
209     push_state : function (state) {
210         this._save_state();
211         this.states.push(state);
212         this._restore_state();
213         state.load(this.window);
214     },
216     pop_state : function (restore_focus) {
217         if (restore_focus === undefined)
218             restore_focus = true;
219         this.current_state.destroy();
220         this.states.pop();
221         this._restore_state(restore_focus);
222     },
224     pop_all : function () {
225         while (this.states.length > 0) {
226             this.current_state.destroy();
227             this.states.pop();
228         }
229     },
231     remove_state : function (state, restore_focus) {
232         if (restore_focus === undefined)
233             restore_focus = true;
234         var i = this.states.indexOf(state);
235         state.destroy();
236         this.states.splice(i, 1);
237         this._restore_state(restore_focus);
238     },
240     _input_mode_enabled : false,
242     /* If _input_mode_enabled is true, this is set to indicate that
243      * the message area is being temporarily shown instead of the
244      * input box. */
245     _showing_message : false,
247     _message_timer_ID : null,
249     /* This must only be called if _input_mode_enabled is true */
250     _ensure_input_area_showing : function () {
251         if (this._showing_message)
252         {
253             this.window.clearTimeout(this._message_timer_ID);
254             this._message_timer_ID = null;
255             this._showing_message = false;
256             this._switch_to_input_mode();
257         }
258     },
260     /* This must only be called if _input_mode_enabled is true */
261     _ensure_message_area_showing : function () {
262         if (this._showing_message)
263             this.window.clearTimeout(this._message_timer_ID);
264         else {
265             this._showing_message = true;
266             this._switch_to_message_mode();
267         }
268         var obj = this;
269         this._message_timer_ID = this.window.setTimeout(function(){
270                 obj._ensure_input_area_showing();
271             }, minibuffer_input_mode_show_message_timeout);
272     },
274     _switch_to_input_mode : function () {
275         this.element.setAttribute("minibuffermode", "input");
276         this.input_element.focus();
277     },
279     _switch_to_message_mode : function () {
280         this.element.setAttribute("minibuffermode", "message");
281     },
283     _restore_state : function (restore_focus) {
284         var s = this.current_state;
285         if (s) {
286             if (!this._input_mode_enabled)
287             {
288                 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
289                 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
290                 this._input_mode_enabled = true;
291                 this._switch_to_input_mode();
292             }
293             this.window.keyboard.set_override_keymap(s.keymap);
294             this._input_text = s.input;
295             this.prompt = s.prompt;
296             this._set_selection(s.selection_start, s.selection_end);
297         } else {
298             if (this._input_mode_enabled)
299             {
300                 this._input_mode_enabled = false;
301                 if (!this._showing_message)
302                     this._switch_to_message_mode();
303                 else {
304                     this.window.clearTimeout(this._message_timer_ID);
305                     this._message_timer_ID = null;
306                     this._showing_message = false;
307                 }
308                 if (restore_focus)
309                 {
310                     if (this.saved_focused_element)
311                         set_focus_no_scroll(this.window, this.saved_focused_element);
312                     else if (this.saved_focused_frame)
313                         set_focus_no_scroll(this.window, this.saved_focused_frame);
314                 }
315                 this.saved_focused_element = null;
316                 this.saved_focused_frame = null;
317                 this.window.keyboard.set_override_keymap(null);
318             }
319         }
320     },
322     _save_state : function () {
323         var s = this.current_state;
324         if (s)
325         {
326             s.input = this._input_text;
327             s.prompt = this.prompt;
328             s.selection_start = this._selection_start;
329             s.selection_end = this._selection_end;
330             s.unload(this.window);
331         }
332     },
334     insert_before : function (element) {
335         this.element.parentNode.insertBefore(element, this.element);
336     }
339 function minibuffer_initialize_window(window)
341     window.minibuffer = new minibuffer(window);
344 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
346 function minibuffer_window_close_handler(window) {
347     window.minibuffer.pop_all();
349 add_hook("window_close_hook", minibuffer_window_close_handler);
351 /* Note: This is concise, but doesn't seem to be useful in practice,
352  * because nothing can be done with the state alone. */
353 minibuffer.prototype.check_state = function(type) {
354     var s = this.current_state;
355     if (!(s instanceof type))
356         throw new Error("Invalid minibuffer state.");
357     return s;