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
7 * Use, modification, and distribution are subject to the terms specified in the
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);
29 var display = ds.QueryInterface(Ci.nsIInterfaceRequestor)
30 .getInterface(Ci.nsISelectionDisplay);
33 return display.QueryInterface(Ci.nsISelectionController);
39 .QueryInterface(Ci.nsIInterfaceRequestor)
40 .getInterface(Ci.nsISelectionDisplay)
41 .QueryInterface(Ci.nsISelectionController);
44 function clear_selection (buffer) {
45 let sel_ctrl = getFocusedSelCtrl(buffer);
47 let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
48 if (caret_enabled(buffer)) {
50 sel.collapseToStart();
52 sel.removeAllRanges();
58 function initial_isearch_state (buffer, frame, forward) {
59 this.screenx = frame.scrollX;
60 this.screeny = frame.scrollY;
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();
71 this.range = frame.document.createRange();
72 this.selection = null;
73 this.direction = forward;
76 function isearch_session (window, forward) {
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));
86 minibuffer_input_state.call(this, window, isearch_keymap, "");
88 isearch_session.prototype = {
89 constructor : isearch_session,
90 __proto__ : minibuffer_input_state.prototype,
93 return this.states[this.states.length - 1];
95 _set_selection : function (range) {
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,
104 } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
106 _clear_selection : function () {
107 const selctrlcomp = Ci.nsISelectionController;
108 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
109 sel.removeAllRanges();
111 restore_state: function () {
112 var m = this.window.minibuffer;
114 m._input_text = s.search_str;
116 this._set_selection(s.selection);
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") + ":");
124 _highlight_find : function (str, wrapped, dir, pt) {
126 var doc = this.frame.document;
127 var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
129 .QueryInterface(Ci.nsIFind));
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);
149 startPt.setStart(body, count);
150 startPt.setEnd(body, count);
152 startPt.setStart(pt.startContainer, pt.startOffset);
153 startPt.setEnd(pt.startContainer, pt.startOffset);
155 endPt.setStart(body, 0);
156 endPt.setEnd(body, 0);
159 startPt.setStart(body, 0);
160 startPt.setEnd(body, 0);
162 startPt.setStart(pt.endContainer, pt.endOffset);
163 startPt.setEnd(pt.endContainer, pt.endOffset);
165 endPt.setStart(body, count);
166 endPt.setEnd(body, count);
170 var selectionRange = null;
175 retRange = finder.Find(str, searchRange, startPt, endPt);
176 var keepSearching = false;
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();
187 startPt.setStart(retRange.startContainer, retRange.startOffset);
188 startPt.setEnd(retRange.startContainer, retRange.startOffset);
190 startPt.setStart(retRange.endContainer, retRange.endOffset);
191 startPt.setEnd(retRange.endContainer, retRange.endOffset);
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);
199 } while (retRange && keepSearching);
201 retRange = finder.Find(str, searchRange, startPt, endPt);
205 this._set_selection(retRange);
206 selectionRange = retRange.cloneRange();
211 return selectionRange;
212 } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
216 find : function (str, dir, pt) {
219 if (str == null || str.length == 0)
222 // Should we wrap this time?
223 var wrapped = s.wrapped;
225 if (s.wrapped == false && s.range == null
226 && s.search_str == str && s.direction == dir)
232 var match_range = this._highlight_find(str, wrapped, dir, point);
235 screenx: this.frame.scrollX,
236 screeny: this.frame.scrollY,
241 selection: match_range ? match_range : s.selection,
244 this.states.push(new_state);
247 focus_link : function () {
248 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
251 var node = sel.focusNode;
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
261 var sel = this.frame.getSelection(
262 Ci.nsISelectionController.SELECTION_NORMAL);
263 if(sel.rangeCount > 0) {
264 var stored_selection = sel.getRangeAt(0).cloneRange();
267 if (stored_selection) {
268 sel.removeAllRanges();
269 sel.addRange(stored_selection);
274 } while ((node = node.parentNode));
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);
285 handle_input : function (m) {
287 this.find(m._input_text, this.top.direction, this.top.point);
288 this.restore_state();
293 destroy : function (window) {
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);
299 this._clear_selection();
302 minibuffer_input_state.prototype.destroy.call(this, window);
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); });