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
13 const CARET_ATTRIBUTE = 'showcaret';
15 define_variable("isearch_keep_selection", false,
16 "Set to `true' to make isearch leave the selection visible when a "+
17 "search is completed.");
19 function caret_enabled (buffer) {
20 return buffer.browser.getAttribute(CARET_ATTRIBUTE);
24 function clear_selection (buffer) {
25 let sel_ctrl = buffer.focused_selection_controller;
27 let sel = sel_ctrl.getSelection(sel_ctrl.SELECTION_NORMAL);
28 if (caret_enabled(buffer)) {
30 sel.collapseToStart();
32 sel.removeAllRanges();
38 function initial_isearch_state (buffer, frame, forward) {
39 this.screenx = frame.scrollX;
40 this.screeny = frame.scrollY;
43 let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
44 if (sel.rangeCount > 0) {
45 this.point = sel.getRangeAt(0);
46 if (caret_enabled(buffer))
47 this.caret = this.point.cloneRange();
51 this.range = frame.document.createRange();
52 this.selection = null;
53 this.direction = forward;
56 function isearch_session (window, forward) {
58 this.buffer = window.buffers.current;
59 this.frame = this.buffer.focused_frame;
60 this.sel_ctrl = this.buffer.focused_selection_controller;
61 this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
62 this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
63 this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
66 minibuffer_input_state.call(this, window, isearch_keymap, "");
68 isearch_session.prototype = {
69 constructor : isearch_session,
70 __proto__ : minibuffer_input_state.prototype,
73 return this.states[this.states.length - 1];
75 _set_selection : function (range) {
77 const selctrlcomp = Ci.nsISelectionController;
78 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
79 sel.removeAllRanges();
80 sel.addRange(range.cloneRange());
81 this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
82 selctrlcomp.SELECTION_FOCUS_REGION,
84 } catch(e) {/*FIXME:figure out if/why this is needed*/ dumpln("setSelection: " + e);}
86 _clear_selection : function () {
87 const selctrlcomp = Ci.nsISelectionController;
88 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
89 sel.removeAllRanges();
91 restore_state: function () {
92 var m = this.window.minibuffer;
94 m._input_text = s.search_str;
96 this._set_selection(s.selection);
98 this._clear_selection();
99 this.frame.scrollTo(s.screenx, s.screeny);
100 m.prompt = ((s.wrapped ? "Wrapped ":"")
101 + (s.range ? "" : "Failing ")
102 + "I-Search" + (s.direction? "": " backward") + ":");
104 _highlight_find : function (str, wrapped, dir, pt) {
106 var doc = this.frame.document;
107 var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
109 .QueryInterface(Ci.nsIFind));
113 var body = doc.documentElement;
115 finder.findBackwards = !dir;
117 searchRange = doc.createRange();
118 startPt = doc.createRange();
119 endPt = doc.createRange();
121 var count = body.childNodes.length;
123 // Search range in the doc
124 searchRange.setStart(body,0);
125 searchRange.setEnd(body, count);
129 startPt.setStart(body, count);
130 startPt.setEnd(body, count);
132 startPt.setStart(pt.startContainer, pt.startOffset);
133 startPt.setEnd(pt.startContainer, pt.startOffset);
135 endPt.setStart(body, 0);
136 endPt.setEnd(body, 0);
139 startPt.setStart(body, 0);
140 startPt.setEnd(body, 0);
142 startPt.setStart(pt.endContainer, pt.endOffset);
143 startPt.setEnd(pt.endContainer, pt.endOffset);
145 endPt.setStart(body, count);
146 endPt.setEnd(body, count);
150 var selectionRange = null;
155 retRange = finder.Find(str, searchRange, startPt, endPt);
156 var keepSearching = false;
158 var sc = retRange.startContainer;
159 var ec = retRange.endContainer;
160 var scp = sc.parentNode;
161 var ecp = ec.parentNode;
162 var sy1 = abs_point(scp).y;
163 var ey2 = abs_point(ecp).y + ecp.offsetHeight;
165 startPt = retRange.startContainer.ownerDocument.createRange();
167 startPt.setStart(retRange.startContainer, retRange.startOffset);
168 startPt.setEnd(retRange.startContainer, retRange.startOffset);
170 startPt.setStart(retRange.endContainer, retRange.endOffset);
171 startPt.setEnd(retRange.endContainer, retRange.endOffset);
173 // We want to find a match that is completely
174 // visible, otherwise the view will scroll just a
175 // bit to fit the selection in completely.
176 keepSearching = (dir && sy1 < this.frame.scrollY)
177 || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
179 } while (retRange && keepSearching);
181 retRange = finder.Find(str, searchRange, startPt, endPt);
185 this._set_selection(retRange);
186 selectionRange = retRange.cloneRange();
191 return selectionRange;
192 } catch(e) { /* FIXME: figure out why this is needed*/ this.window.alert(e); }
196 find : function (str, dir, pt) {
199 if (str == null || str.length == 0)
202 // Should we wrap this time?
203 var wrapped = s.wrapped;
205 if (s.wrapped == false && s.range == null
206 && s.search_str == str && s.direction == dir)
212 var match_range = this._highlight_find(str, wrapped, dir, point);
215 screenx: this.frame.scrollX,
216 screeny: this.frame.scrollY,
221 selection: match_range ? match_range : s.selection,
224 this.states.push(new_state);
227 focus_link : function () {
228 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
231 var node = sel.focusNode;
236 if (node.localName == "A") {
237 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
238 // if there is a selection, preserve it. it is up
239 // to the caller to decide whether or not to keep
241 var sel = this.frame.getSelection(
242 Ci.nsISelectionController.SELECTION_NORMAL);
243 if(sel.rangeCount > 0) {
244 var stored_selection = sel.getRangeAt(0).cloneRange();
247 if (stored_selection) {
248 sel.removeAllRanges();
249 sel.addRange(stored_selection);
254 } while ((node = node.parentNode));
257 collapse_selection : function() {
258 const selctrlcomp = Ci.nsISelectionController;
259 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
260 if(sel.rangeCount > 0) {
261 sel.getRangeAt(0).collapse(true);
265 handle_input : function (m) {
267 this.find(m._input_text, this.top.direction, this.top.point);
268 this.restore_state();
273 destroy : function (window) {
275 this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
276 if (caret_enabled(this.buffer) && this.states[0].caret) {
277 this._set_selection(this.states[0].caret);
279 this._clear_selection();
282 minibuffer_input_state.prototype.destroy.call(this, window);
286 function isearch_continue_noninteractively (window, direction) {
287 var s = new isearch_session(window, direction);
288 if (window.isearch_last_search)
289 s.find(window.isearch_last_search, direction, s.top.point);
291 throw "No previous isearch";
292 window.minibuffer.push_state(s);
294 // if (direction && s.top.point !== null)
295 // isearch_continue (window, direction);
296 isearch_done (window, true);
299 function isearch_continue (window, direction) {
300 var s = window.minibuffer.current_state;
301 // if the minibuffer is not open, this command operates in
302 // non-interactive mode.
304 return isearch_continue_noninteractively(window, direction);
305 if (!(s instanceof isearch_session))
306 throw "Invalid minibuffer state";
307 if (s.states.length == 1 && window.isearch_last_search)
308 s.find(window.isearch_last_search, direction, s.top.point);
310 s.find(s.top.search_str, direction, s.top.range);
311 return s.restore_state();
313 interactive("isearch-continue-forward", null,
314 function (I) { isearch_continue(I.window, true); });
315 interactive("isearch-continue-backward", null,
316 function (I) { isearch_continue(I.window, false); });
318 function isearch_start (window, direction) {
319 var s = new isearch_session(window, direction);
320 window.minibuffer.push_state(s);
323 interactive("isearch-forward", null,
324 function (I) { isearch_start(I.window, true); });
325 interactive("isearch-backward", null,
326 function (I) { isearch_start(I.window, false); });
328 function isearch_backspace (window) {
329 var s = window.minibuffer.current_state;
330 if (!(s instanceof isearch_session))
331 throw "Invalid minibuffer state";
332 if (s.states.length > 1)
336 interactive("isearch-backspace", null,
337 function (I) { isearch_backspace(I.window); });
339 function isearch_done (window, keep_selection) {
340 var s = window.minibuffer.current_state;
341 if (!(s instanceof isearch_session))
342 throw "Invalid minibuffer state";
343 s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
345 // Prevent focus from being reverted
346 window.minibuffer.saved_focused_element = null;
347 window.minibuffer.saved_focused_window = null;
351 window.minibuffer.pop_state();
352 window.isearch_last_search = s.top.search_str;
354 if (! isearch_keep_selection && ! keep_selection)
355 s.collapse_selection();
357 interactive("isearch-done", null,
358 function (I) { isearch_done(I.window); });