X-Git-Url: https://git.donarmstrong.com/?a=blobdiff_plain;f=common%2Fcontent%2Fmarks.js;fp=common%2Fcontent%2Fmarks.js;h=e32d0c6f9c61be4de44cff94a9738131eef0ecbc;hb=9044153cb63835e39b9de8ec4ade237c03e3888a;hp=8e5632838a6270d2fbb675090b6e2c186f454424;hpb=70740024f9c028c1fd63e1a1850ab062ff956054;p=dactyl.git diff --git a/common/content/marks.js b/common/content/marks.js index 8e56328..e32d0c6 100644 --- a/common/content/marks.js +++ b/common/content/marks.js @@ -4,7 +4,7 @@ // // This work is licensed for reuse under an MIT license. Details are // given in the LICENSE.txt file included with this file. -"use strict"; +/* use strict */ /** * @scope modules @@ -32,7 +32,24 @@ var Marks = Module("marks", { this._urlMarks ).sort(function (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 = params || {}; + + 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. @@ -41,29 +58,89 @@ var Marks = Module("marks", { * 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; + add: function (name, silent) { + let mark = this.Mark(); - 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); + if (Marks.isURLMark(name)) { + 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(function (j) j.mark) + }; + }, + /** * Remove all marks matching *filter*. If *special* is given, removes all * local marks. @@ -106,7 +183,7 @@ var Marks = Module("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; } @@ -114,9 +191,9 @@ var Marks = Module("marks", { 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); @@ -147,13 +224,30 @@ var Marks = Module("marks", { 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*. * @@ -174,18 +268,20 @@ var Marks = Module("marks", { 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; } @@ -194,15 +290,25 @@ var Marks = Module("marks", { }, { 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"); @@ -271,7 +377,9 @@ var Marks = Module("marks", { 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 = function ([, 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; }; },