buffer.override_keymaps: dealt with privately inside buffer
[conkeror.git] / modules / content-buffer-input.js
blob7dfa107f336b299ff4da2324648133f5376496a8
1 /**
2  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
3  * (C) Copyright 2008-2009 John J. Foerch
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
9 in_module(null);
11 require("content-buffer.js");
14 // note: The apparent misspellings here are not a bug.
15 // see https://developer.mozilla.org/en/XPath/Functions/translate
17 define_variable(
18     "browser_form_field_xpath_expression",
19     "//input[" + (
20         //        "translate(@type,'RADIO','radio')!='radio' and " +
21         //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
22         "translate(@type,'HIDEN','hiden')!='hidden'"
23         //        "translate(@type,'SUBMIT','submit')!='submit' and " +
24         //        "translate(@type,'REST','rest')!='reset'"
25     ) +  "] | " +
26         "//xhtml:input[" + (
27             //        "translate(@type,'RADIO','radio')!='radio' and " +
28             //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
29             "translate(@type,'HIDEN','hiden')!='hidden'"
30             //        "translate(@type,'SUBMIT','submit')!='submit' and " +
31             //        "translate(@type,'REST','rest')!='reset'"
32         ) +  "] |" +
33         "//select | //xhtml:select | " +
34         "//textarea | //xhtml:textarea | " +
35         "//textbox | //xul:textbox",
36     "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
37         "and `browser-focus-previous-form-field.'");
39 function focus_next (buffer, count, xpath_expr, name) {
40     var focused_elem = buffer.focused_element;
41     if (count == 0)
42         return; // invalid count
44     function helper (win, skip_win) {
45         if (win == skip_win)
46             return null;
47         var doc = win.document;
48         var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
49             Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
50             null /* existing results */);
51         var length = res.snapshotLength;
52         if (length > 0) {
53             let valid_nodes = [];
54             for (let i = 0; i < length; ++i) {
55                 let elem = res.snapshotItem(i);
56                 if (elem.offsetWidth == 0 ||
57                     elem.offsetHeight == 0)
58                     continue;
59                 let style = win.getComputedStyle(elem, "");
60                 if (style.display == "none" || style.visibility == "hidden")
61                     continue;
62                 valid_nodes.push(elem);
63             }
65             if (valid_nodes.length > 0) {
66                 var index = -1;
67                 if (focused_elem != null)
68                     index = valid_nodes.indexOf(focused_elem);
69                 if (index == -1) {
70                     if (count > 0)
71                         index = count - 1;
72                     else
73                         index = -count;
74                 }
75                 else
76                     index = index + count;
77                 index = index % valid_nodes.length;
78                 if (index < 0)
79                     index += valid_nodes.length;
81                 return valid_nodes[index];
82             }
83         }
84         // Recurse on sub-frames
85         for (var i = 0, nframes = win.frames.length; i < nframes; ++i) {
86             var elem = helper(win.frames[i], skip_win);
87             if (elem)
88                 return elem;
89         }
90         return null;
91     }
93     var focused_win = buffer.focused_frame;
94     var elem = helper(focused_win, null);
95     if (!elem)
96         // if focused_frame is top_frame, we're doing twice as much
97         // work as necessary
98         elem = helper(buffer.top_frame, focused_win);
99     if (elem)
100         browser_element_focus(buffer, elem);
101     else
102         throw interactive_error("No "+name+" found");
105 interactive("browser-focus-next-form-field",
106             "Focus the next element matching "+
107             "`browser_form_field_xpath_expression'.",
108             function (I) {
109                 focus_next(I.buffer, I.p,
110                            browser_form_field_xpath_expression,
111                            "form field");
112             });
114 interactive("browser-focus-previous-form-field",
115             "Focus the previous element matching "+
116             "`browser_form_field_xpath_expression'.",
117             function (I) {
118                 focus_next(I.buffer, -I.p,
119                            browser_form_field_xpath_expression,
120                            "form field");
121             });
124 define_variable("links_xpath_expression",
125     "//*[@onclick or @onmouseover or @onmousedown or "+
126         "@onmouseup or @oncommand or @role='link'] | " +
127     "//input[not(@type='hidden')] | //a | //area | "+
128     "//iframe | //textarea | //button | //select",
129     "XPath expression matching elements to be selected by "+
130     "`focus-next-link' and `focus-previous-link.'");
132 interactive("focus-next-link",
133             "Focus the next element matching `links_xpath_expression'.",
134             function (I) {
135                 focus_next(I.buffer, I.p,
136                            links_xpath_expression,
137                            "link");
138             });
140 interactive("focus-previous-link",
141             "Focus the previous element matching `links_xpath_expression'.",
142             function (I) {
143                 focus_next(I.buffer, -I.p,
144                            links_xpath_expression,
145                            "link");
146             });
149 define_variable('edit_field_in_external_editor_extension', "txt",
150     "File extension for the temp files created by "+
151     "edit-current-field-in-external-editor.");
153 function get_filename_for_current_textfield (doc, elem) {
154     var name = doc.URL
155         + "-"
156         + ( elem.getAttribute("name")
157             || elem.getAttribute("id")
158             || "textarea" );
160     // get rid filesystem unfriendly chars
161     name = name.replace(doc.location.protocol, "")
162         .replace(/[^a-zA-Z0-9]+/g, "-")
163         .replace(/(^-+|-+$)/g, "")
164         + '.' + edit_field_in_external_editor_extension;
166     return name;
169 function edit_field_in_external_editor (buffer, elem) {
170     if (elem instanceof Ci.nsIDOMHTMLInputElement) {
171         var type = elem.getAttribute("type");
172         if (type != null)
173             type = type.toLowerCase();
174         if (type == "hidden" || type == "checkbox" || type == "radio")
175             throw interactive_error("Element is not a text field.");
176     } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
177         throw interactive_error("Element is not a text field.");
179     var name = get_filename_for_current_textfield(buffer.document, elem);
180     var file = get_temporary_file(name);
182     // Write to file
183     try {
184         write_text_file(file, elem.value);
185     } catch (e) {
186         file.remove(false);
187         throw e;
188     }
190     // FIXME: decide if we should do this
191     var old_class = elem.className;
192     elem.className = "__conkeror_textbox_edited_externally " + old_class;
194     try {
195         yield open_file_with_external_editor(file);
197         elem.value = read_text_file(file);
198     } finally {
199         elem.className = old_class;
201         file.remove(false);
202     }
205 interactive("edit-current-field-in-external-editor",
206             "Edit the contents of the currently-focused text field in an external editor.",
207             function (I) {
208                 var buf = I.buffer;
209                 var elem = buf.focused_element;
210                 yield edit_field_in_external_editor(buf, elem);
211                 elem.blur();
212             });
214 define_variable("kill_whole_line", false,
215                 "If true, `kill-line' with no arg at beg of line kills the whole line.");
217 function cut_to_end_of_line (buffer) {
218     var elem = buffer.focused_element;
219     try {
220         var st = elem.selectionStart;
221         var en = elem.selectionEnd;
222         if (st == en) {
223             // there is no selection.  set one up.
224             var eol = elem.value.indexOf("\n", en);
225             if (eol == -1)
226                 elem.selectionEnd = elem.textLength;
227             else if (eol == st)
228                 elem.selectionEnd = eol + 1;
229             else if (kill_whole_line &&
230                      (st == 0 || elem.value[st - 1] == "\n"))
231                 elem.selectionEnd = eol + 1;
232             else
233                 elem.selectionEnd = eol;
234         }
235         buffer.do_command('cmd_cut');
236     } catch (e) {
237         /* FIXME: Make this work for richedit mode as well */
238     }
240 interactive("cut-to-end-of-line",
241     null,
242     function (I) {
243         cut_to_end_of_line(I.buffer);
244     });
247 function downcase_word (I) {
248     modify_word_at_point(I, function (word) { return word.toLocaleLowerCase(); });
250 interactive("downcase-word",
251             "Convert following word to lower case, moving over.",
252             downcase_word);
255 function upcase_word (I) {
256     modify_word_at_point(I, function (word) { return word.toLocaleUpperCase(); });
258 interactive("upcase-word",
259             "Convert following word to upper case, moving over.",
260             upcase_word);
263 function capitalize_word (I) {
264     modify_word_at_point(I, function (word) {
265         if (word.length > 0) {
266             return word[0].toLocaleUpperCase() + word.substring(1);
267         }
268         return word;
269     });
271 interactive("capitalize-word",
272             "Capitalize the following word (or arg words), moving over.",
273             capitalize_word);
275 provide("content-buffer-input");