whitespace
[conkeror/arlinius.git] / modules / content-buffer-input.js
blobac36b2c4e6cb9837010afc7eabc3d56a2487303b
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 require("content-buffer.js");
12 // note: The apparent misspellings here are not a bug.
13 // see https://developer.mozilla.org/en/XPath/Functions/translate
15 define_variable(
16     "browser_form_field_xpath_expression",
17     "//input[" + (
18         //        "translate(@type,'RADIO','radio')!='radio' and " +
19         //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
20         "translate(@type,'HIDEN','hiden')!='hidden'"
21         //        "translate(@type,'SUBMIT','submit')!='submit' and " +
22         //        "translate(@type,'REST','rest')!='reset'"
23     ) +  "] | " +
24         "//xhtml:input[" + (
25             //        "translate(@type,'RADIO','radio')!='radio' and " +
26             //        "translate(@type,'CHECKBOX','checkbox')!='checkbox' and " +
27             "translate(@type,'HIDEN','hiden')!='hidden'"
28             //        "translate(@type,'SUBMIT','submit')!='submit' and " +
29             //        "translate(@type,'REST','rest')!='reset'"
30         ) +  "] |" +
31         "//select | //xhtml:select | " +
32         "//textarea | //xhtml:textarea | " +
33         "//textbox | //xul:textbox",
34     "XPath expression matching elements to be selected by `browser-focus-next-form-field' " +
35         "and `browser-focus-previous-form-field.'");
37 function focus_next (buffer, count, xpath_expr, name) {
38     var focused_elem = buffer.focused_element;
39     if (count == 0)
40         return; // invalid count
42     function helper (win, skip_win) {
43         if (win == skip_win)
44             return null;
45         var doc = win.document;
46         var res = doc.evaluate(xpath_expr, doc, xpath_lookup_namespace,
47             Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
48             null /* existing results */);
49         var length = res.snapshotLength;
50         if (length > 0) {
51             let valid_nodes = [];
52             for (let i = 0; i < length; ++i) {
53                 let elem = res.snapshotItem(i);
54                 if (elem.offsetWidth == 0 ||
55                     elem.offsetHeight == 0)
56                     continue;
57                 let style = win.getComputedStyle(elem, "");
58                 if (style.display == "none" || style.visibility == "hidden")
59                     continue;
60                 valid_nodes.push(elem);
61             }
63             if (valid_nodes.length > 0) {
64                 var index = -1;
65                 if (focused_elem != null)
66                     index = valid_nodes.indexOf(focused_elem);
67                 if (index == -1) {
68                     if (count > 0)
69                         index = count - 1;
70                     else
71                         index = -count;
72                 }
73                 else
74                     index = index + count;
75                 index = index % valid_nodes.length;
76                 if (index < 0)
77                     index += valid_nodes.length;
79                 return valid_nodes[index];
80             }
81         }
82         // Recurse on sub-frames
83         for (var i = 0, nframes = win.frames.length; i < nframes; ++i) {
84             var elem = helper(win.frames[i], skip_win);
85             if (elem)
86                 return elem;
87         }
88         return null;
89     }
91     var focused_win = buffer.focused_frame;
92     var elem = helper(focused_win, null);
93     if (!elem)
94         // if focused_frame is top_frame, we're doing twice as much
95         // work as necessary
96         elem = helper(buffer.top_frame, focused_win);
97     if (elem)
98         browser_element_focus(buffer, elem);
99     else
100         throw interactive_error("No "+name+" found");
103 interactive("browser-focus-next-form-field",
104             "Focus the next element matching "+
105             "`browser_form_field_xpath_expression'.",
106             function (I) {
107                 focus_next(I.buffer, I.p,
108                            browser_form_field_xpath_expression,
109                            "form field");
110             });
112 interactive("browser-focus-previous-form-field",
113             "Focus the previous element matching "+
114             "`browser_form_field_xpath_expression'.",
115             function (I) {
116                 focus_next(I.buffer, -I.p,
117                            browser_form_field_xpath_expression,
118                            "form field");
119             });
122 define_variable("links_xpath_expression",
123     "//*[@onclick or @onmouseover or @onmousedown or "+
124         "@onmouseup or @oncommand or @role='link'] | " +
125     "//input[not(@type='hidden')] | //a | //area | "+
126     "//iframe | //textarea | //button | //select",
127     "XPath expression matching elements to be selected by "+
128     "`focus-next-link' and `focus-previous-link.'");
130 interactive("focus-next-link",
131             "Focus the next element matching `links_xpath_expression'.",
132             function (I) {
133                 focus_next(I.buffer, I.p,
134                            links_xpath_expression,
135                            "link");
136             });
138 interactive("focus-previous-link",
139             "Focus the previous element matching `links_xpath_expression'.",
140             function (I) {
141                 focus_next(I.buffer, -I.p,
142                            links_xpath_expression,
143                            "link");
144             });
147 define_variable('edit_field_in_external_editor_extension', "txt",
148     "File extension for the temp files created by "+
149     "edit-current-field-in-external-editor.");
151 function get_filename_for_current_textfield (doc, elem) {
152     var name = doc.URL
153         + "-"
154         + ( elem.getAttribute("name")
155             || elem.getAttribute("id")
156             || "textarea" );
158     // get rid filesystem unfriendly chars
159     name = name.replace(doc.location.protocol, "")
160         .replace(/[^a-zA-Z0-9]+/g, "-")
161         .replace(/(^-+|-+$)/g, "")
162         + '.' + edit_field_in_external_editor_extension;
164     return name;
167 function edit_field_in_external_editor (buffer, elem) {
168     if (elem instanceof Ci.nsIDOMHTMLInputElement) {
169         var type = elem.getAttribute("type");
170         if (type != null)
171             type = type.toLowerCase();
172         if (type == "hidden" || type == "checkbox" || type == "radio")
173             throw interactive_error("Element is not a text field.");
174     } else if (!(elem instanceof Ci.nsIDOMHTMLTextAreaElement))
175         throw interactive_error("Element is not a text field.");
177     var name = get_filename_for_current_textfield(buffer.document, elem);
178     var file = get_temporary_file(name);
180     // Write to file
181     try {
182         write_text_file(file, elem.value);
183     } catch (e) {
184         file.remove(false);
185         throw e;
186     }
188     // FIXME: decide if we should do this
189     var old_class = elem.className;
190     elem.className = "__conkeror_textbox_edited_externally " + old_class;
192     try {
193         yield open_file_with_external_editor(file);
195         elem.value = read_text_file(file);
196     } finally {
197         elem.className = old_class;
199         file.remove(false);
200     }
203 interactive("edit-current-field-in-external-editor",
204             "Edit the contents of the currently-focused text field in an external editor.",
205             function (I) {
206                 var buf = I.buffer;
207                 var elem = buf.focused_element;
208                 yield edit_field_in_external_editor(buf, elem);
209                 elem.blur();
210             });
212 define_variable("kill_whole_line", false,
213                 "If true, `kill-line' with no arg at beg of line kills the whole line.");
215 function cut_to_end_of_line (buffer) {
216     var elem = buffer.focused_element;
217     try {
218         var st = elem.selectionStart;
219         var en = elem.selectionEnd;
220         if (st == en) {
221             // there is no selection.  set one up.
222             var eol = elem.value.indexOf("\n", en);
223             if (eol == -1)
224                 elem.selectionEnd = elem.textLength;
225             else if (eol == st)
226                 elem.selectionEnd = eol + 1;
227             else if (kill_whole_line &&
228                      (st == 0 || elem.value[st - 1] == "\n"))
229                 elem.selectionEnd = eol + 1;
230             else
231                 elem.selectionEnd = eol;
232         }
233         buffer.do_command('cmd_cut');
234     } catch (e) {
235         /* FIXME: Make this work for richedit mode as well */
236     }
238 interactive("cut-to-end-of-line",
239     null,
240     function (I) {
241         cut_to_end_of_line(I.buffer);
242     });
245 function downcase_word (I) {
246     modify_word_at_point(I, function (word) { return word.toLocaleLowerCase(); });
248 interactive("downcase-word",
249             "Convert following word to lower case, moving over.",
250             downcase_word);
253 function upcase_word (I) {
254     modify_word_at_point(I, function (word) { return word.toLocaleUpperCase(); });
256 interactive("upcase-word",
257             "Convert following word to upper case, moving over.",
258             upcase_word);
261 function capitalize_word (I) {
262     modify_word_at_point(I, function (word) {
263         if (word.length > 0) {
264             return word[0].toLocaleUpperCase() + word.substring(1);
265         }
266         return word;
267     });
269 interactive("capitalize-word",
270             "Capitalize the following word (or arg words), moving over.",
271             capitalize_word);