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