1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-23 13:52:26 +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 Cr = Components.results;
const Cu = Components.utils; 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) { function array(obj) {
if (isgenerator(obj)) if (isgenerator(obj))
obj = [k for (k in obj)]; obj = [k for (k in obj)];
@@ -230,8 +243,9 @@ function Class() {
if (callable(args[0])) if (callable(args[0]))
superclass = args.shift(); superclass = args.shift();
var Constructor = eval("(function " + (name || superclass.name) + var Constructor = eval("(function " + (name || superclass.name).replace(/\W/g, "_") +
String.substr(constructor, 20) + ")"); String.substr(constructor, 20) + ")");
Constructor.name = name || superclass.name;
if (!('init' in superclass.prototype)) { if (!('init' in superclass.prototype)) {
var superc = superclass; var superc = superclass;

View File

@@ -430,10 +430,13 @@ const Finder = Module("finder", {
"Highlight previous search pattern matches", "Highlight previous search pattern matches",
"boolean", "false", { "boolean", "false", {
setter: function (value) { setter: function (value) {
try {
if (value) if (value)
finder.highlight(); finder.highlight();
else else
finder.clear(); finder.clear();
}
catch (e) {}
return value; return value;
} }
@@ -464,45 +467,36 @@ const RangeFinder = Module("rangefinder", {
}, },
openPrompt: function (mode) { openPrompt: function (mode) {
let backwards; let backwards = mode == modes.FIND_BACKWARD;
if (mode == modes.FIND_BACKWARD) { commandline.open(backwards ? "?" : "/", "", mode);
commandline.open("?", "", modes.FIND_BACKWARD);
backwards = true;
}
else {
commandline.open("/", "", modes.FIND_FORWARD);
backwards = false;
}
this.find("", backwards); this.find("", backwards);
// TODO: focus the top of the currently visible screen
}, },
find: function (str, backwards) { find: function (str, backwards) {
try {
let caseSensitive = false; let caseSensitive = false;
if (this.rangeFind)
this.clear();
this.rangeFind = RangeFind(caseSensitive, backwards); this.rangeFind = RangeFind(caseSensitive, backwards);
if (!this.rangeFind.search(str)) if (!this.rangeFind.search(str))
setTimeout(function () { liberator.echoerr("E486: Pattern not found: " + str); }, 0); setTimeout(function () { liberator.echoerr("E486: Pattern not found: " + str); }, 0);
return this.rangeFind.found; return this.rangeFind.found;
} catch(e) { liberator.reportError(e) }
}, },
findAgain: function (reverse) { 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); liberator.echoerr("E486: Pattern not found: " + lastSearchPattern);
else if (this.rangeFind.wrapped) { else if (this.rangeFind.wrapped) {
// hack needed, because wrapping causes a "scroll" event which clears // hack needed, because wrapping causes a "scroll" event which clears
// our command line // our command line
setTimeout(function () { setTimeout(function () {
if (rangfinder.rangeFind.backward) let msg = rangfinder.rangeFind.backward ? "search hit TOP, continuing at BOTTOM"
commandline.echo("search hit TOP, continuing at BOTTOM", : "search hit BOTTOM, continuing at TOP";
commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES); commandline.echo(msg, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
else
commandline.echo("search hit BOTTOM, continuing at TOP",
commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
}, 0); }, 0);
} }
else { 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 // Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set
onKeyPress: function (command) { onKeyPress: function (command) {
try {
if (options["incsearch"] && this.rangeFind) if (options["incsearch"] && this.rangeFind)
this.rangeFind.search(command); this.rangeFind.search(command);
} catch(e) { liberator.reportError(e); }
}, },
onSubmit: function (command) { onSubmit: function (command) {
// use the last pattern if none specified if (!options["incsearch"] || !this.rangeFind || !this.rangeFind.found) {
if (!command)
command = lastSearchPattern;
if (!options["incsearch"] || !this.rangeFind.found) {
this.clear(); this.clear();
this.find(command, this.rangeFind.backwards); this.find(command || this._lastSearchString, this._lastSearchBackwards);
} }
this._lastSearchBackwards = this.rangeFind.backwards; this._lastSearchBackwards = this.rangeFind.backwards;
@@ -536,7 +524,7 @@ try {
this._lastSearchString = command; this._lastSearchString = command;
if (options["hlsearch"]) if (options["hlsearch"])
this.highlight(this.rangeFind.searchString); this.highlight();
modes.reset(); modes.reset();
}, },
@@ -558,15 +546,17 @@ try {
* *
* @param {string} str The string to highlight. * @param {string} str The string to highlight.
*/ */
highlight: function (str) { highlight: function () {
return; if (this.rangeFind)
this.rangeFind.highlight();
}, },
/** /**
* Clears all search highlighting. * Clears all search highlighting.
*/ */
clear: function () { clear: function () {
return; if (this.rangeFind)
this.rangeFind.highlight(true);
} }
}, { }, {
}, { }, {
@@ -632,9 +622,9 @@ const RangeFind = Class("RangeFind", {
this._backward = backward; this._backward = backward;
this.ranges = this.makeFrameList(content); 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.startRange.collapse(!backward);
this.range = this.findRange(this.startRange); this.range = this.findRange(this.startRange);
this.ranges.first = this.range; this.ranges.first = this.range;
@@ -645,17 +635,6 @@ const RangeFind = Class("RangeFind", {
this.found = false; 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, sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument,
compareRanges: function (r1, r2) compareRanges: function (r1, r2)
@@ -679,34 +658,25 @@ const RangeFind = Class("RangeFind", {
win = win.top; win = win.top;
let frames = []; let frames = [];
function endpoint(range, before) { function pushRange(start, end) {
range = range.cloneRange(); frames.push(RangeFind.Range(start, end, frames.length));
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 rec(win) { function rec(win) {
let doc = win.document; let doc = win.document;
let pageRange = doc.createRange(); let pageRange = doc.createRange();
pageRange.setStartBefore(doc.body || doc.documentElement.lastChild); pageRange.setStartBefore(doc.body || doc.documentElement.lastChild);
pageRange.setEndAfter(doc.body || doc.documentElement.lastChild); pageRange.setEndAfter(doc.body || doc.documentElement.lastChild);
let pageStart = endpoint(pageRange, true); let pageStart = RangeFind.endpoint(pageRange, true);
let pageEnd = endpoint(pageRange, false); let pageEnd = RangeFind.endpoint(pageRange, false);
for (let frame in util.Array.itervalues(win.frames)) { for (let frame in util.Array.itervalues(win.frames)) {
let range = doc.createRange(); let range = doc.createRange();
range.selectNode(frame.frameElement); range.selectNode(frame.frameElement);
pushRange(pageStart, endpoint(range, true), win); pushRange(pageStart, RangeFind.endpoint(range, true));
pageStart = endpoint(range, false); pageStart = RangeFind.endpoint(range, false);
rec(frame); rec(frame);
} }
pushRange(pageStart, pageEnd, win); pushRange(pageStart, pageEnd);
} }
rec(win); rec(win);
return frames; return frames;
@@ -749,7 +719,23 @@ const RangeFind = Class("RangeFind", {
get searchString() this.lastString, get searchString() this.lastString,
get backward() this.finder.findBackwards, 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; this.finder.findBackwards = reverse ? !this._backward : this._backward;
let again = word == null; let again = word == null;
if (again) if (again)
@@ -758,9 +744,10 @@ const RangeFind = Class("RangeFind", {
word = word.toLowerCase(); word = word.toLowerCase();
if (!again && (word == "" || word.indexOf(this.lastString) != 0 || this.backward)) { if (!again && (word == "" || word.indexOf(this.lastString) != 0 || this.backward)) {
this.unhighlight(); if (!private)
this.range.deselect();
if (word == "") if (word == "")
this.deScroll(this.range); this.range.descroll()
this.lastRange = this.startRange; this.lastRange = this.startRange;
this.range = this.ranges.first; this.range = this.ranges.first;
} }
@@ -772,6 +759,8 @@ const RangeFind = Class("RangeFind", {
let idx = this.range.index; let idx = this.range.index;
for (let i in this.backward ? util.range(idx + 1, -1, -1) : util.range(idx, this.ranges.length)) for (let i in this.backward ? util.range(idx + 1, -1, -1) : util.range(idx, this.ranges.length))
yield i; yield i;
if (private)
return;
this.wrapped = true; this.wrapped = true;
for (let i in this.backward ? util.range(this.ranges.length, idx, -1) : util.range(0, idx)) for (let i in this.backward ? util.range(this.ranges.length, idx, -1) : util.range(0, idx))
yield i; yield i;
@@ -779,15 +768,23 @@ const RangeFind = Class("RangeFind", {
for (let i in indices.call(this)) { for (let i in indices.call(this)) {
this.range = this.ranges[i]; this.range = this.ranges[i];
let start = this.sameDocument(this.lastRange, this.range.range) ? 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); 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; break;
this.deScroll(this.range); if (!private) {
this.unhighlight(); this.range.descroll();
this.range.deselect();
} }
} }
}
if (range)
this.lastRange = range.cloneRange();
if (private)
return range;
this.lastString = word; this.lastString = word;
if (range == null) { if (range == null) {
@@ -796,159 +793,121 @@ const RangeFind = Class("RangeFind", {
return null; return null;
} }
this.wrapped = false; this.wrapped = false;
this.selection.removeAllRanges(); this.range.selection.removeAllRanges();
this.selection.addRange(range); this.range.selection.addRange(range);
this.selectionController.scrollSelectionIntoView(this.selectionController.SELECTION_NORMAL, 0, false); this.range.selectionController.scrollSelectionIntoView(
this.lastRange = range.cloneRange(); this.range.selectionController.SELECTION_NORMAL, 0, false);
this.found = true; this.found = true;
return range; return range;
}, },
cancel: function () { highlight: function (clear) {
if (false) // Later. if (!this.lastString)
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))
return; return;
if (!word) { if (!clear)
let elems = this._highlighter.spans; for (let range in values(this.ranges))
for (let i = elems.length; --i >= 0;) { if (util.evaluateXPath("//@liberator:highlight[1][.='Search']").snapshotLength)
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();
}
return; return;
}
var baseNode = <span highlight="Search"/>; let span = util.xmlToDom(<span highlight="Search"/>, this.range.document);
baseNode = util.xmlToDom(baseNode, window.content.document);
var body = doc.body; function highlight(range) {
var count = body.childNodes.length; let startContainer = range.startContainer;
this.searchRange = doc.createRange(); let startOffset = range.startOffset;
this.startPt = doc.createRange(); let node = startContainer.ownerDocument.importNode(span, true);
this.endPt = doc.createRange();
this.searchRange.setStart(body, 0); let docfrag = range.extractContents();
this.searchRange.setEnd(body, count); let before = startContainer.splitText(startOffset);
let parent = before.parentNode;
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;
node.appendChild(docfrag); node.appendChild(docfrag);
parent.insertBefore(node, before); parent.insertBefore(node, before);
this.spans.push(node); range.selectNode(node);
return 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);
}, },
/** cancel: function () {
* Clears all search highlighting. this.range.deselect();
*/ this.range.descroll()
clear: function () { },
this.spans.forEach(function (span) { }, {
if (span.parentNode) { Range: Class("RangeFind.Range", {
let el = span.firstChild; init: function (start, end, index) {
while (el) { if (start instanceof Ci.nsIDOMWindow) { // Kludge
span.removeChild(el); this.document = start.document;
span.parentNode.insertBefore(el, span); return;
el = span.firstChild;
} }
span.parentNode.removeChild(span);
} this.index = index;
});
this.spans = []; 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: // vim: set fdm=marker sw=4 ts=4 et:

View File

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