2 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2009-2010 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
10 * minibuffer_state: abstact base class for minibuffer states.
12 function minibuffer_state (minibuffer, keymap) {
13 this.minibuffer = minibuffer;
14 this.keymaps = [default_base_keymap, keymap];
16 minibuffer_state.prototype = {
17 constructor: minibuffer_state,
19 unload: function () {},
20 destroy: function () {}
25 * minibuffer_message_state: base class for minibuffer states which do not
26 * use the input element, but still may use a keymap.
28 function minibuffer_message_state (minibuffer, keymap, message, cleanup_function) {
29 minibuffer_state.call(this, minibuffer, keymap);
30 this._message = message;
31 this.cleanup_function = cleanup_function;
34 minibuffer_message_state.prototype = {
35 constructor: minibuffer_message_state,
36 __proto__: minibuffer_state.prototype,
38 get message () { return this._message; },
41 this.minibuffer._restore_normal_state();
42 this.minibuffer._show(this._message);
46 minibuffer_state.prototype.load.call(this);
47 this.minibuffer._show(this.message);
52 minibuffer_state.prototype.unload.call(this);
54 cleanup_function: null,
55 destroy: function () {
56 if (this.cleanup_function)
57 this.cleanup_function();
58 minibuffer_state.prototype.destroy.call(this);
64 * minibuffer_input_state: base class for minibuffer states which use the
67 function minibuffer_input_state (minibuffer, keymap, prompt, input, selection_start, selection_end) {
68 minibuffer_state.call(this, minibuffer, keymap);
75 this.selection_start = selection_start;
77 this.selection_start = 0;
79 this.selection_end = selection_end;
81 this.selection_end = this.selection_start;
83 minibuffer_input_state.prototype = {
84 constructor: minibuffer_input_state,
85 __proto__: minibuffer_state.prototype,
88 minibuffer_state.prototype.load.call(this);
89 this.minibuffer.ignore_input_events = true;
90 this.minibuffer._input_text = this.input;
91 this.minibuffer.ignore_input_events = false;
92 this.minibuffer.prompt = this.prompt;
93 this.minibuffer._set_selection(this.selection_start,
97 this.input = this.minibuffer._input_text;
98 this.prompt = this.minibuffer.prompt;
99 this.selection_start = this.minibuffer._selection_start;
100 this.selection_end = this.minibuffer._selection_end;
101 minibuffer_state.prototype.unload.call(this);
103 destroy: function () {
104 minibuffer_state.prototype.destroy.call(this);
110 * The parameter `args' is an object specifying the arguments for
111 * basic_minibuffer_state. The following properties of args must/may
116 * initial_value: [optional] specifies the initial text
118 * select: [optional] specifies to select the initial text if set to non-null
120 define_keywords("$keymap", "$prompt", "$initial_value", "$select");
121 function basic_minibuffer_state (minibuffer) {
122 keywords(arguments, $keymap = minibuffer_base_keymap);
123 var initial_value = arguments.$initial_value || "";
124 var sel_start, sel_end;
125 if (arguments.$select) {
127 sel_end = initial_value.length;
129 sel_start = sel_end = initial_value.length;
131 minibuffer_input_state.call(this, minibuffer, arguments.$keymap,
132 arguments.$prompt, initial_value,
135 basic_minibuffer_state.prototype = {
136 constructor: basic_minibuffer_state,
137 __proto__: minibuffer_input_state.prototype
141 define_variable("minibuffer_input_mode_show_message_timeout", 1000,
142 "Time duration (in milliseconds) to flash minibuffer messages while in "+
143 "minibuffer input mode.");
146 function minibuffer (window) {
147 this.element = window.document.getElementById("minibuffer");
148 this.output_element = window.document.getElementById("minibuffer-message");
149 this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
150 this.input_element = window.document.getElementById("minibuffer-input");
152 this.input_element.inputField.addEventListener("blur",
154 if (m.active && m._input_mode_enabled && !m._showing_message) {
155 window.setTimeout(function () {
156 m.input_element.inputField.focus();
160 function dispatch_handle_input () {
161 if (m.ignore_input_events || !m._input_mode_enabled)
163 var s = m.current_state;
164 if (s && s.handle_input)
167 this.input_element.addEventListener("input", dispatch_handle_input, true);
168 this.input_element.watch("value",
169 function (prop, oldval, newval) {
170 if (newval != oldval &&
171 !m.ignore_input_events)
173 call_after_timeout(dispatch_handle_input, 0);
177 // Ensure that the input area will have focus if a message is
178 // currently being flashed so that the default handler for key
179 // events will properly add text to the input area.
180 window.addEventListener("keydown",
182 if (m._input_mode_enabled && m._showing_message)
183 m._restore_normal_state();
185 this.window = window;
186 this.last_message = "";
189 minibuffer.prototype = {
190 constructor: minibuffer,
191 toString: function () "#<minibuffer>",
193 get _selection_start () { return this.input_element.selectionStart; },
194 get _selection_end () { return this.input_element.selectionEnd; },
195 get _input_text () { return this.input_element.value; },
196 set _input_text (text) { this.input_element.value = text; },
197 get prompt () { return this.input_prompt_element.value; },
198 set prompt (s) { this.input_prompt_element.value = s; },
200 set_input_state: function (x) {
201 this._input_text = x[0];
202 this._set_selection(x[1], x[2]);
205 _set_selection: function (start, end) {
207 start = this._input_text.length;
209 end = this._input_text.length;
210 this.input_element.setSelectionRange(start,end);
213 /* Saved focus state */
214 saved_focused_frame: null,
215 saved_focused_element: null,
219 current_message: null,
221 /* This method will display the specified string in the
222 * minibuffer, without recording it in any log/Messages buffer. */
223 show: function (str, force) {
224 if (!this.active || force) {
225 this.current_message = str;
230 _show: function (str) {
231 if (this.last_message != str) {
232 this.output_element.value = str;
233 this.last_message = str;
237 message: function (str) {
241 this.show(str, true /* force */);
243 this._flash_temporary_message();
248 this.current_message = null;
250 this._show(this.default_message);
253 set_default_message: function (str) {
254 this.default_message = str;
255 if (this.current_message == null)
259 get current_state () {
260 if (! this.states[0])
262 return this.states[this.states.length - 1];
265 push_state: function (state) {
267 this.states.push(state);
268 this._restore_state();
271 pop_state: function () {
272 this.current_state.destroy();
274 this._restore_state();
277 pop_all: function () {
279 while ((state = this.current_state)) {
285 remove_state: function (state) {
286 var i = this.states.indexOf(state);
289 var was_current = (i == (this.states.length - 1));
291 this.states.splice(i, 1);
293 this._restore_state();
296 _input_mode_enabled: false,
300 /* If _input_mode_enabled is true, this is set to indicate that
301 * the message area is being temporarily shown instead of the
303 _showing_message: false,
305 _message_timer_ID: null,
307 /* This must only be called if _input_mode_enabled is true */
308 //XXX: if it must only be called if _input_mode_enabled is true, then
309 // why does it have an else condition for handling
310 // minibuffer_message_state states?
311 _restore_normal_state: function () {
312 if (this._showing_message) {
313 this.window.clearTimeout(this._message_timer_ID);
314 this._message_timer_ID = null;
315 this._showing_message = false;
317 if (this._input_mode_enabled)
318 this._switch_to_input_mode();
320 // assumes that anything other than an input state is a
321 // minibuffer_message_state.
322 this._show(this.current_state._message);
326 /* This must only be called if _input_mode_enabled is true */
327 _flash_temporary_message: function () {
328 if (this._showing_message)
329 this.window.clearTimeout(this._message_timer_ID);
331 this._showing_message = true;
332 if (this._input_mode_enabled)
333 this._switch_to_message_mode();
336 this._message_timer_ID = this.window.setTimeout(function () {
337 obj._restore_normal_state();
338 }, minibuffer_input_mode_show_message_timeout);
341 _switch_to_input_mode: function () {
342 this.element.setAttribute("minibuffermode", "input");
343 this.input_element.inputField.focus();
346 _switch_to_message_mode: function () {
347 this.element.setAttribute("minibuffermode", "message");
350 _restore_state: function () {
351 var s = this.current_state;
353 this.window.buffers.save_focus();
359 this.window.buffers.restore_focus();
360 this._show(this.current_message || this.default_message);
363 if (this._showing_message) {
364 this.window.clearTimeout(this._message_timer_ID);
365 this._message_timer_ID = null;
366 this._showing_message = false;
368 var want_input_mode = s instanceof minibuffer_input_state;
369 var in_input_mode = this._input_mode_enabled && !this._showing_message;
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;
377 _save_state: function () {
378 var s = this.current_state;
383 insert_before: function (element) {
384 this.element.parentNode.insertBefore(element, this.element);
389 function minibuffer_initialize_window (window) {
390 window.minibuffer = new minibuffer(window);
392 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
395 function minibuffer_window_close_handler (window) {
396 window.minibuffer.pop_all();
398 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.");
410 minibuffer.prototype.show_wait_message = function (initial_message, cleanup_function) {
411 var s = new minibuffer_message_state(this, minibuffer_message_keymap, initial_message, cleanup_function);
416 minibuffer.prototype.wait_for = function (message, coroutine) {
417 let promise = spawn(coroutine);
418 var s = this.show_wait_message(message, promise.cancel);
419 let cleanup = s.minibuffer.remove_state.bind(s);
420 promise.then(cleanup, cleanup);
425 // This should only be used for minibuffer states where it makes
426 // sense. In particular, it should not be used if additional cleanup
428 function minibuffer_abort (window) {
429 var m = window.minibuffer;
430 var s = m.current_state;
432 throw "Invalid minibuffer state";
435 interactive("minibuffer-abort", null, function (I) { minibuffer_abort(I.window); });
439 * Minibuffer-annotation-mode
442 var minibuffer_annotation_mode = {
443 stylesheet: "chrome://conkeror-gui/content/minibuffer-annotation.css",
446 register: function (user) {
447 this.users.push(user);
448 this._switch_if_needed();
450 unregister: function (user) {
451 var i = this.users.indexOf(user);
453 this.users.splice(i, 1);
454 this._switch_if_needed();
456 _switch_if_needed: function (user) {
457 if (this.enabled && this.users.length == 0)
459 if (!this.enabled && this.users.length != 0)
462 _enable: function () {
463 register_agent_stylesheet(this.stylesheet);
466 _disable: function () {
467 unregister_agent_stylesheet(this.stylesheet);
468 this.enabled = false;
472 provide("minibuffer");