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
;
33 minibuffer_message_state
.prototype = {
34 constructor: minibuffer_message_state
,
35 __proto__
: minibuffer_state
.prototype,
37 get message () { return this._message
; },
39 this.minibuffer
._restore_normal_state();
40 this.minibuffer
._show(this._message
);
43 minibuffer_state
.prototype.load
.call(this);
44 this.minibuffer
._show(this.message
);
46 cleanup_function
: null,
47 destroy: function () {
48 if (this.cleanup_function
)
49 this.cleanup_function();
50 minibuffer_state
.prototype.destroy
.call(this);
56 * minibuffer_input_state: base class for minibuffer states which use the
59 function minibuffer_input_state (minibuffer
, keymap
, prompt
, input
, selection_start
, selection_end
) {
60 minibuffer_state
.call(this, minibuffer
, keymap
);
67 this.selection_start
= selection_start
;
69 this.selection_start
= 0;
71 this.selection_end
= selection_end
;
73 this.selection_end
= this.selection_start
;
74 this.minibuffer
.window
.input
.begin_recursion();
76 minibuffer_input_state
.prototype = {
77 constructor: minibuffer_input_state
,
78 __proto__
: minibuffer_state
.prototype,
81 minibuffer_state
.prototype.load
.call(this);
82 this.minibuffer
.ignore_input_events
= true;
83 this.minibuffer
._input_text
= this.input
;
84 this.minibuffer
.ignore_input_events
= false;
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 function dispatch_handle_input () {
155 if (m
.ignore_input_events
|| !m
._input_mode_enabled
)
157 var s
= m
.current_state
;
158 if (s
&& s
.handle_input
)
161 this.input_element
.addEventListener("input", dispatch_handle_input
, true);
162 this.input_element
.watch("value",
163 function (prop
, oldval
, newval
) {
164 if (newval
!= oldval
&&
165 !m
.ignore_input_events
)
167 call_after_timeout(dispatch_handle_input
, 0);
171 // Ensure that the input area will have focus if a message is
172 // currently being flashed so that the default handler for key
173 // events will properly add text to the input area.
174 window
.addEventListener("keydown",
176 if (m
._input_mode_enabled
&& m
._showing_message
)
177 m
._restore_normal_state();
179 this.window
= window
;
180 this.last_message
= "";
183 minibuffer
.prototype = {
184 constructor: minibuffer
,
185 toString: function () "#<minibuffer>",
187 get _selection_start () { return this.input_element
.selectionStart
; },
188 get _selection_end () { return this.input_element
.selectionEnd
; },
189 get _input_text () { return this.input_element
.value
; },
190 set _input_text (text
) { this.input_element
.value
= text
; },
191 get prompt () { return this.input_prompt_element
.value
; },
192 set prompt (s
) { this.input_prompt_element
.value
= s
; },
194 set_input_state: function (x
) {
195 this._input_text
= x
[0];
196 this._set_selection(x
[1], x
[2]);
199 _set_selection: function (start
, end
) {
201 start
= this._input_text
.length
;
203 end
= this._input_text
.length
;
204 this.input_element
.setSelectionRange(start
,end
);
207 /* Saved focus state */
208 saved_focused_frame
: null,
209 saved_focused_element
: null,
213 current_message
: null,
215 /* This method will display the specified string in the
216 * minibuffer, without recording it in any log/Messages buffer. */
217 show: function (str, force) {
218 if (!this.active || force) {
219 this.current_message = str;
224 _show: function (str) {
225 if (this.last_message != str) {
226 this.output_element.value = str;
227 this.last_message = str;
231 message: function (str) {
235 this.show(str, true /* force */);
237 this._flash_temporary_message();
242 this.current_message
= null;
244 this._show(this.default_message
);
247 set_default_message: function (str
) {
248 this.default_message
= str
;
249 if (this.current_message
== null)
253 get current_state () {
254 if (! this.states
[0])
256 return this.states
[this.states
.length
- 1];
259 push_state: function (state
) {
261 this.states
.push(state
);
262 this._restore_state();
265 pop_state: function () {
266 this.current_state
.destroy();
268 this._restore_state();
271 pop_all: function () {
273 while ((state
= this.current_state
)) {
279 //XXX: breaking stack discipline can cause incorrect
280 // input recursion termination
281 remove_state: function (state
) {
282 var i
= this.states
.indexOf(state
);
285 var was_current
= (i
== (this.states
.length
- 1));
287 this.states
.splice(i
, 1);
289 this._restore_state();
292 _input_mode_enabled
: false,
296 /* If _input_mode_enabled is true, this is set to indicate that
297 * the message area is being temporarily shown instead of the
299 _showing_message
: false,
301 _message_timer_ID
: null,
303 /* This must only be called if _input_mode_enabled is true */
304 //XXX: if it must only be called if _input_mode_enabled is true, then
305 // why does it have an else condition for handling
306 // minibuffer_message_state states?
307 _restore_normal_state: function () {
308 if (this._showing_message
) {
309 this.window
.clearTimeout(this._message_timer_ID
);
310 this._message_timer_ID
= null;
311 this._showing_message
= false;
313 if (this._input_mode_enabled
)
314 this._switch_to_input_mode();
316 // assumes that anything other than an input state is a
317 // minibuffer_message_state.
318 this._show(this.current_state
._message
);
322 /* This must only be called if _input_mode_enabled is true */
323 _flash_temporary_message: function () {
324 if (this._showing_message
)
325 this.window
.clearTimeout(this._message_timer_ID
);
327 this._showing_message
= true;
328 if (this._input_mode_enabled
)
329 this._switch_to_message_mode();
332 this._message_timer_ID
= this.window
.setTimeout(function () {
333 obj
._restore_normal_state();
334 }, minibuffer_input_mode_show_message_timeout
);
337 _switch_to_input_mode: function () {
338 this.element
.setAttribute("minibuffermode", "input");
339 this.input_element
.inputField
.focus();
342 _switch_to_message_mode: function () {
343 this.element
.setAttribute("minibuffermode", "message");
346 _restore_state: function () {
347 var s
= this.current_state
;
349 this.window
.buffers
.save_focus();
355 this.window
.buffers
.restore_focus();
356 this._show(this.current_message
|| this.default_message
);
359 if (this._showing_message
) {
360 this.window
.clearTimeout(this._message_timer_ID
);
361 this._message_timer_ID
= null;
362 this._showing_message
= false;
364 var want_input_mode
= s
instanceof minibuffer_input_state
;
365 var in_input_mode
= this._input_mode_enabled
&& !this._showing_message
;
366 if (want_input_mode
&& !in_input_mode
)
367 this._switch_to_input_mode();
368 else if (!want_input_mode
&& in_input_mode
)
369 this._switch_to_message_mode();
370 this._input_mode_enabled
= want_input_mode
;
373 _save_state: function () {
374 var s
= this.current_state
;
379 insert_before: function (element
) {
380 this.element
.parentNode
.insertBefore(element
, this.element
);
385 function minibuffer_initialize_window (window
) {
386 window
.minibuffer
= new minibuffer(window
);
388 add_hook("window_initialize_early_hook", minibuffer_initialize_window
);
391 function minibuffer_window_close_handler (window
) {
392 window
.minibuffer
.pop_all();
394 add_hook("window_close_hook", minibuffer_window_close_handler
);
397 /* Note: This is concise, but doesn't seem to be useful in practice,
398 * because nothing can be done with the state alone. */
399 minibuffer
.prototype.check_state = function (type
) {
400 var s
= this.current_state
;
401 if (!(s
instanceof type
))
402 throw new Error("Invalid minibuffer state.");
406 minibuffer
.prototype.show_wait_message = function (initial_message
, cleanup_function
) {
407 var s
= new minibuffer_message_state(this, minibuffer_message_keymap
, initial_message
, cleanup_function
);
412 minibuffer
.prototype.wait_for = function (message
, coroutine
) {
413 let promise
= spawn(coroutine
);
414 var s
= this.show_wait_message(message
, promise
.cancel
);
415 let cleanup
= s
.minibuffer
.remove_state
.bind(s
);
416 promise
.then(cleanup
, cleanup
);
421 // This should only be used for minibuffer states where it makes
422 // sense. In particular, it should not be used if additional cleanup
424 function minibuffer_abort (window
) {
425 var m
= window
.minibuffer
;
426 var s
= m
.current_state
;
428 throw "Invalid minibuffer state";
430 input_sequence_abort
.call(window
);
432 interactive("minibuffer-abort", null, function (I
) { minibuffer_abort(I
.window
); });
436 * Minibuffer-annotation-mode
439 var minibuffer_annotation_mode
= {
440 stylesheet
: "chrome://conkeror-gui/content/minibuffer-annotation.css",
443 register: function (user
) {
444 this.users
.push(user
);
445 this._switch_if_needed();
447 unregister: function (user
) {
448 var i
= this.users
.indexOf(user
);
450 this.users
.splice(i
, 1);
451 this._switch_if_needed();
453 _switch_if_needed: function (user
) {
454 if (this.enabled
&& this.users
.length
== 0)
456 if (!this.enabled
&& this.users
.length
!= 0)
459 _enable: function () {
460 register_agent_stylesheet(this.stylesheet
);
463 _disable: function () {
464 unregister_agent_stylesheet(this.stylesheet
);
465 this.enabled
= false;
469 provide("minibuffer");