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 "+
16 "visible when a search is completed.");
18 function caret_enabled(buffer)
20 return buffer.browser.getAttribute(CARET_ATTRIBUTE);
23 // turn on the selection in all frames
24 function getFocusedSelCtrl(buffer)
26 var ds = buffer.doc_shell;
27 var dsEnum = ds.getDocShellEnumerator(Components.interfaces.nsIDocShellTreeItem.typeContent,
28 Components.interfaces.nsIDocShell.ENUMERATE_FORWARDS);
29 while (dsEnum.hasMoreElements()) {
30 ds = dsEnum.getNext().QueryInterface(Components.interfaces.nsIDocShell);
33 var display = ds.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
34 .getInterface(Components.interfaces.nsISelectionDisplay);
37 return display.QueryInterface(Components.interfaces.nsISelectionController);
43 .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
44 .getInterface(Components.interfaces.nsISelectionDisplay)
45 .QueryInterface(Components.interfaces.nsISelectionController);
48 function clear_selection(buffer) {
49 let sel_ctrl = getFocusedSelCtrl(buffer);
51 let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
52 if(caret_enabled(buffer)) {
54 sel.collapseToStart();
57 sel.removeAllRanges();
63 function initial_isearch_state(buffer, frame, forward)
65 this.screenx = frame.scrollX;
66 this.screeny = frame.scrollY;
69 let sel = frame.getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
70 if(sel.rangeCount > 0) {
71 this.point = sel.getRangeAt(0);
72 if (caret_enabled(buffer))
73 this.caret = this.point.cloneRange();
77 this.range = frame.document.createRange();
78 this.selection = null;
79 this.direction = forward;
82 function isearch_session(window, forward)
85 this.buffer = window.buffers.current;
86 this.frame = this.buffer.focused_frame;
87 this.sel_ctrl = getFocusedSelCtrl(this.buffer);
88 this.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_ATTENTION);
89 this.sel_ctrl.repaintSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
90 this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
93 minibuffer_input_state.call(this, isearch_keymap, "");
95 isearch_session.prototype = {
96 constructor : isearch_session,
97 __proto__ : minibuffer_input_state.prototype, // inherit from minibuffer_state
100 return this.states[this.states.length - 1];
102 _set_selection : function (range)
105 const selctrlcomp = Components.interfaces.nsISelectionController;
106 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
107 sel.removeAllRanges();
108 sel.addRange(range.cloneRange());
109 this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
110 selctrlcomp.SELECTION_FOCUS_REGION,
112 } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
114 _clear_selection : function ()
116 const selctrlcomp = Components.interfaces.nsISelectionController;
117 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
118 sel.removeAllRanges();
120 restore_state: function () {
121 var m = this.window.minibuffer;
123 m._input_text = s.search_str;
125 this._set_selection(s.selection);
127 this._clear_selection();
128 this.frame.scrollTo(s.screenx, s.screeny);
129 m.prompt = ((s.wrapped ? "Wrapped ":"")
130 + (s.range ? "" : "Failing ")
131 + "I-Search" + (s.direction? "": " backward") + ":");
133 _highlight_find : function (str, wrapped, dir, pt) {
135 var doc = this.frame.document;
136 var finder = (Components.classes["@mozilla.org/embedcomp/rangefind;1"].createInstance()
137 .QueryInterface(Components.interfaces.nsIFind));
141 var body = doc.documentElement;
143 finder.findBackwards = !dir;
145 searchRange = doc.createRange();
146 startPt = doc.createRange();
147 endPt = doc.createRange();
149 var count = body.childNodes.length;
151 // Search range in the doc
152 searchRange.setStart(body,0);
153 searchRange.setEnd(body, count);
157 startPt.setStart(body, count);
158 startPt.setEnd(body, count);
160 startPt.setStart(pt.startContainer, pt.startOffset);
161 startPt.setEnd(pt.startContainer, pt.startOffset);
163 endPt.setStart(body, 0);
164 endPt.setEnd(body, 0);
167 startPt.setStart(body, 0);
168 startPt.setEnd(body, 0);
170 startPt.setStart(pt.endContainer, pt.endOffset);
171 startPt.setEnd(pt.endContainer, pt.endOffset);
173 endPt.setStart(body, count);
174 endPt.setEnd(body, count);
178 var selectionRange = null;
183 retRange = finder.Find(str, searchRange, startPt, endPt);
184 var keepSearching = false;
186 var sc = retRange.startContainer;
187 var ec = retRange.endContainer;
188 var scp = sc.parentNode;
189 var ecp = ec.parentNode;
190 var sy1 = abs_point(scp).y;
191 var ey2 = abs_point(ecp).y + ecp.offsetHeight;
193 startPt = retRange.startContainer.ownerDocument.createRange();
195 startPt.setStart(retRange.startContainer, retRange.startOffset);
196 startPt.setEnd(retRange.startContainer, retRange.startOffset);
198 startPt.setStart(retRange.endContainer, retRange.endOffset);
199 startPt.setEnd(retRange.endContainer, retRange.endOffset);
201 // We want to find a match that is completely
202 // visible, otherwise the view will scroll just a
203 // bit to fit the selection in completely.
204 keepSearching = (dir && sy1 < this.frame.scrollY)
205 || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
207 } while (retRange && keepSearching);
209 retRange = finder.Find(str, searchRange, startPt, endPt);
213 this._set_selection(retRange);
214 selectionRange = retRange.cloneRange();
219 return selectionRange;
220 } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
224 find : function (str, dir, pt) {
227 if (str == null || str.length == 0)
230 // Should we wrap this time?
231 var wrapped = s.wrapped;
233 if (s.wrapped == false && s.range == null && s.search_str == str && s.direction == dir) {
238 var match_range = this._highlight_find(str, wrapped, dir, point);
241 screenx: this.frame.scrollX, screeny: this.frame.scrollY,
242 search_str: str, wrapped: wrapped, point: point,
244 selection: match_range ? match_range : s.selection,
247 this.states.push(new_state);
250 focus_link : function ()
252 var sel = this.frame.getSelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
255 var node = sel.focusNode;
260 if (node.localName == "A") {
261 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
262 // if there is a selection, preserve it. it is up
263 // to the caller to decide whether or not to keep
265 var sel = this.frame.getSelection(
266 Components.interfaces.nsISelectionController.SELECTION_NORMAL);
267 if(sel.rangeCount > 0) {
268 var stored_selection = sel.getRangeAt(0).cloneRange();
271 if (stored_selection) {
272 sel.removeAllRanges();
273 sel.addRange(stored_selection);
278 } while ((node = node.parentNode));
281 collapse_selection : function() {
282 const selctrlcomp = Components.interfaces.nsISelectionController;
283 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
284 if(sel.rangeCount > 0) {
285 sel.getRangeAt(0).collapse(true);
289 handle_input : function (m) {
291 this.find(m._input_text, this.top.direction, this.top.point);
292 this.restore_state();
297 destroy : function () {
299 this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
300 if (caret_enabled(this.buffer) && this.states[0].caret) {
301 this._set_selection(this.states[0].caret);
303 this._clear_selection();
309 function isearch_continue_noninteractively(window, direction) {
310 var s = new isearch_session(window, direction);
311 if (window.isearch_last_search)
312 s.find(window.isearch_last_search, direction, s.top.point);
314 throw "No previous isearch";
315 window.minibuffer.push_state(s);
317 // if (direction && s.top.point !== null)
318 // isearch_continue (window, direction);
319 isearch_done (window, true);
322 function isearch_continue(window, direction) {
323 var s = window.minibuffer.current_state;
324 // if the minibuffer is not open, this command operates in
325 // non-interactive mode.
327 return isearch_continue_noninteractively(window, direction);
328 if (!(s instanceof isearch_session))
329 throw "Invalid minibuffer state";
330 if (s.states.length == 1 && window.isearch_last_search)
331 s.find(window.isearch_last_search, direction, s.top.point);
333 s.find(s.top.search_str, direction, s.top.range);
334 return s.restore_state();
336 interactive("isearch-continue-forward", null, function (I) {isearch_continue(I.window, true);});
337 interactive("isearch-continue-backward", null, function (I) {isearch_continue(I.window, false);});
339 function isearch_start (window, direction)
341 var s = new isearch_session(window, direction);
342 window.minibuffer.push_state(s);
345 interactive("isearch-forward", null, function (I) {isearch_start(I.window, true);});
346 interactive("isearch-backward", null, function (I) {isearch_start(I.window, false);});
348 function isearch_backspace (window)
350 var s = window.minibuffer.current_state;
351 if (!(s instanceof isearch_session))
352 throw "Invalid minibuffer state";
353 if (s.states.length > 1)
357 interactive("isearch-backspace", null, function (I) {isearch_backspace(I.window);});
359 function isearch_done (window, keep_selection)
361 var s = window.minibuffer.current_state;
362 if (!(s instanceof isearch_session))
363 throw "Invalid minibuffer state";
364 s.sel_ctrl.setDisplaySelection(Components.interfaces.nsISelectionController.SELECTION_NORMAL);
366 // Prevent focus from being reverted
367 window.minibuffer.saved_focused_element = null;
368 window.minibuffer.saved_focused_window = null;
372 window.minibuffer.pop_state();
373 window.isearch_last_search = s.top.search_str;
375 if (! isearch_keep_selection && ! keep_selection)
376 s.collapse_selection();
378 interactive("isearch-done", null, function (I) {isearch_done(I.window);});