utils.js: add function string_hashmap.for_each_value
[conkeror.git] / modules / find.js
blobdc234eacdd8d17ca1a42362c868723de2becf68e
1 /***** BEGIN LICENSE BLOCK *****
2 Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 The contents of this file are subject to the Mozilla Public License Version
5 1.1 (the "License"); you may not use this file except in compliance with
6 the License. You may obtain a copy of the License at
7 http://www.mozilla.org/MPL/
9 Software distributed under the License is distributed on an "AS IS" basis,
10 WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 for the specific language governing rights and limitations under the
12 License.
14 The Initial Developer of the Original Code is Shawn Betts.
15 Portions created by the Initial Developer are Copyright (C) 2004,2005
16 by the Initial Developer. All Rights Reserved.
18 Alternatively, the contents of this file may be used under the terms of
19 either the GNU General Public License Version 2 or later (the "GPL"), or
20 the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
21 in which case the provisions of the GPL or the LGPL are applicable instead
22 of those above. If you wish to allow use of your version of this file only
23 under the terms of either the GPL or the LGPL, and not to allow others to
24 use your version of this file under the terms of the MPL, indicate your
25 decision by deleting the provisions above and replace them with the notice
26 and other provisions required by the GPL or the LGPL. If you do not delete
27 the provisions above, a recipient may use your version of this file under
28 the terms of any one of the MPL, the GPL or the LGPL.
29 ***** END LICENSE BLOCK *****/
31 // isearch
33 // turn on the selection in all frames
34 function getFocusedSelCtrl(frame)
36     var ds = frame.buffers.current.doc_shell;
37     var dsEnum = ds.getDocShellEnumerator(Components.interfaces.nsIDocShellTreeItem.typeContent,
38                                           Components.interfaces.nsIDocShell.ENUMERATE_FORWARDS);
39     while (dsEnum.hasMoreElements()) {
40         ds = dsEnum.getNext().QueryInterface(Components.interfaces.nsIDocShell);
41         if (ds.hasFocus) 
42         {
43             var display = ds.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
44                 .getInterface(Components.interfaces.nsISelectionDisplay);
45             if (!display)
46                 return null;
47             return display.QueryInterface(Components.interfaces.nsISelectionController);
48         }
49     }
51   // One last try
52   return frame.buffers.current.doc_shell
53       .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
54       .getInterface(Components.interfaces.nsISelectionDisplay)
55       .QueryInterface(Components.interfaces.nsISelectionController);
59 function initial_isearch_state(window, forward)
61     this.screenx = window.scrollX;
62     this.screeny = window.scrollY;
63     this.search_str = "";
64     this.wrapped = false;
65     this.point = null;
66     this.range = window.document.createRange();
67     this.selection = null;
68     this.direction = forward;
71 function isearch_session(frame, forward)
73     this.states = [];
74     this.window = frame.buffers.current.focused_window();
75     this.sel_ctrl = getFocusedSelCtrl(frame);
76     this.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_ATTENTION);
77     this.sel_ctrl.repaintSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
78     this.states.push(new initial_isearch_state(this.window, forward));
79     this.frame = frame;
81     minibuffer_state.call(this, isearch_kmap, "");
83 isearch_session.prototype = {
84     constructor : isearch_session,
85     __proto__ : minibuffer_state.prototype, // inherit from minibuffer_state
87     get top () {
88         return this.states[this.states.length - 1];
89     },
90     _set_selection : function (range)
91     {
92         try {
93             const selctrlcomp = Components.interfaces.nsISelectionController;
94             var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
95             sel.removeAllRanges();
96             sel.addRange(range.cloneRange());
97             this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
98                                                   selctrlcomp.SELECTION_FOCUS_REGION,
99                                                   true);
100         } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
101     },
102     _clear_selection : function ()
103     {
104         const selctrlcomp = Components.interfaces.nsISelectionController;
105         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
106         sel.removeAllRanges();
107     },
108     restore_state: function () {
109         var m = this.frame.minibuffer;
110         var s = this.top;
111         m._input_text = s.search_str;
112         if (s.selection)
113             this._set_selection(s.selection);
114         else
115             this._clear_selection();
116         this.window.scrollTo(s.screenx, s.screeny);
117         m.prompt = ((s.wrapped ? "Wrapped ":"")
118                     + (s.range ? "" : "Failing ")
119                     + "I-Search" + (s.direction? "": " backward") + ":");
120     },
121     _highlight_find : function (str, wrapped, dir, pt) {
122         try {
123             var doc = this.window.document;
124             var finder = (Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance()
125                           .QueryInterface(Components.interfaces.nsIFind));
126             var searchRange;
127             var startPt;
128             var endPt;
129             var body = doc.documentElement;
131             finder.findBackwards = !dir;
133             searchRange = doc.createRange();
134             startPt = doc.createRange();
135             endPt = doc.createRange();
137             var count = body.childNodes.length;
139             // Search range in the doc
140             searchRange.setStart(body,0);
141             searchRange.setEnd(body, count);
143             if (!dir) {
144                 if (pt == null) {
145                     startPt.setStart(body, count);
146                     startPt.setEnd(body, count);
147                 } else {
148                     startPt.setStart(pt.startContainer, pt.startOffset);
149                     startPt.setEnd(pt.startContainer, pt.startOffset);
150                 }
151                 endPt.setStart(body, 0);
152                 endPt.setEnd(body, 0);
153             } else {
154                 if (pt == null) {
155                     startPt.setStart(body, 0);
156                     startPt.setEnd(body, 0);
157                 } else {
158                     startPt.setStart(pt.endContainer, pt.endOffset);
159                     startPt.setEnd(pt.endContainer, pt.endOffset);
160                 }
161                 endPt.setStart(body, count);
162                 endPt.setEnd(body, count);
163             }
164             // search the doc
165             var retRange = null;
166             var selectionRange = null;
169             if (!wrapped) {
170                 do {
171                     retRange = finder.Find(str, searchRange, startPt, endPt);
172                     var keepSearching = false;
173                     if (retRange) {
174                         var sc = retRange.startContainer;
175                         var ec = retRange.endContainer;
176                         var scp = sc.parentNode;
177                         var ecp = ec.parentNode;
178                         var sy1 = abs_point(scp).y;
179                         var ey2 = abs_point(ecp).y + ecp.offsetHeight;
181                         startPt = retRange.startContainer.ownerDocument.createRange();
182                         if (!dir) {
183                             startPt.setStart(retRange.startContainer, retRange.startOffset);
184                             startPt.setEnd(retRange.startContainer, retRange.startOffset);
185                         } else {
186                             startPt.setStart(retRange.endContainer, retRange.endOffset);
187                             startPt.setEnd(retRange.endContainer, retRange.endOffset);
188                         }
189                         // We want to find a match that is completely
190                         // visible, otherwise the view will scroll just a
191                         // bit to fit the selection in completely.
192                         keepSearching = (dir && sy1 < this.window.scrollY)
193                             || (!dir && ey2 >= this.window.scrollY + this.window.innerHeight);
194                     }
195                 } while (retRange && keepSearching);
196             } else {
197                 retRange = finder.Find(str, searchRange, startPt, endPt);
198             }
200             if (retRange) {
201                 this._set_selection(retRange);
202                 selectionRange = retRange.cloneRange();
203             } else {
204             
205             }
207             return selectionRange;
208         } catch(e) { /* FIXME: figure out why this is needed*/ this.frame.alert(e); }
209         return null;
210     },
212     find : function (str, dir, pt) {
213         var s = this.top;
215         // Should we wrap this time?
216         var wrapped = s.wrapped;
217         var point = pt;
218         if (s.wrapped == false && s.range == null && s.search_str == str && s.direction == dir) {
219             wrapped = true;
220             point = null;
221         }
223         var match_range = this._highlight_find(str, wrapped, dir, point);
225         var new_state = {
226             screenx: this.window.scrollX, screeny: this.window.scrollY,
227             search_str: str, wrapped: wrapped, point: point,
228             range: match_range,
229             selection: match_range ? match_range : s.selection,
230             direction: dir
231         };
232         this.states.push(new_state);
233     },
235     focus_link : function ()
236     {
237         var sel = this.window.getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
238         if (!sel)
239             return;
240         var node = sel.focusNode;
241         if (node == null)
242             return;
243     
244         do {
245             if (node.localName == "A") {
246                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
247                     /* FIXME: This shouldn't be needed anymore, due to
248                      * better xpc wrappers */
249                     Components.lookupMethod(node, "focus").call(node);
250                     return;
251                 }
252             }
253         } while ((node = node.parentNode));
254     }
257 function isearch_continue(frame, direction) {
258     var s = frame.minibuffer.current_state;
259     if (!(s instanceof isearch_session))
260         throw "Invalid minibuffer state";
261     if (s.states.length == 1 && frame.isearch_last_search)
262         s.find(frame.isearch_last_search, direction, s.top.point);
263     else
264         s.find(s.top.search_str, direction, s.top.range);
265     s.restore_state();
267 interactive("isearch-continue-forward", isearch_continue, I.current_frame, true);
268 interactive("isearch-continue-backward", isearch_continue, I.current_frame, false);
270 function isearch_start (frame, direction)
272     var s = new isearch_session(frame, direction);
273     frame.minibuffer.push_state(s);
274     s.restore_state();
276 interactive("isearch-forward", isearch_start, I.current_frame, true);
277 interactive("isearch-backward", isearch_start, I.current_frame, false);
279 function isearch_backspace (frame)
281     var s = frame.minibuffer.current_state;
282     if (!(s instanceof isearch_session))
283         throw "Invalid minibuffer state";
284     if (s.states.length > 1)
285         s.states.pop();
286     s.restore_state();
288 interactive("isearch-backspace", isearch_backspace, I.current_frame);
290 function isearch_abort (frame)
292     var s = frame.minibuffer.current_state;
293     if (!(s instanceof isearch_session))
294         throw "Invalid minibuffer state";
295     frame.minibuffer.pop_state();
296     s.window.scrollTo(s.states[0].screenx, s.states[0].screeny);
297     s._clear_selection();
299 interactive("isearch-abort", isearch_abort, I.current_frame);
302 function isearch_add_character (frame, event)
304     var s = frame.minibuffer.current_state;
305     if (!(s instanceof isearch_session))
306         throw "Invalid minibuffer state";
307     var str = s.top.search_str;
308     str += String.fromCharCode(event.charCode);
309     s.find(str, s.top.direction, s.top.point);
310     s.restore_state();
312 interactive("isearch-add-character", isearch_add_character, I.current_frame, I.e);
314 function isearch_done (frame)
316     var s = frame.minibuffer.current_state;
317     if (!(s instanceof isearch_session))
318         throw "Invalid minibuffer state";
319     s.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
320     frame.minibuffer.pop_state(false /* don't restore focus */);
321     frame.isearch_last_search = s.top.search_str;
322     s.focus_link();
323     s._clear_selection();
325 interactive("isearch-done", isearch_done, I.current_frame);