1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-23 11:58:00 +01:00

Resurrect my range finder, Part III: Search highlighting.

This commit is contained in:
Kris Maglione
2009-11-11 06:09:26 -05:00
parent 0f4598fcd6
commit f8ddb3e9c3
3 changed files with 195 additions and 222 deletions

View File

@@ -8,6 +8,19 @@ const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
// TODO: Move to liberator.
function setTimeout(callback, timeout, self) {
function target() {
try {
callback.call(self);
}
catch (e) {
liberator.reportError(e);
}
}
return window.setTimeout(target, timeout);
}
function array(obj) {
if (isgenerator(obj))
obj = [k for (k in obj)];
@@ -230,8 +243,9 @@ function Class() {
if (callable(args[0]))
superclass = args.shift();
var Constructor = eval("(function " + (name || superclass.name) +
var Constructor = eval("(function " + (name || superclass.name).replace(/\W/g, "_") +
String.substr(constructor, 20) + ")");
Constructor.name = name || superclass.name;
if (!('init' in superclass.prototype)) {
var superc = superclass;

View File

@@ -430,10 +430,13 @@ const Finder = Module("finder", {
"Highlight previous search pattern matches",
"boolean", "false", {
setter: function (value) {
try {
if (value)
finder.highlight();
else
finder.clear();
}
catch (e) {}
return value;
}
@@ -464,45 +467,36 @@ const RangeFinder = Module("rangefinder", {
},
openPrompt: function (mode) {
let backwards;
if (mode == modes.FIND_BACKWARD) {
commandline.open("?", "", modes.FIND_BACKWARD);
backwards = true;
}
else {
commandline.open("/", "", modes.FIND_FORWARD);
backwards = false;
}
let backwards = mode == modes.FIND_BACKWARD;
commandline.open(backwards ? "?" : "/", "", mode);
this.find("", backwards);
// TODO: focus the top of the currently visible screen
},
find: function (str, backwards) {
try {
let caseSensitive = false;
if (this.rangeFind)
this.clear();
this.rangeFind = RangeFind(caseSensitive, backwards);
if (!this.rangeFind.search(str))
setTimeout(function () { liberator.echoerr("E486: Pattern not found: " + str); }, 0);
return this.rangeFind.found;
} catch(e) { liberator.reportError(e) }
},
findAgain: function (reverse) {
if (!this.rangeFind || !this.rangeFind.search(null, reverse))
if (!this.rangeFind)
this.find(this._lastSearchString);
else if (!this.rangeFind.search(null, reverse))
liberator.echoerr("E486: Pattern not found: " + lastSearchPattern);
else if (this.rangeFind.wrapped) {
// hack needed, because wrapping causes a "scroll" event which clears
// our command line
setTimeout(function () {
if (rangfinder.rangeFind.backward)
commandline.echo("search hit TOP, continuing at BOTTOM",
commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
else
commandline.echo("search hit BOTTOM, continuing at TOP",
commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
let msg = rangfinder.rangeFind.backward ? "search hit TOP, continuing at BOTTOM"
: "search hit BOTTOM, continuing at TOP";
commandline.echo(msg, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
}, 0);
}
else {
@@ -515,20 +509,14 @@ try {
// Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set
onKeyPress: function (command) {
try {
if (options["incsearch"] && this.rangeFind)
this.rangeFind.search(command);
} catch(e) { liberator.reportError(e); }
},
onSubmit: function (command) {
// use the last pattern if none specified
if (!command)
command = lastSearchPattern;
if (!options["incsearch"] || !this.rangeFind.found) {
if (!options["incsearch"] || !this.rangeFind || !this.rangeFind.found) {
this.clear();
this.find(command, this.rangeFind.backwards);
this.find(command || this._lastSearchString, this._lastSearchBackwards);
}
this._lastSearchBackwards = this.rangeFind.backwards;
@@ -536,7 +524,7 @@ try {
this._lastSearchString = command;
if (options["hlsearch"])
this.highlight(this.rangeFind.searchString);
this.highlight();
modes.reset();
},
@@ -558,15 +546,17 @@ try {
*
* @param {string} str The string to highlight.
*/
highlight: function (str) {
return;
highlight: function () {
if (this.rangeFind)
this.rangeFind.highlight();
},
/**
* Clears all search highlighting.
*/
clear: function () {
return;
if (this.rangeFind)
this.rangeFind.highlight(true);
}
}, {
}, {
@@ -632,9 +622,9 @@ const RangeFind = Class("RangeFind", {
this._backward = backward;
this.ranges = this.makeFrameList(content);
this.range = { document: (tabs.localStore.focusedFrame || content).document };
this.range = RangeFind.Range(tabs.localStore.focusedFrame || content);
this.startRange = (this.selection.rangeCount ? this.selection.getRangeAt(0) : this.ranges[0].range).cloneRange();
this.startRange = (this.range.selection.rangeCount ? this.range.selection.getRangeAt(0) : this.ranges[0].range).cloneRange();
this.startRange.collapse(!backward);
this.range = this.findRange(this.startRange);
this.ranges.first = this.range;
@@ -645,17 +635,6 @@ const RangeFind = Class("RangeFind", {
this.found = false;
},
get docShell() {
for (let shell in iter(getBrowser().docShell.getDocShellEnumerator(Ci.nsIDocShellTreeItem.typeAll, Ci.nsIDocShell.ENUMERATE_FORWARDS)))
if (shell.QueryInterface(nsIWebNavigation).document == this.range.document)
return shell;
},
get selectionController() this.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController),
get selection() this.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL),
sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument,
compareRanges: function (r1, r2)
@@ -679,34 +658,25 @@ const RangeFind = Class("RangeFind", {
win = win.top;
let frames = [];
function endpoint(range, before) {
range = range.cloneRange();
range.collapse(before);
return range;
}
function pushRange(start, end, win) {
let scroll = Point(win.pageXOffset, win.pageYOffset);
let range = win.document.createRange();
range.setStart(start.startContainer, start.startOffset);
range.setEnd(end.startContainer, end.startOffset);
frames.push({ range: range, index: frames.length, window: win, document: win.document, scroll: scroll });
function pushRange(start, end) {
frames.push(RangeFind.Range(start, end, frames.length));
}
function rec(win) {
let doc = win.document;
let pageRange = doc.createRange();
pageRange.setStartBefore(doc.body || doc.documentElement.lastChild);
pageRange.setEndAfter(doc.body || doc.documentElement.lastChild);
let pageStart = endpoint(pageRange, true);
let pageEnd = endpoint(pageRange, false);
let pageStart = RangeFind.endpoint(pageRange, true);
let pageEnd = RangeFind.endpoint(pageRange, false);
for (let frame in util.Array.itervalues(win.frames)) {
let range = doc.createRange();
range.selectNode(frame.frameElement);
pushRange(pageStart, endpoint(range, true), win);
pageStart = endpoint(range, false);
pushRange(pageStart, RangeFind.endpoint(range, true));
pageStart = RangeFind.endpoint(range, false);
rec(frame);
}
pushRange(pageStart, pageEnd, win);
pushRange(pageStart, pageEnd);
}
rec(win);
return frames;
@@ -749,7 +719,23 @@ const RangeFind = Class("RangeFind", {
get searchString() this.lastString,
get backward() this.finder.findBackwards,
search: function (word, reverse) {
__iterator__: function () {
let range = this.range;
let lastRange = this.lastRange
try {
this.range = this.ranges[0];
this.lastRange = null;
var res;
while (res = this.search(null, this._backward, true))
yield res;
}
finally {
this.range = range;
this.lastRange = lastRange;
}
},
search: function (word, reverse, private) {
this.finder.findBackwards = reverse ? !this._backward : this._backward;
let again = word == null;
if (again)
@@ -758,9 +744,10 @@ const RangeFind = Class("RangeFind", {
word = word.toLowerCase();
if (!again && (word == "" || word.indexOf(this.lastString) != 0 || this.backward)) {
this.unhighlight();
if (!private)
this.range.deselect();
if (word == "")
this.deScroll(this.range);
this.range.descroll()
this.lastRange = this.startRange;
this.range = this.ranges.first;
}
@@ -772,6 +759,8 @@ const RangeFind = Class("RangeFind", {
let idx = this.range.index;
for (let i in this.backward ? util.range(idx + 1, -1, -1) : util.range(idx, this.ranges.length))
yield i;
if (private)
return;
this.wrapped = true;
for (let i in this.backward ? util.range(this.ranges.length, idx, -1) : util.range(0, idx))
yield i;
@@ -779,15 +768,23 @@ const RangeFind = Class("RangeFind", {
for (let i in indices.call(this)) {
this.range = this.ranges[i];
let start = this.sameDocument(this.lastRange, this.range.range) ?
this.lastRange : this.range.range;
RangeFind.endpoint(this.lastRange, this.backward) :
RangeFind.endpoint(this.range.range, !this.backward);;
var range = this.finder.Find(word, this.range.range, start, this.range.range);
if (range && this.compareRanges(range, this.range.range) <= 0)
if (range)
break;
this.deScroll(this.range);
this.unhighlight();
if (!private) {
this.range.descroll();
this.range.deselect();
}
}
}
if (range)
this.lastRange = range.cloneRange();
if (private)
return range;
this.lastString = word;
if (range == null) {
@@ -796,159 +793,121 @@ const RangeFind = Class("RangeFind", {
return null;
}
this.wrapped = false;
this.selection.removeAllRanges();
this.selection.addRange(range);
this.selectionController.scrollSelectionIntoView(this.selectionController.SELECTION_NORMAL, 0, false);
this.lastRange = range.cloneRange();
this.range.selection.removeAllRanges();
this.range.selection.addRange(range);
this.range.selectionController.scrollSelectionIntoView(
this.range.selectionController.SELECTION_NORMAL, 0, false);
this.found = true;
return range;
},
cancel: function () {
if (false) // Later.
this.selection.addRange(this.startRange);
this.unhighlight();
this.deScroll(this.range);
},
unhighlight: function () {
this.selection.removeAllRanges();
},
deScroll: function (range) {
range.window.scrollTo(range.scroll.x, range.scroll.y);
}
});
/* Stolen from toolkit.jar in Firefox, for the time being. The private
* methods were unstable, and changed. The new version is not remotely
* compatible with what we do.
* The following only applies to this object, and may not be
* necessary, or accurate, but, just in case:
* The Original Code is mozilla.org viewsource frontend.
*
* The Initial Developer of the Original Code is
* Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (c) 2003
* by the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Blake Ross <blake@cs.stanford.edu> (Original Author)
* Masayuki Nakano <masayuki@d-toybox.com>
* Ben Basson <contact@cusser.net>
* Jason Barnabe <jason_barnabe@fastmail.fm>
* Asaf Romano <mano@mozilla.com>
* Ehsan Akhgari <ehsan.akhgari@gmail.com>
* Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
*/
const Highlighter = Class("Highlighter", {
init: function (doc) {
this.doc = doc;
},
doc: null,
spans: [],
search: function (word, matchCase) {
var finder = services.create("find");
var range;
while ((range = finder.Find(word, this.searchRange, this.startPt, this.endPt)))
yield range;
},
highlightDoc: function highlightDoc(win, word) {
Array.forEach(win.frames, function (frame) this.highlightDoc(frame, word), this);
var doc = win.document;
if (!doc || !(doc instanceof HTMLDocument))
highlight: function (clear) {
if (!this.lastString)
return;
if (!word) {
let elems = this._highlighter.spans;
for (let i = elems.length; --i >= 0;) {
let elem = elems[i];
let docfrag = doc.createDocumentFragment();
let next = elem.nextSibling;
let parent = elem.parentNode;
let child;
while (child = elem.firstChild)
docfrag.appendChild(child);
parent.removeChild(elem);
parent.insertBefore(docfrag, next);
parent.normalize();
}
if (!clear)
for (let range in values(this.ranges))
if (util.evaluateXPath("//@liberator:highlight[1][.='Search']").snapshotLength)
return;
}
var baseNode = <span highlight="Search"/>;
baseNode = util.xmlToDom(baseNode, window.content.document);
let span = util.xmlToDom(<span highlight="Search"/>, this.range.document);
var body = doc.body;
var count = body.childNodes.length;
this.searchRange = doc.createRange();
this.startPt = doc.createRange();
this.endPt = doc.createRange();
function highlight(range) {
let startContainer = range.startContainer;
let startOffset = range.startOffset;
let node = startContainer.ownerDocument.importNode(span, true);
this.searchRange.setStart(body, 0);
this.searchRange.setEnd(body, count);
this.startPt.setStart(body, 0);
this.startPt.setEnd(body, 0);
this.endPt.setStart(body, count);
this.endPt.setEnd(body, count);
liberator.interrupted = false;
let n = 0;
for (let retRange in this.search(word, this._caseSensitive)) {
// Highlight
var nodeSurround = baseNode.cloneNode(true);
var node = this.highlight(retRange, nodeSurround);
this.startPt = node.ownerDocument.createRange();
this.startPt.setStart(node, node.childNodes.length);
this.startPt.setEnd(node, node.childNodes.length);
if (n++ % 20 == 0)
liberator.threadYield(true);
if (liberator.interrupted)
break;
}
},
highlight: function highlight(range, node) {
var startContainer = range.startContainer;
var startOffset = range.startOffset;
var endOffset = range.endOffset;
var docfrag = range.extractContents();
var before = startContainer.splitText(startOffset);
var parent = before.parentNode;
let docfrag = range.extractContents();
let before = startContainer.splitText(startOffset);
let parent = before.parentNode;
node.appendChild(docfrag);
parent.insertBefore(node, before);
this.spans.push(node);
return node;
range.selectNode(node);
}
function unhighlight(range) {
let elem = range.startContainer;
while (!(elem instanceof Element) && elem.parentNode)
elem = elem.parentNode;
if (elem.getAttributeNS(NS.uri, "highlight") != "Search")
return;
let docfrag = range.extractContents();
let parent = elem.parentNode;
parent.replaceChild(docfrag, elem);
parent.normalize();
}
this.range.save();
liberator.dump(String.quote(this.range.initialSelection));
let action = clear ? unhighlight : highlight;
for (let r in this) {
action(r);
this.lastRange = r;
}
this.range.deselect();
if (!clear)
this.search(null, false);
},
/**
* Clears all search highlighting.
*/
clear: function () {
this.spans.forEach(function (span) {
if (span.parentNode) {
let el = span.firstChild;
while (el) {
span.removeChild(el);
span.parentNode.insertBefore(el, span);
el = span.firstChild;
cancel: function () {
this.range.deselect();
this.range.descroll()
},
}, {
Range: Class("RangeFind.Range", {
init: function (start, end, index) {
if (start instanceof Ci.nsIDOMWindow) { // Kludge
this.document = start.document;
return;
}
span.parentNode.removeChild(span);
}
});
this.spans = [];
this.index = index;
this.document = start.startContainer.ownerDocument;
this.window = this.document.defaultView;
this.range = this.document.createRange();
this.range.setStart(start.startContainer, start.startOffset);
this.range.setEnd(end.startContainer, end.startOffset);
this.save();
},
isHighlighted: function (doc) this.doc == doc && this.spans.length > 0
save: function () {
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
this.initialSelection = null;
if (this.selection.rangeCount)
this.initialSelection = this.selection.getRangeAt(0);
},
descroll: function (range) {
this.window.scrollTo(this.scroll.x, this.scroll.y);
},
deselect: function () {
this.selection.removeAllRanges();
if (this.initialSelection)
this.selection.addRange(this.initialSelection);
},
get docShell() {
for (let shell in iter(getBrowser().docShell.getDocShellEnumerator(Ci.nsIDocShellTreeItem.typeAll, Ci.nsIDocShell.ENUMERATE_FORWARDS)))
if (shell.QueryInterface(nsIWebNavigation).document == this.document)
return shell;
},
get selectionController() this.docShell
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController),
get selection() this.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL),
}),
endpoint: function (range, before) {
range = range.cloneRange();
range.collapse(before);
return range;
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -190,17 +190,17 @@ const Option = Class("Option", {
else
scope = this.scope;
let aValue;
let value;
if (liberator.has("tabs") && (scope & options.OPTION_SCOPE_LOCAL))
aValue = tabs.options[this.name];
if ((scope & options.OPTION_SCOPE_GLOBAL) && (aValue == undefined))
aValue = this.globalValue;
value = tabs.options[this.name];
if ((scope & options.OPTION_SCOPE_GLOBAL) && (value == undefined))
value = this.globalValue;
if (this.getter)
return liberator.trapErrors(this.getter, this, aValue);
return liberator.trapErrors(this.getter, this, value);
return aValue;
return value;
},
/**