getFocusedSelCtrl moved to buffer.focused_selection_controller
[conkeror.git] / modules / find.js
blob8b0799f46dfae5d03341f34bfab95d130aa28efd
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-2009 John Foerch
6  *
7  * Use, modification, and distribution are subject to the terms specified in the
8  * COPYING file.
9 **/
11 in_module(null);
13 const CARET_ATTRIBUTE = 'showcaret';
15 define_variable("isearch_keep_selection", false,
16     "Set to `true' to make isearch leave the selection visible when a "+
17     "search is completed.");
19 function caret_enabled (buffer) {
20     return buffer.browser.getAttribute(CARET_ATTRIBUTE);
24 function clear_selection (buffer) {
25     let sel_ctrl = buffer.focused_selection_controller;
26     if (sel_ctrl) {
27         let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
28         if (caret_enabled(buffer)) {
29             if (sel.anchorNode)
30                 sel.collapseToStart();
31         } else {
32             sel.removeAllRanges();
33         }
34     }
38 function initial_isearch_state (buffer, frame, forward) {
39     this.screenx = frame.scrollX;
40     this.screeny = frame.scrollY;
41     this.search_str = "";
42     this.wrapped = false;
43     let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
44     if (sel.rangeCount > 0) {
45         this.point = sel.getRangeAt(0);
46         if (caret_enabled(buffer))
47             this.caret = this.point.cloneRange();
48     } else {
49         this.point = null;
50     }
51     this.range = frame.document.createRange();
52     this.selection = null;
53     this.direction = forward;
56 function isearch_session (window, forward) {
57     this.states = [];
58     this.buffer = window.buffers.current;
59     this.frame = this.buffer.focused_frame;
60     this.sel_ctrl = this.buffer.focused_selection_controller;
61     this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
62     this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
63     this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
64     this.window = window;
66     minibuffer_input_state.call(this, window, isearch_keymap, "");
68 isearch_session.prototype = {
69     constructor : isearch_session,
70     __proto__ : minibuffer_input_state.prototype,
72     get top () {
73         return this.states[this.states.length - 1];
74     },
75     _set_selection : function (range) {
76         try {
77             const selctrlcomp = Ci.nsISelectionController;
78             var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
79             sel.removeAllRanges();
80             sel.addRange(range.cloneRange());
81             this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
82                                                   selctrlcomp.SELECTION_FOCUS_REGION,
83                                                   true);
84         } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
85     },
86     _clear_selection : function () {
87         const selctrlcomp = Ci.nsISelectionController;
88         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
89         sel.removeAllRanges();
90     },
91     restore_state: function () {
92         var m = this.window.minibuffer;
93         var s = this.top;
94         m._input_text = s.search_str;
95         if (s.selection)
96             this._set_selection(s.selection);
97         else
98             this._clear_selection();
99         this.frame.scrollTo(s.screenx, s.screeny);
100         m.prompt = ((s.wrapped ? "Wrapped ":"")
101                     + (s.range ? "" : "Failing ")
102                     + "I-Search" + (s.direction? "": " backward") + ":");
103     },
104     _highlight_find : function (str, wrapped, dir, pt) {
105         try {
106             var doc = this.frame.document;
107             var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
108                           .createInstance()
109                           .QueryInterface(Ci.nsIFind));
110             var searchRange;
111             var startPt;
112             var endPt;
113             var body = doc.documentElement;
115             finder.findBackwards = !dir;
117             searchRange = doc.createRange();
118             startPt = doc.createRange();
119             endPt = doc.createRange();
121             var count = body.childNodes.length;
123             // Search range in the doc
124             searchRange.setStart(body,0);
125             searchRange.setEnd(body, count);
127             if (!dir) {
128                 if (pt == null) {
129                     startPt.setStart(body, count);
130                     startPt.setEnd(body, count);
131                 } else {
132                     startPt.setStart(pt.startContainer, pt.startOffset);
133                     startPt.setEnd(pt.startContainer, pt.startOffset);
134                 }
135                 endPt.setStart(body, 0);
136                 endPt.setEnd(body, 0);
137             } else {
138                 if (pt == null) {
139                     startPt.setStart(body, 0);
140                     startPt.setEnd(body, 0);
141                 } else {
142                     startPt.setStart(pt.endContainer, pt.endOffset);
143                     startPt.setEnd(pt.endContainer, pt.endOffset);
144                 }
145                 endPt.setStart(body, count);
146                 endPt.setEnd(body, count);
147             }
148             // search the doc
149             var retRange = null;
150             var selectionRange = null;
153             if (!wrapped) {
154                 do {
155                     retRange = finder.Find(str, searchRange, startPt, endPt);
156                     var keepSearching = false;
157                     if (retRange) {
158                         var sc = retRange.startContainer;
159                         var ec = retRange.endContainer;
160                         var scp = sc.parentNode;
161                         var ecp = ec.parentNode;
162                         var sy1 = abs_point(scp).y;
163                         var ey2 = abs_point(ecp).y + ecp.offsetHeight;
165                         startPt = retRange.startContainer.ownerDocument.createRange();
166                         if (!dir) {
167                             startPt.setStart(retRange.startContainer, retRange.startOffset);
168                             startPt.setEnd(retRange.startContainer, retRange.startOffset);
169                         } else {
170                             startPt.setStart(retRange.endContainer, retRange.endOffset);
171                             startPt.setEnd(retRange.endContainer, retRange.endOffset);
172                         }
173                         // We want to find a match that is completely
174                         // visible, otherwise the view will scroll just a
175                         // bit to fit the selection in completely.
176                         keepSearching = (dir && sy1 < this.frame.scrollY)
177                             || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
178                     }
179                 } while (retRange && keepSearching);
180             } else {
181                 retRange = finder.Find(str, searchRange, startPt, endPt);
182             }
184             if (retRange) {
185                 this._set_selection(retRange);
186                 selectionRange = retRange.cloneRange();
187             } else {
189             }
191             return selectionRange;
192         } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
193         return null;
194     },
196     find : function (str, dir, pt) {
197         var s = this.top;
199         if (str == null || str.length == 0)
200             return;
202         // Should we wrap this time?
203         var wrapped = s.wrapped;
204         var point = pt;
205         if (s.wrapped == false && s.range == null
206             && s.search_str == str && s.direction == dir)
207         {
208             wrapped = true;
209             point = null;
210         }
212         var match_range = this._highlight_find(str, wrapped, dir, point);
214         var new_state = {
215             screenx: this.frame.scrollX,
216             screeny: this.frame.scrollY,
217             search_str: str,
218             wrapped: wrapped,
219             point: point,
220             range: match_range,
221             selection: match_range ? match_range : s.selection,
222             direction: dir
223         };
224         this.states.push(new_state);
225     },
227     focus_link : function () {
228         var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
229         if (!sel)
230             return;
231         var node = sel.focusNode;
232         if (node == null)
233             return;
235         do {
236             if (node.localName == "A") {
237                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
238                     // if there is a selection, preserve it.  it is up
239                     // to the caller to decide whether or not to keep
240                     // the selection.
241                     var sel = this.frame.getSelection(
242                         Ci.nsISelectionController.SELECTION_NORMAL);
243                     if(sel.rangeCount > 0) {
244                         var stored_selection = sel.getRangeAt(0).cloneRange();
245                     }
246                     node.focus();
247                     if (stored_selection) {
248                         sel.removeAllRanges();
249                         sel.addRange(stored_selection);
250                     }
251                     return;
252                 }
253             }
254         } while ((node = node.parentNode));
255     },
257     collapse_selection : function() {
258         const selctrlcomp = Ci.nsISelectionController;
259         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
260         if(sel.rangeCount > 0) {
261             sel.getRangeAt(0).collapse(true);
262         }
263     },
265     handle_input : function (m) {
266         m._set_selection();
267         this.find(m._input_text, this.top.direction, this.top.point);
268         this.restore_state();
269     },
271     done : false,
273     destroy : function (window) {
274         if (!this.done)  {
275             this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
276             if (caret_enabled(this.buffer) && this.states[0].caret) {
277                 this._set_selection(this.states[0].caret);
278             } else {
279                 this._clear_selection();
280             }
281         }
282         minibuffer_input_state.prototype.destroy.call(this, window);
283     }
286 function isearch_continue_noninteractively (window, direction) {
287     var s = new isearch_session(window, direction);
288     if (window.isearch_last_search)
289         s.find(window.isearch_last_search, direction, s.top.point);
290     else
291         throw "No previous isearch";
292     window.minibuffer.push_state(s);
293     s.restore_state();
294     // if (direction && s.top.point !== null)
295     //    isearch_continue (window, direction);
296     isearch_done (window, true);
299 function isearch_continue (window, direction) {
300     var s = window.minibuffer.current_state;
301     // if the minibuffer is not open, this command operates in
302     // non-interactive mode.
303     if (s == null)
304         return isearch_continue_noninteractively(window, direction);
305     if (!(s instanceof isearch_session))
306         throw "Invalid minibuffer state";
307     if (s.states.length == 1 && window.isearch_last_search)
308         s.find(window.isearch_last_search, direction, s.top.point);
309     else
310         s.find(s.top.search_str, direction, s.top.range);
311     return s.restore_state();
313 interactive("isearch-continue-forward", null,
314             function (I) { isearch_continue(I.window, true); });
315 interactive("isearch-continue-backward", null,
316             function (I) { isearch_continue(I.window, false); });
318 function isearch_start (window, direction) {
319     var s = new isearch_session(window, direction);
320     window.minibuffer.push_state(s);
321     s.restore_state();
323 interactive("isearch-forward", null,
324             function (I) { isearch_start(I.window, true); });
325 interactive("isearch-backward", null,
326             function (I) { isearch_start(I.window, false); });
328 function isearch_backspace (window) {
329     var s = window.minibuffer.current_state;
330     if (!(s instanceof isearch_session))
331         throw "Invalid minibuffer state";
332     if (s.states.length > 1)
333         s.states.pop();
334     s.restore_state();
336 interactive("isearch-backspace", null,
337             function (I) { isearch_backspace(I.window); });
339 function isearch_done (window, keep_selection) {
340     var s = window.minibuffer.current_state;
341     if (!(s instanceof isearch_session))
342         throw "Invalid minibuffer state";
343     s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
345     // Prevent focus from being reverted
346     window.minibuffer.saved_focused_element = null;
347     window.minibuffer.saved_focused_window = null;
349     s.done = true;
351     window.minibuffer.pop_state();
352     window.isearch_last_search = s.top.search_str;
353     s.focus_link();
354     if (! isearch_keep_selection && ! keep_selection)
355         s.collapse_selection();
357 interactive("isearch-done", null,
358             function (I) { isearch_done(I.window); });
360 provide("find");