ensure that all cwd variables are always nsILocalFile objects
[conkeror.git] / modules / find.js
blob0124877597736d16fbc185e7c31363ee6a14d683
1 /**
2  * (C) Copyright 2004-2005 Shawn Betts
3  * (C) Copyright 2007-2008 Jeremy Maitin-Shepard
4  * (C) Copyright 2008 Nelson Elhage
5  * (C) Copyright 2008 John Foerch
6  *
7  * Use, modification, and distribution are subject to the terms specified in the
8  * COPYING file.
9 **/
11 const CARET_PREF = 'accessibility.browsewithcaret';
12 const CARET_ATTRIBUTE = 'showcaret';
14 define_variable("isearch_keep_selection", false,
15     "Set to `true' to make isearch leave the selection visible when a "+
16     "search is completed.");
18 function caret_enabled (buffer) {
19     return buffer.browser.getAttribute(CARET_ATTRIBUTE);
22 // turn on the selection in all frames
23 function getFocusedSelCtrl (buffer) {
24     var ds = buffer.doc_shell;
25     var dsEnum = ds.getDocShellEnumerator(Ci.nsIDocShellTreeItem.typeContent,
26                                           Ci.nsIDocShell.ENUMERATE_FORWARDS);
27     while (dsEnum.hasMoreElements()) {
28         ds = dsEnum.getNext().QueryInterface(Ci.nsIDocShell);
29         if (ds.hasFocus) {
30             var display = ds.QueryInterface(Ci.nsIInterfaceRequestor)
31                 .getInterface(Ci.nsISelectionDisplay);
32             if (!display)
33                 return null;
34             return display.QueryInterface(Ci.nsISelectionController);
35         }
36     }
38     // One last try
39     return ds
40         .QueryInterface(Ci.nsIInterfaceRequestor)
41         .getInterface(Ci.nsISelectionDisplay)
42         .QueryInterface(Ci.nsISelectionController);
45 function clear_selection (buffer) {
46     let sel_ctrl = getFocusedSelCtrl(buffer);
47     if (sel_ctrl) {
48         let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
49         if (caret_enabled(buffer)) {
50             if (sel.anchorNode)
51                 sel.collapseToStart();
52         } else {
53             sel.removeAllRanges();
54         }
55     }
59 function initial_isearch_state (buffer, frame, forward) {
60     this.screenx = frame.scrollX;
61     this.screeny = frame.scrollY;
62     this.search_str = "";
63     this.wrapped = false;
64     let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
65     if (sel.rangeCount > 0) {
66         this.point = sel.getRangeAt(0);
67         if (caret_enabled(buffer))
68             this.caret = this.point.cloneRange();
69     } else {
70         this.point = null;
71     }
72     this.range = frame.document.createRange();
73     this.selection = null;
74     this.direction = forward;
77 function isearch_session (window, forward) {
78     this.states = [];
79     this.buffer = window.buffers.current;
80     this.frame = this.buffer.focused_frame;
81     this.sel_ctrl = getFocusedSelCtrl(this.buffer);
82     this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
83     this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
84     this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
85     this.window = window;
87     minibuffer_input_state.call(this, isearch_keymap, "");
89 isearch_session.prototype = {
90     constructor : isearch_session,
91     __proto__ : minibuffer_input_state.prototype, // inherit from minibuffer_state
93     get top () {
94         return this.states[this.states.length - 1];
95     },
96     _set_selection : function (range) {
97         try {
98             const selctrlcomp = Ci.nsISelectionController;
99             var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
100             sel.removeAllRanges();
101             sel.addRange(range.cloneRange());
102             this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
103                                                   selctrlcomp.SELECTION_FOCUS_REGION,
104                                                   true);
105         } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
106     },
107     _clear_selection : function () {
108         const selctrlcomp = Ci.nsISelectionController;
109         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
110         sel.removeAllRanges();
111     },
112     restore_state: function () {
113         var m = this.window.minibuffer;
114         var s = this.top;
115         m._input_text = s.search_str;
116         if (s.selection)
117             this._set_selection(s.selection);
118         else
119             this._clear_selection();
120         this.frame.scrollTo(s.screenx, s.screeny);
121         m.prompt = ((s.wrapped ? "Wrapped ":"")
122                     + (s.range ? "" : "Failing ")
123                     + "I-Search" + (s.direction? "": " backward") + ":");
124     },
125     _highlight_find : function (str, wrapped, dir, pt) {
126         try {
127             var doc = this.frame.document;
128             var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
129                           .createInstance()
130                           .QueryInterface(Ci.nsIFind));
131             var searchRange;
132             var startPt;
133             var endPt;
134             var body = doc.documentElement;
136             finder.findBackwards = !dir;
138             searchRange = doc.createRange();
139             startPt = doc.createRange();
140             endPt = doc.createRange();
142             var count = body.childNodes.length;
144             // Search range in the doc
145             searchRange.setStart(body,0);
146             searchRange.setEnd(body, count);
148             if (!dir) {
149                 if (pt == null) {
150                     startPt.setStart(body, count);
151                     startPt.setEnd(body, count);
152                 } else {
153                     startPt.setStart(pt.startContainer, pt.startOffset);
154                     startPt.setEnd(pt.startContainer, pt.startOffset);
155                 }
156                 endPt.setStart(body, 0);
157                 endPt.setEnd(body, 0);
158             } else {
159                 if (pt == null) {
160                     startPt.setStart(body, 0);
161                     startPt.setEnd(body, 0);
162                 } else {
163                     startPt.setStart(pt.endContainer, pt.endOffset);
164                     startPt.setEnd(pt.endContainer, pt.endOffset);
165                 }
166                 endPt.setStart(body, count);
167                 endPt.setEnd(body, count);
168             }
169             // search the doc
170             var retRange = null;
171             var selectionRange = null;
174             if (!wrapped) {
175                 do {
176                     retRange = finder.Find(str, searchRange, startPt, endPt);
177                     var keepSearching = false;
178                     if (retRange) {
179                         var sc = retRange.startContainer;
180                         var ec = retRange.endContainer;
181                         var scp = sc.parentNode;
182                         var ecp = ec.parentNode;
183                         var sy1 = abs_point(scp).y;
184                         var ey2 = abs_point(ecp).y + ecp.offsetHeight;
186                         startPt = retRange.startContainer.ownerDocument.createRange();
187                         if (!dir) {
188                             startPt.setStart(retRange.startContainer, retRange.startOffset);
189                             startPt.setEnd(retRange.startContainer, retRange.startOffset);
190                         } else {
191                             startPt.setStart(retRange.endContainer, retRange.endOffset);
192                             startPt.setEnd(retRange.endContainer, retRange.endOffset);
193                         }
194                         // We want to find a match that is completely
195                         // visible, otherwise the view will scroll just a
196                         // bit to fit the selection in completely.
197                         keepSearching = (dir && sy1 < this.frame.scrollY)
198                             || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
199                     }
200                 } while (retRange && keepSearching);
201             } else {
202                 retRange = finder.Find(str, searchRange, startPt, endPt);
203             }
205             if (retRange) {
206                 this._set_selection(retRange);
207                 selectionRange = retRange.cloneRange();
208             } else {
210             }
212             return selectionRange;
213         } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
214         return null;
215     },
217     find : function (str, dir, pt) {
218         var s = this.top;
220         if (str == null || str.length == 0)
221             return;
223         // Should we wrap this time?
224         var wrapped = s.wrapped;
225         var point = pt;
226         if (s.wrapped == false && s.range == null
227             && s.search_str == str && s.direction == dir)
228         {
229             wrapped = true;
230             point = null;
231         }
233         var match_range = this._highlight_find(str, wrapped, dir, point);
235         var new_state = {
236             screenx: this.frame.scrollX,
237             screeny: this.frame.scrollY,
238             search_str: str,
239             wrapped: wrapped,
240             point: point,
241             range: match_range,
242             selection: match_range ? match_range : s.selection,
243             direction: dir
244         };
245         this.states.push(new_state);
246     },
248     focus_link : function () {
249         var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
250         if (!sel)
251             return;
252         var node = sel.focusNode;
253         if (node == null)
254             return;
256         do {
257             if (node.localName == "A") {
258                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
259                     // if there is a selection, preserve it.  it is up
260                     // to the caller to decide whether or not to keep
261                     // the selection.
262                     var sel = this.frame.getSelection(
263                         Ci.nsISelectionController.SELECTION_NORMAL);
264                     if(sel.rangeCount > 0) {
265                         var stored_selection = sel.getRangeAt(0).cloneRange();
266                     }
267                     node.focus();
268                     if (stored_selection) {
269                         sel.removeAllRanges();
270                         sel.addRange(stored_selection);
271                     }
272                     return;
273                 }
274             }
275         } while ((node = node.parentNode));
276     },
278     collapse_selection : function() {
279         const selctrlcomp = Ci.nsISelectionController;
280         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
281         if(sel.rangeCount > 0) {
282             sel.getRangeAt(0).collapse(true);
283         }
284     },
286     handle_input : function (m) {
287         m._set_selection();
288         this.find(m._input_text, this.top.direction, this.top.point);
289         this.restore_state();
290     },
292     done : false,
294     destroy : function () {
295         if (!this.done)  {
296             this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
297             if (caret_enabled(this.buffer) && this.states[0].caret) {
298                 this._set_selection(this.states[0].caret);
299             } else {
300                 this._clear_selection();
301             }
302         }
303     }
306 function isearch_continue_noninteractively (window, direction) {
307     var s = new isearch_session(window, direction);
308     if (window.isearch_last_search)
309         s.find(window.isearch_last_search, direction, s.top.point);
310     else
311         throw "No previous isearch";
312     window.minibuffer.push_state(s);
313     s.restore_state();
314     // if (direction && s.top.point !== null)
315     //    isearch_continue (window, direction);
316     isearch_done (window, true);
319 function isearch_continue (window, direction) {
320     var s = window.minibuffer.current_state;
321     // if the minibuffer is not open, this command operates in
322     // non-interactive mode.
323     if (s == null)
324         return isearch_continue_noninteractively(window, direction);
325     if (!(s instanceof isearch_session))
326         throw "Invalid minibuffer state";
327     if (s.states.length == 1 && window.isearch_last_search)
328         s.find(window.isearch_last_search, direction, s.top.point);
329     else
330         s.find(s.top.search_str, direction, s.top.range);
331     return s.restore_state();
333 interactive("isearch-continue-forward", null,
334             function (I) { isearch_continue(I.window, true); });
335 interactive("isearch-continue-backward", null,
336             function (I) { isearch_continue(I.window, false); });
338 function isearch_start (window, direction) {
339     var s = new isearch_session(window, direction);
340     window.minibuffer.push_state(s);
341     s.restore_state();
343 interactive("isearch-forward", null,
344             function (I) { isearch_start(I.window, true); });
345 interactive("isearch-backward", null,
346             function (I) { isearch_start(I.window, false); });
348 function isearch_backspace (window) {
349     var s = window.minibuffer.current_state;
350     if (!(s instanceof isearch_session))
351         throw "Invalid minibuffer state";
352     if (s.states.length > 1)
353         s.states.pop();
354     s.restore_state();
356 interactive("isearch-backspace", null,
357             function (I) { isearch_backspace(I.window); });
359 function isearch_done (window, keep_selection) {
360     var s = window.minibuffer.current_state;
361     if (!(s instanceof isearch_session))
362         throw "Invalid minibuffer state";
363     s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
365     // Prevent focus from being reverted
366     window.minibuffer.saved_focused_element = null;
367     window.minibuffer.saved_focused_window = null;
369     s.done = true;
371     window.minibuffer.pop_state();
372     window.isearch_last_search = s.top.search_str;
373     s.focus_link();
374     if (! isearch_keep_selection && ! keep_selection)
375         s.collapse_selection();
377 interactive("isearch-done", null,
378             function (I) { isearch_done(I.window); });