2 * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4 * Use, modification, and distribution are subject to the terms specified in the
8 /* This should only be used for minibuffer states where it makes
9 * sense. In particular, it should not be used if additional cleanup
11 function minibuffer_abort (window)
13 var m = window.minibuffer;
14 var s = m.current_state;
16 throw "Invalid minibuffer state";
19 interactive("minibuffer-abort", null, function (I) {minibuffer_abort(I.window);});
21 define_builtin_commands(
23 function (I, command) {
26 if (m._input_mode_enabled)
28 m._restore_normal_state();
29 var e = m.input_element;
30 var c = e.controllers.getControllerForCommand(command);
32 m.ignore_input_events = true;
33 if (c && c.isCommandEnabled(command))
36 m.ignore_input_events = false;
38 var s = m.current_state;
39 if (s.ran_minibuffer_command)
40 s.ran_minibuffer_command(command);
44 /* Ignore exceptions. */
48 I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
51 function (I) I.minibuffer.current_state.mark_active,
55 function minibuffer_state(keymap, use_input_mode)
58 this.use_input_mode = use_input_mode;
60 minibuffer_state.prototype.load = function () {}
61 minibuffer_state.prototype.unload = function () {}
62 minibuffer_state.prototype.destroy = function () {}
64 function minibuffer_message_state(keymap, message, destroy_function)
66 minibuffer_state.call(this, keymap, false);
67 this._message = message;
69 this.destroy = destroy_function;
71 minibuffer_message_state.prototype = {
72 __proto__: minibuffer_state.prototype,
73 load : function (window) {
76 unload : function (window) {
79 get message () { return this._message; },
82 this.window.minibuffer._restore_normal_state();
83 this.window.minibuffer._show(this._message);
88 function minibuffer_input_state(keymap, prompt, input, selection_start, selection_end)
96 this.selection_start = selection_start;
98 this.selection_start = 0;
100 this.selection_end = selection_end;
102 this.selection_end = this.selection_start;
104 minibuffer_state.call(this, keymap, true);
106 minibuffer_input_state.prototype.__proto__ = minibuffer_state.prototype;
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("$prompt", "$initial_value", "$select");
121 function basic_minibuffer_state()
124 var initial_value = arguments.$initial_value || "";
125 var sel_start, sel_end;
126 if (arguments.$select)
129 sel_end = initial_value.length;
131 sel_start = sel_end = initial_value.length;
133 minibuffer_input_state.call(this, minibuffer_base_keymap,
134 arguments.$prompt, initial_value,
137 basic_minibuffer_state.prototype.__proto__ = minibuffer_input_state.prototype; // inherit from minibuffer_state
139 define_variable("minibuffer_input_mode_show_message_timeout", 1000, "Time duration (in milliseconds) to flash minibuffer messages while in minibuffer input mode.");
141 function minibuffer (window)
143 this.element = window.document.getElementById("minibuffer");
144 this.output_element = window.document.getElementById("minibuffer-message");
145 this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
146 this.input_element = window.document.getElementById("minibuffer-input");
148 this.input_element.inputField.addEventListener("blur", function() {
149 if (m.active && m._input_mode_enabled && !m._showing_message)
153 m.input_element.inputField.focus();
157 this.input_element.addEventListener("input", function(e) {
158 if (m.ignore_input_events || !m._input_mode_enabled)
160 var s = m.current_state;
167 // Ensure that the input area will have focus if a message is
168 // currently being flashed so that the default handler for key
169 // events will properly add text to the input area.
170 window.addEventListener("keydown", function (e) {
171 if (m._input_mode_enabled && m._showing_message)
172 m._restore_normal_state();
174 this.window = window;
175 this.last_message = "";
179 minibuffer.prototype = {
180 constructor : minibuffer.constructor,
181 get _selection_start () { return this.input_element.selectionStart; },
182 get _selection_end () { return this.input_element.selectionEnd; },
183 get _input_text () { return this.input_element.value; },
184 set _input_text (text) { this.input_element.value = text; },
185 get prompt () { return this.input_prompt_element.value; },
186 set prompt (s) { this.input_prompt_element.value = s; },
188 set_input_state : function(x) {
189 this._input_text = x[0];
190 this._set_selection(x[1], x[2]);
193 _set_selection : function (start, end) {
195 start = this._input_text.length;
197 end = this._input_text.length;
198 this.input_element.setSelectionRange(start,end);
201 /* Saved focus state */
202 saved_focused_frame : null,
203 saved_focused_element : null,
205 default_message : "",
207 current_message : null,
209 /* This method will display the specified string in the
210 * minibuffer, without recording it in any log/Messages buffer. */
211 show : function (str, force) {
212 if (!this.active || force) {
213 this.current_message = str;
218 _show : function (str, force) {
219 if (this.last_message != str)
221 this.output_element.value = str;
222 this.last_message = str;
226 message : function (str) {
227 /* TODO: add the message to a *Messages* buffer, and/or
228 * possibly dump them to the console. */
229 this.show(str, true /* force */);
231 if (str.length > 0 && this.active)
232 this._flash_temporary_message();
234 clear : function () {
235 this.current_message = null;
237 this._show(this.default_message);
240 set_default_message : function (str) {
241 this.default_message = str;
242 if (this.current_message == null)
246 get current_state () {
247 if (this.states.length == 0)
249 return this.states[this.states.length - 1];
252 push_state : function (state) {
254 this.states.push(state);
255 this._restore_state();
258 pop_state : function () {
259 this.current_state.destroy();
261 this._restore_state();
264 pop_all : function () {
265 while (this.states.length > 0) {
266 this.current_state.destroy();
271 remove_state : function (state) {
272 var i = this.states.indexOf(state);
275 var was_current = (i == (this.states.length - 1));
277 this.states.splice(i, 1);
279 this._restore_state();
282 _input_mode_enabled : false,
286 /* If _input_mode_enabled is true, this is set to indicate that
287 * the message area is being temporarily shown instead of the
289 _showing_message : false,
291 _message_timer_ID : null,
293 /* This must only be called if _input_mode_enabled is true */
294 _restore_normal_state : function () {
295 if (this._showing_message)
297 this.window.clearTimeout(this._message_timer_ID);
298 this._message_timer_ID = null;
299 this._showing_message = false;
301 if (this._input_mode_enabled)
302 this._switch_to_input_mode();
304 this._show(this.current_state._message);
308 /* This must only be called if _input_mode_enabled is true */
309 _flash_temporary_message : function () {
310 if (this._showing_message)
311 this.window.clearTimeout(this._message_timer_ID);
313 this._showing_message = true;
314 if (this._input_mode_enabled)
315 this._switch_to_message_mode();
318 this._message_timer_ID = this.window.setTimeout(function(){
319 obj._restore_normal_state();
320 }, minibuffer_input_mode_show_message_timeout);
323 _switch_to_input_mode : function () {
324 this.element.setAttribute("minibuffermode", "input");
325 this.input_element.inputField.focus();
328 _switch_to_message_mode : function () {
329 this.element.setAttribute("minibuffermode", "message");
332 _restore_state : function () {
333 var s = this.current_state;
334 var want_input_mode = false;
337 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
338 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
340 if (s.use_input_mode) {
341 want_input_mode = true;
342 this._input_text = s.input;
343 this.prompt = s.prompt;
344 this._set_selection(s.selection_start, s.selection_end);
346 this._show(s._message);
349 this.window.keyboard.set_override_keymap(s.keymap);
354 this.window.keyboard.set_override_keymap(null);
355 if (this.saved_focused_element)
356 set_focus_no_scroll(this.window, this.saved_focused_element);
357 else if (this.saved_focused_frame)
358 set_focus_no_scroll(this.window, this.saved_focused_frame);
359 this.saved_focused_element = null;
360 this.saved_focused_frame = null;
361 this._show(this.current_message || this.default_message);
364 var in_input_mode = this._input_mode_enabled && !this._showing_message;
365 if (this._showing_message) {
366 this.window.clearTimeout(this._message_timer_ID);
367 this._message_timer_ID = null;
368 this._showing_message = false;
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;
381 if (s.use_input_mode) {
382 s.input = this._input_text;
383 s.prompt = this.prompt;
384 s.selection_start = this._selection_start;
385 s.selection_end = this._selection_end;
387 s.unload(this.window);
391 insert_before : function (element) {
392 this.element.parentNode.insertBefore(element, this.element);
396 function minibuffer_initialize_window(window)
398 window.minibuffer = new minibuffer(window);
401 add_hook("window_initialize_early_hook", minibuffer_initialize_window);
403 function minibuffer_window_close_handler(window) {
404 window.minibuffer.pop_all();
406 add_hook("window_close_hook", minibuffer_window_close_handler);
408 /* Note: This is concise, but doesn't seem to be useful in practice,
409 * because nothing can be done with the state alone. */
410 minibuffer.prototype.check_state = function(type) {
411 var s = this.current_state;
412 if (!(s instanceof type))
413 throw new Error("Invalid minibuffer state.");
417 minibuffer.prototype.show_wait_message = function (initial_message, destroy_function) {
418 var s = new minibuffer_message_state(minibuffer_message_keymap, initial_message, destroy_function);
423 minibuffer.prototype.wait_for = function minibuffer__wait_for(message, coroutine) {
424 var cc = yield CONTINUATION;
426 var s = this.show_wait_message(message, function () { if (!done) cc.throw(abort()); });
429 result = yield coroutine;
433 this.remove_state(s);
435 yield co_return(result);