]> git.donarmstrong.com Git - dactyl.git/blob - common/content/history.js
Import 1.0rc1 supporting Firefox up to 11.*
[dactyl.git] / common / content / history.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 var History = Module("history", {
10     SORT_DEFAULT: "-date",
11
12     get format() bookmarks.format,
13
14     get service() services.history,
15
16     get: function get(filter, maxItems, sort) {
17         sort = sort || this.SORT_DEFAULT;
18
19         if (isString(filter))
20             filter = { searchTerms: filter };
21
22         // no query parameters will get all history
23         let query = services.history.getNewQuery();
24         let options = services.history.getNewQueryOptions();
25
26         for (let [k, v] in Iterator(filter))
27             query[k] = v;
28
29         let res = /^([+-])(.+)/.exec(sort);
30         dactyl.assert(res, _("error.invalidSort", sort));
31
32         let [, dir, field] = res;
33         let _sort = "SORT_BY_" + field.toUpperCase() + "_" +
34                     { "+": "ASCENDING", "-": "DESCENDING" }[dir];
35
36         dactyl.assert(_sort in options,
37                       _("error.invalidSort", sort));
38
39         options.sortingMode = options[_sort];
40         options.resultType = options.RESULTS_AS_URI;
41         if (maxItems > 0)
42             options.maxResults = maxItems;
43
44         let root = services.history.executeQuery(query, options).root;
45         root.containerOpen = true;
46         try {
47             var items = iter(util.range(0, root.childCount)).map(function (i) {
48                 let node = root.getChild(i);
49                 return {
50                     url: node.uri,
51                     title: node.title,
52                     icon: node.icon ? node.icon : BookmarkCache.DEFAULT_FAVICON
53                 };
54             }).toArray();
55         }
56         finally {
57             root.containerOpen = false;
58         }
59
60         return items;
61     },
62
63     get session() {
64         let webNav = window.getWebNavigation()
65         let sh = webNav.sessionHistory;
66
67         let obj = [];
68         obj.__defineGetter__("index", function () sh.index);
69         obj.__defineSetter__("index", function (val) { webNav.gotoIndex(val) });
70         obj.__iterator__ = function () array.iterItems(this);
71
72         for (let item in iter(sh.SHistoryEnumerator, Ci.nsIHistoryEntry))
73             obj.push(update(Object.create(item), {
74                 index: obj.length,
75                 icon: Class.Memoize(function () services.favicon.getFaviconImageForPage(this.URI).spec)
76             }));
77         return obj;
78     },
79
80     /**
81      * Step to the given offset in the history stack.
82      *
83      * @param {number} steps The possibly negative number of steps to
84      *      step.
85      * @param {boolean} jumps If true, take into account jumps in the
86      *      marks stack. @optional
87      */
88     stepTo: function stepTo(steps, jumps) {
89         if (dactyl.forceOpen.target == dactyl.NEW_TAB)
90             tabs.cloneTab(tabs.getTab(), true);
91
92         if (jumps)
93             steps -= marks.jump(steps);
94         if (steps == 0)
95             return;
96
97         let sh = this.session;
98         dactyl.assert(steps > 0 && sh.index < sh.length - 1 || steps < 0 && sh.index > 0);
99
100         try {
101             sh.index = Math.constrain(sh.index + steps, 0, sh.length - 1);
102         }
103         catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {}
104     },
105
106     /**
107      * Search for the *steps*th next *item* in the history list.
108      *
109      * @param {string} item The nebulously defined item to search for.
110      * @param {number} steps The number of steps to step.
111      */
112     search: function search(item, steps) {
113         var ctxt;
114         var filter = function (item) true;
115         if (item == "domain")
116             var filter = function (item) {
117                 let res = item.URI.hostPort != ctxt;
118                 ctxt = item.URI.hostPort;
119                 return res;
120             };
121
122         let sh = this.session;
123         let idx;
124         let sign = steps / Math.abs(steps);
125
126         filter(sh[sh.index]);
127         for (let i = sh.index + sign; steps && i >= 0 && i < sh.length; i += sign)
128             if (filter(sh[i])) {
129                 idx = i;
130                 steps -= sign;
131             }
132
133         dactyl.assert(idx != null);
134         sh.index = idx;
135     },
136
137     goToStart: function goToStart() {
138         let index = window.getWebNavigation().sessionHistory.index;
139
140         if (index > 0)
141             window.getWebNavigation().gotoIndex(0);
142         else
143             dactyl.beep();
144
145     },
146
147     goToEnd: function goToEnd() {
148         let sh = window.getWebNavigation().sessionHistory;
149         let max = sh.count - 1;
150
151         if (sh.index < max)
152             window.getWebNavigation().gotoIndex(max);
153         else
154             dactyl.beep();
155
156     },
157
158     // if openItems is true, open the matching history items in tabs rather than display
159     list: function list(filter, openItems, maxItems, sort) {
160         // FIXME: returning here doesn't make sense
161         //   Why the hell doesn't it make sense? --Kris
162         // See comment at bookmarks.list --djk
163         if (!openItems)
164             return completion.listCompleter("history", filter, maxItems, maxItems, sort);
165         let items = completion.runCompleter("history", filter, maxItems, maxItems, sort);
166
167         if (items.length)
168             return dactyl.open(items.map(function (i) i.url), dactyl.NEW_TAB);
169
170         if (filter.length > 0)
171             dactyl.echoerr(_("history.noMatching", filter.quote()));
172         else
173             dactyl.echoerr(_("history.none"));
174         return null;
175     }
176 }, {
177 }, {
178     commands: function () {
179         commands.add(["ba[ck]"],
180             "Go back in the browser history",
181             function (args) {
182                 let url = args[0];
183
184                 if (args.bang)
185                     history.goToStart();
186                 else {
187                     if (url) {
188                         let sh = history.session;
189                         if (/^\d+(:|$)/.test(url) && sh.index - parseInt(url) in sh)
190                             return void window.getWebNavigation().gotoIndex(sh.index - parseInt(url));
191
192                         for (let [i, ent] in Iterator(sh.slice(0, sh.index).reverse()))
193                             if (ent.URI.spec == url)
194                                 return void window.getWebNavigation().gotoIndex(i);
195                         dactyl.echoerr(_("history.noURL"));
196                     }
197                     else
198                         history.stepTo(-Math.max(args.count, 1));
199                 }
200                 return null;
201             },
202             {
203                 argCount: "?",
204                 bang: true,
205                 completer: function completer(context) {
206                     let sh = history.session;
207
208                     context.anchored = false;
209                     context.compare = CompletionContext.Sort.unsorted;
210                     context.filters = [CompletionContext.Filter.textDescription];
211                     context.completions = sh.slice(0, sh.index).reverse();
212                     context.keys = { text: function (item) (sh.index - item.index) + ": " + item.URI.spec, description: "title", icon: "icon" };
213                 },
214                 count: true,
215                 literal: 0,
216                 privateData: true
217             });
218
219         commands.add(["fo[rward]", "fw"],
220             "Go forward in the browser history",
221             function (args) {
222                 let url = args.literalArg;
223
224                 if (args.bang)
225                     history.goToEnd();
226                 else {
227                     if (url) {
228                         let sh = history.session;
229                         if (/^\d+(:|$)/.test(url) && sh.index + parseInt(url) in sh)
230                             return void window.getWebNavigation().gotoIndex(sh.index + parseInt(url));
231
232                         for (let [i, ent] in Iterator(sh.slice(sh.index + 1)))
233                             if (ent.URI.spec == url)
234                                 return void window.getWebNavigation().gotoIndex(i);
235                         dactyl.echoerr(_("history.noURL"));
236                     }
237                     else
238                         history.stepTo(Math.max(args.count, 1));
239                 }
240                 return null;
241             },
242             {
243                 argCount: "?",
244                 bang: true,
245                 completer: function completer(context) {
246                     let sh = history.session;
247
248                     context.anchored = false;
249                     context.compare = CompletionContext.Sort.unsorted;
250                     context.filters = [CompletionContext.Filter.textDescription];
251                     context.completions = sh.slice(sh.index + 1);
252                     context.keys = { text: function (item) (item.index - sh.index) + ": " + item.URI.spec, description: "title", icon: "icon" };
253                 },
254                 count: true,
255                 literal: 0,
256                 privateData: true
257             });
258
259         commands.add(["hist[ory]", "hs"],
260             "Show recently visited URLs",
261             function (args) { history.list(args.join(" "), args.bang, args["-max"], args["-sort"]); }, {
262                 bang: true,
263                 completer: function (context, args) completion.history(context, args["-max"], args["-sort"]),
264                 options: [
265                     {
266                         names: ["-max", "-m"],
267                         description: "The maximum number of items to list",
268                         default: 1000,
269                         type: CommandOption.INT
270                     },
271                     {
272                         names: ["-sort", "-s"],
273                         type: CommandOption.STRING,
274                         description: "The sort order of the results",
275                         completer: function (context, args) {
276                             context.compare = CompletionContext.Sort.unsorted;
277                             return array.flatten([
278                                 "annotation",
279                                 "date",
280                                 "date added",
281                                 "keyword",
282                                 "last modified",
283                                 "tags",
284                                 "title",
285                                 "uri",
286                                 "visitcount"
287                             ].map(function (order) [
288                                   ["+" + order.replace(" ", ""), /*L*/"Sort by " + order + " ascending"],
289                                   ["-" + order.replace(" ", ""), /*L*/"Sort by " + order + " descending"]
290                             ]));
291                         }
292                     }
293                 ],
294                 privateData: true
295             });
296
297         commands.add(["ju[mps]"],
298             "Show jumplist",
299             function () {
300                 let sh = history.session;
301                 let index = sh.index;
302
303                 let jumps = marks.jumps;
304                 if (jumps.index < 0)
305                     jumps = [sh[sh.index]];
306                 else {
307                     index += jumps.index;
308                     jumps = jumps.locations.map(function (l) ({
309                         __proto__: l,
310                         title: buffer.title,
311                         get URI() util.newURI(this.location)
312                     }));
313                 }
314
315                 let list = sh.slice(0, sh.index)
316                              .concat(jumps)
317                              .concat(sh.slice(sh.index + 1));
318
319                 commandline.commandOutput(template.jumps(index, list));
320             },
321             { argCount: "0" });
322
323     },
324     completion: function () {
325         completion.domain = function (context) {
326             context.anchored = false;
327             context.compare = function (a, b) String.localeCompare(a.key, b.key);
328             context.keys = { text: util.identity, description: util.identity,
329                 key: function (host) host.split(".").reverse().join(".") };
330
331             // FIXME: Schema-specific
332             context.generate = function () [
333                 Array.slice(row.rev_host).reverse().join("").slice(1)
334                 for (row in iter(services.history.DBConnection
335                                          .createStatement("SELECT DISTINCT rev_host FROM moz_places WHERE rev_host IS NOT NULL;")))
336             ].slice(2);
337         };
338
339         completion.history = function _history(context, maxItems, sort) {
340             context.format = history.format;
341             context.title = ["History"];
342             context.compare = CompletionContext.Sort.unsorted;
343             //context.background = true;
344             if (maxItems == null)
345                 context.maxItems = maxItems;
346             if (maxItems && context.maxItems == null)
347                 context.maxItems = 100;
348             context.regenerate = true;
349             context.generate = function () history.get(context.filter, this.maxItems, sort);
350         };
351
352         completion.addUrlCompleter("history", "History", completion.history);
353     },
354     mappings: function () {
355         function bind() mappings.add.apply(mappings, [config.browserModes].concat(Array.slice(arguments)));
356
357         bind(["<C-o>"], "Go to an older position in the jump list",
358              function ({ count }) { history.stepTo(-Math.max(count, 1), true); },
359              { count: true });
360
361         bind(["<C-i>"], "Go to a newer position in the jump list",
362              function ({ count }) { history.stepTo(Math.max(count, 1), true); },
363              { count: true });
364
365         bind(["H", "<A-Left>", "<M-Left>"], "Go back in the browser history",
366              function ({ count }) { history.stepTo(-Math.max(count, 1)); },
367              { count: true });
368
369         bind(["L", "<A-Right>", "<M-Right>"], "Go forward in the browser history",
370              function ({ count }) { history.stepTo(Math.max(count, 1)); },
371              { count: true });
372
373         bind(["[d"], "Go back to the previous domain in the browser history",
374              function ({ count }) { history.search("domain", -Math.max(count, 1)) },
375              { count: true });
376
377         bind(["]d"], "Go forward to the next domain in the browser history",
378              function ({ count }) { history.search("domain", Math.max(count, 1)) },
379              { count: true });
380     }
381 });
382
383 // vim: set fdm=marker sw=4 ts=4 et: