CodeSnippet/conkeror/modules/isearch.js

371 lines
13 KiB
JavaScript

/**
* (C) Copyright 2004-2005 Shawn Betts
* (C) Copyright 2007-2008 Jeremy Maitin-Shepard
* (C) Copyright 2008 Nelson Elhage
* (C) Copyright 2008-2010 John Foerch
*
* Use, modification, and distribution are subject to the terms specified in the
* COPYING file.
**/
define_variable("isearch_keep_selection", false,
"Set to `true' to make isearch leave the selection visible when a "+
"search is completed.");
define_variable("isearch_scroll_center_vertically", false,
"Set to `true' to vertically center the selection when scrolling during " +
"an isearch. Only available with XULRunner >= 12.");
function initial_isearch_state (buffer, frame, forward) {
this.screenx = frame.scrollX;
this.screeny = frame.scrollY;
this.search_str = "";
this.wrapped = false;
let sel = frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
if (sel.rangeCount > 0) {
this.point = sel.getRangeAt(0);
if (caret_enabled(buffer))
this.caret = this.point.cloneRange();
} else {
this.point = null;
}
this.range = frame.document.createRange();
this.selection = null;
this.direction = forward;
}
function isearch_session (minibuffer, forward) {
minibuffer_input_state.call(this, minibuffer, isearch_keymap, "");
this.states = [];
this.buffer = this.minibuffer.window.buffers.current;
this.frame = this.buffer.focused_frame;
this.sel_ctrl = this.buffer.focused_selection_controller;
this.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_ATTENTION);
this.sel_ctrl.repaintSelection(Ci.nsISelectionController.SELECTION_NORMAL);
this.states.push(new initial_isearch_state(this.buffer, this.frame, forward));
}
isearch_session.prototype = {
constructor: isearch_session,
__proto__: minibuffer_input_state.prototype,
get top () {
return this.states[this.states.length - 1];
},
_set_selection: function (range) {
const selctrlcomp = Ci.nsISelectionController;
var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
sel.removeAllRanges();
sel.addRange(range.cloneRange());
var xulrunner_version = get_mozilla_version();
if (version_compare(xulrunner_version, "2.0") < 0) {
var flags = true;
} else {
flags = selctrlcomp.SCROLL_SYNCHRONOUS;
if (isearch_scroll_center_vertically &&
version_compare(xulrunner_version, "12.0") >= 0)
{
flags |= selctrlcomp.SCROLL_CENTER_VERTICALLY;
}
}
this.sel_ctrl.scrollSelectionIntoView(selctrlcomp.SELECTION_NORMAL,
selctrlcomp.SELECTION_FOCUS_REGION,
flags);
},
_clear_selection: function () {
const selctrlcomp = Ci.nsISelectionController;
var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
sel.removeAllRanges();
},
restore_state: function () {
var m = this.minibuffer;
var s = this.top;
m.ignore_input_events = true;
m._input_text = s.search_str;
m.ignore_input_events = false;
if (s.selection)
this._set_selection(s.selection);
else
this._clear_selection();
this.frame.scrollTo(s.screenx, s.screeny);
m.prompt = ((s.wrapped ? "Wrapped ":"")
+ (s.range ? "" : "Failing ")
+ "I-Search" + (s.direction? "": " backward") + ":");
},
_highlight_find: function (str, wrapped, dir, pt) {
var doc = this.frame.document;
var finder = (Cc["@mozilla.org/embedcomp/rangefind;1"]
.createInstance()
.QueryInterface(Ci.nsIFind));
var searchRange;
var startPt;
var endPt;
var body = doc.documentElement;
finder.findBackwards = !dir;
finder.caseSensitive = (str != str.toLowerCase());
searchRange = doc.createRange();
startPt = doc.createRange();
endPt = doc.createRange();
var count = body.childNodes.length;
// Search range in the doc
searchRange.setStart(body,0);
searchRange.setEnd(body, count);
if (!dir) {
if (pt == null) {
startPt.setStart(body, count);
startPt.setEnd(body, count);
} else {
startPt.setStart(pt.startContainer, pt.startOffset);
startPt.setEnd(pt.startContainer, pt.startOffset);
}
endPt.setStart(body, 0);
endPt.setEnd(body, 0);
} else {
if (pt == null) {
startPt.setStart(body, 0);
startPt.setEnd(body, 0);
} else {
startPt.setStart(pt.endContainer, pt.endOffset);
startPt.setEnd(pt.endContainer, pt.endOffset);
}
endPt.setStart(body, count);
endPt.setEnd(body, count);
}
// search the doc
var retRange = null;
var selectionRange = null;
if (!wrapped) {
do {
retRange = finder.Find(str, searchRange, startPt, endPt);
var keepSearching = false;
if (retRange) {
var sc = retRange.startContainer;
var ec = retRange.endContainer;
var scp = sc.parentNode;
var ecp = ec.parentNode;
var sy1 = abs_point(scp).y;
var ey2 = abs_point(ecp).y + ecp.offsetHeight;
startPt = retRange.startContainer.ownerDocument.createRange();
if (!dir) {
startPt.setStart(retRange.startContainer, retRange.startOffset);
startPt.setEnd(retRange.startContainer, retRange.startOffset);
} else {
startPt.setStart(retRange.endContainer, retRange.endOffset);
startPt.setEnd(retRange.endContainer, retRange.endOffset);
}
// We want to find a match that is completely
// visible, otherwise the view will scroll just a
// bit to fit the selection in completely.
keepSearching = (dir && sy1 < this.frame.scrollY)
|| (!dir && ey2 >= this.frame.scrollY + this.frame.innerHeight);
}
} while (retRange && keepSearching);
} else {
retRange = finder.Find(str, searchRange, startPt, endPt);
}
if (retRange) {
this._set_selection(retRange);
selectionRange = retRange.cloneRange();
}
return selectionRange;
},
find: function (str, dir, pt) {
var s = this.top;
if (str == null || str.length == 0)
return;
// Should we wrap this time?
var wrapped = s.wrapped;
var point = pt;
if (s.wrapped == false && s.range == null
&& s.search_str == str && s.direction == dir)
{
wrapped = true;
point = null;
}
var match_range = this._highlight_find(str, wrapped, dir, point);
var new_state = {
screenx: this.frame.scrollX,
screeny: this.frame.scrollY,
search_str: str,
wrapped: wrapped,
point: point,
range: match_range,
selection: match_range ? match_range : s.selection,
direction: dir
};
this.states.push(new_state);
},
focus_link: function () {
var sel = this.frame.getSelection(Ci.nsISelectionController.SELECTION_NORMAL);
if (!sel)
return;
var node = sel.focusNode;
if (node == null)
return;
do {
if (node.localName && node.localName.toLowerCase() == "a") {
if (node.hasAttributes && node.attributes.getNamedItem("href")) {
// if there is a selection, preserve it. it is up
// to the caller to decide whether or not to keep
// the selection.
var sel = this.frame.getSelection(
Ci.nsISelectionController.SELECTION_NORMAL);
if (sel.rangeCount > 0)
var stored_selection = sel.getRangeAt(0).cloneRange();
node.focus();
if (stored_selection) {
sel.removeAllRanges();
sel.addRange(stored_selection);
}
return;
}
}
} while ((node = node.parentNode));
},
collapse_selection: function() {
const selctrlcomp = Ci.nsISelectionController;
var sel = this.sel_ctrl.getSelection(selctrlcomp.SELECTION_NORMAL);
if (sel.rangeCount > 0)
sel.getRangeAt(0).collapse(true);
},
handle_input: function (m) {
m._set_selection();
this.find(m._input_text, this.top.direction, this.top.point);
this.restore_state();
},
done: false,
destroy: function () {
if (! this.done) {
this.frame.scrollTo(this.states[0].screenx, this.states[0].screeny);
if (caret_enabled(this.buffer) && this.states[0].caret)
this._set_selection(this.states[0].caret);
else
this._clear_selection();
}
minibuffer_input_state.prototype.destroy.call(this);
}
};
function isearch_continue_noninteractively (window, direction) {
var s = new isearch_session(window.minibuffer, direction);
if (window.isearch_last_search)
s.find(window.isearch_last_search, direction, s.top.point);
else
throw "No previous isearch";
window.minibuffer.push_state(s);
s.restore_state();
// if (direction && s.top.point !== null)
// isearch_continue (window, direction);
isearch_done(window, true);
}
function isearch_continue (window, direction) {
var s = window.minibuffer.current_state;
// if the minibuffer is not open, this command operates in
// non-interactive mode.
if (s == null)
return isearch_continue_noninteractively(window, direction);
if (!(s instanceof isearch_session))
throw "Invalid minibuffer state";
if (s.states.length == 1 && window.isearch_last_search)
s.find(window.isearch_last_search, direction, s.top.point);
else
s.find(s.top.search_str, direction, s.top.range);
return s.restore_state();
}
interactive("isearch-continue",
"Continue the last isearch in the same direction.",
function (I) {
isearch_continue(I.window, I.window.isearch_last_direction || false);
});
interactive("isearch-continue-reverse",
"Continue the last isearch in the opposite direction.",
function (I) {
isearch_continue(I.window, !(I.window.isearch_last_direction || false));
});
interactive("isearch-continue-forward",
"Continue the last isearch, forward.",
function (I) {
I.window.isearch_last_direction = true;
isearch_continue(I.window, true);
});
interactive("isearch-continue-backward",
"Continue the last isearch, backward.",
function (I) {
I.window.isearch_last_direction = false;
isearch_continue(I.window, false);
});
function isearch_start (window, direction) {
var s = new isearch_session(window.minibuffer, direction);
window.isearch_last_direction = direction;
window.minibuffer.push_state(s);
s.restore_state();
}
interactive("isearch-forward",
"Start interactive text search, forward from point.",
function (I) { isearch_start(I.window, true); });
interactive("isearch-backward",
"Start interactive text search, backwards from point.",
function (I) { isearch_start(I.window, false); });
function isearch_backspace (window) {
var s = window.minibuffer.current_state;
if (!(s instanceof isearch_session))
throw "Invalid minibuffer state";
if (s.states.length > 1)
s.states.pop();
s.restore_state();
}
interactive("isearch-backspace",
"Undo last action in interactive search.",
function (I) { isearch_backspace(I.window); });
function isearch_done (window, keep_selection) {
var s = window.minibuffer.current_state;
if (!(s instanceof isearch_session))
throw "Invalid minibuffer state";
s.sel_ctrl.setDisplaySelection(Ci.nsISelectionController.SELECTION_NORMAL);
// Prevent focus from being reverted
s.buffer.clear_saved_focus();
s.done = true;
window.minibuffer.pop_state();
window.isearch_last_search = s.top.search_str;
s.focus_link();
if (! isearch_keep_selection && ! keep_selection)
s.collapse_selection();
}
interactive("isearch-done",
"Complete interactive search.",
function (I) { isearch_done(I.window); });
provide("isearch");