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>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
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 });
19 if (isArray(Iterator(this._localMarks).next()[1]))
20 this._localMarks.clear();
24 this._pendingJumps = [];
28 * @property {Array} Returns all marks, both local and URL, in a sorted
31 get all() iter(this._localMarks.get(this.localURI) || {},
33 ).sort(function (a, b) String.localeCompare(a[0], b[0])),
35 get localURI() buffer.focusedFrame.document.documentURI,
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.
44 * @param {string} mark The mark name.
45 * @param {boolean} silent Whether to output error messages.
47 add: function (mark, silent) {
48 let win = buffer.focusedFrame;
49 let doc = win.document;
51 let position = { x: buffer.scrollXPercent / 100, y: buffer.scrollYPercent / 100 };
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 });
56 dactyl.log(_("mark.addURL", Marks.markToString(mark, res)), 5);
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();
63 dactyl.log(_("mark.addLocal", Marks.markToString(mark, marks[mark])), 5);
68 * Remove all marks matching *filter*. If *special* is given, removes all
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.
74 remove: function (filter, special) {
76 this._localMarks.remove(this.localURI);
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);
87 Iterator(local).next();
88 this._localMarks.changed();
91 this._localMarks.remove(this.localURI);
97 * Jumps to the named mark. See {@link #add}
99 * @param {string} char The mark to jump to.
101 jumpTo: function (char) {
102 if (Marks.isURLMark(char)) {
103 let mark = this._urlMarks.get(char);
104 dactyl.assert(mark, _("mark.unset", char));
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)
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);
122 this._pendingJumps.push(mark);
124 let sh = tab.linkedBrowser.sessionHistory;
125 let items = array(util.range(0, sh.count));
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();
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);
137 dactyl.open(mark.location);
141 this._pendingJumps.push(mark);
142 dactyl.open(mark.location, dactyl.NEW_TAB);
145 else if (Marks.isLocalMark(char)) {
146 let mark = (this._localMarks.get(this.localURI) || {})[char];
147 dactyl.assert(mark, _("mark.unset", char));
149 dactyl.log(_("mark.jumpingToLocal", Marks.markToString(char, mark)), 5);
150 buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
153 dactyl.echoerr(_("mark.invalid"));
158 * List all marks matching *filter*.
160 * @param {string} filter List of marks to show, e.g. "ab A-I".
162 list: function (filter) {
163 let marks = this.all;
165 dactyl.assert(marks.length > 0, _("mark.none"));
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()));
173 commandline.commandOutput(
175 ["Mark", "HPos", "VPos", "File"],
176 ["", "text-align: right", "text-align: right", "color: green"],
178 Math.round(mark[1].position.x * 100) + "%",
179 Math.round(mark[1].position.y * 100) + "%",
181 for ([, mark] in Iterator(marks)))));
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);
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) : "");
203 isLocalMark: function isLocalMark(mark) /^[a-z`']$/.test(mark),
205 isURLMark: function isURLMark(mark) /^[A-Z]$/.test(mark)
207 events: function () {
208 let appContent = document.getElementById("appcontent");
210 events.listen(appContent, "load", marks.closure._onPageLoad, true);
212 mappings: function () {
213 var myModes = config.browserModes;
215 mappings.add(myModes,
216 ["m"], "Set mark at the cursor position",
218 dactyl.assert(/^[a-zA-Z]$/.test(arg), _("mark.invalid"));
223 mappings.add(myModes,
224 ["'", "`"], "Jump to the mark in the current buffer",
225 function ({ arg }) { marks.jumpTo(arg); },
229 commands: function () {
230 commands.add(["delm[arks]"],
231 "Delete the specified marks",
233 let special = args.bang;
234 let arg = args[0] || "";
236 // assert(special ^ args)
237 dactyl.assert( special || arg, _("error.argumentRequired"));
238 dactyl.assert(!special || !arg, _("error.invalidArgument"));
240 marks.remove(arg, special);
244 completer: function (context) completion.mark(context),
248 commands.add(["ma[rk]"],
249 "Mark current location within the web page",
251 let mark = args[0] || "";
252 dactyl.assert(mark.length <= 1, _("error.trailingCharacters"));
253 dactyl.assert(/[a-zA-Z]/.test(mark), _("mark.invalid"));
259 commands.add(["marks"],
260 "Show the specified marks",
262 marks.list(args[0] || "");
264 completer: function (context) completion.mark(context),
269 completion: function () {
270 completion.mark = function mark(context) {
271 function percent(i) Math.round(i * 100);
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;
278 sanitizer: function () {
279 sanitizer.addItem("marks", {
280 description: "Local and URL marks",
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)));
287 for (let [url, local] in marks._localMarks)
288 if (matchhost(url)) {
289 for (let key in match(local))
291 if (!Object.keys(local).length)
292 marks._localMarks.remove(url);
294 marks._localMarks.changed();
296 for (let key in match(marks._urlMarks))
297 marks._urlMarks.remove(key);
303 // vim: set fdm=marker sw=4 ts=4 et: