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
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 (minibuffer, forward) {
37 minibuffer_input_state.call(this, minibuffer, isearch_keymap, "");
39 this.buffer = this.minibuffer.window.buffers.current;
40 this.frame = this.buffer.focused_frame;
41 this.sel_ctrl = this.buffer.focused_selection_controller;
42 this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
43 this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
44 this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
46 isearch_session.prototype = {
47 constructor: isearch_session,
48 __proto__: minibuffer_input_state.prototype,
51 return this.states[this.states.length - 1];
53 _set_selection: function (range) {
54 const selctrlcomp = Ci.nsISelectionController;
55 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
56 sel.removeAllRanges();
57 sel.addRange(range.cloneRange());
58 this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
59 selctrlcomp.SELECTION_FOCUS_REGION,
62 _clear_selection: function () {
63 const selctrlcomp = Ci.nsISelectionController;
64 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
65 sel.removeAllRanges();
67 restore_state: function () {
68 var m = this.minibuffer;
70 m.ignore_input_events = true;
71 m._input_text = s.search_str;
72 m.ignore_input_events = false;
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;
93 finder.caseSensitive = (str != str.toLowerCase());
95 searchRange = doc.createRange();
96 startPt = doc.createRange();
97 endPt = doc.createRange();
99 var count = body.childNodes.length;
101 // Search range in the doc
102 searchRange.setStart(body,0);
103 searchRange.setEnd(body, count);
107 startPt.setStart(body, count);
108 startPt.setEnd(body, count);
110 startPt.setStart(pt.startContainer, pt.startOffset);
111 startPt.setEnd(pt.startContainer, pt.startOffset);
113 endPt.setStart(body, 0);
114 endPt.setEnd(body, 0);
117 startPt.setStart(body, 0);
118 startPt.setEnd(body, 0);
120 startPt.setStart(pt.endContainer, pt.endOffset);
121 startPt.setEnd(pt.endContainer, pt.endOffset);
123 endPt.setStart(body, count);
124 endPt.setEnd(body, count);
128 var selectionRange = null;
132 retRange = finder.Find(str, searchRange, startPt, endPt);
133 var keepSearching = false;
135 var sc = retRange.startContainer;
136 var ec = retRange.endContainer;
137 var scp = sc.parentNode;
138 var ecp = ec.parentNode;
139 var sy1 = abs_point(scp).y;
140 var ey2 = abs_point(ecp).y + ecp.offsetHeight;
142 startPt = retRange.startContainer.ownerDocument.createRange();
144 startPt.setStart(retRange.startContainer, retRange.startOffset);
145 startPt.setEnd(retRange.startContainer, retRange.startOffset);
147 startPt.setStart(retRange.endContainer, retRange.endOffset);
148 startPt.setEnd(retRange.endContainer, retRange.endOffset);
150 // We want to find a match that is completely
151 // visible, otherwise the view will scroll just a
152 // bit to fit the selection in completely.
153 keepSearching = (dir && sy1 < this.frame.scrollY)
154 || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
156 } while (retRange && keepSearching);
158 retRange = finder.Find(str, searchRange, startPt, endPt);
162 this._set_selection(retRange);
163 selectionRange = retRange.cloneRange();
166 return selectionRange;
169 find: function (str, dir, pt) {
172 if (str == null || str.length == 0)
175 // Should we wrap this time?
176 var wrapped = s.wrapped;
178 if (s.wrapped == false && s.range == null
179 && s.search_str == str && s.direction == dir)
185 var match_range = this._highlight_find(str, wrapped, dir, point);
188 screenx: this.frame.scrollX,
189 screeny: this.frame.scrollY,
194 selection: match_range ? match_range : s.selection,
197 this.states.push(new_state);
200 focus_link: function () {
201 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
204 var node = sel.focusNode;
208 if (node.localName && node.localName.toLowerCase() == "a") {
209 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
210 // if there is a selection, preserve it. it is up
211 // to the caller to decide whether or not to keep
213 var sel = this.frame.getSelection(
214 Ci.nsISelectionController.SELECTION_NORMAL);
215 if (sel.rangeCount > 0)
216 var stored_selection = sel.getRangeAt(0).cloneRange();
218 if (stored_selection) {
219 sel.removeAllRanges();
220 sel.addRange(stored_selection);
225 } while ((node = node.parentNode));
228 collapse_selection: function() {
229 const selctrlcomp = Ci.nsISelectionController;
230 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
231 if (sel.rangeCount > 0)
232 sel.getRangeAt(0).collapse(true);
235 handle_input: function (m) {
237 this.find(m._input_text, this.top.direction, this.top.point);
238 this.restore_state();
243 destroy: function () {
245 this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
246 if (caret_enabled(this.buffer) && this.states[0].caret)
247 this._set_selection(this.states[0].caret);
249 this._clear_selection();
251 minibuffer_input_state.prototype.destroy.call(this);
255 function isearch_continue_noninteractively (window, direction) {
256 var s = new isearch_session(window.minibuffer, direction);
257 if (window.isearch_last_search)
258 s.find(window.isearch_last_search, direction, s.top.point);
260 throw "No previous isearch";
261 window.minibuffer.push_state(s);
263 // if (direction && s.top.point !== null)
264 // isearch_continue (window, direction);
265 isearch_done(window, true);
268 function isearch_continue (window, direction) {
269 var s = window.minibuffer.current_state;
270 // if the minibuffer is not open, this command operates in
271 // non-interactive mode.
273 return isearch_continue_noninteractively(window, direction);
274 if (!(s instanceof isearch_session))
275 throw "Invalid minibuffer state";
276 if (s.states.length == 1 && window.isearch_last_search)
277 s.find(window.isearch_last_search, direction, s.top.point);
279 s.find(s.top.search_str, direction, s.top.range);
280 return s.restore_state();
282 interactive("isearch-continue-forward", null,
283 function (I) { isearch_continue(I.window, true); });
284 interactive("isearch-continue-backward", null,
285 function (I) { isearch_continue(I.window, false); });
287 function isearch_start (window, direction) {
288 var s = new isearch_session(window.minibuffer, direction);
289 window.minibuffer.push_state(s);
292 interactive("isearch-forward", null,
293 function (I) { isearch_start(I.window, true); });
294 interactive("isearch-backward", null,
295 function (I) { isearch_start(I.window, false); });
297 function isearch_backspace (window) {
298 var s = window.minibuffer.current_state;
299 if (!(s instanceof isearch_session))
300 throw "Invalid minibuffer state";
301 if (s.states.length > 1)
305 interactive("isearch-backspace", null,
306 function (I) { isearch_backspace(I.window); });
308 function isearch_done (window, keep_selection) {
309 var s = window.minibuffer.current_state;
310 if (!(s instanceof isearch_session))
311 throw "Invalid minibuffer state";
312 s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
314 // Prevent focus from being reverted
315 window.minibuffer.saved_focused_element = null;
316 window.minibuffer.saved_focused_window = null;
320 window.minibuffer.pop_state();
321 window.isearch_last_search = s.top.search_str;
323 if (! isearch_keep_selection && ! keep_selection)
324 s.collapse_selection();
326 interactive("isearch-done", null,
327 function (I) { isearch_done(I.window); });