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.");
16 function initial_isearch_state (buffer, frame, forward) {
17 this.screenx = frame.scrollX;
18 this.screeny = frame.scrollY;
21 let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
22 if (sel.rangeCount > 0) {
23 this.point = sel.getRangeAt(0);
24 if (caret_enabled(buffer))
25 this.caret = this.point.cloneRange();
29 this.range = frame.document.createRange();
30 this.selection = null;
31 this.direction = forward;
34 function isearch_session (minibuffer, forward) {
35 minibuffer_input_state.call(this, minibuffer, isearch_keymap, "");
37 this.buffer = this.minibuffer.window.buffers.current;
38 this.frame = this.buffer.focused_frame;
39 this.sel_ctrl = this.buffer.focused_selection_controller;
40 this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
41 this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
42 this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
44 isearch_session.prototype = {
45 constructor: isearch_session,
46 __proto__: minibuffer_input_state.prototype,
49 return this.states[this.states.length - 1];
51 _set_selection: function (range) {
52 const selctrlcomp = Ci.nsISelectionController;
53 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
54 sel.removeAllRanges();
55 sel.addRange(range.cloneRange());
56 this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
57 selctrlcomp.SELECTION_FOCUS_REGION,
60 _clear_selection: function () {
61 const selctrlcomp = Ci.nsISelectionController;
62 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
63 sel.removeAllRanges();
65 restore_state: function () {
66 var m = this.minibuffer;
68 m.ignore_input_events = true;
69 m._input_text = s.search_str;
70 m.ignore_input_events = false;
72 this._set_selection(s.selection);
74 this._clear_selection();
75 this.frame.scrollTo(s.screenx, s.screeny);
76 m.prompt = ((s.wrapped ? "Wrapped ":"")
77 + (s.range ? "" : "Failing ")
78 + "I-Search" + (s.direction? "": " backward") + ":");
80 _highlight_find: function (str, wrapped, dir, pt) {
81 var doc = this.frame.document;
82 var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
84 .QueryInterface(Ci.nsIFind));
88 var body = doc.documentElement;
90 finder.findBackwards = !dir;
91 finder.caseSensitive = (str != str.toLowerCase());
93 searchRange = doc.createRange();
94 startPt = doc.createRange();
95 endPt = doc.createRange();
97 var count = body.childNodes.length;
99 // Search range in the doc
100 searchRange.setStart(body,0);
101 searchRange.setEnd(body, count);
105 startPt.setStart(body, count);
106 startPt.setEnd(body, count);
108 startPt.setStart(pt.startContainer, pt.startOffset);
109 startPt.setEnd(pt.startContainer, pt.startOffset);
111 endPt.setStart(body, 0);
112 endPt.setEnd(body, 0);
115 startPt.setStart(body, 0);
116 startPt.setEnd(body, 0);
118 startPt.setStart(pt.endContainer, pt.endOffset);
119 startPt.setEnd(pt.endContainer, pt.endOffset);
121 endPt.setStart(body, count);
122 endPt.setEnd(body, count);
126 var selectionRange = null;
130 retRange = finder.Find(str, searchRange, startPt, endPt);
131 var keepSearching = false;
133 var sc = retRange.startContainer;
134 var ec = retRange.endContainer;
135 var scp = sc.parentNode;
136 var ecp = ec.parentNode;
137 var sy1 = abs_point(scp).y;
138 var ey2 = abs_point(ecp).y + ecp.offsetHeight;
140 startPt = retRange.startContainer.ownerDocument.createRange();
142 startPt.setStart(retRange.startContainer, retRange.startOffset);
143 startPt.setEnd(retRange.startContainer, retRange.startOffset);
145 startPt.setStart(retRange.endContainer, retRange.endOffset);
146 startPt.setEnd(retRange.endContainer, retRange.endOffset);
148 // We want to find a match that is completely
149 // visible, otherwise the view will scroll just a
150 // bit to fit the selection in completely.
151 keepSearching = (dir && sy1 < this.frame.scrollY)
152 || (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
154 } while (retRange && keepSearching);
156 retRange = finder.Find(str, searchRange, startPt, endPt);
160 this._set_selection(retRange);
161 selectionRange = retRange.cloneRange();
164 return selectionRange;
167 find: function (str, dir, pt) {
170 if (str == null || str.length == 0)
173 // Should we wrap this time?
174 var wrapped = s.wrapped;
176 if (s.wrapped == false && s.range == null
177 && s.search_str == str && s.direction == dir)
183 var match_range = this._highlight_find(str, wrapped, dir, point);
186 screenx: this.frame.scrollX,
187 screeny: this.frame.scrollY,
192 selection: match_range ? match_range : s.selection,
195 this.states.push(new_state);
198 focus_link: function () {
199 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
202 var node = sel.focusNode;
206 if (node.localName && node.localName.toLowerCase() == "a") {
207 if (node.hasAttributes && node.attributes.getNamedItem("href")) {
208 // if there is a selection, preserve it. it is up
209 // to the caller to decide whether or not to keep
211 var sel = this.frame.getSelection(
212 Ci.nsISelectionController.SELECTION_NORMAL);
213 if (sel.rangeCount > 0)
214 var stored_selection = sel.getRangeAt(0).cloneRange();
216 if (stored_selection) {
217 sel.removeAllRanges();
218 sel.addRange(stored_selection);
223 } while ((node = node.parentNode));
226 collapse_selection: function() {
227 const selctrlcomp = Ci.nsISelectionController;
228 var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
229 if (sel.rangeCount > 0)
230 sel.getRangeAt(0).collapse(true);
233 handle_input: function (m) {
235 this.find(m._input_text, this.top.direction, this.top.point);
236 this.restore_state();
241 destroy: function () {
243 this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
244 if (caret_enabled(this.buffer) && this.states[0].caret)
245 this._set_selection(this.states[0].caret);
247 this._clear_selection();
249 minibuffer_input_state.prototype.destroy.call(this);
253 function isearch_continue_noninteractively (window, direction) {
254 var s = new isearch_session(window.minibuffer, direction);
255 if (window.isearch_last_search)
256 s.find(window.isearch_last_search, direction, s.top.point);
258 throw "No previous isearch";
259 window.minibuffer.push_state(s);
261 // if (direction && s.top.point !== null)
262 // isearch_continue (window, direction);
263 isearch_done(window, true);
266 function isearch_continue (window, direction) {
267 var s = window.minibuffer.current_state;
268 // if the minibuffer is not open, this command operates in
269 // non-interactive mode.
271 return isearch_continue_noninteractively(window, direction);
272 if (!(s instanceof isearch_session))
273 throw "Invalid minibuffer state";
274 if (s.states.length == 1 && window.isearch_last_search)
275 s.find(window.isearch_last_search, direction, s.top.point);
277 s.find(s.top.search_str, direction, s.top.range);
278 return s.restore_state();
281 interactive("isearch-continue",
282 "Continue the last isearch in the same direction.",
284 isearch_continue(I.window, I.window.isearch_last_direction || false);
287 interactive("isearch-continue-reverse",
288 "Continue the last isearch in the opposite direction.",
290 isearch_continue(I.window, !(I.window.isearch_last_direction || false));
293 interactive("isearch-continue-forward",
294 "Continue the last isearch, forward.",
296 I.window.isearch_last_direction = true;
297 isearch_continue(I.window, true);
300 interactive("isearch-continue-backward",
301 "Continue the last isearch, backward.",
303 I.window.isearch_last_direction = false;
304 isearch_continue(I.window, false);
307 function isearch_start (window, direction) {
308 var s = new isearch_session(window.minibuffer, direction);
309 window.isearch_last_direction = direction;
310 window.minibuffer.push_state(s);
314 interactive("isearch-forward",
315 "Start interactive text search, forward from point.",
316 function (I) { isearch_start(I.window, true); });
318 interactive("isearch-backward",
319 "Start interactive text search, backwards from point.",
320 function (I) { isearch_start(I.window, false); });
322 function isearch_backspace (window) {
323 var s = window.minibuffer.current_state;
324 if (!(s instanceof isearch_session))
325 throw "Invalid minibuffer state";
326 if (s.states.length > 1)
330 interactive("isearch-backspace",
331 "Undo last action in interactive search.",
332 function (I) { isearch_backspace(I.window); });
334 function isearch_done (window, keep_selection) {
335 var s = window.minibuffer.current_state;
336 if (!(s instanceof isearch_session))
337 throw "Invalid minibuffer state";
338 s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
340 // Prevent focus from being reverted
341 s.buffer.clear_saved_focus();
345 window.minibuffer.pop_state();
346 window.isearch_last_search = s.top.search_str;
348 if (! isearch_keep_selection && ! keep_selection)
349 s.collapse_selection();
351 interactive("isearch-done",
352 "Complete interactive search.",
353 function (I) { isearch_done(I.window); });