input_handle_command: allow event to be a string
[conkeror/arlinius.git] / modules / find.js
blobbbec082d5bffd9813ae66dd89770f3a38fe142a4
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 const CARET_ATTRIBUTE = 'showcaret';
13 define_variable("isearch_keep_selection", false,
14     "Set to `true' to make isearch leave the selection visible when a "+
15     "search is completed.");
17 function caret_enabled (buffer) {
18     return buffer.browser.getAttribute(CARET_ATTRIBUTE);
21 // turn on the selection in all frames
22 function getFocusedSelCtrl (buffer) {
23     var ds = buffer.doc_shell;
24     var dsEnum = ds.getDocShellEnumerator(Ci.nsIDocShellTreeItem.typeContent,
25                                           Ci.nsIDocShell.ENUMERATE_FORWARDS);
26     while (dsEnum.hasMoreElements()) {
27         ds = dsEnum.getNext().QueryInterface(Ci.nsIDocShell);
28         if (ds.hasFocus) {
29             var display = ds.QueryInterface(Ci.nsIInterfaceRequestor)
30                 .getInterface(Ci.nsISelectionDisplay);
31             if (!display)
32                 return null;
33             return display.QueryInterface(Ci.nsISelectionController);
34         }
35     }
37     // One last try
38     return ds
39         .QueryInterface(Ci.nsIInterfaceRequestor)
40         .getInterface(Ci.nsISelectionDisplay)
41         .QueryInterface(Ci.nsISelectionController);
44 function clear_selection (buffer) {
45     let sel_ctrl = getFocusedSelCtrl(buffer);
46     if (sel_ctrl) {
47         let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
48         if (caret_enabled(buffer)) {
49             if (sel.anchorNode)
50                 sel.collapseToStart();
51         } else {
52             sel.removeAllRanges();
53         }
54     }
58 function initial_isearch_state (buffer, frame, forward) {
59     this.screenx = frame.scrollX;
60     this.screeny = frame.scrollY;
61     this.search_str = "";
62     this.wrapped = false;
63     let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
64     if (sel.rangeCount > 0) {
65         this.point = sel.getRangeAt(0);
66         if (caret_enabled(buffer))
67             this.caret = this.point.cloneRange();
68     } else {
69         this.point = null;
70     }
71     this.range = frame.document.createRange();
72     this.selection = null;
73     this.direction = forward;
76 function isearch_session (window, forward) {
77     this.states = [];
78     this.buffer = window.buffers.current;
79     this.frame = this.buffer.focused_frame;
80     this.sel_ctrl = getFocusedSelCtrl(this.buffer);
81     this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
82     this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
83     this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
84     this.window = window;
86     minibuffer_input_state.call(this, window, isearch_keymap, "");
88 isearch_session.prototype = {
89     constructor : isearch_session,
90     __proto__ : minibuffer_input_state.prototype,
92     get top () {
93         return this.states[this.states.length - 1];
94     },
95     _set_selection : function (range) {
96         try {
97             const selctrlcomp = Ci.nsISelectionController;
98             var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
99             sel.removeAllRanges();
100             sel.addRange(range.cloneRange());
101             this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
102                                                   selctrlcomp.SELECTION_FOCUS_REGION,
103                                                   true);
104         } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
105     },
106     _clear_selection : function () {
107         const selctrlcomp = Ci.nsISelectionController;
108         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
109         sel.removeAllRanges();
110     },
111     restore_state: function () {
112         var m = this.window.minibuffer;
113         var s = this.top;
114         m._input_text = s.search_str;
115         if (s.selection)
116             this._set_selection(s.selection);
117         else
118             this._clear_selection();
119         this.frame.scrollTo(s.screenx, s.screeny);
120         m.prompt = ((s.wrapped ? "Wrapped ":"")
121                     + (s.range ? "" : "Failing ")
122                     + "I-Search" + (s.direction? "": " backward") + ":");
123     },
124     _highlight_find : function (str, wrapped, dir, pt) {
125         try {
126             var doc = this.frame.document;
127             var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
128                           .createInstance()
129                           .QueryInterface(Ci.nsIFind));
130             var searchRange;
131             var startPt;
132             var endPt;
133             var body = doc.documentElement;
135             finder.findBackwards = !dir;
137             searchRange = doc.createRange();
138             startPt = doc.createRange();
139             endPt = doc.createRange();
141             var count = body.childNodes.length;
143             // Search range in the doc
144             searchRange.setStart(body,0);
145             searchRange.setEnd(body, count);
147             if (!dir) {
148                 if (pt == null) {
149                     startPt.setStart(body, count);
150                     startPt.setEnd(body, count);
151                 } else {
152                     startPt.setStart(pt.startContainer, pt.startOffset);
153                     startPt.setEnd(pt.startContainer, pt.startOffset);
154                 }
155                 endPt.setStart(body, 0);
156                 endPt.setEnd(body, 0);
157             } else {
158                 if (pt == null) {
159                     startPt.setStart(body, 0);
160                     startPt.setEnd(body, 0);
161                 } else {
162                     startPt.setStart(pt.endContainer, pt.endOffset);
163                     startPt.setEnd(pt.endContainer, pt.endOffset);
164                 }
165                 endPt.setStart(body, count);
166                 endPt.setEnd(body, count);
167             }
168             // search the doc
169             var retRange = null;
170             var selectionRange = null;
173             if (!wrapped) {
174                 do {
175                     retRange = finder.Find(str, searchRange, startPt, endPt);
176                     var keepSearching = false;
177                     if (retRange) {
178                         var sc = retRange.startContainer;
179                         var ec = retRange.endContainer;
180                         var scp = sc.parentNode;
181                         var ecp = ec.parentNode;
182                         var sy1 = abs_point(scp).y;
183                         var ey2 = abs_point(ecp).y + ecp.offsetHeight;
185                         startPt = retRange.startContainer.ownerDocument.createRange();
186                         if (!dir) {
187                             startPt.setStart(retRange.startContainer, retRange.startOffset);
188                             startPt.setEnd(retRange.startContainer, retRange.startOffset);
189                         } else {
190                             startPt.setStart(retRange.endContainer, retRange.endOffset);
191                             startPt.setEnd(retRange.endContainer, retRange.endOffset);
192                         }
193                         // We want to find a match that is completely
194                         // visible, otherwise the view will scroll just a
195                         // bit to fit the selection in completely.
196                         keepSearching = (dir && sy1 < this.frame.scrollY)
197                             || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
198                     }
199                 } while (retRange && keepSearching);
200             } else {
201                 retRange = finder.Find(str, searchRange, startPt, endPt);
202             }
204             if (retRange) {
205                 this._set_selection(retRange);
206                 selectionRange = retRange.cloneRange();
207             } else {
209             }
211             return selectionRange;
212         } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
213         return null;
214     },
216     find : function (str, dir, pt) {
217         var s = this.top;
219         if (str == null || str.length == 0)
220             return;
222         // Should we wrap this time?
223         var wrapped = s.wrapped;
224         var point = pt;
225         if (s.wrapped == false && s.range == null
226             && s.search_str == str && s.direction == dir)
227         {
228             wrapped = true;
229             point = null;
230         }
232         var match_range = this._highlight_find(str, wrapped, dir, point);
234         var new_state = {
235             screenx: this.frame.scrollX,
236             screeny: this.frame.scrollY,
237             search_str: str,
238             wrapped: wrapped,
239             point: point,
240             range: match_range,
241             selection: match_range ? match_range : s.selection,
242             direction: dir
243         };
244         this.states.push(new_state);
245     },
247     focus_link : function () {
248         var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
249         if (!sel)
250             return;
251         var node = sel.focusNode;
252         if (node == null)
253             return;
255         do {
256             if (node.localName == "A") {
257                 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
258                     // if there is a selection, preserve it.  it is up
259                     // to the caller to decide whether or not to keep
260                     // the selection.
261                     var sel = this.frame.getSelection(
262                         Ci.nsISelectionController.SELECTION_NORMAL);
263                     if(sel.rangeCount > 0) {
264                         var stored_selection = sel.getRangeAt(0).cloneRange();
265                     }
266                     node.focus();
267                     if (stored_selection) {
268                         sel.removeAllRanges();
269                         sel.addRange(stored_selection);
270                     }
271                     return;
272                 }
273             }
274         } while ((node = node.parentNode));
275     },
277     collapse_selection : function() {
278         const selctrlcomp = Ci.nsISelectionController;
279         var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
280         if(sel.rangeCount > 0) {
281             sel.getRangeAt(0).collapse(true);
282         }
283     },
285     handle_input : function (m) {
286         m._set_selection();
287         this.find(m._input_text, this.top.direction, this.top.point);
288         this.restore_state();
289     },
291     done : false,
293     destroy : function (window) {
294         if (!this.done)  {
295             this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
296             if (caret_enabled(this.buffer) && this.states[0].caret) {
297                 this._set_selection(this.states[0].caret);
298             } else {
299                 this._clear_selection();
300             }
301         }
302         minibuffer_input_state.prototype.destroy.call(this, window);
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); });