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
12 * minibuffer_state: abstact base class for minibuffer states.
14 function minibuffer_state (minibuffer, keymap) {
15 this.minibuffer = minibuffer;
18 minibuffer_state.prototype = {
19 constructor: minibuffer_state,
21 unload: function () {},
22 destroy: function () {}
27 * minibuffer_message_state: base class for minibuffer states which do not
28 * use the input element, but still may use a keymap.
30 function minibuffer_message_state (minibuffer, keymap, message, cleanup_function) {
31 minibuffer_state.call(this, minibuffer, keymap);
32 this._message = message;
33 this.cleanup_function = cleanup_function;
35 minibuffer_message_state.prototype = {
36 constructor: minibuffer_message_state,
37 __proto__: minibuffer_state.prototype,
39 get message () { return this._message; },
41 this.minibuffer._restore_normal_state();
42 this.minibuffer._show(this._message);
45 minibuffer_state.prototype.load.call(this);
46 this.minibuffer._show(this.message);
48 cleanup_function: null,
49 destroy: function () {
50 if (this.cleanup_function)
51 this.cleanup_function();
52 minibuffer_state.prototype.destroy.call(this);
58 * minibuffer_input_state: base class for minibuffer states which use the
61 function minibuffer_input_state (minibuffer, keymap, prompt, input, selection_start, selection_end) {
62 minibuffer_state.call(this, minibuffer, keymap);
69 this.selection_start = selection_start;
71 this.selection_start = 0;
73 this.selection_end = selection_end;
75 this.selection_end = this.selection_start;
76 this.minibuffer.window.input.begin_recursion();
78 minibuffer_input_state.prototype = {
79 constructor: minibuffer_input_state,
80 __proto__: minibuffer_state.prototype,
83 minibuffer_state.prototype.load.call(this);
84 this.minibuffer._input_text = this.input;
85 this.minibuffer.prompt = this.prompt;
86 this.minibuffer._set_selection(this.selection_start,
90 this.input = this.minibuffer._input_text;
91 this.prompt = this.minibuffer.prompt;
92 this.selection_start = this.minibuffer._selection_start;
93 this.selection_end = this.minibuffer._selection_end;
94 minibuffer_state.prototype.unload.call(this);
96 destroy: function () {
97 this.minibuffer.window.input.end_recursion();
98 minibuffer_state.prototype.destroy.call(this);
104 * The parameter `args' is an object specifying the arguments for
105 * basic_minibuffer_state. The following properties of args must/may
110 * initial_value: [optional] specifies the initial text
112 * select: [optional] specifies to select the initial text if set to non-null
114 define_keywords("$keymap", "$prompt", "$initial_value", "$select");
115 function basic_minibuffer_state (minibuffer) {
116 keywords(arguments, $keymap = minibuffer_base_keymap);
117 var initial_value = arguments.$initial_value || "";
118 var sel_start, sel_end;
119 if (arguments.$select) {
121 sel_end = initial_value.length;
123 sel_start = sel_end = initial_value.length;
125 minibuffer_input_state.call(this, minibuffer, arguments.$keymap,
126 arguments.$prompt, initial_value,
129 basic_minibuffer_state.prototype = {
130 constructor: basic_minibuffer_state,
131 __proto__: minibuffer_input_state.prototype
135 define_variable("minibuffer_input_mode_show_message_timeout", 1000,
136 "Time duration (in milliseconds) to flash minibuffer messages while in "+
137 "minibuffer input mode.");
140 function minibuffer (window) {
141 this.element = window.document.getElementById("minibuffer");
142 this.output_element = window.document.getElementById("minibuffer-message");
143 this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
144 this.input_element = window.document.getElementById("minibuffer-input");
146 this.input_element.inputField.addEventListener("blur",
148 if (m.active && m._input_mode_enabled && !m._showing_message) {
149 window.setTimeout(function () {
150 m.input_element.inputField.focus();
154 this.input_element.addEventListener("input",
156 if (m.ignore_input_events || !m._input_mode_enabled)
158 var s = m.current_state;
165 // Ensure that the input area will have focus if a message is
166 // currently being flashed so that the default handler for key
167 // events will properly add text to the input area.
168 window.addEventListener("keydown",
170 if (m._input_mode_enabled && m._showing_message)
171 m._restore_normal_state();
173 this.window = window;
174 this.last_message = "";
177 minibuffer.prototype = {
178 constructor: minibuffer,
179 get _selection_start () { return this.input_element.selectionStart; },
180 get _selection_end () { return this.input_element.selectionEnd; },
181 get _input_text () { return this.input_element.value; },
182 set _input_text (text) { this.input_element.value = text; },
183 get prompt () { return this.input_prompt_element.value; },
184 set prompt (s) { this.input_prompt_element.value = s; },
186 set_input_state: function (x) {
187 this._input_text = x[0];
188 this._set_selection(x[1], x[2]);
191 _set_selection: function (start, end) {
193 start = this._input_text.length;
195 end = this._input_text.length;
196 this.input_element.setSelectionRange(start,end);
199 /* Saved focus state */
200 saved_focused_frame: null,
201 saved_focused_element: null,
205 current_message: null,
207 /* This method will display the specified string in the
208 * minibuffer, without recording it in any log/Messages buffer. */
209 show: function (str, force) {
210 if (!this.active || force) {
211 this.current_message = str;
216 _show: function (str) {
217 if (this.last_message != str) {
218 this.output_element.value = str;
219 this.last_message = str;
223 message: function (str) {
224 /* TODO: add the message to a *Messages* buffer, and/or
225 * possibly dump them to the console. */
229 this.show(str, true /* force */);
231 this._flash_temporary_message();
236 this.current_message = null;
238 this._show(this.default_message);
241 set_default_message: function (str) {
242 this.default_message = str;
243 if (this.current_message == null)
247 get current_state () {
248 if (! this.states[0])
250 return this.states[this.states.length - 1];
253 push_state: function (state) {
255 this.states.push(state);
256 this._restore_state();
259 pop_state: function () {
260 this.current_state.destroy();
262 this._restore_state();
265 pop_all: function () {
267 while ((state = this.current_state)) {
273 //XXX: breaking stack discipline can cause incorrect
274 // input recursion termination
275 remove_state: function (state) {
276 var i = this.states.indexOf(state);
279 var was_current = (i == (this.states.length - 1));
281 this.states.splice(i, 1);
283 this._restore_state();
286 _input_mode_enabled: false,
290 /* If _input_mode_enabled is true, this is set to indicate that
291 * the message area is being temporarily shown instead of the
293 _showing_message: false,
295 _message_timer_ID: null,
297 /* This must only be called if _input_mode_enabled is true */
298 //XXX: if it must only be called if _input_mode_enabled is true, then
299 // why does it have an else condition for handling
300 // minibuffer_message_state states?
301 _restore_normal_state: function () {
302 if (this._showing_message) {
303 this.window.clearTimeout(this._message_timer_ID);
304 this._message_timer_ID = null;
305 this._showing_message = false;
307 if (this._input_mode_enabled)
308 this._switch_to_input_mode();
310 // assumes that anything other than an input state is a
311 // minibuffer_message_state.
312 this._show(this.current_state._message);
316 /* This must only be called if _input_mode_enabled is true */
317 _flash_temporary_message: function () {
318 if (this._showing_message)
319 this.window.clearTimeout(this._message_timer_ID);
321 this._showing_message = true;
322 if (this._input_mode_enabled)
323 this._switch_to_message_mode();
326 this._message_timer_ID = this.window.setTimeout(function () {
327 obj._restore_normal_state();
328 }, minibuffer_input_mode_show_message_timeout);
331 _switch_to_input_mode: function () {
332 this.element.setAttribute("minibuffermode", "input");
333 this.input_element.inputField.focus();
336 _switch_to_message_mode: function () {
337 this.element.setAttribute("minibuffermode", "message");
340 _restore_state: function () {
341 var s = this.current_state;
344 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
345 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
352 this.window.buffers.current.browser.focus();
353 if (this.saved_focused_element && this.saved_focused_element.focus)
354 set_focus_no_scroll(this.window, this.saved_focused_element);
355 else if (this.saved_focused_frame)
356 set_focus_no_scroll(this.window, this.saved_focused_frame);
357 this.saved_focused_element = null;
358 this.saved_focused_frame = null;
359 this._show(this.current_message || this.default_message);
362 if (this._showing_message) {
363 this.window.clearTimeout(this._message_timer_ID);
364 this._message_timer_ID = null;
365 this._showing_message = false;
367 var want_input_mode = s instanceof minibuffer_input_state;
368 var in_input_mode = this._input_mode_enabled && !this._showing_message;
369 if (want_input_mode && !in_input_mode)
370 this._switch_to_input_mode();
371 else if (!want_input_mode && in_input_mode)
372 this._switch_to_message_mode();
373 this._input_mode_enabled = want_input_mode;
376 _save_state: function () {
377 var s = this.current_state;
382 insert_before: function (element) {
383 this.element.parentNode.insertBefore(element, this.element);
388 function minibuffer_initialize_window (window) {
389 window.minibuffer = new minibuffer(window);
391 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
394 function minibuffer_window_close_handler (window) {
395 window.minibuffer.pop_all();
397 add_hook("window_close_hook", minibuffer_window_close_handler);
400 /* Note: This is concise, but doesn't seem to be useful in practice,
401 * because nothing can be done with the state alone. */
402 minibuffer.prototype.check_state = function (type) {
403 var s = this.current_state;
404 if (!(s instanceof type))
405 throw new Error("Invalid minibuffer state.");
409 minibuffer.prototype.show_wait_message = function (initial_message, cleanup_function) {
410 var s = new minibuffer_message_state(this, minibuffer_message_keymap, initial_message, cleanup_function);
415 minibuffer.prototype.wait_for = function (message, coroutine) {
416 var cc = yield CONTINUATION;
418 var s = this.show_wait_message(message, function () { if (!done) cc.throw(abort()); });
421 result = yield coroutine;
424 this.remove_state(s);
426 yield co_return(result);
430 // This should only be used for minibuffer states where it makes
431 // sense. In particular, it should not be used if additional cleanup
433 function minibuffer_abort (window) {
434 var m = window.minibuffer;
435 var s = m.current_state;
437 throw "Invalid minibuffer state";
439 input_sequence_abort.call(window);
441 interactive("minibuffer-abort", null, function (I) { minibuffer_abort(I.window); });
443 define_builtin_commands("minibuffer-",
444 function (I, command) {
446 var m = I.minibuffer;
447 if (m._input_mode_enabled) {
448 m._restore_normal_state();
449 var e = m.input_element;
450 var c = e.controllers.getControllerForCommand(command);
452 m.ignore_input_events = true;
453 if (c && c.isCommandEnabled(command))
454 c.doCommand(command);
456 m.ignore_input_events = false;
458 var s = m.current_state;
459 if (s.ran_minibuffer_command)
460 s.ran_minibuffer_command(m, command);
463 /* Ignore exceptions. */
466 function (I) { //XXX: need return??
467 I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
469 function (I) I.minibuffer.current_state.mark_active,
473 provide("minibuffer");