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 define_variable("isearch_keep_selection", false,
14 "Set to `true' to make isearch leave the selection visible when a "+
15 "search is completed.");
18 function initial_isearch_state (buffer, frame, forward) {
19 this.screenx = frame.scrollX;
20 this.screeny = frame.scrollY;
23 let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
24 if (sel.rangeCount > 0) {
25 this.point = sel.getRangeAt(0);
26 if (caret_enabled(buffer))
27 this.caret = this.point.cloneRange();
31 this.range = frame.document.createRange();
32 this.selection = null;
33 this.direction = forward;
36 function isearch_session (window, forward) {
38 this.buffer = window.buffers.current;
39 this.frame = this.buffer.focused_frame;
40 this.sel_ctrl = this.buffer.focused_selection_controller;
41 this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
42 this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
43 this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
46 minibuffer_input_state.call(this, window, isearch_keymap, "");
48 isearch_session.prototype = {
49 constructor: isearch_session,
50 __proto__: minibuffer_input_state.prototype,
53 return this.states[this.states.length - 1];
55 _set_selection: function (range) {
56 const selctrlcomp = Ci.nsISelectionController;
57 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
58 sel.removeAllRanges();
59 sel.addRange(range.cloneRange());
60 this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
61 selctrlcomp.SELECTION_FOCUS_REGION,
64 _clear_selection: function () {
65 const selctrlcomp = Ci.nsISelectionController;
66 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
67 sel.removeAllRanges();
69 restore_state: function () {
70 var m = this.window.minibuffer;
72 m._input_text = s.search_str;
74 this._set_selection(s.selection);
76 this._clear_selection();
77 this.frame.scrollTo(s.screenx, s.screeny);
78 m.prompt = ((s.wrapped ? "Wrapped ":"")
79 + (s.range ? "" : "Failing ")
80 + "I-Search" + (s.direction? "": " backward") + ":");
82 _highlight_find: function (str, wrapped, dir, pt) {
83 var doc = this.frame.document;
84 var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
86 .QueryInterface(Ci.nsIFind));
90 var body = doc.documentElement;
92 finder.findBackwards = !dir;
94 searchRange = doc.createRange();
95 startPt = doc.createRange();
96 endPt = doc.createRange();
98 var count = body.childNodes.length;
100 // Search range in the doc
101 searchRange.setStart(body,0);
102 searchRange.setEnd(body, count);
106 startPt.setStart(body, count);
107 startPt.setEnd(body, count);
109 startPt.setStart(pt.startContainer, pt.startOffset);
110 startPt.setEnd(pt.startContainer, pt.startOffset);
112 endPt.setStart(body, 0);
113 endPt.setEnd(body, 0);
116 startPt.setStart(body, 0);
117 startPt.setEnd(body, 0);
119 startPt.setStart(pt.endContainer, pt.endOffset);
120 startPt.setEnd(pt.endContainer, pt.endOffset);
122 endPt.setStart(body, count);
123 endPt.setEnd(body, count);
127 var selectionRange = null;
131 retRange = finder.Find(str, searchRange, startPt, endPt);
132 var keepSearching = false;
134 var sc = retRange.startContainer;
135 var ec = retRange.endContainer;
136 var scp = sc.parentNode;
137 var ecp = ec.parentNode;
138 var sy1 = abs_point(scp).y;
139 var ey2 = abs_point(ecp).y + ecp.offsetHeight;
141 startPt = retRange.startContainer.ownerDocument.createRange();
143 startPt.setStart(retRange.startContainer, retRange.startOffset);
144 startPt.setEnd(retRange.startContainer, retRange.startOffset);
146 startPt.setStart(retRange.endContainer, retRange.endOffset);
147 startPt.setEnd(retRange.endContainer, retRange.endOffset);
149 // We want to find a match that is completely
150 // visible, otherwise the view will scroll just a
151 // bit to fit the selection in completely.
152 keepSearching = (dir && sy1 < this.frame.scrollY)
153 || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
155 } while (retRange && keepSearching);
157 retRange = finder.Find(str, searchRange, startPt, endPt);
161 this._set_selection(retRange);
162 selectionRange = retRange.cloneRange();
165 return selectionRange;
168 find: function (str, dir, pt) {
171 if (str == null || str.length == 0)
174 // Should we wrap this time?
175 var wrapped = s.wrapped;
177 if (s.wrapped == false && s.range == null
178 && s.search_str == str && s.direction == dir)
184 var match_range = this._highlight_find(str, wrapped, dir, point);
187 screenx: this.frame.scrollX,
188 screeny: this.frame.scrollY,
193 selection: match_range ? match_range : s.selection,
196 this.states.push(new_state);
199 focus_link: function () {
200 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
203 var node = sel.focusNode;
207 if (node.localName && node.localName.toLowerCase() == "a") {
208 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
209 // if there is a selection, preserve it. it is up
210 // to the caller to decide whether or not to keep
212 var sel = this.frame.getSelection(
213 Ci.nsISelectionController.SELECTION_NORMAL);
214 if (sel.rangeCount > 0)
215 var stored_selection = sel.getRangeAt(0).cloneRange();
217 if (stored_selection) {
218 sel.removeAllRanges();
219 sel.addRange(stored_selection);
224 } while ((node = node.parentNode));
227 collapse_selection: function() {
228 const selctrlcomp = Ci.nsISelectionController;
229 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
230 if (sel.rangeCount > 0)
231 sel.getRangeAt(0).collapse(true);
234 handle_input: function (m) {
236 this.find(m._input_text, this.top.direction, this.top.point);
237 this.restore_state();
242 destroy: function (window) {
244 this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
245 if (caret_enabled(this.buffer) && this.states[0].caret)
246 this._set_selection(this.states[0].caret);
248 this._clear_selection();
250 minibuffer_input_state.prototype.destroy.call(this, window);
254 function isearch_continue_noninteractively (window, direction) {
255 var s = new isearch_session(window, direction);
256 if (window.isearch_last_search)
257 s.find(window.isearch_last_search, direction, s.top.point);
259 throw "No previous isearch";
260 window.minibuffer.push_state(s);
262 // if (direction && s.top.point !== null)
263 // isearch_continue (window, direction);
264 isearch_done(window, true);
267 function isearch_continue (window, direction) {
268 var s = window.minibuffer.current_state;
269 // if the minibuffer is not open, this command operates in
270 // non-interactive mode.
272 return isearch_continue_noninteractively(window, direction);
273 if (!(s instanceof isearch_session))
274 throw "Invalid minibuffer state";
275 if (s.states.length == 1 && window.isearch_last_search)
276 s.find(window.isearch_last_search, direction, s.top.point);
278 s.find(s.top.search_str, direction, s.top.range);
279 return s.restore_state();
281 interactive("isearch-continue-forward", null,
282 function (I) { isearch_continue(I.window, true); });
283 interactive("isearch-continue-backward", null,
284 function (I) { isearch_continue(I.window, false); });
286 function isearch_start (window, direction) {
287 var s = new isearch_session(window, direction);
288 window.minibuffer.push_state(s);
291 interactive("isearch-forward", null,
292 function (I) { isearch_start(I.window, true); });
293 interactive("isearch-backward", null,
294 function (I) { isearch_start(I.window, false); });
296 function isearch_backspace (window) {
297 var s = window.minibuffer.current_state;
298 if (!(s instanceof isearch_session))
299 throw "Invalid minibuffer state";
300 if (s.states.length > 1)
304 interactive("isearch-backspace", null,
305 function (I) { isearch_backspace(I.window); });
307 function isearch_done (window, keep_selection) {
308 var s = window.minibuffer.current_state;
309 if (!(s instanceof isearch_session))
310 throw "Invalid minibuffer state";
311 s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
313 // Prevent focus from being reverted
314 window.minibuffer.saved_focused_element = null;
315 window.minibuffer.saved_focused_window = null;
319 window.minibuffer.pop_state();
320 window.isearch_last_search = s.top.search_str;
322 if (! isearch_keep_selection && ! keep_selection)
323 s.collapse_selection();
325 interactive("isearch-done", null,
326 function (I) { isearch_done(I.window); });