caret_enabled: move to buffer.js
[conkeror.git] / modules / find.js
bloba164daa4762644097000ccb57fcb7def2ea9596d
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 define_variable("isearch_keep_selection", false,
14     "Set to `true' to make isearch leave the selection visible when a "+
15     "search is completed.");
18 function initial_isearch_state (buffer, frame, forward) {
19     this.screenx = frame.scrollX;
20     this.screeny = frame.scrollY;
21     this.search_str = "";
22     this.wrapped = false;
23     let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
24     if (sel.rangeCount > 0) {
25         this.point = sel.getRangeAt(0);
26         if (caret_enabled(buffer))
27             this.caret = this.point.cloneRange();
28     } else {
29         this.point = null;
30     }
31     this.range = frame.document.createRange();
32     this.selection = null;
33     this.direction = forward;
36 function isearch_session (window, forward) {
37     this.states = [];
38     this.buffer = window.buffers.current;
39     this.frame = this.buffer.focused_frame;
40     this.sel_ctrl = this.buffer.focused_selection_controller;
41     this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
42     this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
43     this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
44     this.window = window;
46     minibuffer_input_state.call(this, window, isearch_keymap, "");
48 isearch_session.prototype = {
49     constructor: isearch_session,
50     __proto__: minibuffer_input_state.prototype,
52     get top () {
53         return this.states[this.states.length - 1];
54     },
55     _set_selection: function (range) {
56         const selctrlcomp = Ci.nsISelectionController;
57         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
58         sel.removeAllRanges();
59         sel.addRange(range.cloneRange());
60         this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
61                                               selctrlcomp.SELECTION_FOCUS_REGION,
62                                               true);
63     },
64     _clear_selection: function () {
65         const selctrlcomp = Ci.nsISelectionController;
66         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
67         sel.removeAllRanges();
68     },
69     restore_state: function () {
70         var m = this.window.minibuffer;
71         var s = this.top;
72         m._input_text = s.search_str;
73         if (s.selection)
74             this._set_selection(s.selection);
75         else
76             this._clear_selection();
77         this.frame.scrollTo(s.screenx, s.screeny);
78         m.prompt = ((s.wrapped ? "Wrapped ":"")
79                     + (s.range ? "" : "Failing ")
80                     + "I-Search" + (s.direction? "": " backward") + ":");
81     },
82     _highlight_find: function (str, wrapped, dir, pt) {
83         var doc = this.frame.document;
84         var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
85                       .createInstance()
86                       .QueryInterface(Ci.nsIFind));
87         var searchRange;
88         var startPt;
89         var endPt;
90         var body = doc.documentElement;
92         finder.findBackwards = !dir;
94         searchRange = doc.createRange();
95         startPt = doc.createRange();
96         endPt = doc.createRange();
98         var count = body.childNodes.length;
100         // Search range in the doc
101         searchRange.setStart(body,0);
102         searchRange.setEnd(body, count);
104         if (!dir) {
105             if (pt == null) {
106                 startPt.setStart(body, count);
107                 startPt.setEnd(body, count);
108             } else {
109                 startPt.setStart(pt.startContainer, pt.startOffset);
110                 startPt.setEnd(pt.startContainer, pt.startOffset);
111             }
112             endPt.setStart(body, 0);
113             endPt.setEnd(body, 0);
114         } else {
115             if (pt == null) {
116                 startPt.setStart(body, 0);
117                 startPt.setEnd(body, 0);
118             } else {
119                 startPt.setStart(pt.endContainer, pt.endOffset);
120                 startPt.setEnd(pt.endContainer, pt.endOffset);
121             }
122             endPt.setStart(body, count);
123             endPt.setEnd(body, count);
124         }
125         // search the doc
126         var retRange = null;
127         var selectionRange = null;
129         if (!wrapped) {
130             do {
131                 retRange = finder.Find(str, searchRange, startPt, endPt);
132                 var keepSearching = false;
133                 if (retRange) {
134                     var sc = retRange.startContainer;
135                     var ec = retRange.endContainer;
136                     var scp = sc.parentNode;
137                     var ecp = ec.parentNode;
138                     var sy1 = abs_point(scp).y;
139                     var ey2 = abs_point(ecp).y + ecp.offsetHeight;
141                     startPt = retRange.startContainer.ownerDocument.createRange();
142                     if (!dir) {
143                         startPt.setStart(retRange.startContainer, retRange.startOffset);
144                         startPt.setEnd(retRange.startContainer, retRange.startOffset);
145                     } else {
146                         startPt.setStart(retRange.endContainer, retRange.endOffset);
147                         startPt.setEnd(retRange.endContainer, retRange.endOffset);
148                     }
149                     // We want to find a match that is completely
150                     // visible, otherwise the view will scroll just a
151                     // bit to fit the selection in completely.
152                     keepSearching = (dir && sy1 < this.frame.scrollY)
153                         || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
154                 }
155             } while (retRange && keepSearching);
156         } else {
157             retRange = finder.Find(str, searchRange, startPt, endPt);
158         }
160         if (retRange) {
161             this._set_selection(retRange);
162             selectionRange = retRange.cloneRange();
163         }
165         return selectionRange;
166     },
168     find: function (str, dir, pt) {
169         var s = this.top;
171         if (str == null || str.length == 0)
172             return;
174         // Should we wrap this time?
175         var wrapped = s.wrapped;
176         var point = pt;
177         if (s.wrapped == false && s.range == null
178             && s.search_str == str && s.direction == dir)
179         {
180             wrapped = true;
181             point = null;
182         }
184         var match_range = this._highlight_find(str, wrapped, dir, point);
186         var new_state = {
187             screenx: this.frame.scrollX,
188             screeny: this.frame.scrollY,
189             search_str: str,
190             wrapped: wrapped,
191             point: point,
192             range: match_range,
193             selection: match_range ? match_range : s.selection,
194             direction: dir
195         };
196         this.states.push(new_state);
197     },
199     focus_link: function () {
200         var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
201         if (!sel)
202             return;
203         var node = sel.focusNode;
204         if (node == null)
205             return;
206         do {
207             if (node.localName && node.localName.toLowerCase() == "a") {
208                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
209                     // if there is a selection, preserve it.  it is up
210                     // to the caller to decide whether or not to keep
211                     // the selection.
212                     var sel = this.frame.getSelection(
213                         Ci.nsISelectionController.SELECTION_NORMAL);
214                     if (sel.rangeCount > 0)
215                         var stored_selection = sel.getRangeAt(0).cloneRange();
216                     node.focus();
217                     if (stored_selection) {
218                         sel.removeAllRanges();
219                         sel.addRange(stored_selection);
220                     }
221                     return;
222                 }
223             }
224         } while ((node = node.parentNode));
225     },
227     collapse_selection: function() {
228         const selctrlcomp = Ci.nsISelectionController;
229         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
230         if (sel.rangeCount > 0)
231             sel.getRangeAt(0).collapse(true);
232     },
234     handle_input: function (m) {
235         m._set_selection();
236         this.find(m._input_text, this.top.direction, this.top.point);
237         this.restore_state();
238     },
240     done: false,
242     destroy: function (window) {
243         if (! this.done) {
244             this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
245             if (caret_enabled(this.buffer) && this.states[0].caret)
246                 this._set_selection(this.states[0].caret);
247             else
248                 this._clear_selection();
249         }
250         minibuffer_input_state.prototype.destroy.call(this, window);
251     }
254 function isearch_continue_noninteractively (window, direction) {
255     var s = new isearch_session(window, direction);
256     if (window.isearch_last_search)
257         s.find(window.isearch_last_search, direction, s.top.point);
258     else
259         throw "No previous isearch";
260     window.minibuffer.push_state(s);
261     s.restore_state();
262     // if (direction && s.top.point !== null)
263     //    isearch_continue (window, direction);
264     isearch_done(window, true);
267 function isearch_continue (window, direction) {
268     var s = window.minibuffer.current_state;
269     // if the minibuffer is not open, this command operates in
270     // non-interactive mode.
271     if (s == null)
272         return isearch_continue_noninteractively(window, direction);
273     if (!(s instanceof isearch_session))
274         throw "Invalid minibuffer state";
275     if (s.states.length == 1 && window.isearch_last_search)
276         s.find(window.isearch_last_search, direction, s.top.point);
277     else
278         s.find(s.top.search_str, direction, s.top.range);
279     return s.restore_state();
281 interactive("isearch-continue-forward", null,
282             function (I) { isearch_continue(I.window, true); });
283 interactive("isearch-continue-backward", null,
284             function (I) { isearch_continue(I.window, false); });
286 function isearch_start (window, direction) {
287     var s = new isearch_session(window, direction);
288     window.minibuffer.push_state(s);
289     s.restore_state();
291 interactive("isearch-forward", null,
292             function (I) { isearch_start(I.window, true); });
293 interactive("isearch-backward", null,
294             function (I) { isearch_start(I.window, false); });
296 function isearch_backspace (window) {
297     var s = window.minibuffer.current_state;
298     if (!(s instanceof isearch_session))
299         throw "Invalid minibuffer state";
300     if (s.states.length > 1)
301         s.states.pop();
302     s.restore_state();
304 interactive("isearch-backspace", null,
305             function (I) { isearch_backspace(I.window); });
307 function isearch_done (window, keep_selection) {
308     var s = window.minibuffer.current_state;
309     if (!(s instanceof isearch_session))
310         throw "Invalid minibuffer state";
311     s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
313     // Prevent focus from being reverted
314     window.minibuffer.saved_focused_element = null;
315     window.minibuffer.saved_focused_window = null;
317     s.done = true;
319     window.minibuffer.pop_state();
320     window.isearch_last_search = s.top.search_str;
321     s.focus_link();
322     if (! isearch_keep_selection && ! keep_selection)
323         s.collapse_selection();
325 interactive("isearch-done", null,
326             function (I) { isearch_done(I.window); });
328 provide("find");