371 lines
13 KiB
JavaScript
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");
|