-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2012 Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
-Components.utils.import("resource://dactyl/bootstrap.jsm");
defineModule("finder", {
exports: ["RangeFind", "RangeFinder", "rangefinder"],
- require: ["prefs"],
- use: ["messages", "services", "util"]
-}, this);
+ require: ["prefs", "util"]
+});
-function equals(a, b) XPCNativeWrapper(a) == XPCNativeWrapper(b);
+lazyRequire("buffer", ["Buffer"]);
+lazyRequire("overlay", ["overlay"]);
-try {
+function id(w) w.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils)
+ .outerWindowID;
+function equals(a, b) id(a) == id(b);
/** @instance rangefinder */
var RangeFinder = Module("rangefinder", {
this.lastFindPattern = "";
},
+ get content() {
+ let { window } = this.modes.getStack(0).params;
+ return window || this.window.content;
+ },
+
get rangeFind() {
- let find = modules.buffer.localStore.rangeFind;
- if (find && find.stale || !isinstance(find, RangeFind))
+ let find = overlay.getData(this.content.document,
+ "range-find", null);
+
+ if (!isinstance(find, RangeFind) || find.stale)
return this.rangeFind = null;
return find;
},
- set rangeFind(val) modules.buffer.localStore.rangeFind = val
+ set rangeFind(val) overlay.setData(this.content.document,
+ "range-find", val)
}),
init: function init() {
prefs.safeSet("accessibility.typeaheadfind.autostart", false);
- // The above should be sufficient, but: http://dactyl.sf.net/bmo/348187
+ // The above should be sufficient, but: http://bugzil.la/348187
prefs.safeSet("accessibility.typeaheadfind", false);
},
+ cleanup: function cleanup() {
+ for (let doc in util.iterDocuments()) {
+ let find = overlay.getData(doc, "range-find", null);
+ if (find)
+ find.highlight(true);
+
+ overlay.setData(doc, "range-find", null);
+ }
+ },
+
get commandline() this.modules.commandline,
get modes() this.modules.modes,
get options() this.modules.options,
- openPrompt: function (mode) {
+ openPrompt: function openPrompt(mode) {
+ this.modules.marks.push();
this.commandline;
- this.CommandMode(mode).open();
+ this.CommandMode(mode, this.content).open();
+
+ Buffer(this.content).resetCaret();
if (this.rangeFind && equals(this.rangeFind.window.get(), this.window))
this.rangeFind.reset();
this.find("", mode == this.modes.FIND_BACKWARD);
},
- bootstrap: function (str, backward) {
+ bootstrap: function bootstrap(str, backward) {
if (arguments.length < 2 && this.rangeFind)
backward = this.rangeFind.reverse;
let matchCase = this.options["findcase"] === "smart" ? /[A-Z]/.test(str) :
this.options["findcase"] === "ignore" ? false : true;
- str = str.replace(/\\(.|$)/g, function (m, n1) {
+ function replacer(m, n1) {
if (n1 == "c")
matchCase = false;
else if (n1 == "C")
else
return m;
return "";
- });
+ }
+ this.options["findflags"].forEach(function (f) replacer(f, f));
+
+ let pattern = str.replace(/\\(.|$)/g, replacer);
+
+ if (str)
+ this.lastFindPattern = str;
// It's possible, with :tabdetach for instance, for the rangeFind to
// actually move from one window to another, which breaks things.
if (!this.rangeFind
if (this.rangeFind)
this.rangeFind.cancel();
- this.rangeFind = RangeFind(this.window, matchCase, backward,
+ this.rangeFind = null;
+ this.rangeFind = RangeFind(this.window, this.content, matchCase, backward,
linksOnly && this.options.get("hinttags").matcher,
regexp);
this.rangeFind.highlighted = highlighted;
this.rangeFind.selections = selections;
}
- return this.lastFindPattern = str;
+ this.rangeFind.pattern = str;
+ return pattern;
},
- find: function (pattern, backwards) {
+ find: function find(pattern, backwards) {
+ this.modules.marks.push();
let str = this.bootstrap(pattern, backwards);
+ this.backward = this.rangeFind.reverse;
+
if (!this.rangeFind.find(str))
this.dactyl.echoerr(_("finder.notFound", pattern),
this.commandline.FORCE_SINGLELINE);
return this.rangeFind.found;
},
- findAgain: function (reverse) {
+ findAgain: function findAgain(reverse) {
+ this.modules.marks.push();
if (!this.rangeFind)
this.find(this.lastFindPattern);
else if (!this.rangeFind.find(null, reverse))
| this.commandline.FORCE_SINGLELINE);
}
else
- this.commandline.echo((this.rangeFind.backward ? "?" : "/") + this.lastFindPattern,
+ this.commandline.echo((this.rangeFind.backward ? "?" : "/") + this.rangeFind.pattern,
"Normal", this.commandline.FORCE_SINGLELINE);
if (this.options["hlfind"])
this.rangeFind.focus();
},
- onCancel: function () {
+ onCancel: function onCancel() {
if (this.rangeFind)
this.rangeFind.cancel();
},
- onChange: function (command) {
+ onChange: function onChange(command) {
if (this.options["incfind"]) {
command = this.bootstrap(command);
this.rangeFind.find(command);
}
},
- onHistory: function () {
+ onHistory: function onHistory() {
this.rangeFind.found = false;
},
- onSubmit: function (command) {
+ onSubmit: function onSubmit(command) {
+ if (!command && this.lastFindPattern) {
+ this.find(this.lastFindPattern, this.backward);
+ this.findAgain();
+ return;
+ }
+
if (!this.options["incfind"] || !this.rangeFind || !this.rangeFind.found) {
this.clear();
- this.find(command || this.lastFindPattern, this.modes.extended & this.modes.FIND_BACKWARD);
+ this.find(command || this.lastFindPattern, this.backward);
}
if (this.options["hlfind"])
* Highlights all occurrences of the last sought for string in the
* current buffer.
*/
- highlight: function () {
+ highlight: function highlight() {
if (this.rangeFind)
this.rangeFind.highlight();
},
/**
* Clears all find highlighting.
*/
- clear: function () {
+ clear: function clear() {
if (this.rangeFind)
this.rangeFind.highlight(true);
}
commandline: function initCommandline(dactyl, modules, window) {
const { rangefinder } = modules;
rangefinder.CommandMode = Class("CommandFindMode", modules.CommandMode, {
- init: function init(mode) {
+ init: function init(mode, window) {
this.mode = mode;
+ this.window = window;
init.supercall(this);
},
get onSubmit() modules.rangefinder.closure.onSubmit
});
},
- mappings: function (dactyl, modules, window) {
+ mappings: function initMappings(dactyl, modules, window) {
const { Buffer, buffer, config, mappings, modes, rangefinder } = modules;
var myModes = config.browserModes.concat([modes.CARET]);
function () { rangefinder.openPrompt(modes.FIND_FORWARD); });
mappings.add(myModes,
- ["?", "<find-backward>"], "Find a pattern backward of the current caret position",
+ ["?", "<find-backward>", "<S-Slash>"], "Find a pattern backward of the current caret position",
function () { rangefinder.openPrompt(modes.FIND_BACKWARD); });
mappings.add(myModes,
});
},
- options: function (dactyl, modules, window) {
+ options: function initOptions(dactyl, modules, window) {
const { options, rangefinder } = modules;
- const { prefs } = require("prefs");
options.add(["hlfind", "hlf"],
"Highlight all /find pattern matches on the current page after submission",
}
});
+ options.add(["findflags", "ff"],
+ "Default flags for find invocations",
+ "charlist", "",
+ {
+ values: {
+ "c": "Ignore case",
+ "C": "Match case",
+ "r": "Perform a regular expression search",
+ "R": "Perform a plain string search",
+ "l": "Search only in links",
+ "L": "Search all text"
+ }
+ });
+
options.add(["incfind", "if"],
"Find a pattern incrementally as it is typed rather than awaiting c_<Return>",
"boolean", true);
* large amounts of data are concerned (e.g., for API documents).
*/
var RangeFind = Class("RangeFind", {
- init: function init(window, matchCase, backward, elementPath, regexp) {
- this.window = Cu.getWeakReference(window);
- this.content = window.content;
+ init: function init(window, content, matchCase, backward, elementPath, regexp) {
+ this.window = util.weakReference(window);
+ this.content = content;
- this.baseDocument = Cu.getWeakReference(this.content.document);
+ this.baseDocument = util.weakReference(this.content.document);
this.elementPath = elementPath || null;
this.reverse = Boolean(backward);
this.lastString = "";
},
- get store() this.content.document.dactylStore = this.content.document.dactylStore || {},
+ get store() overlay.getData(this.content.document, "buffer", Object),
get backward() this.finder.findBackwards,
+ set backward(val) this.finder.findBackwards = val,
get matchCase() this.finder.caseSensitive,
set matchCase(val) this.finder.caseSensitive = Boolean(val),
- get regexp() this.finder.regularExpression || false,
- set regexp(val) {
- try {
- return this.finder.regularExpression = Boolean(val);
- }
- catch (e) {
- return false;
- }
- },
-
get findString() this.lastString,
+ get flags() this.matchCase ? "" : "i",
+
get selectedRange() {
let win = this.store.focusedFrame && this.store.focusedFrame.get() || this.content;
this.range.selectionController.scrollSelectionIntoView(
this.range.selectionController.SELECTION_NORMAL, 0, false);
- this.store.focusedFrame = Cu.getWeakReference(range.startContainer.ownerDocument.defaultView);
+ this.store.focusedFrame = util.weakReference(range.startContainer.ownerDocument.defaultView);
},
cancel: function cancel() {
this.purgeListeners();
- this.range.deselect();
- this.range.descroll();
+ if (this.range) {
+ this.range.deselect();
+ this.range.descroll();
+ }
},
compareRanges: function compareRanges(r1, r2) {
focus: function focus() {
if (this.lastRange)
- var node = util.evaluateXPath(RangeFind.selectNodePath,
- this.lastRange.commonAncestorContainer).snapshotItem(0);
+ var node = DOM.XPath(RangeFind.selectNodePath,
+ this.lastRange.commonAncestorContainer).snapshotItem(0);
if (node) {
node.focus();
// Re-highlight collapsed selection
}
},
- indexIter: function (private_) {
+ indexIter: function indexIter(private_) {
let idx = this.range.index;
if (this.backward)
var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)];
}
},
- iter: function (word) {
- let saved = ["lastRange", "lastString", "range"].map(function (s) [s, this[s]], this);
+ iter: function iter(word) {
+ let saved = ["lastRange", "lastString", "range", "regexp"].map(function (s) [s, this[s]], this);
+ let res;
try {
- this.range = this.ranges[0];
+ let regexp = this.regexp && word != util.regexp.escape(word);
this.lastRange = null;
- this.lastString = word;
- var res;
- while (res = this.find(null, this.reverse, true))
- yield res;
+ this.regexp = false;
+ if (regexp) {
+ let re = RegExp(word, "gm" + this.flags);
+ for (this.range in array.iterValues(this.ranges)) {
+ for (let match in util.regexp.iterate(re, DOM.stringify(this.range.range, true))) {
+ let lastRange = this.lastRange;
+ if (res = this.find(null, this.reverse, true))
+ yield res;
+ else
+ this.lastRange = lastRange;
+ }
+ }
+ }
+ else {
+ this.range = this.ranges[0];
+ this.lastString = word;
+ while (res = this.find(null, this.reverse, true))
+ yield res;
+ }
}
finally {
saved.forEach(function ([k, v]) this[k] = v, this);
}
},
- makeFrameList: function (win) {
+ makeFrameList: function makeFrameList(win) {
const self = this;
win = win.top;
let frames = [];
for (let frame in array.iterValues(win.frames)) {
let range = doc.createRange();
- if (util.computedStyle(frame.frameElement).visibility == "visible") {
+ if (DOM(frame.frameElement).style.visibility == "visible") {
range.selectNode(frame.frameElement);
pushRange(pageStart, RangeFind.endpoint(range, true));
pageStart = RangeFind.endpoint(range, false);
return frames;
},
- reset: function () {
+ reset: function reset() {
this.ranges = this.makeFrameList(this.content);
this.startRange = this.selectedRange;
this.startRange.collapse(!this.reverse);
this.lastRange = this.selectedRange;
- this.range = this.findRange(this.startRange);
+ this.range = this.findRange(this.startRange) || this.ranges[0];
+ util.assert(this.range, "Null range", false);
this.ranges.first = this.range;
this.ranges.forEach(function (range) range.save());
this.forward = null;
this.found = false;
},
- // This doesn't work yet.
- resetCaret: function () {
- let equal = RangeFind.equal;
- let selection = this.win.getSelection();
- if (selection.rangeCount == 0)
- selection.addRange(this.pageStart);
- function getLines() {
- let orig = selection.getRangeAt(0);
- function getRanges(forward) {
- selection.removeAllRanges();
- selection.addRange(orig);
- let cur = orig;
- while (true) {
- var last = cur;
- this.sel.lineMove(forward, false);
- cur = selection.getRangeAt(0);
- if (equal(cur, last))
- break;
- yield cur;
- }
- }
- yield orig;
- for (let range in getRanges(true))
- yield range;
- for (let range in getRanges(false))
- yield range;
- }
- for (let range in getLines()) {
- if (this.sel.checkVisibility(range.startContainer, range.startOffset, range.startOffset))
- return range;
- }
- return null;
- },
-
- find: function (word, reverse, private_) {
+ find: function find(pattern, reverse, private_) {
if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange))
this.reset();
this.wrapped = false;
- this.finder.findBackwards = reverse ? !this.reverse : this.reverse;
- let again = word == null;
+ this.backward = reverse ? !this.reverse : this.reverse;
+ let again = pattern == null;
if (again)
- word = this.lastString;
+ pattern = this.lastString;
if (!this.matchCase)
- word = word.toLowerCase();
+ pattern = pattern.toLowerCase();
- if (!again && (word === "" || word.indexOf(this.lastString) !== 0 || this.backward)) {
+ if (!again && (pattern === "" || pattern.indexOf(this.lastString) !== 0 || this.backward)) {
if (!private_)
this.range.deselect();
- if (word === "")
+ if (pattern === "")
this.range.descroll();
this.lastRange = this.startRange;
this.range = this.ranges.first;
}
- if (word == "")
+ let word = pattern;
+ let regexp = this.regexp && word != util.regexp.escape(word);
+
+ if (regexp)
+ try {
+ RegExp(pattern);
+ }
+ catch (e) {
+ pattern = "";
+ }
+
+ if (pattern == "")
var range = this.startRange;
else
for (let i in this.indexIter(private_)) {
if (this.backward && !again)
start = RangeFind.endpoint(this.startRange, false);
+ if (regexp) {
+ let range = this.range.range.cloneRange();
+ range[this.backward ? "setEnd" : "setStart"](
+ start.startContainer, start.startOffset);
+ range = DOM.stringify(range);
+
+ if (!this.backward)
+ var match = RegExp(pattern, "m" + this.flags).exec(range);
+ else {
+ match = RegExp("[^]*(?:" + pattern + ")", "m" + this.flags).exec(range);
+ if (match)
+ match = RegExp(pattern + "$", this.flags).exec(match[0]);
+ }
+ if (!(match && match[0]))
+ continue;
+ word = match[0];
+ }
+
var range = this.finder.Find(word, this.range.range, start, this.range.range);
- if (range)
+ if (range && DOM(range.commonAncestorContainer).isVisible)
break;
}
if (range)
this.lastRange = range.cloneRange();
if (!private_) {
- this.lastString = word;
+ this.lastString = pattern;
if (range == null) {
this.cancel();
this.found = false;
get stale() this._stale || this.baseDocument.get() != this.content.document,
set stale(val) this._stale = val,
- addListeners: function () {
+ addListeners: function addListeners() {
for (let range in array.iterValues(this.ranges))
range.window.addEventListener("unload", this.closure.onUnload, true);
},
- purgeListeners: function () {
+ purgeListeners: function purgeListeners() {
for (let range in array.iterValues(this.ranges))
try {
range.window.removeEventListener("unload", this.closure.onUnload, true);
}
catch (e if e.result === Cr.NS_ERROR_FAILURE) {}
},
- onUnload: function (event) {
+ onUnload: function onUnload(event) {
this.purgeListeners();
if (this.highlighted)
this.highlight(true);
}
}, {
Range: Class("RangeFind.Range", {
- init: function (range, index) {
+ init: function init(range, index) {
this.index = index;
this.range = range;
this.document = range.startContainer.ownerDocument;
this.window = this.document.defaultView;
- this.docShell = this.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation)
- .QueryInterface(Ci.nsIDocShell);
if (this.selection == null)
return false;
this.save();
},
+ docShell: Class.Memoize(function () util.docShell(this.window)),
+
intersects: function (range) RangeFind.intersects(this.range, range),
- save: function () {
+ save: function save() {
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
this.initialSelection = null;
this.initialSelection = this.selection.getRangeAt(0);
},
- descroll: function () {
+ descroll: function descroll() {
this.window.scrollTo(this.scroll.x, this.scroll.y);
},
- deselect: function () {
+ deselect: function deselect() {
if (this.selection) {
this.selection.removeAllRanges();
if (this.initialSelection)
}
}
}),
- contains: function (range, r) {
+ contains: function contains(range, r, quiet) {
try {
return range.compareBoundaryPoints(range.START_TO_END, r) >= 0 &&
range.compareBoundaryPoints(range.END_TO_START, r) <= 0;
}
catch (e) {
- util.reportError(e, true);
+ if (e.result != Cr.NS_ERROR_DOM_WRONG_DOCUMENT_ERR && !quiet)
+ util.reportError(e, true);
return false;
}
},
- intersects: function (range, r) {
+ containsNode: function containsNode(range, n, quiet) n.ownerDocument && this.contains(range, RangeFind.nodeRange(n), quiet),
+ intersects: function intersects(range, r) {
try {
return r.compareBoundaryPoints(range.START_TO_END, range) >= 0 &&
r.compareBoundaryPoints(range.END_TO_START, range) <= 0;
return false;
}
},
- endpoint: function (range, before) {
+ endpoint: function endpoint(range, before) {
range = range.cloneRange();
range.collapse(before);
return range;
},
- equal: function (r1, r2) {
+ equal: function equal(r1, r2) {
try {
return !r1.compareBoundaryPoints(r1.START_TO_START, r2) && !r1.compareBoundaryPoints(r1.END_TO_END, r2);
}
return false;
}
},
- nodeContents: function (node) {
+ nodeContents: function nodeContents(node) {
let range = node.ownerDocument.createRange();
try {
range.selectNodeContents(node);
catch (e) {}
return range;
},
- nodeRange: function (node) {
+ nodeRange: function nodeRange(node) {
let range = node.ownerDocument.createRange();
try {
range.selectNode(node);
catch (e) {}
return range;
},
- sameDocument: function (r1, r2) {
+ sameDocument: function sameDocument(r1, r2) {
if (!(r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument))
return false;
try {
}
return true;
},
- selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | ")
+ selectNodePath: ["a", "xhtml:a", "*[@onclick]"].map(function (p) "ancestor-or-self::" + p).join(" | "),
+ union: function union(a, b) {
+ let start = a.compareBoundaryPoints(a.START_TO_START, b) < 0 ? a : b;
+ let end = a.compareBoundaryPoints(a.END_TO_END, b) > 0 ? a : b;
+ let res = start.cloneRange();
+ res.setEnd(end.endContainer, end.endOffset);
+ return res;
+ }
});
-} catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
+// catch(e){ if (typeof e === "string") e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
endModule();
-
-// vim: set fdm=marker sw=4 ts=4 et ft=javascript:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et ft=javascript: