diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js index b4a01295..95663eb2 100644 --- a/common/content/bookmarks.js +++ b/common/content/bookmarks.js @@ -706,9 +706,9 @@ var Bookmarks = Module("bookmarks", { }); }; - 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", "Suggest engines", completion.searchEngineSuggest); + completion.addUrlCompleter("bookmark", "Bookmarks", completion.bookmark); + completion.addUrlCompleter("search", "Search engines and keyword URLs", completion.search); } }); diff --git a/common/content/buffer.js b/common/content/buffer.js index db647fb9..00c96972 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -1955,7 +1955,7 @@ var Buffer = Module("buffer", { dactyl.assert(url, _("error.clipboardEmpty")); let proto = /^([-\w]+):/.exec(url); - if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc && !RegExp(options["urlseparator"]).test(url)) + if (proto && services.PROTOCOL + proto[1] in Cc && !RegExp(options["urlseparator"]).test(url)) return url.replace(/\s+/g, ""); return url; } diff --git a/common/content/dactyl.js b/common/content/dactyl.js index be494453..f64a639f 100644 --- a/common/content/dactyl.js +++ b/common/content/dactyl.js @@ -1108,7 +1108,7 @@ var Dactyl = Module("dactyl", XPCOM(Ci.nsISupportsWeakReference, ModuleBase), { // If it starts with a valid protocol, pass it through. let proto = /^([-\w]+):/.exec(url); - if (proto && "@mozilla.org/network/protocol;1?name=" + proto[1] in Cc) + if (proto && services.PROTOCOL + proto[1] in Cc) return url; // Check for a matching search keyword. diff --git a/common/content/history.js b/common/content/history.js index 9dba789d..ff1781de 100644 --- a/common/content/history.js +++ b/common/content/history.js @@ -312,7 +312,7 @@ var History = Module("history", { context.generate = function () history.get(context.filter, this.maxItems, sort); }; - completion.addUrlCompleter("h", "History", completion.history); + completion.addUrlCompleter("history", "History", completion.history); }, mappings: function () { function bind() mappings.add.apply(mappings, [config.browserModes].concat(Array.slice(arguments))); diff --git a/common/content/key-processors.js b/common/content/key-processors.js index c90d1528..63b27938 100644 --- a/common/content/key-processors.js +++ b/common/content/key-processors.js @@ -279,9 +279,9 @@ var KeyProcessor = Class("KeyProcessor", { return KeyArgProcessor(this, map, true, "motion"); return this.execute(map, { - keyEvents: this.keyEvents, command: this.command, count: this.count, + keyEvents: events.keyEvents, keypressEvents: this.events }); } @@ -313,7 +313,8 @@ var KeyArgProcessor = Class("KeyArgProcessor", KeyProcessor, { let args = { command: this.parent.command, count: this.count || this.parent.count, - events: this.parent.events.concat(this.events) + keyEvents: events.keyEvents, + keypressEvents: this.parent.events.concat(this.events) }; args[this.argName] = this.command; diff --git a/common/locale/en-US/messages.properties b/common/locale/en-US/messages.properties index e144728f..b586ac05 100644 --- a/common/locale/en-US/messages.properties +++ b/common/locale/en-US/messages.properties @@ -37,6 +37,10 @@ autocmd.noMatching = No matching autocommands autocmd.noGroup-1 = No such group or event: %S autocmd.cantExecuteAll = Can't execute autocommands for ALL events +autocomplete.description-1 = Native '%S' autocompletions +autocomplete.noSuchProvider-1 = No such autocomplete provider '%S' +autocomplete.title-1 = '%S' + bookmark.noMatching-2 = No bookmarks matching tags %S and string %S bookmark.noMatchingTags-1 = No bookmarks matching tags %S bookmark.noMatchingString-1 = No bookmarks matching string %S diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml index 8f530c9c..465ed092 100644 --- a/common/locale/en-US/options.xml +++ b/common/locale/en-US/options.xml @@ -510,22 +510,35 @@

