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) {
12 var m = window.minibuffer;
13 var s = m.current_state;
15 throw "Invalid minibuffer state";
18 interactive("minibuffer-abort", null, function (I) { minibuffer_abort(I.window); });
20 define_builtin_commands("minibuffer-",
21 function (I, command) {
24 if (m._input_mode_enabled) {
25 m._restore_normal_state();
26 var e = m.input_element;
27 var c = e.controllers.getControllerForCommand(command);
29 m.ignore_input_events = true;
30 if (c && c.isCommandEnabled(command))
33 m.ignore_input_events = false;
35 var s = m.current_state;
36 if (s.ran_minibuffer_command)
37 s.ran_minibuffer_command(m, command);
40 /* Ignore exceptions. */
43 function (I) { //XXX: need return??
44 I.minibuffer.current_state.mark_active = !I.minibuffer.current_state.mark_active;
46 function (I) I.minibuffer.current_state.mark_active,
49 function minibuffer_state (keymap, use_input_mode) {
51 this.use_input_mode = use_input_mode;
53 minibuffer_state.prototype.load = function () {};
54 minibuffer_state.prototype.unload = function () {};
55 minibuffer_state.prototype.destroy = function () {};
57 function minibuffer_message_state (keymap, message, destroy_function) {
58 minibuffer_state.call(this, keymap, false);
59 this._message = message;
61 this.destroy = destroy_function;
63 minibuffer_message_state.prototype = {
64 __proto__: minibuffer_state.prototype,
65 load : function (window) {
68 unload : function (window) {
71 get message () { return this._message; },
74 this.window.minibuffer._restore_normal_state();
75 this.window.minibuffer._show(this._message);
80 function minibuffer_input_state (keymap, prompt, input, selection_start, selection_end) {
87 this.selection_start = selection_start;
89 this.selection_start = 0;
91 this.selection_end = selection_end;
93 this.selection_end = this.selection_start;
95 minibuffer_state.call(this, keymap, true);
97 minibuffer_input_state.prototype.__proto__ = minibuffer_state.prototype;
101 * The parameter `args' is an object specifying the arguments for
102 * basic_minibuffer_state. The following properties of args must/may
107 * initial_value: [optional] specifies the initial text
109 * select: [optional] specifies to select the initial text if set to non-null
111 define_keywords("$prompt", "$initial_value", "$select");
112 function basic_minibuffer_state () {
114 var initial_value = arguments.$initial_value || "";
115 var sel_start, sel_end;
116 if (arguments.$select) {
118 sel_end = initial_value.length;
120 sel_start = sel_end = initial_value.length;
122 minibuffer_input_state.call(this, minibuffer_base_keymap,
123 arguments.$prompt, initial_value,
126 basic_minibuffer_state.prototype.__proto__ = minibuffer_input_state.prototype; // inherit from minibuffer_state
129 define_variable("minibuffer_input_mode_show_message_timeout", 1000,
130 "Time duration (in milliseconds) to flash minibuffer messages while in "+
131 "minibuffer input mode.");
134 function minibuffer (window) {
135 this.element = window.document.getElementById("minibuffer");
136 this.output_element = window.document.getElementById("minibuffer-message");
137 this.input_prompt_element = window.document.getElementById("minibuffer-prompt");
138 this.input_element = window.document.getElementById("minibuffer-input");
140 this.input_element.inputField.addEventListener("blur",
142 if (m.active && m._input_mode_enabled && !m._showing_message) {
143 window.setTimeout(function () {
144 m.input_element.inputField.focus();
148 this.input_element.addEventListener("input",
150 if (m.ignore_input_events || !m._input_mode_enabled)
152 var s = m.current_state;
159 // Ensure that the input area will have focus if a message is
160 // currently being flashed so that the default handler for key
161 // events will properly add text to the input area.
162 window.addEventListener("keydown",
164 if (m._input_mode_enabled && m._showing_message)
165 m._restore_normal_state();
167 this.window = window;
168 this.last_message = "";
172 minibuffer.prototype = {
173 constructor : minibuffer.constructor,
174 get _selection_start () { return this.input_element.selectionStart; },
175 get _selection_end () { return this.input_element.selectionEnd; },
176 get _input_text () { return this.input_element.value; },
177 set _input_text (text) { this.input_element.value = text; },
178 get prompt () { return this.input_prompt_element.value; },
179 set prompt (s) { this.input_prompt_element.value = s; },
181 set_input_state : function (x) {
182 this._input_text = x[0];
183 this._set_selection(x[1], x[2]);
186 _set_selection : function (start, end) {
188 start = this._input_text.length;
190 end = this._input_text.length;
191 this.input_element.setSelectionRange(start,end);
194 /* Saved focus state */
195 saved_focused_frame : null,
196 saved_focused_element : null,
198 default_message : "",
200 current_message : null,
202 /* This method will display the specified string in the
203 * minibuffer, without recording it in any log/Messages buffer. */
204 show : function (str, force) {
205 if (!this.active || force) {
206 this.current_message = str;
211 _show : function (str, force) {
212 if (this.last_message != str) {
213 this.output_element.value = str;
214 this.last_message = str;
218 message : function (str) {
219 /* TODO: add the message to a *Messages* buffer, and/or
220 * possibly dump them to the console. */
221 this.show(str, true /* force */);
223 if (str.length > 0 && this.active)
224 this._flash_temporary_message();
227 clear : function () {
228 this.current_message = null;
230 this._show(this.default_message);
233 set_default_message : function (str) {
234 this.default_message = str;
235 if (this.current_message == null)
239 get current_state () {
240 if (this.states.length == 0)
242 return this.states[this.states.length - 1];
245 push_state : function (state) {
247 this.states.push(state);
248 this._restore_state();
251 pop_state : function () {
252 this.current_state.destroy();
254 this._restore_state();
257 pop_all : function () {
258 while (this.states.length > 0) {
259 this.current_state.destroy();
264 remove_state : function (state) {
265 var i = this.states.indexOf(state);
268 var was_current = (i == (this.states.length - 1));
270 this.states.splice(i, 1);
272 this._restore_state();
275 _input_mode_enabled : false,
279 /* If _input_mode_enabled is true, this is set to indicate that
280 * the message area is being temporarily shown instead of the
282 _showing_message : false,
284 _message_timer_ID : null,
286 /* This must only be called if _input_mode_enabled is true */
287 _restore_normal_state : function () {
288 if (this._showing_message) {
289 this.window.clearTimeout(this._message_timer_ID);
290 this._message_timer_ID = null;
291 this._showing_message = false;
293 if (this._input_mode_enabled)
294 this._switch_to_input_mode();
296 this._show(this.current_state._message);
300 /* This must only be called if _input_mode_enabled is true */
301 _flash_temporary_message : function () {
302 if (this._showing_message)
303 this.window.clearTimeout(this._message_timer_ID);
305 this._showing_message = true;
306 if (this._input_mode_enabled)
307 this._switch_to_message_mode();
310 this._message_timer_ID = this.window.setTimeout(function () {
311 obj._restore_normal_state();
312 }, minibuffer_input_mode_show_message_timeout);
315 _switch_to_input_mode : function () {
316 this.element.setAttribute("minibuffermode", "input");
317 this.input_element.inputField.focus();
320 _switch_to_message_mode : function () {
321 this.element.setAttribute("minibuffermode", "message");
324 _restore_state : function () {
325 var s = this.current_state;
326 var want_input_mode = false;
329 this.saved_focused_frame = this.window.document.commandDispatcher.focusedWindow;
330 this.saved_focused_element = this.window.document.commandDispatcher.focusedElement;
332 if (s.use_input_mode) {
333 want_input_mode = true;
334 this._input_text = s.input;
335 this.prompt = s.prompt;
336 this._set_selection(s.selection_start, s.selection_end);
338 this._show(s._message);
341 this.window.keyboard.set_override_keymap(s.keymap);
346 this.window.keyboard.set_override_keymap(null);
347 if (this.saved_focused_element)
348 set_focus_no_scroll(this.window, this.saved_focused_element);
349 else if (this.saved_focused_frame)
350 set_focus_no_scroll(this.window, this.saved_focused_frame);
351 this.saved_focused_element = null;
352 this.saved_focused_frame = null;
353 this._show(this.current_message || this.default_message);
356 var in_input_mode = this._input_mode_enabled && !this._showing_message;
357 if (this._showing_message) {
358 this.window.clearTimeout(this._message_timer_ID);
359 this._message_timer_ID = null;
360 this._showing_message = false;
362 if (want_input_mode && !in_input_mode)
363 this._switch_to_input_mode();
364 else if (!want_input_mode && in_input_mode)
365 this._switch_to_message_mode();
366 this._input_mode_enabled = want_input_mode;
369 _save_state : function () {
370 var s = this.current_state;
372 if (s.use_input_mode) {
373 s.input = this._input_text;
374 s.prompt = this.prompt;
375 s.selection_start = this._selection_start;
376 s.selection_end = this._selection_end;
378 s.unload(this.window);
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, destroy_function) {
410 var s = new minibuffer_message_state(minibuffer_message_keymap, initial_message, destroy_function);
415 minibuffer.prototype.wait_for = function minibuffer__wait_for (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);