Loading of optional modules now controlled by prefs
[conkeror.git] / modules / find.js
blob4cb07354aa8b774c8922e07547f7ec52a798a4dc
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(window)
36     var ds = window.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 window.buffers.current.doc_shell
53       .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
54       .getInterface(Components.interfaces.nsISelectionDisplay)
55       .QueryInterface(Components.interfaces.nsISelectionController);
59 function initial_isearch_state(frame, forward)
61     this.screenx = frame.scrollX;
62     this.screeny = frame.scrollY;
63     this.search_str = "";
64     this.wrapped = false;
65     this.point = null;
66     this.range = frame.document.createRange();
67     this.selection = null;
68     this.direction = forward;
71 function isearch_session(window, forward)
73     this.states = [];
74     this.frame = window.buffers.current.focused_frame;
75     this.sel_ctrl = getFocusedSelCtrl(window);
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.frame, forward));
79     this.window = window;
81     minibuffer_state.call(this, isearch_keymap, "");
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.window.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.frame.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.frame.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.frame.scrollY)
193                             || (!dir && ey2 >= this.frame.scrollY + this.frame.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.window.alert(e); }
209         return null;
210     },
212     find : function (str, dir, pt) {
213         var s = this.top;
215         if (str == null || str.length == 0)
216             return;
218         // Should we wrap this time?
219         var wrapped = s.wrapped;
220         var point = pt;
221         if (s.wrapped == false && s.range == null && s.search_str == str && s.direction == dir) {
222             wrapped = true;
223             point = null;
224         }
226         var match_range = this._highlight_find(str, wrapped, dir, point);
228         var new_state = {
229             screenx: this.frame.scrollX, screeny: this.frame.scrollY,
230             search_str: str, wrapped: wrapped, point: point,
231             range: match_range,
232             selection: match_range ? match_range : s.selection,
233             direction: dir
234         };
235         this.states.push(new_state);
236     },
238     focus_link : function ()
239     {
240         var sel = this.frame.getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
241         if (!sel)
242             return;
243         var node = sel.focusNode;
244         if (node == null)
245             return;
246     
247         do {
248             if (node.localName == "A") {
249                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
250                     /* FIXME: This shouldn't be needed anymore, due to
251                      * better xpc wrappers */
252                     Components.lookupMethod(node, "focus").call(node);
253                     return;
254                 }
255             }
256         } while ((node = node.parentNode));
257     }
260 function isearch_continue(window, direction) {
261     var s = window.minibuffer.current_state;
262     if (!(s instanceof isearch_session))
263         throw "Invalid minibuffer state";
264     if (s.states.length == 1 && window.isearch_last_search)
265         s.find(window.isearch_last_search, direction, s.top.point);
266     else
267         s.find(s.top.search_str, direction, s.top.range);
268     s.restore_state();
270 interactive("isearch-continue-forward", function (I) {isearch_continue(I.window, true);});
271 interactive("isearch-continue-backward", function (I) {isearch_continue(I.window, false);});
273 function isearch_start (window, direction)
275     var s = new isearch_session(window, direction);
276     window.minibuffer.push_state(s);
277     s.restore_state();
279 interactive("isearch-forward", function (I) {isearch_start(I.window, true);});
280 interactive("isearch-backward", function (I) {isearch_start(I.window, false);});
282 function isearch_backspace (window)
284     var s = window.minibuffer.current_state;
285     if (!(s instanceof isearch_session))
286         throw "Invalid minibuffer state";
287     if (s.states.length > 1)
288         s.states.pop();
289     s.restore_state();
291 interactive("isearch-backspace", function (I) {isearch_backspace(I.window);});
293 /* FIXME: do this stuff in .destroy instead */
294 function isearch_abort (window)
296     var s = window.minibuffer.current_state;
297     if (!(s instanceof isearch_session))
298         throw "Invalid minibuffer state";
299     window.minibuffer.pop_state();
300     s.frame.scrollTo(s.states[0].screenx, s.states[0].screeny);
301     s._clear_selection();
303 interactive("isearch-abort", function(I) {isearch_abort(I.window);});
306 function isearch_add_character (window, event)
308     var s = window.minibuffer.current_state;
309     if (!(s instanceof isearch_session))
310         throw "Invalid minibuffer state";
311     var str = s.top.search_str;
312     str += String.fromCharCode(event.charCode);
313     s.find(str, s.top.direction, s.top.point);
314     s.restore_state();
316 interactive("isearch-add-character", function (I) {isearch_add_character(I.window, I.event);});
318 function isearch_done (window)
320     var s = window.minibuffer.current_state;
321     if (!(s instanceof isearch_session))
322         throw "Invalid minibuffer state";
323     s.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
324     window.minibuffer.pop_state(false /* don't restore focus */);
325     window.isearch_last_search = s.top.search_str;
326     s.focus_link();
327     s._clear_selection();
329 interactive("isearch-done", function (I) {isearch_done(I.window);});