]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/bookmarks.js
Imported Upstream version 1.1+hg7904
[dactyl.git] / common / content / bookmarks.js
index ade210df2b7cac3033ced3a768d1ab1516a9ef21..dd64e545cc8e0a763f5c3f5079f4b77d6692a1f5 100644 (file)
@@ -1,33 +1,42 @@
 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2014 Kris Maglione <maglione.k@gmail.com>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
 "use strict";
 
-var DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
-
 // also includes methods for dealing with keywords and search engines
 var Bookmarks = Module("bookmarks", {
     init: function () {
+        this.timer = Timer(0, 100, function () {
+            this.checkBookmarked(buffer.uri);
+        }, this);
+
         storage.addObserver("bookmark-cache", function (key, event, arg) {
             if (["add", "change", "remove"].indexOf(event) >= 0)
-                autocommands.trigger("Bookmark" + event[0].toUpperCase() + event.substr(1),
+                autocommands.trigger("Bookmark" + util.capitalize(event),
                      iter({
                          bookmark: {
                              toString: function () "bookmarkcache.bookmarks[" + arg.id + "]",
                              valueOf: function () arg
                          }
-                     }, arg));
-            statusline.updateStatus();
+                     }, arg).toObject());
+            bookmarks.timer.tell();
         }, window);
     },
 
+    signals: {
+        "browser.locationChange": function (webProgress, request, uri) {
+            statusline.bookmarked = false;
+            this.checkBookmarked(uri);
+        }
+    },
+
     get format() ({
         anchored: false,
         title: ["URL", "Info"],
-        keys: { text: "url", description: "title", icon: "icon", extra: "extra", tags: "tags" },
+        keys: { text: "url", description: "title", icon: "icon", extra: "extra", tags: "tags", isURI: function () true },
         process: [template.icon, template.bookmarkDescription]
     }),
 
@@ -52,49 +61,54 @@ var Bookmarks = Module("bookmarks", {
      *      Otherwise, if a bookmark for the given URL exists it is
      *      updated instead.
      *      @optional
-     * @returns {boolean} True if the bookmark was added or updated
-     *      successfully.
+     * @returns {boolean} True if the bookmark was updated, false if a
+     *      new bookmark was added.
      */
     add: function add(unfiled, title, url, keyword, tags, force) {
         // FIXME
         if (isObject(unfiled))
-            var { unfiled, title, url, keyword, tags, post, charset, force } = unfiled;
-
-        try {
-            let uri = util.createURI(url);
-            if (!force && bookmarkcache.isBookmarked(uri))
-                for (var bmark in bookmarkcache)
-                    if (bmark.url == uri.spec) {
-                        if (title)
-                            bmark.title = title;
+            var { id, unfiled, title, url, keyword, tags, post, charset, force } = unfiled;
+
+        let uri = util.createURI(url);
+        if (id != null)
+            var bmark = bookmarkcache.bookmarks[id];
+        else if (!force) {
+            if (keyword && hasOwnProperty(bookmarkcache.keywords, keyword))
+                bmark = bookmarkcache.keywords[keyword];
+            else if (bookmarkcache.isBookmarked(uri))
+                for (bmark in bookmarkcache)
+                    if (bmark.url == uri.spec)
                         break;
-                    }
+        }
 
-            if (tags) {
-                PlacesUtils.tagging.untagURI(uri, null);
-                PlacesUtils.tagging.tagURI(uri, tags);
-            }
-            if (bmark == undefined)
-                bmark = bookmarkcache.bookmarks[
-                    services.bookmarks.insertBookmark(
-                         services.bookmarks[unfiled ? "unfiledBookmarksFolder" : "bookmarksMenuFolder"],
-                         uri, -1, title || url)];
-            if (!bmark)
-                return false;
-
-            if (charset !== undefined)
-                bmark.charset = charset;
-            if (post !== undefined)
-                bmark.post = post;
-            if (keyword)
-                bmark.keyword = keyword;
+        if (tags) {
+            PlacesUtils.tagging.untagURI(uri, null);
+            PlacesUtils.tagging.tagURI(uri, tags);
         }
-        catch (e) {
-            util.reportError(e);
-            return false;
+
+        let updated = !!bmark;
+        if (bmark == undefined)
+            bmark = bookmarkcache.bookmarks[
+                services.bookmarks.insertBookmark(
+                     services.bookmarks[unfiled ? "unfiledBookmarksFolder" : "bookmarksMenuFolder"],
+                     uri, -1, title || url)];
+        else {
+            if (title)
+                bmark.title = title;
+            if (!uri.equals(bmark.uri))
+                bmark.uri = uri;
         }
 
-        return true;
+        util.assert(bmark);
+
+        if (charset !== undefined)
+            bmark.charset = charset;
+        if (post !== undefined)
+            bmark.post = post;
+        if (keyword)
+            bmark.keyword = keyword;
+
+        return updated;
     },
 
     /**
@@ -103,15 +117,15 @@ var Bookmarks = Module("bookmarks", {
      *
      * @param {Element} elem A form element for which to add a keyword.
      */
-    addSearchKeyword: function (elem) {
-        if (elem instanceof HTMLFormElement || elem.form)
-            var [url, post, charset] = util.parseForm(elem);
+    addSearchKeyword: function addSearchKeyword(elem) {
+        if (elem instanceof Ci.nsIDOMHTMLFormElement || elem.form)
+            var { url, postData, charset } = DOM(elem).formData;
         else
-            var [url, post, charset] = [elem.href || elem.src, null, elem.ownerDocument.characterSet];
+            var [url, postData, charset] = [elem.href || elem.src, null, elem.ownerDocument.characterSet];
 
         let options = { "-title": "Search " + elem.ownerDocument.title };
-        if (post != null)
-            options["-post"] = post;
+        if (postData != null)
+            options["-post"] = postData;
         if (charset != null && charset !== "UTF-8")
             options["-charset"] = charset;
 
@@ -119,6 +133,17 @@ var Bookmarks = Module("bookmarks", {
             commands.commandToString({ command: "bmark", options: options, arguments: [url] }) + " -keyword ");
     },
 
+    checkBookmarked: function checkBookmarked(uri) {
+        if (PlacesUtils.asyncGetBookmarkIds)
+            PlacesUtils.asyncGetBookmarkIds(uri, function withBookmarkIDs(ids) {
+                statusline.bookmarked = ids.length;
+            });
+        else
+            this.timeout(function () {
+                statusline.bookmarked = bookmarkcache.isBookmarked(uri);
+            });
+    },
+
     /**
      * Toggles the bookmarked state of the given URL. If the URL is
      * bookmarked, all bookmarks for said URL are removed.
@@ -141,12 +166,13 @@ var Bookmarks = Module("bookmarks", {
             let extra = "";
             if (title != url)
                 extra = " (" + title + ")";
+
             this.add({ unfiled: true, title: title, url: url });
             dactyl.echomsg({ domains: [util.getHost(url)], message: _("bookmark.added", url + extra) });
         }
     },
 
-    isBookmarked: deprecated("bookmarkcache.isBookmarked", { get: function isBookmarked() bookmarkcache.closure.isBookmarked }),
+    isBookmarked: deprecated("bookmarkcache.isBookmarked", { get: function isBookmarked() bookmarkcache.bound.isBookmarked }),
 
     /**
      * Remove a bookmark or bookmarks. If *ids* is an array, removes the
@@ -163,7 +189,7 @@ var Bookmarks = Module("bookmarks", {
                 let uri = util.newURI(ids);
                 ids = services.bookmarks
                               .getBookmarkIdsForURI(uri, {})
-                              .filter(bookmarkcache.closure.isRegularBookmark);
+                              .filter(bookmarkcache.bound.isRegularBookmark);
             }
             ids.forEach(function (id) {
                 let bmark = bookmarkcache.bookmarks[id];
@@ -197,7 +223,7 @@ var Bookmarks = Module("bookmarks", {
             if (!alias)
                 alias = "search"; // for search engines which we can't find a suitable alias
 
-            if (set.has(aliases, alias))
+            if (hasOwnProperty(aliases, alias))
                 alias += ++aliases[alias];
             else
                 aliases[alias] = 0;
@@ -206,6 +232,31 @@ var Bookmarks = Module("bookmarks", {
         }).toObject();
     },
 
+    /**
+     * Returns true if the given search engine provides suggestions.
+     * engine based on the given *query*. The results are always in the
+     * form of an array of strings. If *callback* is provided, the
+     * request is executed asynchronously and *callback* is called on
+     * completion. Otherwise, the request is executed synchronously and
+     * the results are returned.
+     *
+     * @param {string} engineName The name of the search engine from
+     *      which to request suggestions.
+     * @returns {boolean}
+     */
+    hasSuggestions: function hasSuggestions(engineName, query, callback) {
+        const responseType = "application/x-suggestions+json";
+
+        if (hasOwnProperty(this.suggestionProviders, engineName))
+            return true;
+
+        let engine = hasOwnProperty(this.searchEngines, engineName) && this.searchEngines[engineName];
+        if (engine && engine.supportsResponseType(responseType))
+            return true;
+
+        return false;
+    },
+
     /**
      * Retrieves a list of search suggestions from the named search
      * engine based on the given *query*. The results are always in the
@@ -225,29 +276,50 @@ var Bookmarks = Module("bookmarks", {
     getSuggestions: function getSuggestions(engineName, query, callback) {
         const responseType = "application/x-suggestions+json";
 
-        let engine = set.has(this.searchEngines, engineName) && this.searchEngines[engineName];
+        if (hasOwnProperty(this.suggestionProviders, engineName))
+            return this.suggestionProviders[engineName](query, callback);
+
+        let engine = hasOwnProperty(this.searchEngines, engineName) && this.searchEngines[engineName];
         if (engine && engine.supportsResponseType(responseType))
             var queryURI = engine.getSubmission(query, responseType).uri.spec;
+
         if (!queryURI)
-            return (callback || util.identity)([]);
+            return promises.fail();
+
+        function parse(req) JSON.parse(req.responseText)[1].filter(isString);
+        return this.makeSuggestions(queryURI, parse, callback);
+    },
 
-        function process(req) {
+    /**
+     * Given a query URL, response parser, and optionally a callback,
+     * fetch and parse search query results for {@link getSuggestions}.
+     *
+     * @param {string} url The URL to fetch.
+     * @param {function(XMLHttpRequest):[string]} parser The function which
+     *      parses the response.
+     * @returns {Promise<Array>}
+     */
+    makeSuggestions: function makeSuggestions(url, parser) {
+        let deferred = Promise.defer();
+
+        let req = util.fetchUrl(url);
+        req.then(function process(req) {
             let results = [];
             try {
-                results = JSON.parse(req.responseText)[1].filter(isString);
+                results = parser(req);
             }
-            catch (e) {}
-            if (callback)
-                return callback(results);
-            return results;
-        }
+            catch (e) {
+                return deferred.reject(e);
+            }
+            deferred.resolve(results);
+        }, Cu.reportError);
 
-        let req = util.httpGet(queryURI, callback && process);
-        if (callback)
-            return req;
-        return process(req);
+        promises.oncancel(deferred, r => promises.cancel(req, reason));
+        return deferred.promise;
     },
 
+    suggestionProviders: {},
+
     /**
      * Returns an array containing a search URL and POST data for the
      * given search string. If *useDefsearch* is true, the string is
@@ -274,7 +346,7 @@ var Bookmarks = Module("bookmarks", {
             param = query.substr(offset + 1);
         }
 
-        var engine = set.has(bookmarks.searchEngines, keyword) && bookmarks.searchEngines[keyword];
+        var engine = hasOwnProperty(bookmarks.searchEngines, keyword) && bookmarks.searchEngines[keyword];
         if (engine) {
             if (engine.searchForm && !param)
                 return engine.searchForm;
@@ -299,11 +371,12 @@ var Bookmarks = Module("bookmarks", {
                 catch (e) {}
 
             if (charset)
-                var encodedParam = escape(window.convertFromUnicode(charset, param));
+                var encodedParam = escape(window.convertFromUnicode(charset, param)).replace(/\+/g, encodeURIComponent);
             else
-                encodedParam = bookmarkcache.keywords[keyword].encodeURIComponent(param);
+                encodedParam = bookmarkcache.keywords[keyword.toLowerCase()].encodeURIComponent(param);
 
-            url = url.replace(/%s/g, encodedParam).replace(/%S/g, param);
+            url = url.replace(/%s/g, () => encodedParam)
+                     .replace(/%S/g, () => param);
             if (/%s/i.test(data))
                 postData = window.getPostDataStream(data, param, encodedParam, "application/x-www-form-urlencoded");
         }
@@ -341,7 +414,7 @@ var Bookmarks = Module("bookmarks", {
         let items = completion.runCompleter("bookmark", filter, maxItems, tags, extra);
 
         if (items.length)
-            return dactyl.open(items.map(function (i) i.url), dactyl.NEW_TAB);
+            return dactyl.open(items.map(i => i.url), dactyl.NEW_TAB);
 
         if (filter.length > 0 && tags.length > 0)
             dactyl.echoerr(_("bookmark.noMatching", tags.map(String.quote), filter.quote()));
@@ -355,21 +428,16 @@ var Bookmarks = Module("bookmarks", {
     }
 }, {
 }, {
-    commands: function () {
-        commands.add(["ju[mps]"],
-            "Show jumplist",
-            function () {
-                let sh = history.session;
-                commandline.commandOutput(template.jumps(sh.index, sh));
-            },
-            { argCount: "0" });
-
+    commands: function initCommands() {
         // TODO: Clean this up.
         const tags = {
             names: ["-tags", "-T"],
             description: "A comma-separated list of tags",
             completer: function tags(context, args) {
-                context.generate = function () array(b.tags for (b in bookmarkcache) if (b.tags)).flatten().uniq().array;
+                context.generate = function () array(b.tags
+                                                     for (b in bookmarkcache)
+                                                     if (b.tags))
+                                                  .flatten().uniq().array;
                 context.keys = { text: util.identity, description: util.identity };
             },
             type: CommandOption.LIST
@@ -381,8 +449,8 @@ var Bookmarks = Module("bookmarks", {
             completer: function title(context, args) {
                 let frames = buffer.allFrames();
                 if (!args.bang)
-                    return  [
-                        [win.document.title, frames.length == 1 ? "Current Location" : "Frame: " + win.location.href]
+                    return [
+                        [win.document.title, frames.length == 1 ? /*L*/"Current Location" : /*L*/"Frame: " + win.location.href]
                         for ([, win] in Iterator(frames))];
                 context.keys.text = "title";
                 context.keys.description = "url";
@@ -410,15 +478,19 @@ var Bookmarks = Module("bookmarks", {
                 return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
             },
             type: CommandOption.STRING,
-            validator: function (arg) /^\S+$/.test(arg)
+            validator: bind("test", /^\S+$/)
         };
 
         commands.add(["bma[rk]"],
             "Add a bookmark",
             function (args) {
+                dactyl.assert(!args.bang || args["-id"] == null,
+                              _("bookmark.bangOrID"));
+
                 let opts = {
                     force: args.bang,
                     unfiled: false,
+                    id: args["-id"],
                     keyword: args["-keyword"] || null,
                     charset: args["-charset"],
                     post: args["-post"],
@@ -427,13 +499,13 @@ var Bookmarks = Module("bookmarks", {
                     url: args.length === 0 ? buffer.uri.spec : args[0]
                 };
 
-                if (bookmarks.add(opts)) {
-                    let extra = (opts.title == opts.url) ? "" : " (" + opts.title + ")";
-                    dactyl.echomsg({ domains: [util.getHost(opts.url)], message: _("bookmark.added", opts.url + extra) },
-                                   1, commandline.FORCE_SINGLELINE);
-                }
-                else
-                    dactyl.echoerr(_("bookmark.cantAdd", opts.title.quote()));
+                let updated = bookmarks.add(opts);
+                let action  = updated ? "updated" : "added";
+
+                let extra   = (opts.title && opts.title != opts.url) ? " (" + opts.title + ")" : "";
+
+                dactyl.echomsg({ domains: [util.getHost(opts.url)], message: _("bookmark." + action, opts.url + extra) },
+                               1, commandline.FORCE_SINGLELINE);
             }, {
                 argCount: "?",
                 bang: true,
@@ -442,7 +514,7 @@ var Bookmarks = Module("bookmarks", {
                         context.title = ["Page URL"];
                         let frames = buffer.allFrames();
                         context.completions = [
-                            [win.document.documentURI, frames.length == 1 ? "Current Location" : "Frame: " + win.document.title]
+                            [win.document.documentURI, frames.length == 1 ? /*L*/"Current Location" : /*L*/"Frame: " + win.document.title]
                             for ([, win] in Iterator(frames))];
                         return;
                     }
@@ -455,6 +527,11 @@ var Bookmarks = Module("bookmarks", {
                         type: CommandOption.STRING,
                         completer: function (context) completion.charset(context),
                         validator: Option.validateCompleter
+                    },
+                    {
+                        names: ["-id"],
+                        description: "The ID of the bookmark to update",
+                        type: CommandOption.INT
                     }
                 ]
             });
@@ -485,11 +562,11 @@ var Bookmarks = Module("bookmarks", {
             "Delete a bookmark",
             function (args) {
                 if (args.bang)
-                    commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
+                    commandline.input(_("bookmark.prompt.deleteAll") + " ").then(
                         function (resp) {
                             if (resp && resp.match(/^y(es)?$/i)) {
                                 bookmarks.remove(Object.keys(bookmarkcache.bookmarks));
-                                dactyl.echomsg(_("bookmark.allGone"));
+                                dactyl.echomsg(_("bookmark.allDeleted"));
                             }
                         });
                 else {
@@ -499,7 +576,9 @@ var Bookmarks = Module("bookmarks", {
                         let context = CompletionContext(args.join(" "));
                         context.fork("bookmark", 0, completion, "bookmark",
                                      args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
-                        var deletedCount = bookmarks.remove(context.allItems.items.map(function (item) item.item.id));
+
+                        deletedCount = bookmarks.remove(context.allItems.items
+                                                               .map(item => item.item.id));
                     }
 
                     dactyl.echomsg({ message: _("bookmark.deleted", deletedCount) });
@@ -517,7 +596,7 @@ var Bookmarks = Module("bookmarks", {
                 privateData: true
             });
     },
-    mappings: function () {
+    mappings: function initMappings() {
         var myModes = config.browserModes;
 
         mappings.add(myModes, ["a"],
@@ -526,11 +605,12 @@ var Bookmarks = Module("bookmarks", {
                 let options = {};
 
                 let url = buffer.uri.spec;
-                let bmarks = bookmarks.get(url).filter(function (bmark) bmark.url == url);
+                let bmarks = bookmarks.get(url).filter(bmark => bmark.url == url);
 
                 if (bmarks.length == 1) {
                     let bmark = bmarks[0];
 
+                    options["-id"] = bmark.id;
                     options["-title"] = bmark.title;
                     if (bmark.charset)
                         options["-charset"] = bmark.charset;
@@ -556,7 +636,7 @@ var Bookmarks = Module("bookmarks", {
             "Toggle bookmarked state of current URL",
             function () { bookmarks.toggle(buffer.uri.spec); });
     },
-    options: function () {
+    options: function initOptions() {
         options.add(["defsearch", "ds"],
             "The default search engine",
             "string", "google",
@@ -573,15 +653,15 @@ var Bookmarks = Module("bookmarks", {
              { completer: function completer(context) completion.searchEngine(context, true), });
     },
 
-    completion: function () {
-        completion.bookmark = function bookmark(context, tags, extra) {
+    completion: function initCompletion() {
+        completion.bookmark = function bookmark(context, tags, extra={}) {
             context.title = ["Bookmark", "Title"];
             context.format = bookmarks.format;
-            iter(extra || {}).forEach(function ([k, v]) {
+            iter(extra).forEach(function ([k, v]) {
                 if (v != null)
                     context.filters.push(function (item) item.item[k] != null && this.matchString(v, item.item[k]));
             });
-            context.generate = function () values(bookmarkcache.bookmarks);
+            context.generate = () => values(bookmarkcache.bookmarks);
             completion.urls(context, tags);
         };
 
@@ -601,10 +681,11 @@ var Bookmarks = Module("bookmarks", {
                          keyword, true);
 
             let item = keywords[keyword];
-            if (item && item.url.indexOf("%s") > -1)
+            if (item && item.url.contains("%s"))
                 context.fork("keyword/" + keyword, keyword.length + space.length, null, function (context) {
                     context.format = history.format;
-                    context.title = [keyword + " Quick Search"];
+                    context.title = [/*L*/keyword + " Quick Search"];
+                    context.keys = { text: "url", description: "title", icon: "icon" };
                     // context.background = true;
                     context.compare = CompletionContext.Sort.unsorted;
                     context.generate = function () {
@@ -613,7 +694,7 @@ var Bookmarks = Module("bookmarks", {
                         return history.get({ uri: util.newURI(begin), uriIsPrefix: true }).map(function (item) {
                             let rest = item.url.length - end.length;
                             let query = item.url.substring(begin.length, rest);
-                            if (item.url.substr(rest) == end && query.indexOf("&") == -1)
+                            if (item.url.substr(rest) == end && query.contains("&"))
                                 try {
                                     item.url = decodeURIComponent(query.replace(/#.*/, "").replace(/\+/g, " "));
                                     return item;
@@ -630,7 +711,7 @@ var Bookmarks = Module("bookmarks", {
              context.keys = { text: "keyword", description: "title", icon: "icon" };
              context.completions = values(bookmarks.searchEngines);
              if (suggest)
-                 context.filters.push(function ({ item }) item.supportsResponseType("application/x-suggestions+json"));
+                 context.filters.push(({ item }) => item.supportsResponseType("application/x-suggestions+json"));
 
         };
 
@@ -641,36 +722,52 @@ var Bookmarks = Module("bookmarks", {
             let engineList = (engineAliases || options["suggestengines"].join(",") || "google").split(",");
 
             engineList.forEach(function (name) {
-                let engine = bookmarks.searchEngines[name];
-                if (!engine)
+                if (!bookmarks.hasSuggestions(name))
                     return;
+
+                var desc = name;
+                let engine = bookmarks.searchEngines[name];
+                if (engine)
+                    desc = engine.description;
+
+
                 let [, word] = /^\s*(\S+)/.exec(context.filter) || [];
                 if (!kludge && word == name) // FIXME: Check for matching keywords
                     return;
+
                 let ctxt = context.fork(name, 0);
 
-                ctxt.title = [engine.description + " Suggestions"];
+                ctxt.title = [/*L*/desc + " Suggestions"];
                 ctxt.keys = { text: util.identity, description: function () "" };
                 ctxt.compare = CompletionContext.Sort.unsorted;
                 ctxt.filterFunc = null;
 
+                if (ctxt.waitingForTab)
+                    return;
+
                 let words = ctxt.filter.toLowerCase().split(/\s+/g);
-                ctxt.completions = ctxt.completions.filter(function (i) words.every(function (w) i.toLowerCase().indexOf(w) >= 0));
+                ctxt.completions = ctxt.completions.filter(i => words.every(w => i.toLowerCase().contains(w)));
 
                 ctxt.hasItems = ctxt.completions.length;
                 ctxt.incomplete = true;
-                ctxt.cache.request = bookmarks.getSuggestions(name, ctxt.filter, function (compl) {
+                ctxt.cache.request = bookmarks.getSuggestions(name, ctxt.filter);
+                ctxt.cache.request.then(function (compl) {
                     ctxt.incomplete = false;
-                    ctxt.completions = array.uniq(ctxt.completions.filter(function (c) compl.indexOf(c) >= 0)
+                    ctxt.completions = array.uniq(ctxt.completions.filter(c => compl.contains(c))
                                                       .concat(compl), true);
+                }, function (e) {
+                    ctxt.incomplete = false;
+                    ctxt.completions = [];
+                    if (e)
+                        Cu.reportError(e);
                 });
             });
         };
 
-        completion.addUrlCompleter("S", "Suggest engines", completion.searchEngineSuggest);
-        completion.addUrlCompleter("b", "Bookmarks", completion.bookmark);
-        completion.addUrlCompleter("s", "Search engines and keyword URLs", completion.search);
+        completion.addUrlCompleter("suggestion", "Search engine suggestions", completion.searchEngineSuggest);
+        completion.addUrlCompleter("bookmark", "Bookmarks", completion.bookmark);
+        completion.addUrlCompleter("search", "Search engines and keyword URLs", completion.search);
     }
 });
 
-// vim: set fdm=marker sw=4 ts=4 et:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et: