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>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
9 var History = Module("history", {
10 SORT_DEFAULT: "-date",
12 get format() bookmarks.format,
14 get service() services.history,
16 get: function get(filter, maxItems, sort = this.SORT_DEFAULT) {
18 filter = { searchTerms: filter };
20 // no query parameters will get all history
21 let query = services.history.getNewQuery();
22 let options = services.history.getNewQueryOptions();
24 for (let [k, v] in Iterator(filter))
27 let res = /^([+-])(.+)/.exec(sort);
28 dactyl.assert(res, _("error.invalidSort", sort));
30 let [, dir, field] = res;
31 let _sort = "SORT_BY_" + field.toUpperCase() + "_" +
32 { "+": "ASCENDING", "-": "DESCENDING" }[dir];
34 dactyl.assert(_sort in options,
35 _("error.invalidSort", sort));
37 options.sortingMode = options[_sort];
38 options.resultType = options.RESULTS_AS_URI;
40 options.maxResults = maxItems;
42 let root = services.history.executeQuery(query, options).root;
43 root.containerOpen = true;
45 var items = iter(util.range(0, root.childCount)).map(function (i) {
46 let node = root.getChild(i);
50 icon: node.icon ? node.icon : BookmarkCache.DEFAULT_FAVICON
55 root.containerOpen = false;
62 let webNav = window.getWebNavigation();
63 let sh = webNav.sessionHistory;
66 obj.__defineGetter__("index", () => sh.index);
67 obj.__defineSetter__("index", function (val) { webNav.gotoIndex(val); });
68 obj.__iterator__ = function () array.iterItems(this);
70 for (let item in iter(sh.SHistoryEnumerator, Ci.nsISHEntry))
71 obj.push(update(Object.create(item), {
73 icon: Class.Memoize(function () services.favicon.getFaviconImageForPage(this.URI).spec)
79 * Step to the given offset in the history stack.
81 * @param {number} steps The possibly negative number of steps to
83 * @param {boolean} jumps If true, take into account jumps in the
84 * marks stack. @optional
86 stepTo: function stepTo(steps, jumps) {
87 if (dactyl.forceOpen.target == dactyl.NEW_TAB)
88 tabs.cloneTab(tabs.getTab(), true);
91 steps -= marks.jump(steps);
95 let sh = this.session;
96 dactyl.assert(steps > 0 && sh.index < sh.length - 1 || steps < 0 && sh.index > 0);
99 sh.index = Math.constrain(sh.index + steps, 0, sh.length - 1);
101 catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {}
105 * Search for the *steps*th next *item* in the history list.
107 * @param {string} item The nebulously defined item to search for.
108 * @param {number} steps The number of steps to step.
110 search: function search(item, steps) {
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;
120 let sh = this.session;
122 let sign = steps / Math.abs(steps);
124 filter(sh[sh.index]);
125 for (let i = sh.index + sign; steps && i >= 0 && i < sh.length; i += sign)
131 dactyl.assert(idx != null);
135 goToStart: function goToStart() {
136 let index = window.getWebNavigation().sessionHistory.index;
139 window.getWebNavigation().gotoIndex(0);
145 goToEnd: function goToEnd() {
146 let sh = window.getWebNavigation().sessionHistory;
147 let max = sh.count - 1;
150 window.getWebNavigation().gotoIndex(max);
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
162 return completion.listCompleter("history", filter, maxItems, maxItems, sort);
163 let items = completion.runCompleter("history", filter, maxItems, maxItems, sort);
166 return dactyl.open(items.map(i => i.url), dactyl.NEW_TAB);
168 if (filter.length > 0)
169 dactyl.echoerr(_("history.noMatching", filter.quote()));
171 dactyl.echoerr(_("history.none"));
176 commands: function initCommands() {
177 commands.add(["ba[ck]"],
178 "Go back in the browser history",
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));
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"));
196 history.stepTo(-Math.max(args.count, 1));
203 completer: function completer(context) {
204 let sh = history.session;
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",
219 commands.add(["fo[rward]", "fw"],
220 "Go forward in the browser history",
222 let url = args.literalArg;
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));
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"));
238 history.stepTo(Math.max(args.count, 1));
245 completer: function completer(context) {
246 let sh = history.session;
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",
261 commands.add(["hist[ory]", "hs"],
262 "Show recently visited URLs",
263 function (args) { history.list(args.join(" "), args.bang, args["-max"], args["-sort"]); }, {
265 completer: function (context, args) completion.history(context, args["-max"], args["-sort"]),
268 names: ["-max", "-m"],
269 description: "The maximum number of items to list",
271 type: CommandOption.INT
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([
290 ["+" + order.replace(" ", ""), /*L*/"Sort by " + order + " ascending"],
291 ["-" + order.replace(" ", ""), /*L*/"Sort by " + order + " descending"]
299 commands.add(["ju[mps]"],
302 let sh = history.session;
303 let index = sh.index;
305 let jumps = marks.jumps;
307 jumps = [sh[sh.index]];
309 index += jumps.index;
310 jumps = jumps.locations.map(l => ({
313 get URI() util.newURI(this.location)
317 let list = sh.slice(0, sh.index)
319 .concat(sh.slice(sh.index + 1));
321 commandline.commandOutput(template.jumps(index, list));
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(".") };
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;")))
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);
354 completion.addUrlCompleter("history", "History", completion.history);
356 mappings: function initMappings() {
357 function bind(...args) mappings.add.apply(mappings, [config.browserModes].concat(args));
359 bind(["<C-o>"], "Go to an older position in the jump list",
360 function ({ count }) { history.stepTo(-Math.max(count, 1), true); },
363 bind(["<C-i>"], "Go to a newer position in the jump list",
364 function ({ count }) { history.stepTo(Math.max(count, 1), true); },
367 bind(["H", "<A-Left>", "<M-Left>"], "Go back in the browser history",
368 function ({ count }) { history.stepTo(-Math.max(count, 1)); },
371 bind(["L", "<A-Right>", "<M-Right>"], "Go forward in the browser history",
372 function ({ count }) { history.stepTo(Math.max(count, 1)); },
375 bind(["[d"], "Go back to the previous domain in the browser history",
376 function ({ count }) { history.search("domain", -Math.max(count, 1)); },
379 bind(["]d"], "Go forward to the next domain in the browser history",
380 function ({ count }) { history.search("domain", Math.max(count, 1)); },
385 // vim: set fdm=marker sw=4 sts=4 ts=8 et: