]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/marks.js
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / content / marks.js
index 8e5632838a6270d2fbb675090b6e2c186f454424..e32d0c6f9c61be4de44cff94a9738131eef0ecbc 100644 (file)
@@ -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;
         };
     },