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.
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) {
17 sort = sort || this.SORT_DEFAULT;
20 filter = { searchTerms: filter };
22 // no query parameters will get all history
23 let query = services.history.getNewQuery();
24 let options = services.history.getNewQueryOptions();
26 for (let [k, v] in Iterator(filter))
29 let res = /^([+-])(.+)/.exec(sort);
30 dactyl.assert(res, _("error.invalidSort", sort));
32 let [, dir, field] = res;
33 let _sort = "SORT_BY_" + field.toUpperCase() + "_" +
34 { "+": "ASCENDING", "-": "DESCENDING" }[dir];
36 dactyl.assert(_sort in options,
37 _("error.invalidSort", sort));
39 options.sortingMode = options[_sort];
40 options.resultType = options.RESULTS_AS_URI;
42 options.maxResults = maxItems;
44 let root = services.history.executeQuery(query, options).root;
45 root.containerOpen = true;
47 var items = iter(util.range(0, root.childCount)).map(function (i) {
48 let node = root.getChild(i);
52 icon: node.icon ? node.icon : BookmarkCache.DEFAULT_FAVICON
57 root.containerOpen = false;
64 let webNav = window.getWebNavigation()
65 let sh = webNav.sessionHistory;
68 obj.__defineGetter__("index", function () sh.index);
69 obj.__defineSetter__("index", function (val) { webNav.gotoIndex(val) });
70 obj.__iterator__ = function () array.iterItems(this);
72 for (let item in iter(sh.SHistoryEnumerator, Ci.nsIHistoryEntry))
73 obj.push(update(Object.create(item), {
75 icon: Class.Memoize(function () services.favicon.getFaviconImageForPage(this.URI).spec)
81 * Step to the given offset in the history stack.
83 * @param {number} steps The possibly negative number of steps to
85 * @param {boolean} jumps If true, take into account jumps in the
86 * marks stack. @optional
88 stepTo: function stepTo(steps, jumps) {
89 if (dactyl.forceOpen.target == dactyl.NEW_TAB)
90 tabs.cloneTab(tabs.getTab(), true);
93 steps -= marks.jump(steps);
97 let sh = this.session;
98 dactyl.assert(steps > 0 && sh.index < sh.length - 1 || steps < 0 && sh.index > 0);
101 sh.index = Math.constrain(sh.index + steps, 0, sh.length - 1);
103 catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) {}
107 * Search for the *steps*th next *item* in the history list.
109 * @param {string} item The nebulously defined item to search for.
110 * @param {number} steps The number of steps to step.
112 search: function search(item, steps) {
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;
122 let sh = this.session;
124 let sign = steps / Math.abs(steps);
126 filter(sh[sh.index]);
127 for (let i = sh.index + sign; steps && i >= 0 && i < sh.length; i += sign)
133 dactyl.assert(idx != null);
137 goToStart: function goToStart() {
138 let index = window.getWebNavigation().sessionHistory.index;
141 window.getWebNavigation().gotoIndex(0);
147 goToEnd: function goToEnd() {
148 let sh = window.getWebNavigation().sessionHistory;
149 let max = sh.count - 1;
152 window.getWebNavigation().gotoIndex(max);
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
164 return completion.listCompleter("history", filter, maxItems, maxItems, sort);
165 let items = completion.runCompleter("history", filter, maxItems, maxItems, sort);
168 return dactyl.open(items.map(function (i) i.url), dactyl.NEW_TAB);
170 if (filter.length > 0)
171 dactyl.echoerr(_("history.noMatching", filter.quote()));
173 dactyl.echoerr(_("history.none"));
178 commands: function () {
179 commands.add(["ba[ck]"],
180 "Go back in the browser history",
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));
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"));
198 history.stepTo(-Math.max(args.count, 1));
205 completer: function completer(context) {
206 let sh = history.session;
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" };
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, description: "title", icon: "icon" };
259 commands.add(["hist[ory]", "hs"],
260 "Show recently visited URLs",
261 function (args) { history.list(args.join(" "), args.bang, args["-max"], args["-sort"]); }, {
263 completer: function (context, args) completion.history(context, args["-max"], args["-sort"]),
266 names: ["-max", "-m"],
267 description: "The maximum number of items to list",
269 type: CommandOption.INT
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([
287 ].map(function (order) [
288 ["+" + order.replace(" ", ""), /*L*/"Sort by " + order + " ascending"],
289 ["-" + order.replace(" ", ""), /*L*/"Sort by " + order + " descending"]
297 commands.add(["ju[mps]"],
300 let sh = history.session;
301 let index = sh.index;
303 let jumps = marks.jumps;
305 jumps = [sh[sh.index]];
307 index += jumps.index;
308 jumps = jumps.locations.map(function (l) ({
311 get URI() util.newURI(this.location)
315 let list = sh.slice(0, sh.index)
317 .concat(sh.slice(sh.index + 1));
319 commandline.commandOutput(template.jumps(index, list));
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(".") };
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;")))
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);
352 completion.addUrlCompleter("history", "History", completion.history);
354 mappings: function () {
355 function bind() mappings.add.apply(mappings, [config.browserModes].concat(Array.slice(arguments)));
357 bind(["<C-o>"], "Go to an older position in the jump list",
358 function ({ count }) { history.stepTo(-Math.max(count, 1), true); },
361 bind(["<C-i>"], "Go to a newer position in the jump list",
362 function ({ count }) { history.stepTo(Math.max(count, 1), true); },
365 bind(["H", "<A-Left>", "<M-Left>"], "Go back in the browser history",
366 function ({ count }) { history.stepTo(-Math.max(count, 1)); },
369 bind(["L", "<A-Right>", "<M-Right>"], "Go forward in the browser history",
370 function ({ count }) { history.stepTo(Math.max(count, 1)); },
373 bind(["[d"], "Go back to the previous domain in the browser history",
374 function ({ count }) { history.search("domain", -Math.max(count, 1)) },
377 bind(["]d"], "Go forward to the next domain in the browser history",
378 function ({ count }) { history.search("domain", Math.max(count, 1)) },
383 // vim: set fdm=marker sw=4 ts=4 et: