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
7 * Use, modification, and distribution are subject to the terms specified in the
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);
30 var display = ds.QueryInterface(Ci.nsIInterfaceRequestor)
31 .getInterface(Ci.nsISelectionDisplay);
34 return display.QueryInterface(Ci.nsISelectionController);
40 .QueryInterface(Ci.nsIInterfaceRequestor)
41 .getInterface(Ci.nsISelectionDisplay)
42 .QueryInterface(Ci.nsISelectionController);
45 function clear_selection (buffer) {
46 let sel_ctrl = getFocusedSelCtrl(buffer);
48 let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
49 if (caret_enabled(buffer)) {
51 sel.collapseToStart();
53 sel.removeAllRanges();
59 function initial_isearch_state (buffer, frame, forward) {
60 this.screenx = frame.scrollX;
61 this.screeny = frame.scrollY;
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();
72 this.range = frame.document.createRange();
73 this.selection = null;
74 this.direction = forward;
77 function isearch_session (window, forward) {
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));
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
94 return this.states[this.states.length - 1];
96 _set_selection : function (range) {
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,
105 } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
107 _clear_selection : function () {
108 const selctrlcomp = Ci.nsISelectionController;
109 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
110 sel.removeAllRanges();
112 restore_state: function () {
113 var m = this.window.minibuffer;
115 m._input_text = s.search_str;
117 this._set_selection(s.selection);
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") + ":");
125 _highlight_find : function (str, wrapped, dir, pt) {
127 var doc = this.frame.document;
128 var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
130 .QueryInterface(Ci.nsIFind));
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);
150 startPt.setStart(body, count);
151 startPt.setEnd(body, count);
153 startPt.setStart(pt.startContainer, pt.startOffset);
154 startPt.setEnd(pt.startContainer, pt.startOffset);
156 endPt.setStart(body, 0);
157 endPt.setEnd(body, 0);
160 startPt.setStart(body, 0);
161 startPt.setEnd(body, 0);
163 startPt.setStart(pt.endContainer, pt.endOffset);
164 startPt.setEnd(pt.endContainer, pt.endOffset);
166 endPt.setStart(body, count);
167 endPt.setEnd(body, count);
171 var selectionRange = null;
176 retRange = finder.Find(str, searchRange, startPt, endPt);
177 var keepSearching = false;
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();
188 startPt.setStart(retRange.startContainer, retRange.startOffset);
189 startPt.setEnd(retRange.startContainer, retRange.startOffset);
191 startPt.setStart(retRange.endContainer, retRange.endOffset);
192 startPt.setEnd(retRange.endContainer, retRange.endOffset);
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);
200 } while (retRange && keepSearching);
202 retRange = finder.Find(str, searchRange, startPt, endPt);
206 this._set_selection(retRange);
207 selectionRange = retRange.cloneRange();
212 return selectionRange;
213 } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
217 find : function (str, dir, pt) {
220 if (str == null || str.length == 0)
223 // Should we wrap this time?
224 var wrapped = s.wrapped;
226 if (s.wrapped == false && s.range == null
227 && s.search_str == str && s.direction == dir)
233 var match_range = this._highlight_find(str, wrapped, dir, point);
236 screenx: this.frame.scrollX,
237 screeny: this.frame.scrollY,
242 selection: match_range ? match_range : s.selection,
245 this.states.push(new_state);
248 focus_link : function () {
249 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
252 var node = sel.focusNode;
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
262 var sel = this.frame.getSelection(
263 Ci.nsISelectionController.SELECTION_NORMAL);
264 if(sel.rangeCount > 0) {
265 var stored_selection = sel.getRangeAt(0).cloneRange();
268 if (stored_selection) {
269 sel.removeAllRanges();
270 sel.addRange(stored_selection);
275 } while ((node = node.parentNode));
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);
286 handle_input : function (m) {
288 this.find(m._input_text, this.top.direction, this.top.point);
289 this.restore_state();
294 destroy : function () {
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);
300 this._clear_selection();
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);
311 throw "No previous isearch";
312 window.minibuffer.push_state(s);
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.
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);
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);
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)
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;
371 window.minibuffer.pop_state();
372 window.isearch_last_search = s.top.search_str;
374 if (! isearch_keep_selection && ! keep_selection)
375 s.collapse_selection();
377 interactive("isearch-done", null,
378 function (I) { isearch_done(I.window); });