Items which are completed at the :open prompts. Available items:

-
s
Search engines and keyword URLs
-
f
Local files
-
l
&dactyl.host; location bar entries (bookmarks and history sorted in an intelligent way)
-
b
Bookmarks
-
h
History
-
S
Search engine suggestions
+
search
Search engines and keyword URLs
+
file
Local files
+
location
&dactyl.host; location bar entries (bookmarks and history sorted in an intelligent way)
+
bookmark
Bookmarks
+
history
History
+
suggest
Search engine suggestions
+

+ Additionally, native search providers can be added by prefixing + their names with the string native:. These + providers are often added by other add-ons and are occasionally + useful. +

+

The order is important, such that bsf will list bookmarks followed by matching quick searches and then matching files.

+ + For backward compatibility, this option currently accepts a single + entry containing single-letter names for completers. This usage + is deprecated and will be removed in the future. + + - Using b and h can make completion very slow if + Using bookmark and history can make completion very slow if there are many items. diff --git a/common/modules/bookmarkcache.jsm b/common/modules/bookmarkcache.jsm index 6f303226..8bf2ba16 100644 --- a/common/modules/bookmarkcache.jsm +++ b/common/modules/bookmarkcache.jsm @@ -10,7 +10,14 @@ defineModule("bookmarkcache", { require: ["services", "storage", "util"] }, this); -function newURI(url, charset, base) services.io.newURI(url, charset, base); +function newURI(url, charset, base) { + try { + return services.io.newURI(url, charset, base); + } + catch (e) { + throw Error(e); + } +} var Bookmark = Struct("url", "title", "icon", "post", "keyword", "tags", "charset", "id"); var Keyword = Struct("keyword", "title", "icon", "url"); diff --git a/common/modules/completion.jsm b/common/modules/completion.jsm index f23f2330..cf52e7fb 100644 --- a/common/modules/completion.jsm +++ b/common/modules/completion.jsm @@ -211,6 +211,16 @@ var CompletionContext = Class("CompletionContext", { return this; }, + __title: Class.Memoize(function () this._title.map(function (s) + typeof s == "string" ? messages.get("completion.title." + s, s) + : s)), + + set title(val) { + delete this.__title; + return this._title = val; + }, + get title() this.__title, + // Temporary /** * @property {Object} @@ -631,7 +641,17 @@ var CompletionContext = Class("CompletionContext", { start = Math.max(0, start || 0); end = Math.min(items.length, end != null ? end : items.length); for (let i in util.range(start, end, step)) - yield [i, cache[i] = cache[i] || util.xmlToDom(self.createRow(items[i]), doc)]; + try { + yield [i, cache[i] = cache[i] || util.xmlToDom(self.createRow(items[i]), doc)]; + } + catch (e) { + util.reportError(e); + yield [i, cache[i] = cache[i] || util.xmlToDom( +
+
  • {items[i].text} 
  • +
  • {e} 
  • +
    , doc)]; + } }, /** @@ -849,6 +869,7 @@ var Completion = Module("completion", { Local: function (dactyl, modules, window) ({ urlCompleters: {}, + get modules() modules, get options() modules.options, // FIXME @@ -920,8 +941,9 @@ var Completion = Module("completion", { if (/^about:/.test(context.filter)) context.fork("about", 6, this, function (context) { context.generate = function () { - const PREFIX = "@mozilla.org/network/protocol/about;1?what="; - return [[k.substr(PREFIX.length), ""] for (k in Cc) if (k.indexOf(PREFIX) == 0)]; + return [[k.substr(services.ABOUT.length), ""] + for (k in Cc) + if (k.indexOf(services.ABOUT) == 0)]; }; }); @@ -930,7 +952,7 @@ var Completion = Module("completion", { // Will, and should, throw an error if !(c in opts) Array.forEach(complete, function (c) { - let completer = this.urlCompleters[c]; + let completer = this.urlCompleters[c] || { args: [], completer: this.autocomplete(c.replace(/^native:/, "")) }; context.forkapply(c, 0, this, completer.completer, completer.args); }, this); }, @@ -941,6 +963,64 @@ var Completion = Module("completion", { this.urlCompleters[opt] = completer; }, + autocomplete: curry(function autocomplete(provider, context) { + let running = context.getCache("autocomplete-search-running", Object); + + let name = "autocomplete:" + provider; + if (!services.has(name)) + services.add(name, services.AUTOCOMPLETE + provider, "nsIAutoCompleteSearch"); + let service = services[name]; + + util.assert(service, _("autocomplete.noSuchProvider", provider), false); + + if (running[provider]) { + this.completions = this.completions; + this.cancel(); + } + + context.anchored = false; + context.compare = CompletionContext.Sort.unsorted; + context.filterFunc = null; + + let words = context.filter.toLowerCase().split(/\s+/g); + context.hasItems = true; + context.completions = context.completions.filter(function ({ url, title }) + words.every(function (w) (url + " " + title).toLowerCase().indexOf(w) >= 0)) + context.incomplete = true; + + context.format = this.modules.bookmarks.format; + context.keys.extra = function (item) { + try { + return bookmarkcache.get(item.url).extra; + } + catch (e) {} + return null; + }; + context.title = [_("autocomplete.title", provider)]; + + context.cancel = function () { + this.incomplete = false; + if (running[provider]) + service.stopSearch(); + running[provider] = false; + }; + + service.startSearch(context.filter, "", context.result, { + onSearchResult: function onSearchResult(search, result) { + if (result.searchResult <= result.RESULT_SUCCESS) + running[provider] = null; + + context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING; + context.completions = [ + { url: result.getValueAt(i), title: result.getCommentAt(i), icon: result.getImageAt(i) } + for (i in util.range(0, result.matchCount)) + ]; + }, + get onUpdateSearchResult() this.onSearchResult + }); + running[provider] = true; + }), + urls: function (context, tags) { let compare = String.localeCompare; let contains = String.indexOf; @@ -1053,8 +1133,32 @@ var Completion = Module("completion", { options.add(["complete", "cpt"], "Items which are completed at the :open prompts", - "charlist", config.defaults.complete == null ? "slf" : config.defaults.complete, - { get values() values(completion.urlCompleters).toArray() }); + "stringlist", config.defaults.complete == null ? "slf" : config.defaults.complete, + { + valueMap: { + S: "suggestion", + b: "bookmark", + f: "file", + h: "history", + l: "location", + s: "search" + }, + + get values() values(completion.urlCompleters).toArray() + .concat([let (name = k.substr(services.AUTOCOMPLETE.length)) + ["native:" + name, _("autocomplete.description", name)] + for (k in Cc) + if (k.indexOf(services.AUTOCOMPLETE) == 0)]), + + setter: function setter(values) { + if (values.length == 1 && !Set.has(values[0], this.values) + && Array.every(values[0], Set.has(this.valueMap))) + return Array.map(values[0], function (v) this[v], this.valueMap); + return values; + }, + + validator: function validator(values) validator.supercall(this, this.setter(values)) + }); options.add(["wildanchor", "wia"], "Define which completion groups only match at the beginning of their text", diff --git a/common/modules/config.jsm b/common/modules/config.jsm index df607445..4453edf4 100644 --- a/common/modules/config.jsm +++ b/common/modules/config.jsm @@ -19,7 +19,7 @@ AboutHandler.prototype = { classID: Components.ID("81495d80-89ee-4c36-a88d-ea7c4e5ac63f"), - get contractID() "@mozilla.org/network/protocol/about;1?what=" + config.name, + get contractID() services.ABOUT + config.name, QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]), diff --git a/common/modules/io.jsm b/common/modules/io.jsm index e724ab6d..c81ff18e 100644 --- a/common/modules/io.jsm +++ b/common/modules/io.jsm @@ -976,7 +976,7 @@ unlet s:cpo_save }; }; - completion.addUrlCompleter("f", "Local files", function (context, full) { + completion.addUrlCompleter("file", "Local files", function (context, full) { let match = util.regexp( diff --git a/common/modules/protocol.jsm b/common/modules/protocol.jsm index 06d25498..ba02a156 100644 --- a/common/modules/protocol.jsm +++ b/common/modules/protocol.jsm @@ -93,7 +93,7 @@ function ProtocolBase() { }; } ProtocolBase.prototype = { - get contractID() "@mozilla.org/network/protocol;1?name=" + this.scheme, + get contractID() services.PROTOCOL + this.scheme, get classDescription() this.scheme + " utility protocol", QueryInterface: XPCOMUtils.generateQI([Ci.nsIProtocolHandler]), diff --git a/common/modules/services.jsm b/common/modules/services.jsm index ba007dfa..f70dd543 100644 --- a/common/modules/services.jsm +++ b/common/modules/services.jsm @@ -16,13 +16,16 @@ defineModule("services", { * A lazily-instantiated XPCOM class and service cache. */ var Services = Module("Services", { + ABOUT: "@mozilla.org/network/protocol/about;1?what=", + AUTOCOMPLETE: "@mozilla.org/autocomplete/search;1?name=", + PROTOCOL: "@mozilla.org/network/protocol;1?name=", + init: function () { this.services = {}; this.add("annotation", "@mozilla.org/browser/annotation-service;1", "nsIAnnotationService"); this.add("appShell", "@mozilla.org/appshell/appShellService;1", "nsIAppShellService"); this.add("appStartup", "@mozilla.org/toolkit/app-startup;1", "nsIAppStartup"); - this.add("autoCompleteSearch", "@mozilla.org/autocomplete/search;1?name=history", "nsIAutoCompleteSearch"); this.add("bookmarks", "@mozilla.org/browser/nav-bookmarks-service;1", "nsINavBookmarksService"); this.add("bootstrap", "@dactyl.googlecode.com/base/bootstrap"); this.add("browserSearch", "@mozilla.org/browser/search-service;1", "nsIBrowserSearchService"); @@ -34,7 +37,7 @@ var Services = Module("Services", { this.add("commandLineHandler", "@mozilla.org/commandlinehandler/general-startup;1?type=dactyl"); this.add("console", "@mozilla.org/consoleservice;1", "nsIConsoleService"); this.add("dactyl", "@dactyl.googlecode.com/extra/utils", "dactylIUtils"); - this.add("dactyl:", "@mozilla.org/network/protocol;1?name=dactyl"); + this.add("dactyl:", this.PROTOCOL + "dactyl"); this.add("debugger", "@mozilla.org/js/jsd/debugger-service;1", "jsdIDebuggerService"); this.add("directory", "@mozilla.org/file/directory_service;1", "nsIProperties"); this.add("downloadManager", "@mozilla.org/download-manager;1", "nsIDownloadManager"); @@ -43,7 +46,7 @@ var Services = Module("Services", { this.add("externalApp", "@mozilla.org/uriloader/external-helper-app-service;1", "nsPIExternalAppLauncher") this.add("externalProtocol", "@mozilla.org/uriloader/external-protocol-service;1", "nsIExternalProtocolService"); this.add("favicon", "@mozilla.org/browser/favicon-service;1", "nsIFaviconService"); - this.add("file:", "@mozilla.org/network/protocol;1?name=file", "nsIFileProtocolHandler"); + this.add("file:", this.PROTOCOL + "file", "nsIFileProtocolHandler"); this.add("focus", "@mozilla.org/focus-manager;1", "nsIFocusManager"); this.add("history", "@mozilla.org/browser/global-history;2", ["nsIBrowserHistory", "nsIGlobalHistory2", "nsINavHistoryService", "nsPIPlacesDatabase"]); @@ -57,7 +60,7 @@ var Services = Module("Services", { this.add("pref", "@mozilla.org/preferences-service;1", ["nsIPrefBranch2", "nsIPrefService"]); this.add("privateBrowsing", "@mozilla.org/privatebrowsing;1", "nsIPrivateBrowsingService"); this.add("profile", "@mozilla.org/toolkit/profile-service;1", "nsIToolkitProfileService"); - this.add("resource:", "@mozilla.org/network/protocol;1?name=resource", ["nsIProtocolHandler", "nsIResProtocolHandler"]); + this.add("resource:", this.PROTOCOL + "resource", ["nsIProtocolHandler", "nsIResProtocolHandler"]); this.add("runtime", "@mozilla.org/xre/runtime;1", ["nsIXULAppInfo", "nsIXULRuntime"]); this.add("rdf", "@mozilla.org/rdf/rdf-service;1", "nsIRDFService"); this.add("sessionStore", "@mozilla.org/browser/sessionstore;1", "nsISessionStore"); diff --git a/pentadactyl/NEWS b/pentadactyl/NEWS index c5e1c429..ad3a2521 100644 --- a/pentadactyl/NEWS +++ b/pentadactyl/NEWS @@ -168,6 +168,8 @@ - Boolean options no longer accept an argument. [b4] - 'cdpath' and 'runtimepath' no longer treat ",," specially. Use "." instead. [b2] + - 'complete' is now a [stringlist] rather than a [charlist] + and supports native autocomplete providers. [b8] - 'extendedhinttags' is now a [regexpmap] rather than a string. [b2] - 'guioptions' default value has changed. [b4][b7] diff --git a/pentadactyl/content/config.js b/pentadactyl/content/config.js index 4d289f28..0b787978 100644 --- a/pentadactyl/content/config.js +++ b/pentadactyl/content/config.js @@ -128,7 +128,7 @@ var Config = Module("config", ConfigBase, { }, defaults: { - complete: "slf", + complete: "search,location,file", guioptions: "bCrs", showtabline: "always", titlestring: "Pentadactyl" @@ -280,55 +280,12 @@ var Config = Module("config", ConfigBase, { const { CompletionContext, bookmarkcache, completion } = modules; const { document } = window; - var searchRunning = null; completion.location = function location(context) { - if (!services.autoCompleteSearch) - return; - - if (searchRunning) { - searchRunning.completions = searchRunning.completions; - searchRunning.cancel(); - } - - context.anchored = false; - context.compare = CompletionContext.Sort.unsorted; - context.filterFunc = null; - - let words = context.filter.toLowerCase().split(/\s+/g); - context.hasItems = true; - context.completions = context.completions.filter(function ({ url, title }) - words.every(function (w) (url + " " + title).toLowerCase().indexOf(w) >= 0)) - context.incomplete = true; - - context.format = modules.bookmarks.format; - context.keys.extra = function (item) (bookmarkcache.get(item.url) || {}).extra; + completion.autocomplete("history", context); context.title = ["Smart Completions"]; - - context.cancel = function () { - this.incomplete = false; - if (searchRunning === this) { - services.autoCompleteSearch.stopSearch(); - searchRunning = null; - } - }; - - services.autoCompleteSearch.startSearch(context.filter, "", context.result, { - onSearchResult: function onSearchResult(search, result) { - if (result.searchResult <= result.RESULT_SUCCESS) - searchRunning = null; - - context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING; - context.completions = [ - { url: result.getValueAt(i), title: result.getCommentAt(i), icon: result.getImageAt(i) } - for (i in util.range(0, result.matchCount)) - ]; - }, - get onUpdateSearchResult() this.onSearchResult - }); - searchRunning = context; }; - completion.addUrlCompleter("l", + completion.addUrlCompleter("location", "Firefox location bar entries (bookmarks and history sorted in an intelligent way)", completion.location);