minibuffer-completion.js: Avoid some minor errors
[conkeror.git] / modules / content-buffer-input.js
blob3c92ce3b135188d9310cf3c761975f50ab2acac7
1 require("content-buffer.js");
3 define_current_buffer_hook("current_buffer_input_mode_change_hook", "input_mode_change_hook");
5 var content_buffer_input_mode_keymaps = {};
7 function define_input_mode(base_name, display_name, keymap_name, doc) {
8     var name = base_name + "_input_mode";
9     content_buffer_input_mode_keymaps[name] = keymap_name;
10     define_buffer_mode(name,
11                        display_name,
12                        $class = "input_mode",
13                        $enable = function (buffer) { check_buffer(buffer, content_buffer);
14                                                      content_buffer_update_keymap_for_input_mode(buffer); },
15                        $disable = false,
16                        $doc = doc);
18 ignore_function_for_get_caller_source_code_reference("define_input_mode");
20 function content_buffer_update_keymap_for_input_mode(buffer) {
21     if (buffer.input_mode)
22         buffer.keymap = buffer.get(content_buffer_input_mode_keymaps[buffer.input_mode]);
25 add_hook("page_mode_change_hook", content_buffer_update_keymap_for_input_mode);
27 add_hook("content_buffer_location_change_hook", function (buf) { normal_input_mode(buf, true); });
29 define_input_mode("normal", null, "content_buffer_normal_keymap");
31 // For SELECT elements
32 define_input_mode("select", "input:SELECT", "content_buffer_select_keymap");
34 // For text INPUT and TEXTAREA elements
35 define_input_mode("text", "input:TEXT", "content_buffer_text_keymap");
36 define_input_mode("textarea", "input:TEXTAREA", "content_buffer_textarea_keymap");
37 define_input_mode("richedit", "input:RICHEDIT", "content_buffer_richedit_keymap");
39 define_input_mode("checkbox", "input:CHECKBOX/RADIOBUTTON", "content_buffer_checkbox_keymap");
41 define_input_mode(
42     "quote_next", "input:PASS-THROUGH(next)", "content_buffer_quote_next_keymap",
43     "This input mode sends the next key combo to the buffer, "+
44         "bypassing Conkeror's normal key handling.  The mode disengages "+
45         "after one key combo.");
46 define_input_mode(
47     "quote", "input:PASS-THROUGH", "content_buffer_quote_keymap",
48     "This input mode sends all key combos to the buffer, "+
49         "bypassing Conkeror's normal key handling, until the "+
50         "Escape key is pressed.");
52 function content_buffer_update_input_mode_for_focus(buffer, force) {
53     var mode = buffer.input_mode;
54     var form_input_mode_enabled = (mode == "text_input_mode" ||
55                                    mode == "textarea_input_mode" ||
56                                    mode == "select_input_mode" ||
57                                    mode == "checkbox_input_mode" ||
58                                    mode == "richedit_input_mode");
60     if (force || form_input_mode_enabled || mode == "normal_input_mode") {
61         let elem = buffer.focused_element;
63         if (elem) {
64             var input_mode_function = null;
65             if (elem instanceof Ci.nsIDOMHTMLInputElement) {
66                 var type = elem.getAttribute("type");
67                 if (type != null) type = type.toLowerCase();
68                 if (type == "checkbox" || type == "radio")
69                     input_mode_function = checkbox_input_mode;
70                 else if (type != "submit" &&
71                          type != "reset")
72                     input_mode_function = text_input_mode;
73             }
74             else if (elem instanceof Ci.nsIDOMHTMLTextAreaElement)
75                 input_mode_function = textarea_input_mode;
77             else if (elem instanceof Ci.nsIDOMHTMLSelectElement)
78                 input_mode_function = select_input_mode;
79         }
81         if (!input_mode_function) {
83             let frame = buffer.focused_frame;
84             let in_design_mode = false;
85             if (frame && frame.document.designMode == "on")
86                 in_design_mode = true;
87             else {
88                 outer:
89                 while (elem) {
90                     switch (elem.contentEditable) {
91                     case "true":
92                         in_design_mode = true;
93                         break outer;
94                     case "false":
95                         break outer;
96                     default: // == "inherit"
97                         elem = elem.parentNode;
98                     }
99                 }
100             }
101             if (in_design_mode)
102                 input_mode_function = richedit_input_mode;
103         }
105         if (input_mode_function) {
106             if (!force &&
107                 browser_prevent_automatic_form_focus_mode_enabled &&
108                 !form_input_mode_enabled &&
109                 (buffer.last_user_input_received == null ||
110                  (Date.now() - buffer.last_user_input_received)
111                  > browser_automatic_form_focus_window_duration)) {
112                 // Automatic focus attempt blocked
113                 elem.blur();
114             } else
115                 input_mode_function(buffer, true);
116             return;
117         }
119         normal_input_mode(buffer, true);
120     }
123 add_hook("content_buffer_focus_change_hook", function (buf) { content_buffer_update_input_mode_for_focus(buf, false); } );
125 interactive("content-buffer-update-input-mode-for-focus", function (I) {
126     content_buffer_update_input_mode_for_focus(I.buffer, true);
129 function minibuffer_input_mode_indicator(window) {
130     this.window = window;
131     this.hook_func = method_caller(this, this.update);
132     add_hook.call(window, "select_buffer_hook", this.hook_func);
133     add_hook.call(window, "current_buffer_input_mode_change_hook", this.hook_func);
134     this.update();
137 minibuffer_input_mode_indicator.prototype = {
138     update : function () {
139         var buf = this.window.buffers.current;
140         var mode = buf.input_mode;
141         if (mode)
142             this.window.minibuffer.element.className = "minibuffer-" + buf.input_mode.replace("_","-","g");
143     },
144     uninstall : function () {
145         remove_hook.call(window, "select_buffer_hook", this.hook_func);
146         remove_hook.call(window, "current_buffer_input_mode_change_hook", this.hook_func);
147     }
150 define_global_window_mode("minibuffer_input_mode_indicator", "window_initialize_hook");
151 minibuffer_input_mode_indicator_mode(true);
153 // Milliseconds
154 define_variable("browser_automatic_form_focus_window_duration", 20, "Time window (in milliseconds) during which a form element is allowed to gain focus following a mouse click or key press, if `browser_prevent_automatic_form_focus_mode' is enabled.");;
156 define_global_mode("browser_prevent_automatic_form_focus_mode",
157                    function () {}, // enable
158                    function () {} // disable
159                   );
160 browser_prevent_automatic_form_focus_mode(true);
162 define_variable(
163     "browser_form_field_xpath_expression",
164     "//input[" + (
165         //        "translate(@type,'RADIO','radio')!='radio' and " +
166         //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
167         "translate(@type,'HIDEN','hiden')!='hidden'"
168         //        "translate(@type,'SUBMIT','submit')!='submit' and " +
169         //        "translate(@type,'REST','rest')!='reset'"
170     ) +  "] | " +
171         "//xhtml:input[" + (
172             //        "translate(@type,'RADIO','radio')!='radio' and " +
173             //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
174             "translate(@type,'HIDEN','hiden')!='hidden'"
175             //        "translate(@type,'SUBMIT','submit')!='submit' and " +
176             //        "translate(@type,'REST','rest')!='reset'"
177         ) +  "] |" +
178         "//select | //xhtml:select | " +
179         "//textarea | //xhtml:textarea | " +
180         "//textbox | //xul:textbox",
181     "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
182         "and `browser-focus-previous-form-field.'");
184 function browser_focus_next_form_field(buffer, count, xpath_expr) {
185     var focused_elem = buffer.focused_element;
186     if (count == 0)
187         return; // invalid count
189     function helper(win, skip_win) {
190         if (win == skip_win)
191             return null;
192         var doc = win.document;
193         var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
194             Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
195             null /* existing results */);
196         var length = res.snapshotLength;
197         if (length > 0) {
198             var index = null;
199             if (focused_elem != null) {
200                 for (var i = 0; i < length; ++i) {
201                     if (res.snapshotItem(i) == focused_elem) {
202                         index = i;
203                         break;
204                     }
205                 }
206             }
207             if (index == null) {
208                 if (count > 0)
209                     index = count - 1;
210                 else
211                     index = -count;
212             }
213             else
214                 index = index + count;
215             index = index % length;
216             if (index < 0)
217                 index += length;
219             return res.snapshotItem(index);
220         }
222         // Recurse on sub-frames
223         for (var i = 0; i < win.frames.length; ++i) {
224             var elem = helper(win.frames[i], skip_win);
225             if (elem)
226                 return elem;
227         }
228         return null;
229     }
231     var focused_win = buffer.focused_frame;
232     var elem = helper(focused_win, null);
233     if (!elem)
234         elem = helper(buffer.top_frame, focused_win);
235     if (elem) {
236         browser_element_focus(buffer, elem);
237     } else
238         throw interactive_error("No form field found");
241 interactive("browser-focus-next-form-field", "Focus the next element matching `browser_form_field_xpath_expression'.",
242             function (I) {
243                 browser_focus_next_form_field(I.buffer, I.p, browser_form_field_xpath_expression);
244             });
246 interactive("browser-focus-previous-form-field",  "Focus the previous element matching `browser_form_field_xpath_expression'.",
247             function (I) {
248                 browser_focus_next_form_field(I.buffer, -I.p, browser_form_field_xpath_expression);
249             });
251 function edit_field_in_external_editor(buffer, elem) {
252     if (elem instanceof Ci.nsIDOMHTMLInputElement) {
253         var type = elem.getAttribute("type");
254         if (type != null)
255             type = type.toLowerCase();
256         if (type == "hidden" || type == "checkbox" || type == "radio")
257             throw interactive_error("Element is not a text field.");
258     } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
259         throw interactive_error("Element is not a text field.");
261     var name = elem.getAttribute("name");
262     if (!name || name.length == 0)
263         name = elem.getAttribute("id");
264     if (!name)
265         name = "";
266     name = name.replace(/[^a-zA-Z0-9\-_]/g, "");
267     if (name.length == 0)
268         name = "text";
269     name += ".txt";
270     var file = get_temporary_file(name);
272     // Write to file
273     try {
274         write_text_file(file, elem.value);
275     } catch (e) {
276         file.remove(false);
277         throw e;
278     }
280     // FIXME: decide if we should do this
281     var old_class = elem.className;
282     elem.className = "__conkeror_textbox_edited_externally " + old_class;
284     try {
285         yield open_file_with_external_editor(file);
287         elem.value = read_text_file(file);
288     } finally {
289         elem.className = old_class;
291         file.remove(false);
292     }
294 interactive("edit-current-field-in-external-editor", "Edit the contents of the currently-focused text field in an external editor.",
295             function (I) {
296                 var buf = I.buffer;
297                 yield edit_field_in_external_editor(buf, buf.focused_element);
298                 unfocus(buf);
299             });
301 define_variable("kill_whole_line", false, "If true, `kill-line' with no arg at beg of line kills the whole line.");
303 function cut_to_end_of_line (buffer) {
304     var elem = buffer.focused_element;
305     try {
306         var st = elem.selectionStart;
307         var en = elem.selectionEnd;
308         if (st == en) {
309             // there is no selection.  set one up.
310             var eol = elem.value.indexOf ("\n", en);
311             if (eol == -1)
312             {
313                 elem.selectionEnd = elem.textLength;
314             } else if (eol == st) {
315                 elem.selectionEnd = eol + 1;
316             } else if (kill_whole_line &&
317                        (st == 0 || elem.value[st - 1] == "\n"))
318             {
319                 elem.selectionEnd = eol + 1;
320             } else {
321                 elem.selectionEnd = eol;
322             }
323         }
324         buffer.do_command ('cmd_cut');
325     } catch (e) {
326         /* FIXME: Make this work for richedit mode as well */
327     }
330 interactive (
331     "cut-to-end-of-line",
332     function (I) {
333         cut_to_end_of_line (I.buffer);
334     });