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 window
.minibuffer
.saved_focused_element
= null;
342 window
.minibuffer
.saved_focused_window
= null;
346 window
.minibuffer
.pop_state();
347 window
.isearch_last_search
= s
.top
.search_str
;
349 if (! isearch_keep_selection
&& ! keep_selection
)
350 s
.collapse_selection();
352 interactive("isearch-done",
353 "Complete interactive search.",
354 function (I
) { isearch_done(I
.window
); });