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-2010 John Foerch
7 * Use, modification, and distribution are subject to the terms specified in the
11 define_variable("isearch_keep_selection", false,
12 "Set to `true' to make isearch leave the selection visible when a "+
13 "search is completed.");
15 define_variable("isearch_scroll_center_vertically", false,
16 "Set to `true' to vertically center the selection when scrolling during " +
17 "an isearch. Only available with XULRunner >= 12.");
19 function initial_isearch_state (buffer, frame, forward) {
20 this.screenx = frame.scrollX;
21 this.screeny = frame.scrollY;
24 let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
25 if (sel.rangeCount > 0) {
26 this.point = sel.getRangeAt(0);
27 if (caret_enabled(buffer))
28 this.caret = this.point.cloneRange();
32 this.range = frame.document.createRange();
33 this.selection = null;
34 this.direction = forward;
37 function isearch_session (minibuffer, forward) {
38 minibuffer_input_state.call(this, minibuffer, isearch_keymap, "");
40 this.buffer = this.minibuffer.window.buffers.current;
41 this.frame = this.buffer.focused_frame;
42 this.sel_ctrl = this.buffer.focused_selection_controller;
43 this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
44 this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
45 this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
47 isearch_session.prototype = {
48 constructor: isearch_session,
49 __proto__: minibuffer_input_state.prototype,
52 return this.states[this.states.length - 1];
54 _set_selection: function (range) {
55 const selctrlcomp = Ci.nsISelectionController;
56 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
57 sel.removeAllRanges();
58 sel.addRange(range.cloneRange());
59 var xulrunner_version = get_mozilla_version();
60 if (version_compare(xulrunner_version, "2.0") < 0) {
63 flags = selctrlcomp.SCROLL_SYNCHRONOUS;
64 if (isearch_scroll_center_vertically &&
65 version_compare(xulrunner_version, "12.0") >= 0)
67 flags |= selctrlcomp.SCROLL_CENTER_VERTICALLY;
70 this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
71 selctrlcomp.SELECTION_FOCUS_REGION,
75 _clear_selection: function () {
76 const selctrlcomp = Ci.nsISelectionController;
77 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
78 sel.removeAllRanges();
80 restore_state: function () {
81 var m = this.minibuffer;
83 m.ignore_input_events = true;
84 m._input_text = s.search_str;
85 m.ignore_input_events = false;
87 this._set_selection(s.selection);
89 this._clear_selection();
90 this.frame.scrollTo(s.screenx, s.screeny);
91 m.prompt = ((s.wrapped ? "Wrapped ":"")
92 + (s.range ? "" : "Failing ")
93 + "I-Search" + (s.direction? "": " backward") + ":");
95 _highlight_find: function (str, wrapped, dir, pt) {
96 var doc = this.frame.document;
97 var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
99 .QueryInterface(Ci.nsIFind));
103 var body = doc.documentElement;
105 finder.findBackwards = !dir;
106 finder.caseSensitive = (str != str.toLowerCase());
108 searchRange = doc.createRange();
109 startPt = doc.createRange();
110 endPt = doc.createRange();
112 var count = body.childNodes.length;
114 // Search range in the doc
115 searchRange.setStart(body,0);
116 searchRange.setEnd(body, count);
120 startPt.setStart(body, count);
121 startPt.setEnd(body, count);
123 startPt.setStart(pt.startContainer, pt.startOffset);
124 startPt.setEnd(pt.startContainer, pt.startOffset);
126 endPt.setStart(body, 0);
127 endPt.setEnd(body, 0);
130 startPt.setStart(body, 0);
131 startPt.setEnd(body, 0);
133 startPt.setStart(pt.endContainer, pt.endOffset);
134 startPt.setEnd(pt.endContainer, pt.endOffset);
136 endPt.setStart(body, count);
137 endPt.setEnd(body, count);
141 var selectionRange = null;
145 retRange = finder.Find(str, searchRange, startPt, endPt);
146 var keepSearching = false;
148 var sc = retRange.startContainer;
149 var ec = retRange.endContainer;
150 var scp = sc.parentNode;
151 var ecp = ec.parentNode;
152 var sy1 = abs_point(scp).y;
153 var ey2 = abs_point(ecp).y + ecp.offsetHeight;
155 startPt = retRange.startContainer.ownerDocument.createRange();
157 startPt.setStart(retRange.startContainer, retRange.startOffset);
158 startPt.setEnd(retRange.startContainer, retRange.startOffset);
160 startPt.setStart(retRange.endContainer, retRange.endOffset);
161 startPt.setEnd(retRange.endContainer, retRange.endOffset);
163 // We want to find a match that is completely
164 // visible, otherwise the view will scroll just a
165 // bit to fit the selection in completely.
166 keepSearching = (dir && sy1 < this.frame.scrollY)
167 || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
169 } while (retRange && keepSearching);
171 retRange = finder.Find(str, searchRange, startPt, endPt);
175 this._set_selection(retRange);
176 selectionRange = retRange.cloneRange();
179 return selectionRange;
182 find: function (str, dir, pt) {
185 if (str == null || str.length == 0)
188 // Should we wrap this time?
189 var wrapped = s.wrapped;
191 if (s.wrapped == false && s.range == null
192 && s.search_str == str && s.direction == dir)
198 var match_range = this._highlight_find(str, wrapped, dir, point);
201 screenx: this.frame.scrollX,
202 screeny: this.frame.scrollY,
207 selection: match_range ? match_range : s.selection,
210 this.states.push(new_state);
213 focus_link: function () {
214 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
217 var node = sel.focusNode;
221 if (node.localName && node.localName.toLowerCase() == "a") {
222 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
223 // if there is a selection, preserve it. it is up
224 // to the caller to decide whether or not to keep
226 var sel = this.frame.getSelection(
227 Ci.nsISelectionController.SELECTION_NORMAL);
228 if (sel.rangeCount > 0)
229 var stored_selection = sel.getRangeAt(0).cloneRange();
231 if (stored_selection) {
232 sel.removeAllRanges();
233 sel.addRange(stored_selection);
238 } while ((node = node.parentNode));
241 collapse_selection: function() {
242 const selctrlcomp = Ci.nsISelectionController;
243 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
244 if (sel.rangeCount > 0)
245 sel.getRangeAt(0).collapse(true);
248 handle_input: function (m) {
250 this.find(m._input_text, this.top.direction, this.top.point);
251 this.restore_state();
256 destroy: function () {
258 this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
259 if (caret_enabled(this.buffer) && this.states[0].caret)
260 this._set_selection(this.states[0].caret);
262 this._clear_selection();
264 minibuffer_input_state.prototype.destroy.call(this);
268 function isearch_continue_noninteractively (window, direction) {
269 var s = new isearch_session(window.minibuffer, direction);
270 if (window.isearch_last_search)
271 s.find(window.isearch_last_search, direction, s.top.point);
273 throw "No previous isearch";
274 window.minibuffer.push_state(s);
276 // if (direction && s.top.point !== null)
277 // isearch_continue (window, direction);
278 isearch_done(window, true);
281 function isearch_continue (window, direction) {
282 var s = window.minibuffer.current_state;
283 // if the minibuffer is not open, this command operates in
284 // non-interactive mode.
286 return isearch_continue_noninteractively(window, direction);
287 if (!(s instanceof isearch_session))
288 throw "Invalid minibuffer state";
289 if (s.states.length == 1 && window.isearch_last_search)
290 s.find(window.isearch_last_search, direction, s.top.point);
292 s.find(s.top.search_str, direction, s.top.range);
293 return s.restore_state();
296 interactive("isearch-continue",
297 "Continue the last isearch in the same direction.",
299 isearch_continue(I.window, I.window.isearch_last_direction || false);
302 interactive("isearch-continue-reverse",
303 "Continue the last isearch in the opposite direction.",
305 isearch_continue(I.window, !(I.window.isearch_last_direction || false));
308 interactive("isearch-continue-forward",
309 "Continue the last isearch, forward.",
311 I.window.isearch_last_direction = true;
312 isearch_continue(I.window, true);
315 interactive("isearch-continue-backward",
316 "Continue the last isearch, backward.",
318 I.window.isearch_last_direction = false;
319 isearch_continue(I.window, false);
322 function isearch_start (window, direction) {
323 var s = new isearch_session(window.minibuffer, direction);
324 window.isearch_last_direction = direction;
325 window.minibuffer.push_state(s);
329 interactive("isearch-forward",
330 "Start interactive text search, forward from point.",
331 function (I) { isearch_start(I.window, true); });
333 interactive("isearch-backward",
334 "Start interactive text search, backwards from point.",
335 function (I) { isearch_start(I.window, false); });
337 function isearch_backspace (window) {
338 var s = window.minibuffer.current_state;
339 if (!(s instanceof isearch_session))
340 throw "Invalid minibuffer state";
341 if (s.states.length > 1)
345 interactive("isearch-backspace",
346 "Undo last action in interactive search.",
347 function (I) { isearch_backspace(I.window); });
349 function isearch_done (window, keep_selection) {
350 var s = window.minibuffer.current_state;
351 if (!(s instanceof isearch_session))
352 throw "Invalid minibuffer state";
353 s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
355 // Prevent focus from being reverted
356 s.buffer.clear_saved_focus();
360 window.minibuffer.pop_state();
361 window.isearch_last_search = s.top.search_str;
363 if (! isearch_keep_selection && ! keep_selection)
364 s.collapse_selection();
366 interactive("isearch-done",
367 "Complete interactive search.",
368 function (I) { isearch_done(I.window); });