// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2014 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.
*/
get all() iter(this._localMarks.get(this.localURI) || {},
this._urlMarks
- ).sort(function (a, b) String.localeCompare(a[0], b[0])),
+ ).sort((a, b) => String.localeCompare(a[0], b[0])),
- get localURI() buffer.focusedFrame.document.documentURI,
+ get localURI() buffer.focusedFrame.document.documentURI.replace(/#.*/, ""),
+
+ Mark: function Mark(params={}) {
+ let win = buffer.focusedFrame;
+ let doc = win.document;
+
+ params.location = doc.documentURI.replace(/#.*/, ""),
+ params.offset = buffer.scrollPosition;
+ params.path = DOM(buffer.findScrollable(0, false)).xpath;
+ params.timestamp = Date.now() * 1000;
+ params.equals = function (m) this.location == m.location
+ && this.offset.x == m.offset.x
+ && this.offset.y == m.offset.y
+ && this.path == m.path;
+ return params;
+ },
/**
* Add a named mark for the current buffer, at its current position.
* selected from. If it matches [a-z], it's a local mark, and can
* only be recalled from a buffer with a matching URL.
*
- * @param {string} mark The mark name.
+ * @param {string} name The mark name.
* @param {boolean} silent Whether to output error messages.
*/
- add: function (mark, silent) {
- let win = buffer.focusedFrame;
- let doc = win.document;
-
- let position = { x: buffer.scrollXPercent / 100, y: buffer.scrollYPercent / 100 };
-
- if (Marks.isURLMark(mark)) {
- let res = this._urlMarks.set(mark, { location: doc.documentURI, position: position, tab: Cu.getWeakReference(tabs.getTab()), timestamp: Date.now()*1000 });
- if (!silent)
- dactyl.log(_("mark.addURL", Marks.markToString(mark, res)), 5);
+ add: function (name, silent) {
+ let mark = this.Mark();
+
+ if (Marks.isURLMark(name)) {
+ // FIXME: Disabled due to cross-compartment magic.
+ // mark.tab = util.weakReference(tabs.getTab());
+ this._urlMarks.set(name, mark);
+ var message = "mark.addURL";
}
- else if (Marks.isLocalMark(mark)) {
- let marks = this._localMarks.get(doc.documentURI, {});
- marks[mark] = { location: doc.documentURI, position: position, timestamp: Date.now()*1000 };
+ else if (Marks.isLocalMark(name)) {
+ this._localMarks.get(mark.location, {})[name] = mark;
this._localMarks.changed();
- if (!silent)
- dactyl.log(_("mark.addLocal", Marks.markToString(mark, marks[mark])), 5);
+ message = "mark.addLocal";
+ }
+
+ if (!silent)
+ dactyl.log(_(message, Marks.markToString(name, mark)), 5);
+ return mark;
+ },
+
+ /**
+ * Push the current buffer position onto the jump stack.
+ *
+ * @param {string} reason The reason for this scroll event. Multiple
+ * scroll events for the same reason are coalesced. @optional
+ */
+ push: function push(reason) {
+ let store = buffer.localStore;
+ let jump = store.jumps[store.jumpsIndex];
+
+ if (reason && jump && jump.reason == reason)
+ return;
+
+ let mark = this.add("'");
+ if (jump && mark.equals(jump.mark))
+ return;
+
+ if (!this.jumping) {
+ store.jumps[++store.jumpsIndex] = { mark: mark, reason: reason };
+ store.jumps.length = store.jumpsIndex + 1;
+
+ if (store.jumps.length > this.maxJumps) {
+ store.jumps = store.jumps.slice(-this.maxJumps);
+ store.jumpsIndex = store.jumps.length - 1;
+ }
}
},
+ maxJumps: 200,
+
+ /**
+ * Jump to the given offset in the jump stack.
+ *
+ * @param {number} offset The offset from the current position in
+ * the jump stack to jump to.
+ * @returns {number} The actual change in offset.
+ */
+ jump: function jump(offset) {
+ let store = buffer.localStore;
+ if (offset < 0 && store.jumpsIndex == store.jumps.length - 1)
+ this.push();
+
+ return this.withSavedValues(["jumping"], function _jump() {
+ this.jumping = true;
+ let idx = Math.constrain(store.jumpsIndex + offset, 0, store.jumps.length - 1);
+ let orig = store.jumpsIndex;
+
+ if (idx in store.jumps && !dactyl.trapErrors("_scrollTo", this, store.jumps[idx].mark))
+ store.jumpsIndex = idx;
+ return store.jumpsIndex - orig;
+ });
+ },
+
+ get jumps() {
+ let store = buffer.localStore;
+ return {
+ index: store.jumpsIndex,
+ locations: store.jumps.map(j => j.mark)
+ };
+ },
+
/**
* Remove all marks matching *filter*. If *special* is given, removes all
* local marks.
let tab = mark.tab && mark.tab.get();
if (!tab || !tab.linkedBrowser || tabs.allTabs.indexOf(tab) == -1)
for ([, tab] in iter(tabs.visibleTabs, tabs.allTabs)) {
- if (tab.linkedBrowser.contentDocument.documentURI === mark.location)
+ if (tab.linkedBrowser.contentDocument.documentURI.replace(/#.*/, "") === mark.location)
break;
tab = null;
}
if (tab) {
tabs.select(tab);
let doc = tab.linkedBrowser.contentDocument;
- if (doc.documentURI == mark.location) {
+ if (doc.documentURI.replace(/#.*/, "") == mark.location) {
dactyl.log(_("mark.jumpingToURL", Marks.markToString(char, mark)), 5);
- buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
+ this._scrollTo(mark);
}
else {
this._pendingJumps.push(mark);
dactyl.assert(mark, _("mark.unset", char));
dactyl.log(_("mark.jumpingToLocal", Marks.markToString(char, mark)), 5);
- buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
+ this._scrollTo(mark);
}
else
dactyl.echoerr(_("mark.invalid"));
},
+ _scrollTo: function _scrollTo(mark) {
+ if (!mark.path)
+ var node = buffer.findScrollable(0, (mark.offset || mark.position).x);
+ else
+ for (node in DOM.XPath(mark.path, buffer.focusedFrame.document))
+ break;
+
+ util.assert(node);
+ if (node instanceof Element)
+ DOM(node).scrollIntoView();
+
+ if (mark.offset)
+ Buffer.scrollToPosition(node, mark.offset.x, mark.offset.y);
+ else if (mark.position)
+ Buffer.scrollToPercent(node, mark.position.x * 100, mark.position.y * 100);
+ },
+
/**
* List all marks matching *filter*.
*
if (filter.length > 0) {
let pattern = util.charListToRegexp(filter, "a-zA-Z");
- marks = marks.filter(function ([k, ]) pattern.test(k));
+ marks = marks.filter(([k]) => (pattern.test(k)));
dactyl.assert(marks.length > 0, _("mark.noMatching", filter.quote()));
}
template.tabular(
["Mark", "HPos", "VPos", "File"],
["", "text-align: right", "text-align: right", "color: green"],
- ([mark[0],
- Math.round(mark[1].position.x * 100) + "%",
- Math.round(mark[1].position.y * 100) + "%",
- mark[1].location]
- for ([, mark] in Iterator(marks)))));
+ ([name,
+ mark.offset ? Math.round(mark.offset.x)
+ : Math.round(mark.position.x * 100) + "%",
+ mark.offset ? Math.round(mark.offset.y)
+ : Math.round(mark.position.y * 100) + "%",
+ mark.location]
+ for ([, [name, mark]] in Iterator(marks)))));
},
_onPageLoad: function _onPageLoad(event) {
let win = event.originalTarget.defaultView;
for (let [i, mark] in Iterator(this._pendingJumps)) {
if (win && win.location.href == mark.location) {
- buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
+ this._scrollTo(mark);
this._pendingJumps.splice(i, 1);
return;
}
}, {
markToString: function markToString(name, mark) {
let tab = mark.tab && mark.tab.get();
- return name + ", " + mark.location +
- ", (" + Math.round(mark.position.x * 100) +
- "%, " + Math.round(mark.position.y * 100) + "%)" +
- (tab ? ", tab: " + tabs.index(tab) : "");
+ if (mark.offset)
+ return [name, mark.location,
+ "(" + Math.round(mark.offset.x * 100),
+ Math.round(mark.offset.y * 100) + ")",
+ (tab && "tab: " + tabs.index(tab))
+ ].filter(util.identity).join(", ");
+
+ if (mark.position)
+ return [name, mark.location,
+ "(" + Math.round(mark.position.x * 100) + "%",
+ Math.round(mark.position.y * 100) + "%)",
+ (tab && "tab: " + tabs.index(tab))
+ ].filter(util.identity).join(", ");
},
- isLocalMark: function isLocalMark(mark) /^[a-z`']$/.test(mark),
+ isLocalMark: bind("test", /^[a-z`']$/),
- isURLMark: function isURLMark(mark) /^[A-Z]$/.test(mark)
+ isURLMark: bind("test", /^[A-Z]$/)
}, {
events: function () {
let appContent = document.getElementById("appcontent");
if (appContent)
- events.listen(appContent, "load", marks.closure._onPageLoad, true);
+ events.listen(appContent, "load", marks.bound._onPageLoad, true);
},
mappings: function () {
var myModes = config.browserModes;
{ arg: true });
},
- commands: function () {
+ commands: function initCommands() {
commands.add(["delm[arks]"],
"Delete the specified marks",
function (args) {
});
},
- completion: function () {
+ completion: function initCompletion() {
completion.mark = function mark(context) {
function percent(i) Math.round(i * 100);
context.title = ["Mark", "HPos VPos File"];
- context.keys.description = function ([, m]) percent(m.position.x) + "% " + percent(m.position.y) + "% " + m.location;
+ context.keys.description = ([, m]) => (m.offset ? Math.round(m.offset.x) + " " + Math.round(m.offset.y)
+ : percent(m.position.x) + "% " + percent(m.position.y) + "%"
+ ) + " " + m.location;
context.completions = marks.all;
};
},
- sanitizer: function () {
+ sanitizer: function initSanitizer() {
sanitizer.addItem("marks", {
description: "Local and URL marks",
persistent: true,
}
});
-// vim: set fdm=marker sw=4 ts=4 et:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et: