2 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3 * (C) Copyright 2009 John J. Foerch
5 * Use, modification, and distribution are subject to the terms specified in the
11 // This should only be used for minibuffer states where it makes
12 // sense. In particular, it should not be used if additional cleanup
14 function minibuffer_abort (window) {
15 var m = window.minibuffer;
16 var s = m.current_state;
18 throw "Invalid minibuffer state";
20 input_sequence_abort.call(window);
22 interactive("minibuffer-abort", null, function (I) { minibuffer_abort(I.window); });
24 define_builtin_commands("minibuffer-",
25 function (I, command) {
28 if (m._input_mode_enabled) {
29 m._restore_normal_state();
30 var e = m.input_element;
31 var c = e.controllers.getControllerForCommand(command);
33 m.ignore_input_events = true;
34 if (c && c.isCommandEnabled(command))
37 m.ignore_input_events = false;
39 var s = m.current_state;
40 if (s.ran_minibuffer_command)
41 s.ran_minibuffer_command(m, command);
44 /* Ignore exceptions. */
47 function (I) { //XXX: need return??
48 I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
50 function (I) I.minibuffer.current_state.mark_active,
55 * minibuffer_state: abstact base class for minibuffer states.
57 function minibuffer_state (keymap, use_input_mode) {
59 this.use_input_mode = use_input_mode;
61 minibuffer_state.prototype = {
63 unload: function () {},
64 destroy: function () {}
68 function minibuffer_message_state (keymap, message, destroy_function) {
69 minibuffer_state.call(this, keymap, false);
70 this._message = message;
72 this.destroy = destroy_function;
74 minibuffer_message_state.prototype = {
75 __proto__: minibuffer_state.prototype,
76 load: function (window) {
77 minibuffer_state.prototype.load.call(this, window);
80 unload: function (window) {
82 minibuffer_state.prototype.unload.call(this, window);
84 get message () { return this._message; },
87 this.window.minibuffer._restore_normal_state();
88 this.window.minibuffer._show(this._message);
94 function minibuffer_input_state (window, keymap, prompt, input, selection_start, selection_end) {
95 minibuffer_state.call(this, keymap, true);
102 this.selection_start = selection_start;
104 this.selection_start = 0;
106 this.selection_end = selection_end;
108 this.selection_end = this.selection_start;
109 window.input.begin_recursion();
111 minibuffer_input_state.prototype = {
112 __proto__: minibuffer_state.prototype,
114 destroy: function (window) {
115 window.input.end_recursion();
116 minibuffer_state.prototype.destroy.call(this, window);
122 * The parameter `args' is an object specifying the arguments for
123 * basic_minibuffer_state. The following properties of args must/may
128 * initial_value: [optional] specifies the initial text
130 * select: [optional] specifies to select the initial text if set to non-null
132 define_keywords("$keymap", "$prompt", "$initial_value", "$select");
133 function basic_minibuffer_state (window) {
134 keywords(arguments, $keymap = minibuffer_base_keymap);
135 var initial_value = arguments.$initial_value || "";
136 var sel_start, sel_end;
137 if (arguments.$select) {
139 sel_end = initial_value.length;
141 sel_start = sel_end = initial_value.length;
143 minibuffer_input_state.call(this, window, arguments.$keymap,
144 arguments.$prompt, initial_value,
147 basic_minibuffer_state.prototype.__proto__ = minibuffer_input_state.prototype;
150 define_variable("minibuffer_input_mode_show_message_timeout", 1000,
151 "Time duration (in milliseconds) to flash minibuffer messages while in "+
152 "minibuffer input mode.");
155 function minibuffer (window) {
156 this.element = window.document.getElementById("minibuffer");
157 this.output_element = window.document.getElementById("minibuffer-message");
158 this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
159 this.input_element = window.document.getElementById("minibuffer-input");
161 this.input_element.inputField.addEventListener("blur",
163 if (m.active && m._input_mode_enabled && !m._showing_message) {
164 window.setTimeout(function () {
165 m.input_element.inputField.focus();
169 this.input_element.addEventListener("input",
171 if (m.ignore_input_events || !m._input_mode_enabled)
173 var s = m.current_state;
180 // Ensure that the input area will have focus if a message is
181 // currently being flashed so that the default handler for key
182 // events will properly add text to the input area.
183 window.addEventListener("keydown",
185 if (m._input_mode_enabled && m._showing_message)
186 m._restore_normal_state();
188 this.window = window;
189 this.last_message = "";
193 minibuffer.prototype = {
194 constructor: minibuffer.constructor,
195 get _selection_start () { return this.input_element.selectionStart; },
196 get _selection_end () { return this.input_element.selectionEnd; },
197 get _input_text () { return this.input_element.value; },
198 set _input_text (text) { this.input_element.value = text; },
199 get prompt () { return this.input_prompt_element.value; },
200 set prompt (s) { this.input_prompt_element.value = s; },
202 set_input_state: function (x) {
203 this._input_text = x[0];
204 this._set_selection(x[1], x[2]);
207 _set_selection: function (start, end) {
209 start = this._input_text.length;
211 end = this._input_text.length;
212 this.input_element.setSelectionRange(start,end);
215 /* Saved focus state */
216 saved_focused_frame: null,
217 saved_focused_element: null,
221 current_message: null,
223 /* This method will display the specified string in the
224 * minibuffer, without recording it in any log/Messages buffer. */
225 show: function (str, force) {
226 if (!this.active || force) {
227 this.current_message = str;
232 _show: function (str, force) {
233 if (this.last_message != str) {
234 this.output_element.value = str;
235 this.last_message = str;
239 message: function (str) {
240 /* TODO: add the message to a *Messages* buffer, and/or
241 * possibly dump them to the console. */
242 this.show(str, true /* force */);
244 if (str.length > 0 && this.active)
245 this._flash_temporary_message();
249 this.current_message = null;
251 this._show(this.default_message);
254 set_default_message: function (str) {
255 this.default_message = str;
256 if (this.current_message == null)
260 get current_state () {
261 if (this.states.length == 0)
263 return this.states[this.states.length - 1];
266 push_state: function (state) {
268 this.states.push(state);
269 this._restore_state();
272 pop_state: function () {
273 this.current_state.destroy(this.window);
275 this._restore_state();
278 pop_all: function () {
279 while (this.states.length > 0) {
280 this.current_state.destroy(this.window);
285 remove_state: function (state) {
286 var i = this.states.indexOf(state);
289 var was_current = (i == (this.states.length - 1));
290 state.destroy(this.window);
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,
309 // then why does it have an else condition?
310 _restore_normal_state: function () {
311 if (this._showing_message) {
312 this.window.clearTimeout(this._message_timer_ID);
313 this._message_timer_ID = null;
314 this._showing_message = false;
316 if (this._input_mode_enabled)
317 this._switch_to_input_mode();
319 // assumes that anything other than an input state is a
320 // minibuffer_message_state.
321 this._show(this.current_state._message);
325 /* This must only be called if _input_mode_enabled is true */
326 _flash_temporary_message: function () {
327 if (this._showing_message)
328 this.window.clearTimeout(this._message_timer_ID);
330 this._showing_message = true;
331 if (this._input_mode_enabled)
332 this._switch_to_message_mode();
335 this._message_timer_ID = this.window.setTimeout(function () {
336 obj._restore_normal_state();
337 }, minibuffer_input_mode_show_message_timeout);
340 _switch_to_input_mode: function () {
341 this.element.setAttribute("minibuffermode", "input");
342 this.input_element.inputField.focus();
345 _switch_to_message_mode: function () {
346 this.element.setAttribute("minibuffermode", "message");
349 _restore_state: function () {
350 var s = this.current_state;
351 var want_input_mode = false;
354 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
355 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
357 if (s.use_input_mode) {
358 want_input_mode = true;
359 this._input_text = s.input;
360 this.prompt = s.prompt;
361 this._set_selection(s.selection_start, s.selection_end);
363 this._show(s._message);
366 this.window.input.current.override_keymap = s.keymap;
371 this.window.input.current.override_keymap = null;
372 if (this.saved_focused_element)
373 set_focus_no_scroll(this.window, this.saved_focused_element);
374 else if (this.saved_focused_frame)
375 set_focus_no_scroll(this.window, this.saved_focused_frame);
376 this.window.buffers.current.browser.focus();
377 this.saved_focused_element = null;
378 this.saved_focused_frame = null;
379 this._show(this.current_message || this.default_message);
382 var in_input_mode = this._input_mode_enabled && !this._showing_message;
383 if (this._showing_message) {
384 this.window.clearTimeout(this._message_timer_ID);
385 this._message_timer_ID = null;
386 this._showing_message = false;
388 if (want_input_mode && !in_input_mode)
389 this._switch_to_input_mode();
390 else if (!want_input_mode && in_input_mode)
391 this._switch_to_message_mode();
392 this._input_mode_enabled = want_input_mode;
395 _save_state: function () {
396 var s = this.current_state;
398 if (s.use_input_mode) {
399 s.input = this._input_text;
400 s.prompt = this.prompt;
401 s.selection_start = this._selection_start;
402 s.selection_end = this._selection_end;
404 s.unload(this.window);
408 insert_before: function (element) {
409 this.element.parentNode.insertBefore(element, this.element);
414 function minibuffer_initialize_window (window) {
415 window.minibuffer = new minibuffer(window);
417 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
420 function minibuffer_window_close_handler (window) {
421 window.minibuffer.pop_all();
423 add_hook("window_close_hook", minibuffer_window_close_handler);
426 /* Note: This is concise, but doesn't seem to be useful in practice,
427 * because nothing can be done with the state alone. */
428 minibuffer.prototype.check_state = function (type) {
429 var s = this.current_state;
430 if (!(s instanceof type))
431 throw new Error("Invalid minibuffer state.");
435 minibuffer.prototype.show_wait_message = function (initial_message, destroy_function) {
436 var s = new minibuffer_message_state(minibuffer_message_keymap, initial_message, destroy_function);
441 minibuffer.prototype.wait_for = function (message, coroutine) {
442 var cc = yield CONTINUATION;
444 var s = this.show_wait_message(message, function () { if (!done) cc.throw(abort()); });
447 result = yield coroutine;
450 this.remove_state(s);
452 yield co_return(result);
455 provide("minibuffer");