]> git.donarmstrong.com Git - dactyl.git/blob - common/content/marks.js
Import 1.0b7.1 supporting Firefox up to 8.*
[dactyl.git] / common / content / marks.js
1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
4 //
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
7 "use strict";
8
9 /**
10  * @scope modules
11  * @instance marks
12  */
13 var Marks = Module("marks", {
14     init: function init() {
15         this._localMarks = storage.newMap("local-marks", { privateData: true, replacer: Storage.Replacer.skipXpcom, store: true });
16         this._urlMarks = storage.newMap("url-marks", { privateData: true, replacer: Storage.Replacer.skipXpcom, store: true });
17
18         try {
19             if (isArray(Iterator(this._localMarks).next()[1]))
20                 this._localMarks.clear();
21         }
22         catch (e) {}
23
24         this._pendingJumps = [];
25     },
26
27     /**
28      * @property {Array} Returns all marks, both local and URL, in a sorted
29      *     array.
30      */
31     get all() iter(this._localMarks.get(this.localURI) || {},
32                    this._urlMarks
33                   ).sort(function (a, b) String.localeCompare(a[0], b[0])),
34
35     get localURI() buffer.focusedFrame.document.documentURI,
36
37     /**
38      * Add a named mark for the current buffer, at its current position.
39      * If mark matches [A-Z], it's considered a URL mark, and will jump to
40      * the same position at the same URL no matter what buffer it's
41      * selected from. If it matches [a-z], it's a local mark, and can
42      * only be recalled from a buffer with a matching URL.
43      *
44      * @param {string} mark The mark name.
45      * @param {boolean} silent Whether to output error messages.
46      */
47     add: function (mark, silent) {
48         let win = buffer.focusedFrame;
49         let doc = win.document;
50
51         let position = { x: buffer.scrollXPercent / 100, y: buffer.scrollYPercent / 100 };
52
53         if (Marks.isURLMark(mark)) {
54             let res = this._urlMarks.set(mark, { location: doc.documentURI, position: position, tab: Cu.getWeakReference(tabs.getTab()), timestamp: Date.now()*1000 });
55             if (!silent)
56                 dactyl.log(_("mark.addURL", Marks.markToString(mark, res)), 5);
57         }
58         else if (Marks.isLocalMark(mark)) {
59             let marks = this._localMarks.get(doc.documentURI, {});
60             marks[mark] = { location: doc.documentURI, position: position, timestamp: Date.now()*1000 };
61             this._localMarks.changed();
62             if (!silent)
63                 dactyl.log(_("mark.addLocal", Marks.markToString(mark, marks[mark])), 5);
64         }
65     },
66
67     /**
68      * Remove all marks matching *filter*. If *special* is given, removes all
69      * local marks.
70      *
71      * @param {string} filter The list of marks to delete, e.g. "aA b C-I"
72      * @param {boolean} special Whether to delete all local marks.
73      */
74     remove: function (filter, special) {
75         if (special)
76             this._localMarks.remove(this.localURI);
77         else {
78             let pattern = util.charListToRegexp(filter, "a-zA-Z");
79             let local = this._localMarks.get(this.localURI);
80             this.all.forEach(function ([k, ]) {
81                 if (pattern.test(k)) {
82                     local && delete local[k];
83                     marks._urlMarks.remove(k);
84                 }
85             });
86             try {
87                 Iterator(local).next();
88                 this._localMarks.changed();
89             }
90             catch (e) {
91                 this._localMarks.remove(this.localURI);
92             }
93         }
94     },
95
96     /**
97      * Jumps to the named mark. See {@link #add}
98      *
99      * @param {string} char The mark to jump to.
100      */
101     jumpTo: function (char) {
102         if (Marks.isURLMark(char)) {
103             let mark = this._urlMarks.get(char);
104             dactyl.assert(mark, _("mark.unset", char));
105
106             let tab = mark.tab && mark.tab.get();
107             if (!tab || !tab.linkedBrowser || tabs.allTabs.indexOf(tab) == -1)
108                 for ([, tab] in iter(tabs.visibleTabs, tabs.allTabs)) {
109                     if (tab.linkedBrowser.contentDocument.documentURI === mark.location)
110                         break;
111                     tab = null;
112                 }
113
114             if (tab) {
115                 tabs.select(tab);
116                 let doc = tab.linkedBrowser.contentDocument;
117                 if (doc.documentURI == mark.location) {
118                     dactyl.log(_("mark.jumpingToURL", Marks.markToString(char, mark)), 5);
119                     buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
120                 }
121                 else {
122                     this._pendingJumps.push(mark);
123
124                     let sh = tab.linkedBrowser.sessionHistory;
125                     let items = array(util.range(0, sh.count));
126
127                     let a = items.slice(0, sh.index).reverse();
128                     let b = items.slice(sh.index);
129                     a.length = b.length = Math.max(a.length, b.length);
130                     items = array(a).zip(b).flatten().compact();
131
132                     for (let i in items.iterValues()) {
133                         let entry = sh.getEntryAtIndex(i, false);
134                         if (entry.URI.spec.replace(/#.*/, "") == mark.location)
135                             return void tab.linkedBrowser.webNavigation.gotoIndex(i);
136                     }
137                     dactyl.open(mark.location);
138                 }
139             }
140             else {
141                 this._pendingJumps.push(mark);
142                 dactyl.open(mark.location, dactyl.NEW_TAB);
143             }
144         }
145         else if (Marks.isLocalMark(char)) {
146             let mark = (this._localMarks.get(this.localURI) || {})[char];
147             dactyl.assert(mark, _("mark.unset", char));
148
149             dactyl.log(_("mark.jumpingToLocal", Marks.markToString(char, mark)), 5);
150             buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
151         }
152         else
153             dactyl.echoerr(_("mark.invalid"));
154
155     },
156
157     /**
158      * List all marks matching *filter*.
159      *
160      * @param {string} filter List of marks to show, e.g. "ab A-I".
161      */
162     list: function (filter) {
163         let marks = this.all;
164
165         dactyl.assert(marks.length > 0, _("mark.none"));
166
167         if (filter.length > 0) {
168             let pattern = util.charListToRegexp(filter, "a-zA-Z");
169             marks = marks.filter(function ([k, ]) pattern.test(k));
170             dactyl.assert(marks.length > 0, _("mark.noMatching", filter.quote()));
171         }
172
173         commandline.commandOutput(
174             template.tabular(
175                 ["Mark",   "HPos",              "VPos",              "File"],
176                 ["",       "text-align: right", "text-align: right", "color: green"],
177                 ([mark[0],
178                   Math.round(mark[1].position.x * 100) + "%",
179                   Math.round(mark[1].position.y * 100) + "%",
180                   mark[1].location]
181                   for ([, mark] in Iterator(marks)))));
182     },
183
184     _onPageLoad: function _onPageLoad(event) {
185         let win = event.originalTarget.defaultView;
186         for (let [i, mark] in Iterator(this._pendingJumps)) {
187             if (win && win.location.href == mark.location) {
188                 buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
189                 this._pendingJumps.splice(i, 1);
190                 return;
191             }
192         }
193     },
194 }, {
195     markToString: function markToString(name, mark) {
196         let tab = mark.tab && mark.tab.get();
197         return name + ", " + mark.location +
198                 ", (" + Math.round(mark.position.x * 100) +
199                 "%, " + Math.round(mark.position.y * 100) + "%)" +
200                 (tab ? ", tab: " + tabs.index(tab) : "");
201     },
202
203     isLocalMark: function isLocalMark(mark) /^[a-z`']$/.test(mark),
204
205     isURLMark: function isURLMark(mark) /^[A-Z]$/.test(mark)
206 }, {
207     events: function () {
208         let appContent = document.getElementById("appcontent");
209         if (appContent)
210             events.listen(appContent, "load", marks.closure._onPageLoad, true);
211     },
212     mappings: function () {
213         var myModes = config.browserModes;
214
215         mappings.add(myModes,
216             ["m"], "Set mark at the cursor position",
217             function ({ arg }) {
218                 dactyl.assert(/^[a-zA-Z]$/.test(arg), _("mark.invalid"));
219                 marks.add(arg);
220             },
221             { arg: true });
222
223         mappings.add(myModes,
224             ["'", "`"], "Jump to the mark in the current buffer",
225             function ({ arg }) { marks.jumpTo(arg); },
226             { arg: true });
227     },
228
229     commands: function () {
230         commands.add(["delm[arks]"],
231             "Delete the specified marks",
232             function (args) {
233                 let special = args.bang;
234                 let arg = args[0] || "";
235
236                 // assert(special ^ args)
237                 dactyl.assert( special ||  arg, _("error.argumentRequired"));
238                 dactyl.assert(!special || !arg, _("error.invalidArgument"));
239
240                 marks.remove(arg, special);
241             },
242             {
243                 bang: true,
244                 completer: function (context) completion.mark(context),
245                 literal: 0
246             });
247
248         commands.add(["ma[rk]"],
249             "Mark current location within the web page",
250             function (args) {
251                 let mark = args[0] || "";
252                 dactyl.assert(mark.length <= 1, _("error.trailingCharacters"));
253                 dactyl.assert(/[a-zA-Z]/.test(mark), _("mark.invalid"));
254
255                 marks.add(mark);
256             },
257             { argCount: "1" });
258
259         commands.add(["marks"],
260             "Show the specified marks",
261             function (args) {
262                 marks.list(args[0] || "");
263             }, {
264                 completer: function (context) completion.mark(context),
265                 literal: 0
266             });
267     },
268
269     completion: function () {
270         completion.mark = function mark(context) {
271             function percent(i) Math.round(i * 100);
272
273             context.title = ["Mark", "HPos VPos File"];
274             context.keys.description = function ([, m]) percent(m.position.x) + "% " + percent(m.position.y) + "% " + m.location;
275             context.completions = marks.all;
276         };
277     },
278     sanitizer: function () {
279         sanitizer.addItem("marks", {
280             description: "Local and URL marks",
281             persistent: true,
282             contains: ["history"],
283             action: function (timespan, host) {
284                 function matchhost(url) !host || util.isDomainURL(url, host);
285                 function match(marks) (k for ([k, v] in Iterator(marks)) if (timespan.contains(v.timestamp) && matchhost(v.location)));
286
287                 for (let [url, local] in marks._localMarks)
288                     if (matchhost(url)) {
289                         for (let key in match(local))
290                             delete local[key];
291                         if (!Object.keys(local).length)
292                             marks._localMarks.remove(url);
293                     }
294                 marks._localMarks.changed();
295
296                 for (let key in match(marks._urlMarks))
297                     marks._urlMarks.remove(key);
298             }
299         });
300     }
301 });
302
303 // vim: set fdm=marker sw=4 ts=4 et: