minibuffer-completion.js: Avoid some minor errors
[conkeror.git] / modules / find.js
blob25091efdd8eda353726d75011b4fce07a71cf267
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_input_state.call(this, isearch_keymap, "");
83 isearch_session.prototype = {
84     constructor : isearch_session,
85     __proto__ : minibuffer_input_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     },
259     handle_input : function (m) {
260         m._set_selection();
261         this.find(m._input_text, this.top.direction, this.top.point);
262         this.restore_state();
263     },
265     done : false,
267     destroy : function () {
268         if (!this.done)  {
269             this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
270             this._clear_selection();
271         }
272     }
275 function isearch_continue(window, direction) {
276     var s = window.minibuffer.current_state;
277     if (!(s instanceof isearch_session))
278         throw "Invalid minibuffer state";
279     if (s.states.length == 1 && window.isearch_last_search)
280         s.find(window.isearch_last_search, direction, s.top.point);
281     else
282         s.find(s.top.search_str, direction, s.top.range);
283     s.restore_state();
285 interactive("isearch-continue-forward", function (I) {isearch_continue(I.window, true);});
286 interactive("isearch-continue-backward", function (I) {isearch_continue(I.window, false);});
288 function isearch_start (window, direction)
290     var s = new isearch_session(window, direction);
291     window.minibuffer.push_state(s);
292     s.restore_state();
294 interactive("isearch-forward", function (I) {isearch_start(I.window, true);});
295 interactive("isearch-backward", function (I) {isearch_start(I.window, false);});
297 function isearch_backspace (window)
299     var s = window.minibuffer.current_state;
300     if (!(s instanceof isearch_session))
301         throw "Invalid minibuffer state";
302     if (s.states.length > 1)
303         s.states.pop();
304     s.restore_state();
306 interactive("isearch-backspace", function (I) {isearch_backspace(I.window);});
308 function isearch_done (window)
310     var s = window.minibuffer.current_state;
311     if (!(s instanceof isearch_session))
312         throw "Invalid minibuffer state";
313     s.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
315     // Prevent focus from being reverted
316     window.minibuffer.saved_focused_element = null;
317     window.minibuffer.saved_focused_window = null;
319     s.done = true;
321     window.minibuffer.pop_state();
322     window.isearch_last_search = s.top.search_str;
323     s.focus_link();
324     s._clear_selection();
326 interactive("isearch-done", function (I) {isearch_done(I.window);});