new module to enable editing and deleting of bookmarks
[conkeror/arlinius.git] / modules / isearch.js
blob3c11cb1a9adace5e4794e5ff4a48403cd778a2b8
1 /**
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
8 * COPYING file.
9 **/
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;
19 this.search_str = "";
20 this.wrapped = false;
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();
26 } else {
27 this.point = null;
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, "");
36 this.states = [];
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,
48 get top () {
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,
58 true);
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;
67 var s = this.top;
68 m.ignore_input_events = true;
69 m._input_text = s.search_str;
70 m.ignore_input_events = false;
71 if (s.selection)
72 this._set_selection(s.selection);
73 else
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"]
83 .createInstance()
84 .QueryInterface(Ci.nsIFind));
85 var searchRange;
86 var startPt;
87 var endPt;
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);
103 if (!dir) {
104 if (pt == null) {
105 startPt.setStart(body, count);
106 startPt.setEnd(body, count);
107 } else {
108 startPt.setStart(pt.startContainer, pt.startOffset);
109 startPt.setEnd(pt.startContainer, pt.startOffset);
111 endPt.setStart(body, 0);
112 endPt.setEnd(body, 0);
113 } else {
114 if (pt == null) {
115 startPt.setStart(body, 0);
116 startPt.setEnd(body, 0);
117 } else {
118 startPt.setStart(pt.endContainer, pt.endOffset);
119 startPt.setEnd(pt.endContainer, pt.endOffset);
121 endPt.setStart(body, count);
122 endPt.setEnd(body, count);
124 // search the doc
125 var retRange = null;
126 var selectionRange = null;
128 if (!wrapped) {
129 do {
130 retRange = finder.Find(str, searchRange, startPt, endPt);
131 var keepSearching = false;
132 if (retRange) {
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();
141 if (!dir) {
142 startPt.setStart(retRange.startContainer, retRange.startOffset);
143 startPt.setEnd(retRange.startContainer, retRange.startOffset);
144 } else {
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);
155 } else {
156 retRange = finder.Find(str, searchRange, startPt, endPt);
159 if (retRange) {
160 this._set_selection(retRange);
161 selectionRange = retRange.cloneRange();
164 return selectionRange;
167 find: function (str, dir, pt) {
168 var s = this.top;
170 if (str == null || str.length == 0)
171 return;
173 // Should we wrap this time?
174 var wrapped = s.wrapped;
175 var point = pt;
176 if (s.wrapped == false && s.range == null
177 && s.search_str == str && s.direction == dir)
179 wrapped = true;
180 point = null;
183 var match_range = this._highlight_find(str, wrapped, dir, point);
185 var new_state = {
186 screenx: this.frame.scrollX,
187 screeny: this.frame.scrollY,
188 search_str: str,
189 wrapped: wrapped,
190 point: point,
191 range: match_range,
192 selection: match_range ? match_range : s.selection,
193 direction: dir
195 this.states.push(new_state);
198 focus_link: function () {
199 var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
200 if (!sel)
201 return;
202 var node = sel.focusNode;
203 if (node == null)
204 return;
205 do {
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
210 // the selection.
211 var sel = this.frame.getSelection(
212 Ci.nsISelectionController.SELECTION_NORMAL);
213 if (sel.rangeCount > 0)
214 var stored_selection = sel.getRangeAt(0).cloneRange();
215 node.focus();
216 if (stored_selection) {
217 sel.removeAllRanges();
218 sel.addRange(stored_selection);
220 return;
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) {
234 m._set_selection();
235 this.find(m._input_text, this.top.direction, this.top.point);
236 this.restore_state();
239 done: false,
241 destroy: function () {
242 if (! this.done) {
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);
246 else
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);
257 else
258 throw "No previous isearch";
259 window.minibuffer.push_state(s);
260 s.restore_state();
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.
270 if (s == null)
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);
276 else
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.",
283 function (I) {
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.",
289 function (I) {
290 isearch_continue(I.window, !(I.window.isearch_last_direction || false));
293 interactive("isearch-continue-forward",
294 "Continue the last isearch, forward.",
295 function (I) {
296 I.window.isearch_last_direction = true;
297 isearch_continue(I.window, true);
300 interactive("isearch-continue-backward",
301 "Continue the last isearch, backward.",
302 function (I) {
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);
311 s.restore_state();
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)
327 s.states.pop();
328 s.restore_state();
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;
344 s.done = true;
346 window.minibuffer.pop_state();
347 window.isearch_last_search = s.top.search_str;
348 s.focus_link();
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); });
356 provide("isearch");