2 function find_complete_match(matches, val)
4 for (var i=0; i<matches.length; i++) {
5 if (matches[i][0] == val)
11 function exit_minibuffer(frame)
13 var m = frame.minibuffer;
14 var s = m.current_state;
15 if (!(s instanceof text_entry_minibuffer_state))
16 throw "Invalid minibuffer state";
19 var val = trim_whitespace(m._input_text);
20 if (s instanceof completion_minibuffer_state) {
21 if (val.length == 0 && s.default_match != null)
22 val = s.default_match;
23 match = find_complete_match(s.completions, val);
26 // Check if we are allowed to exit here
27 if ((s instanceof completion_minibuffer_state) && !match && !s.allow_non_matches)
33 if (s.history.length > minibuffer_history_max_items)
34 s.history.splice(0, s.history.length - minibuffer_history_max_items);
38 if (s instanceof completion_minibuffer_state) {
39 if (s.allow_non_matches)
40 s.callback(match, val);
41 else // match must be non-null because of previous check
48 interactive("exit-minibuffer", exit_minibuffer, I.current_frame);
50 function minibuffer_history_next (frame)
52 var m = frame.minibuffer;
53 var s = m.current_state;
54 if (!(s instanceof text_entry_minibuffer_state))
55 throw "Invalid minibuffer state";
58 m._ensure_input_area_showing();
59 s.history_index = Math.max(s.history_index + 1, s.history.length - 1);
60 m._input_text = s.history[s.history_index];
63 interactive("minibuffer-history-next", minibuffer_history_next, I.current_frame);
65 function minibuffer_history_previous (frame)
67 var m = frame.minibuffer;
68 var s = m.current_state;
69 if (!(s instanceof text_entry_minibuffer_state))
70 throw "Invalid minibuffer state";
73 m._ensure_input_area_showing();
74 s.history_index = Math.min(s.history_index - 1, 0);
75 m._input_text = s.history[s.history_index];
78 interactive("minibuffer-history-previous", minibuffer_history_previous, I.current_frame);
80 function minibuffer_abort (frame)
82 var m = frame.minibuffer;
83 var s = m.current_state;
84 if (!(s instanceof text_entry_minibuffer_state))
85 throw "Invalid minibuffer state";
90 interactive("minibuffer-abort", minibuffer_abort, I.current_frame);
92 /* FIXME: Change this to use a binary search */
93 function get_completions(str, matches)
98 for (var i=0; i<matches.length; i++)
100 if (str == matches[i][0].substr(0, str.length))
101 ret.push(matches[i]);
106 function find_longest_common_prefix(matches)
108 var a = matches[0][0];
109 var b = matches[matches.length - 1][0];
110 for (var i = 0; i < a.length && i < b.length; i++)
111 if (a.charAt(i) != b.charAt(i))
113 return Math.min(a.length,b.length);
116 function minibuffer_complete (frame)
118 var m = frame.minibuffer;
119 var s = m.current_state;
120 if (!(s instanceof completion_minibuffer_state))
121 throw "Invalid minibuffer state";
122 var str = m._input_text;
123 var entered_text = str.substring(0, m._selection_start);
124 var matches = get_completions(entered_text, s.completions);
126 // If no completions, then nothing to do
127 if (matches.length == 0)
129 m._set_selection(); // moves cursor to end
133 /* Find longest common prefix; since s.completions is sorted, this
135 var lcp = find_longest_common_prefix(matches);
137 if (lcp == entered_text.length)
139 /* Cycle: find current match */
140 var current_index = -1;
141 for (var i = 0; i < matches.length; ++i)
142 if (matches[i][0] == str)
147 current_index = (current_index + 1) % matches.length;
148 m._input_text = matches[current_index][0];
149 m._set_selection(lcp); // select after lcp
151 m._input_text = matches[0][0];
152 m._set_selection(lcp);
155 interactive("minibuffer-complete", minibuffer_complete, I.current_frame);
158 function minibuffer_accept_match (frame)
160 var m = frame.minibuffer;
161 var s = m.current_state;
162 if (!(s instanceof completion_minibuffer_state))
163 throw "Invalid minibuffer state";
164 var sel_start = m._selection_start;
165 var sel_end = m._selection_end;
166 var str = m._input_text;
167 if (sel_start == sel_end) {
168 m._input_text = str.substr(0, sel_start) + " " + str.substr(sel_end);
169 m._set_selection(sel_start + 1, sel_start + 1);
171 // When we allow non-matches it generally means the
172 // completion takes an argument. So add a space.
173 if (s.allow_non_matches && str[str.length-1] != ' ')
174 m._input_text = str + " ";
178 interactive("minibuffer-accept-match", minibuffer_accept_match, I.current_frame);
180 function minibuffer_do_command(frame, command) {
181 /* FIXME: Once we have the minibuffer capable of flashing a
182 * message, this should always revert to input mode immediately */
184 var m = frame.minibuffer;
185 if (m._input_mode_enabled)
187 m._ensure_input_area_showing();
188 var e = m.input_element;
189 var c = e.controllers.getControllerForCommand(command);
190 if (c && c.isCommandEnabled(command))
191 c.doCommand(command);
195 dumpln("minibuffer_do_command: " + e);
199 /* FIXME: These should all be defined more compactly using a loop */
200 interactive("minibuffer-cmd_beginLine", minibuffer_do_command, I.current_frame, 'cmd_beginLine');
201 interactive("minibuffer-cmd_copy", minibuffer_do_command, I.current_frame, 'cmd_copy');
202 interactive("minibuffer-cmd_copyOrDelete", minibuffer_do_command, I.current_frame, 'cmd_copyOrDelete');
203 interactive("minibuffer-cmd_cut", minibuffer_do_command, I.current_frame, 'cmd_cut');
204 interactive("minibuffer-cmd_cutOrDelete", minibuffer_do_command, I.current_frame, 'cmd_cutOrDelete');
205 interactive("minibuffer-cmd_deleteToBeginningOfLine", minibuffer_do_command, I.current_frame, 'cmd_deleteToBeginningOfLine');
206 interactive("minibuffer-cmd_deleteToEndOfLine", minibuffer_do_command, I.current_frame, 'cmd_deleteToEndOfLine');
207 interactive("minibuffer-cmd_endLine", minibuffer_do_command, I.current_frame, 'cmd_endLine');
208 interactive("minibuffer-cmd_moveTop", minibuffer_do_command, I.current_frame, 'cmd_moveTop');
209 interactive("minibuffer-cmd_moveBottom", minibuffer_do_command, I.current_frame, 'cmd_moveBottom');
210 interactive("minibuffer-cmd_selectAll", minibuffer_do_command, I.current_frame, 'cmd_selectAll');
211 interactive("minibuffer-cmd_selectBeginLine", minibuffer_do_command, I.current_frame, 'cmd_selectBeginLine');
212 interactive("minibuffer-cmd_selectBottom", minibuffer_do_command, I.current_frame, 'cmd_selectBottom');
213 interactive("minibuffer-cmd_selectEndLine", minibuffer_do_command, I.current_frame, 'cmd_selectEndLine');
214 interactive("minibuffer-cmd_selectTop", minibuffer_do_command, I.current_frame, 'cmd_selectTop');
215 interactive("minibuffer-cmd_scrollBeginLine", minibuffer_do_command, I.current_frame, 'cmd_scrollBeginLine');
216 interactive("minibuffer-cmd_scrollEndLine", minibuffer_do_command, I.current_frame, 'cmd_scrollEndLine');
217 interactive("minibuffer-cmd_scrollTop", minibuffer_do_command, I.current_frame, 'cmd_scrollTop');
218 interactive("minibuffer-cmd_scrollBottom", minibuffer_do_command, I.current_frame, 'cmd_scrollBottom');
220 interactive("minibuffer-cmd_charNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_charNext');
221 interactive("minibuffer-cmd_charPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_charPrevious');
222 interactive("minibuffer-cmd_deleteCharBackward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteCharBackward');
223 interactive("minibuffer-cmd_deleteCharForward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteCharForward');
224 interactive("minibuffer-cmd_deleteWordBackward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteWordBackward');
225 interactive("minibuffer-cmd_deleteWordForward", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_deleteWordForward');
226 interactive("minibuffer-cmd_lineNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_lineNext');
227 interactive("minibuffer-cmd_linePrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_linePrevious');
228 interactive("minibuffer-cmd_movePageDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_movePageDown');
229 interactive("minibuffer-cmd_movePageUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_movePageUp');
230 interactive("minibuffer-cmd_redo", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_redo');
231 interactive("minibuffer-cmd_selectCharNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectCharNext');
232 interactive("minibuffer-cmd_selectCharPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectCharPrevious');
233 interactive("minibuffer-cmd_selectLineNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectLineNext');
234 interactive("minibuffer-cmd_selectLinePrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectLinePrevious');
235 interactive("minibuffer-cmd_selectPageDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectPageDown');
236 interactive("minibuffer-cmd_selectPageUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectPageUp');
237 interactive("minibuffer-cmd_selectWordNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectWordNext');
238 interactive("minibuffer-cmd_selectWordPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_selectWordPrevious');
239 interactive("minibuffer-cmd_undo", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_undo');
240 interactive("minibuffer-cmd_wordNext", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_wordNext');
241 interactive("minibuffer-cmd_wordPrevious", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_wordPrevious');
242 interactive("minibuffer-cmd_scrollPageUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollPageUp');
243 interactive("minibuffer-cmd_scrollPageDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollPageDown');
244 interactive("minibuffer-cmd_scrollLineUp", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollLineUp');
245 interactive("minibuffer-cmd_scrollLineDown", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollLineDown');
246 interactive("minibuffer-cmd_scrollLeft", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollLeft');
247 interactive("minibuffer-cmd_scrollRight", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_scrollRight');
248 interactive("minibuffer-cmd_paste", do_N_times, minibuffer_do_command, I.p, I.current_frame, 'cmd_paste');
250 function minibuffer_insert_character(frame, n, event)
252 var m = frame.minibuffer;
253 var s = m.current_state;
254 if (!(s instanceof basic_minibuffer_state))
255 throw "Invalid minibuffer state";
256 m._ensure_input_area_showing();
257 var val = m._input_text;
258 var sel_start = m._selection_start;
259 var sel_end = m._selection_end;
260 var insert = String.fromCharCode(event.charCode);
261 var out = val.substr(0, sel_start);
262 for (var i = 0; i < n; ++i)
264 out += val.substr(sel_end);
266 var new_sel = sel_end + n;
267 m._set_selection(new_sel, new_sel);
269 interactive("minibuffer-insert-character", minibuffer_insert_character,
270 I.current_frame, I.p, I.e);
272 function minibuffer_insert_character_complete(frame, n, event)
274 var m = frame.minibuffer;
275 var s = m.current_state;
276 if (!(s instanceof completion_minibuffer_state))
277 throw "Invalid minibuffer state";
279 minibuffer_insert_character(frame, n, event);
281 // Check for completions
283 var entered_text = m._input_text.substring(0, m._selection_start);
284 var matches = get_completions(entered_text, s.completions);
285 if (matches.length == 0)
287 m._input_text = matches[0][0];
288 m._set_selection(entered_text.length);
290 interactive("minibuffer-insert-character-complete", minibuffer_insert_character_complete,
291 I.current_frame, I.p, I.e);
293 var minibuffer_history_data = new string_hashmap();
295 /* FIXME: These should possibly be saved to disk somewhere */
296 /* USER PREFERENCE */
297 var minibuffer_history_max_items = 100;
300 function minibuffer_state(keymap, prompt, input, selection_start, selection_end)
302 this.keymap = keymap;
303 this.prompt = prompt;
309 this.selection_start = selection_start;
311 this.selection_start = 0;
313 this.selection_end = selection_end;
315 this.selection_end = this.selection_start;
321 * The parameter `args' is an object specifying the arguments for
322 * basic_minibuffer_state. The following properties of args must/may
327 * initial_value: [optional] specifies the initial text
329 * select: [optional] specifies to select the initial text if set to non-null
331 define_keywords("$prompt", "$initial_value", "$select");
332 function basic_minibuffer_state()
334 keywords(arguments, $initial_value = "");
335 var sel_start, sel_end;
336 if (arguments.$select)
339 sel_end = arguments.$initial_value.length;
341 sel_start = sel_end = arguments.$initial_value.length;
343 minibuffer_state.call(this, minibuffer_base_kmap,
344 arguments.$prompt, arguments.$initial_value,
347 basic_minibuffer_state.prototype.__proto__ = minibuffer_state.prototype; // inherit from minibuffer_state
349 /* The parameter `args' specifies the arguments. In addition, the
350 * arguments for basic_minibuffer_state are also allowed.
352 * history: [optional] specifies a string to identify the history list to use
354 * callback: [optional] function called once the user successfully enters a value; it is
355 * called with the value entered by the user.
357 * abort_callback: [optional] called if the operaion is aborted
359 define_keywords("$callback", "$abort_callback", "$history");
360 function text_entry_minibuffer_state() {
363 basic_minibuffer_state.call(this, forward_keywords(arguments));
364 this.keymap = minibuffer_kmap;
366 this.callback = arguments.$callback;
367 this.abort_callback = arguments.$abort_callback;
368 if (arguments.$history)
370 this.history = minibuffer_history_data.get_put_default(arguments.$history, []);
371 this.history_index = this.history.length;
374 // inherit from basic_minibuffer_state
375 text_entry_minibuffer_state.prototype.__proto__ = basic_minibuffer_state.prototype;
378 * The parameter `args' specifies the arguments. In addition, the
379 * arguments for text_entry_minibuffer_state are also allowed.
381 * completions: [required] specifies an array of possible completions
383 * allow_non_matches: [optional] if completions is non-null, setting
384 * this allows a non-match to be successfully entered; the callback
385 * with be called with the match as the first argument, and the true
386 * value as the second argument.
388 * default_match: [optional] if completions is non-null, specifies a default
389 * match to use if the user entered a blank string.
391 * callback: [optional] Called with the match as the first argument, and possibly with
392 * the true value as the second argument, depending on allow_non_matches.
395 define_keywords("$completions", "$allow_non_matches", "$default_match");
396 function completion_minibuffer_state() {
397 text_entry_minibuffer_state.call(this, forward_keywords(arguments));
398 keywords(arguments, $allow_non_matches = false);
399 this.keymap = minibuffer_completion_kmap;
400 this.completions = arguments.$completions.slice().sort(function (a,b) {
401 if (a[0] < b[0]) return -1;
402 else if (a[0] == b[0]) return 0;
405 this.allow_non_matches = arguments.$allow_non_matches;
406 this.default_match = arguments.$default_match;
408 // inherit from text_entry_minibuffer_state
409 completion_minibuffer_state.prototype.__proto__ = text_entry_minibuffer_state.prototype;
411 /* USER PREFERENCE */
412 var minibuffer_input_mode_show_message_timeout = 1000;
414 function minibuffer (frame)
416 this.output_element = frame.document.getElementById("minibuffer-output");
417 this.input_prompt_element = frame.document.getElementById("input-prompt");
418 this.input_element = frame.document.getElementById("input-field");
420 this.input_element.inputField.addEventListener("blur", function() {
421 if (m._input_mode_enabled && !m._showing_message)
425 m.input_element.focus();
430 this.last_message = "";
432 this._keymap_set = new keymap_set();
435 minibuffer.prototype = {
436 constructor : minibuffer.constructor,
437 get _selection_start () { return this.input_element.selectionStart; },
438 get _selection_end () { return this.input_element.selectionEnd; },
439 get _input_text () { return this.input_element.value; },
440 set _input_text (text) { this.input_element.value = text; },
441 get prompt () { return this.input_prompt_element.value; },
442 set prompt (s) { this.input_prompt_element.value = s; },
444 _set_selection : function (start, end) {
446 start = this._input_text.length;
448 end = this._input_text.length;
449 this.input_element.setSelectionRange(start,end);
452 /* Saved focus state */
453 saved_focused_window : null,
454 saved_focused_element : null,
456 /* This method will display the specified string in the
457 * minibuffer, without recording it in any log/Messages buffer. */
458 show : function (str) {
459 if (this.last_message != str)
461 this.output_element.value = str;
462 this.last_message = str;
465 dumpln("MINIBUFFER: " + str);
469 if (str.length > 0 && this._input_mode_enabled)
470 this._ensure_message_area_showing();
472 message : function (str) {
473 /* TODO: add the message to a *Messages* buffer, and/or
474 * possibly dump them to the console. */
477 clear : function () {
481 get current_state () {
482 if (this.states.length == 0)
484 return this.states[this.states.length - 1];
487 push_state : function (state) {
489 this.states.push(state);
490 this._restore_state();
493 pop_state : function (restore_focus) {
494 if (restore_focus === undefined)
495 restore_focus = true;
497 this._restore_state(restore_focus);
500 _input_mode_enabled : false,
502 /* If _input_mode_enabled is true, this is set to indicate that
503 * the message area is being temporarily shown instead of the
505 _showing_message : false,
507 _message_timer_ID : null,
509 /* This must only be called if _input_mode_enabled is true */
510 _ensure_input_area_showing : function () {
511 if (this._showing_message)
513 this.frame.clearTimeout(this._message_timer_ID);
514 this._message_timer_ID = null;
515 this._showing_message = false;
516 this._switch_to_input_mode();
520 /* This must only be called if _input_mode_enabled is true */
521 _ensure_message_area_showing : function () {
522 if (this._showing_message)
523 this.frame.clearTimeout(this._message_timer_ID);
525 this._showing_message = true;
526 this._switch_to_message_mode();
529 this._message_timer_ID = this.frame.setTimeout(function(){
530 obj._ensure_input_area_showing();
531 }, minibuffer_input_mode_show_message_timeout);
534 _switch_to_input_mode : function () {
535 this.output_element.collapsed = true;
536 this.input_prompt_element.collapsed = false;
537 this.input_element.collapsed = false;
538 this.input_element.focus();
541 _switch_to_message_mode : function () {
542 this.output_element.collapsed = false;
543 this.input_prompt_element.collapsed = true;
544 this.input_element.collapsed = true;
547 _restore_state : function (restore_focus) {
548 var s = this.current_state;
550 if (!this._input_mode_enabled)
552 this.saved_focused_window = this.frame.document.commandDispatcher.focusedWindow;
553 this.saved_focused_element = this.frame.document.commandDispatcher.focusedElement;
554 this._input_mode_enabled = true;
555 this._switch_to_input_mode();
557 this._keymap_set.default_keymap = s.keymap;
558 this.frame.keyboard_state.override_keymap_set = this._keymap_set;
559 this._input_text = s.input;
560 this.prompt = s.prompt;
561 this._set_selection(s.selection_start, s.selection_end);
563 if (this._input_mode_enabled)
565 this._input_mode_enabled = false;
566 if (!this._showing_message)
567 this._switch_to_message_mode();
569 this.frame.clearTimeout(this._message_timer_ID);
570 this._message_timer_ID = null;
571 this._showing_message = false;
575 if (this.saved_focused_element)
576 set_focus_no_scroll(this.frame, this.saved_focused_element);
577 else if (this.saved_focused_window)
578 set_focus_no_scroll(this.frame, this.saved_focused_window);
580 this.saved_focused_element = null;
581 this.saved_focused_window = null;
582 this.frame.keyboard_state.override_keymap_set = null;
583 this._keymap_set.default_keymap = null;
588 _save_state : function () {
589 var s = this.current_state;
592 s.input = this._input_text;
593 s.prompt = this.prompt;
594 s.selection_start = this._selection_start;
595 s.selection_end = this._selection_end;
599 /* See basic_minibuffer_state for a description of arguments */
601 /* FIXME: have policy for deciding whether to refuse a
602 * recursive minibuffer operation */
603 this.push_state(new text_entry_minibuffer_state(forward_keywords(arguments)));
606 read_with_completion : function () {
607 this.push_state(new completion_minibuffer_state(forward_keywords(arguments)));
611 function minibuffer_initialize_frame(frame)
613 frame.minibuffer = new minibuffer(frame);
616 add_hook("frame_initialize_early_hook", minibuffer_initialize_frame);
618 /* Note: This is concise, but doesn't seem to be useful in practice,
619 * because nothing can be done with the state alone.
621 I.minibuffer_state = interactive_method(
622 $doc = "Topmost minibuffer state",
623 $sync = function (ctx, type) {
624 var s = ctx.frame.minibuffer.current_state;
625 if (!s || (type && !(s instanceof type)))
626 throw new Error("Invalid minibuffer state.");