rename browser-follow-next/previous to follow-next/previous
[conkeror.git] / modules / find.js
blob5e7d0b555ab1fdc28803a0ab6505b1804610c7b5
1 /**
2  * (C) Copyright 2004-2005 Shawn Betts
3  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4  *
5  * Use, modification, and distribution are subject to the terms specified in the
6  * COPYING file.
7 **/
9 // turn on the selection in all frames
10 function getFocusedSelCtrl(window)
12     var ds = window.buffers.current.doc_shell;
13     var dsEnum = ds.getDocShellEnumerator(Components.interfaces.nsIDocShellTreeItem.typeContent,
14                                           Components.interfaces.nsIDocShell.ENUMERATE_FORWARDS);
15     while (dsEnum.hasMoreElements()) {
16         ds = dsEnum.getNext().QueryInterface(Components.interfaces.nsIDocShell);
17         if (ds.hasFocus)
18         {
19             var display = ds.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
20                 .getInterface(Components.interfaces.nsISelectionDisplay);
21             if (!display)
22                 return null;
23             return display.QueryInterface(Components.interfaces.nsISelectionController);
24         }
25     }
27   // One last try
28   return window.buffers.current.doc_shell
29       .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
30       .getInterface(Components.interfaces.nsISelectionDisplay)
31       .QueryInterface(Components.interfaces.nsISelectionController);
35 function initial_isearch_state(frame, forward)
37     this.screenx = frame.scrollX;
38     this.screeny = frame.scrollY;
39     this.search_str = "";
40     this.wrapped = false;
41     this.point = null;
42     this.range = frame.document.createRange();
43     this.selection = null;
44     this.direction = forward;
47 function isearch_session(window, forward)
49     this.states = [];
50     this.frame = window.buffers.current.focused_frame;
51     this.sel_ctrl = getFocusedSelCtrl(window);
52     this.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_ATTENTION);
53     this.sel_ctrl.repaintSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
54     this.states.push(new initial_isearch_state(this.frame, forward));
55     this.window = window;
57     minibuffer_input_state.call(this, isearch_keymap, "");
59 isearch_session.prototype = {
60     constructor : isearch_session,
61     __proto__ : minibuffer_input_state.prototype, // inherit from minibuffer_state
63     get top () {
64         return this.states[this.states.length - 1];
65     },
66     _set_selection : function (range)
67     {
68         try {
69             const selctrlcomp = Components.interfaces.nsISelectionController;
70             var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
71             sel.removeAllRanges();
72             sel.addRange(range.cloneRange());
73             this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
74                                                   selctrlcomp.SELECTION_FOCUS_REGION,
75                                                   true);
76         } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
77     },
78     _clear_selection : function ()
79     {
80         const selctrlcomp = Components.interfaces.nsISelectionController;
81         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
82         sel.removeAllRanges();
83     },
84     restore_state: function () {
85         var m = this.window.minibuffer;
86         var s = this.top;
87         m._input_text = s.search_str;
88         if (s.selection)
89             this._set_selection(s.selection);
90         else
91             this._clear_selection();
92         this.frame.scrollTo(s.screenx, s.screeny);
93         m.prompt = ((s.wrapped ? "Wrapped ":"")
94                     + (s.range ? "" : "Failing ")
95                     + "I-Search" + (s.direction? "": " backward") + ":");
96     },
97     _highlight_find : function (str, wrapped, dir, pt) {
98         try {
99             var doc = this.frame.document;
100             var finder = (Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance()
101                           .QueryInterface(Components.interfaces.nsIFind));
102             var searchRange;
103             var startPt;
104             var endPt;
105             var body = doc.documentElement;
107             finder.findBackwards = !dir;
109             searchRange = doc.createRange();
110             startPt = doc.createRange();
111             endPt = doc.createRange();
113             var count = body.childNodes.length;
115             // Search range in the doc
116             searchRange.setStart(body,0);
117             searchRange.setEnd(body, count);
119             if (!dir) {
120                 if (pt == null) {
121                     startPt.setStart(body, count);
122                     startPt.setEnd(body, count);
123                 } else {
124                     startPt.setStart(pt.startContainer, pt.startOffset);
125                     startPt.setEnd(pt.startContainer, pt.startOffset);
126                 }
127                 endPt.setStart(body, 0);
128                 endPt.setEnd(body, 0);
129             } else {
130                 if (pt == null) {
131                     startPt.setStart(body, 0);
132                     startPt.setEnd(body, 0);
133                 } else {
134                     startPt.setStart(pt.endContainer, pt.endOffset);
135                     startPt.setEnd(pt.endContainer, pt.endOffset);
136                 }
137                 endPt.setStart(body, count);
138                 endPt.setEnd(body, count);
139             }
140             // search the doc
141             var retRange = null;
142             var selectionRange = null;
145             if (!wrapped) {
146                 do {
147                     retRange = finder.Find(str, searchRange, startPt, endPt);
148                     var keepSearching = false;
149                     if (retRange) {
150                         var sc = retRange.startContainer;
151                         var ec = retRange.endContainer;
152                         var scp = sc.parentNode;
153                         var ecp = ec.parentNode;
154                         var sy1 = abs_point(scp).y;
155                         var ey2 = abs_point(ecp).y + ecp.offsetHeight;
157                         startPt = retRange.startContainer.ownerDocument.createRange();
158                         if (!dir) {
159                             startPt.setStart(retRange.startContainer, retRange.startOffset);
160                             startPt.setEnd(retRange.startContainer, retRange.startOffset);
161                         } else {
162                             startPt.setStart(retRange.endContainer, retRange.endOffset);
163                             startPt.setEnd(retRange.endContainer, retRange.endOffset);
164                         }
165                         // We want to find a match that is completely
166                         // visible, otherwise the view will scroll just a
167                         // bit to fit the selection in completely.
168                         keepSearching = (dir && sy1 < this.frame.scrollY)
169                             || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
170                     }
171                 } while (retRange && keepSearching);
172             } else {
173                 retRange = finder.Find(str, searchRange, startPt, endPt);
174             }
176             if (retRange) {
177                 this._set_selection(retRange);
178                 selectionRange = retRange.cloneRange();
179             } else {
181             }
183             return selectionRange;
184         } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
185         return null;
186     },
188     find : function (str, dir, pt) {
189         var s = this.top;
191         if (str == null || str.length == 0)
192             return;
194         // Should we wrap this time?
195         var wrapped = s.wrapped;
196         var point = pt;
197         if (s.wrapped == false && s.range == null && s.search_str == str && s.direction == dir) {
198             wrapped = true;
199             point = null;
200         }
202         var match_range = this._highlight_find(str, wrapped, dir, point);
204         var new_state = {
205             screenx: this.frame.scrollX, screeny: this.frame.scrollY,
206             search_str: str, wrapped: wrapped, point: point,
207             range: match_range,
208             selection: match_range ? match_range : s.selection,
209             direction: dir
210         };
211         this.states.push(new_state);
212     },
214     focus_link : function ()
215     {
216         var sel = this.frame.getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
217         if (!sel)
218             return;
219         var node = sel.focusNode;
220         if (node == null)
221             return;
223         do {
224             if (node.localName == "A") {
225                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
226                     /* FIXME: This shouldn't be needed anymore, due to
227                      * better xpc wrappers */
228                     Components.lookupMethod(node, "focus").call(node);
229                     return;
230                 }
231             }
232         } while ((node = node.parentNode));
233     },
235     handle_input : function (m) {
236         m._set_selection();
237         this.find(m._input_text, this.top.direction, this.top.point);
238         this.restore_state();
239     },
241     done : false,
243     destroy : function () {
244         if (!this.done)  {
245             this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
246             this._clear_selection();
247         }
248     }
251 function isearch_continue(window, direction) {
252     var s = window.minibuffer.current_state;
253     if (!(s instanceof isearch_session))
254         throw "Invalid minibuffer state";
255     if (s.states.length == 1 && window.isearch_last_search)
256         s.find(window.isearch_last_search, direction, s.top.point);
257     else
258         s.find(s.top.search_str, direction, s.top.range);
259     s.restore_state();
261 interactive("isearch-continue-forward", function (I) {isearch_continue(I.window, true);});
262 interactive("isearch-continue-backward", function (I) {isearch_continue(I.window, false);});
264 function isearch_start (window, direction)
266     var s = new isearch_session(window, direction);
267     window.minibuffer.push_state(s);
268     s.restore_state();
270 interactive("isearch-forward", function (I) {isearch_start(I.window, true);});
271 interactive("isearch-backward", function (I) {isearch_start(I.window, false);});
273 function isearch_backspace (window)
275     var s = window.minibuffer.current_state;
276     if (!(s instanceof isearch_session))
277         throw "Invalid minibuffer state";
278     if (s.states.length > 1)
279         s.states.pop();
280     s.restore_state();
282 interactive("isearch-backspace", function (I) {isearch_backspace(I.window);});
284 function isearch_done (window)
286     var s = window.minibuffer.current_state;
287     if (!(s instanceof isearch_session))
288         throw "Invalid minibuffer state";
289     s.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
291     // Prevent focus from being reverted
292     window.minibuffer.saved_focused_element = null;
293     window.minibuffer.saved_focused_window = null;
295     s.done = true;
297     window.minibuffer.pop_state();
298     window.isearch_last_search = s.top.search_str;
299     s.focus_link();
300     s._clear_selection();
302 interactive("isearch-done", function (I) {isearch_done(I.window);});