1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-23 13:42:27 +01:00

More completion stuff.

This commit is contained in:
Kris Maglione
2008-11-22 06:53:44 +00:00
parent 4f1c195aa4
commit fcc799aa6a
12 changed files with 292 additions and 266 deletions

View File

@@ -240,10 +240,10 @@ function Bookmarks() //{{{
"Set the default search engine", "Set the default search engine",
"string", "google", "string", "google",
{ {
completer: function (filter) completion.url("", "s")[1], completer: function (filter) completion._url(filter, "s").items,
validator: function (value) validator: function (value)
{ {
return completion.url("", "s")[1].some(function (s) s[0] == value); return completion._url("", "s").items.some(function (s) s[0] == value);
} }
}); });
@@ -350,7 +350,6 @@ function Bookmarks() //{{{
{ {
if (bypassCache) // Is this really necessary anymore? if (bypassCache) // Is this really necessary anymore?
cache.load(); cache.load();
return completion.cached("bookmarks", filter, function () cache.bookmarks, "filterURLArray", tags); return completion.cached("bookmarks", filter, function () cache.bookmarks, "filterURLArray", tags);
}, },

View File

@@ -564,7 +564,7 @@ function Buffer() //{{{
{ {
argCount: "?", argCount: "?",
bang: true, bang: true,
completer: function (filter) completion.file(filter) completer: function (filter, bang, args, context) completion.file(context)
}); });
commands.add(["st[op]"], commands.add(["st[op]"],
@@ -578,7 +578,7 @@ function Buffer() //{{{
{ {
argCount: "?", argCount: "?",
bang: true, bang: true,
completer: function (filter) completion.url(filter, "bhf") completer: function (filter, bang, args, context) completion.url(context, "bhf")
}); });
commands.add(["zo[om]"], commands.add(["zo[om]"],

View File

@@ -112,8 +112,7 @@ Command.prototype = {
commandline.inputMultiline(new RegExp("^" + matches[2] + "$", "m"), commandline.inputMultiline(new RegExp("^" + matches[2] + "$", "m"),
function (args) function (args)
{ {
args = this.parseArgs(matches[1] + "\n" + args); args = self.parseArgs(matches[1] + "\n" + args);
if (args) if (args)
self.action.call(self, args, special, count, modifiers); self.action.call(self, args, special, count, modifiers);
}); });
@@ -122,7 +121,6 @@ Command.prototype = {
} }
args = this.parseArgs(args); args = this.parseArgs(args);
if (args) if (args)
this.action.call(this, args, special, count, modifiers); this.action.call(this, args, special, count, modifiers);
}, },

View File

@@ -32,31 +32,78 @@ modules._cleanEval = function (__liberator_eval_arg, __liberator_eval_tmp)
return window.eval(__liberator_eval_arg); return window.eval(__liberator_eval_arg);
} }
function CompletionContext(editor, offset) function CompletionContext(editor, name, offset)
{ {
if (!name)
name = "";
if (editor instanceof arguments.callee) if (editor instanceof arguments.callee)
{ {
let parent = editor; let parent = editor;
name = parent.name + "/" + name;
this.contexts = parent.contexts;
if (name in this.contexts)
return this.contexts[name];
this.contexts[name] = this;
this.parent = parent; this.parent = parent;
this.editor = parent.editor; this.editor = parent.editor;
this.offset = parent.offset + (offset || 0); this.offset = parent.offset + (offset || 0);
this.__defineGetter__("tabPressed", function () this.parent.tabPressed); this.__defineGetter__("tabPressed", function () this.parent.tabPressed);
this.contexts = this.parent.contexts; this.__defineGetter__("onUpdate", function () this.parent.onUpdate);
this.incomplete = false;
} }
else else
{ {
this.editor = editor; this.editor = editor;
this.offset = offset || 0; this.offset = offset || 0;
this.tabPressed = false; this.tabPressed = false;
this.contexts = {}; this.onUpdate = function () true;
this.contexts = { name: this };
this.__defineGetter__("incomplete", function () this.contextList.some(function (c) c.parent && c.incomplete));
this.reset();
} }
this.name = name || "";
this._items = []; // FIXME
this.selectionTypes = {}; this.selectionTypes = {};
} }
CompletionContext.prototype = { CompletionContext.prototype = {
// Temporary
get allItems()
{
let minStart = Math.min.apply(Math, [context.offset for ([k, context] in Iterator(this.contexts)) if (context.items.length && context.hasItems)]);
let items = [];
for each (let [k, context] in Iterator(this.contexts))
{
let prefix = this.value.substring(minStart, context.offset);
if (context.hasItems)
{
items.push(context.items.map(function (item) {
if (!("text" in item))
item = { icon: item[2], text: item[0], description: item[1] };
else // FIXME
item = util.Array.assocToObj([x for (x in Iterator(item))]);
item.text = prefix + item.text;
return item;
}));
}
}
return { start: minStart, items: util.Array.flatten(items) }
},
get caret() this.editor.selection.getRangeAt(0).startOffset - this.offset, get caret() this.editor.selection.getRangeAt(0).startOffset - this.offset,
get contextList() [v for ([k, v] in Iterator(this.contexts))],
get filter() this.value.substr(this.offset, this.caret), get filter() this.value.substr(this.offset, this.caret),
get items() this._items,
set items(items)
{
this.hasItems = items.length > 0;
this._items = items;
this.onUpdate.call(this);
},
get value() this.editor.rootElement.textContent, get value() this.editor.rootElement.textContent,
advance: function (count) advance: function (count)
@@ -64,12 +111,12 @@ CompletionContext.prototype = {
this.offset += count; this.offset += count;
}, },
fork: function (name, offset) fork: function (name, offset, completer, self)
{ {
if (!(name in this.contexts)) let context = new CompletionContext(this, name, offset);
this.contexts[name] = new CompletionContext(this); if (completer)
this.contexts[name].offset = this.offset + offset; return completer.apply(self, [context].concat(Array.slice(arguments, 4)));
return this.contexts[name]; return context;
}, },
highlight: function (start, length, type) highlight: function (start, length, type)
@@ -98,14 +145,23 @@ CompletionContext.prototype = {
reset: function () reset: function ()
{ {
let self = this;
if (this.parent)
throw Error();
// Not ideal. // Not ideal.
for (let type in this.selectionTypes) for each (let type in this.selectionTypes)
this.highlight(0, 0, type); this.highlight(0, 0, type);
this.selectionTypes = {}; this.selectionTypes = {};
this.tabPressed = false; this.tabPressed = false;
this.offset = 0; this.offset = 0;
//for (let key in (k for ([k, v] in Iterator(self.contexts)) if (v.offset > this.caret)))
// delete this.contexts[key];
for each (let context in this.contexts)
{
context.hasItems = false;
context.incomplete = false;
}
}, },
} }
function Completion() //{{{ function Completion() //{{{
@@ -130,23 +186,6 @@ function Completion() //{{{
var cacheResults = {} var cacheResults = {}
var substrings = []; var substrings = [];
var historyCache = []; var historyCache = [];
var historyResult = null;
var completionCache = [];
var historyTimer = new util.Timer(50, 100, function histTimer() {
let comp = [];
for (let i in util.range(0, historyResult.matchCount))
comp.push([historyResult.getValueAt(i),
historyResult.getCommentAt(i),
historyResult.getImageAt(i)]);
//let foo = ["", "IGNORED", "FAILURE", "NOMATCH", "SUCCESS", "NOMATCH_ONGOING", "SUCCESS_ONGOING"];
// TODO: we need to have a "completionCacheAfter" to allow cpt=slf
historyCache = comp;
commandline.setCompletions({ get items() completionCache.concat(historyCache),
incompleteResult: historyResult.searchResult >= historyResult.RESULT_NOMATCH_ONGOING ? true : false });
});
function Javascript() function Javascript()
{ {
@@ -228,6 +267,8 @@ function Completion() //{{{
// Things we can dereference // Things we can dereference
if (["object", "string", "function"].indexOf(typeof obj) == -1) if (["object", "string", "function"].indexOf(typeof obj) == -1)
continue; continue;
if (!obj)
continue;
// XPCNativeWrappers, etc, don't show all accessible // XPCNativeWrappers, etc, don't show all accessible
// members until they're accessed, so, we look at // members until they're accessed, so, we look at
@@ -431,7 +472,7 @@ function Completion() //{{{
lastIdx = i; lastIdx = i;
} }
this.complete = function complete(context) this.complete = function (context)
{ {
this.context = context; this.context = context;
let string = context.filter; let string = context.filter;
@@ -446,10 +487,10 @@ function Completion() //{{{
catch (e) catch (e)
{ {
if (e.message != "Invalid JS") if (e.message != "Invalid JS")
Components.utils.reportError(e); liberator.reportError(e);
// liberator.dump(util.escapeString(string) + ": " + e + "\n" + e.stack); // liberator.dump(util.escapeString(string) + ": " + e + "\n" + e.stack);
lastIdx = 0; lastIdx = 0;
return [0, []]; return;
} }
/* Okay, have parse stack. Figure out what we're completing. */ /* Okay, have parse stack. Figure out what we're completing. */
@@ -488,7 +529,7 @@ function Completion() //{{{
cacheKey = str.substring(statement, dot); cacheKey = str.substring(statement, dot);
obj = self.eval(s, cacheKey, obj); obj = self.eval(s, cacheKey, obj);
} }
return obj; return [[obj], str.substring(statement, stop + 1)];
} }
function getObjKey(frame) function getObjKey(frame)
@@ -498,7 +539,7 @@ function Completion() //{{{
let end = (frame == -1 ? lastIdx : get(frame + 1)[OFFSET]); let end = (frame == -1 ? lastIdx : get(frame + 1)[OFFSET]);
cacheKey = null; cacheKey = null;
let obj = [modules, window]; // Default objects; let obj = [[modules, "modules"], [window, "window"]]; // Default objects;
/* Is this an object dereference? */ /* Is this an object dereference? */
if (dot < statement) // No. if (dot < statement) // No.
dot = statement - 1; dot = statement - 1;
@@ -509,6 +550,16 @@ function Completion() //{{{
return [dot + 1 + space.length, obj, key]; return [dot + 1 + space.length, obj, key];
} }
function complete(objects, key, compl, string, last)
{
for (let [,obj] in Iterator(objects))
{
let ctxt = this.context.fork(obj[1], top[OFFSET]);
ctxt.title = [obj[1]];
ctxt.items = this.filter(compl || this.objectKeys(obj[0]), key + (string || ""), last, key.length);
}
}
// In a string. Check if we're dereferencing an object. // In a string. Check if we're dereferencing an object.
// Otherwise, do nothing. // Otherwise, do nothing.
if (last == "'" || last == '"') if (last == "'" || last == '"')
@@ -537,7 +588,7 @@ function Completion() //{{{
// Yes. If the [ starts at the begining of a logical // Yes. If the [ starts at the begining of a logical
// statement, we're in an array literal, and we're done. // statement, we're in an array literal, and we're done.
if (get(-3, 0, STATEMENTS) == get(-2)[OFFSET]) if (get(-3, 0, STATEMENTS) == get(-2)[OFFSET])
return [0, []]; return;
// Begining of the statement upto the opening [ // Begining of the statement upto the opening [
let obj = getObj(-3, get(-2)[OFFSET]); let obj = getObj(-3, get(-2)[OFFSET]);
@@ -546,8 +597,7 @@ function Completion() //{{{
// Now eval the key, to process any referenced variables. // Now eval the key, to process any referenced variables.
key = this.eval(key); key = this.eval(key);
let compl = this.objectKeys(obj); return complete.call(this, obj, key, null, string, last);
return [top[OFFSET], this.filter(compl, key + string, last, key.length)];
} }
// Is this a function call? // Is this a function call?
@@ -561,7 +611,7 @@ function Completion() //{{{
// Does the opening "(" mark a function call? // Does the opening "(" mark a function call?
if (get(-3, 0, FUNCTIONS) != get(-2)[OFFSET]) if (get(-3, 0, FUNCTIONS) != get(-2)[OFFSET])
return [0, []]; // No. We're done. return; // No. We're done.
let [offset, obj, func] = getObjKey(-3); let [offset, obj, func] = getObjKey(-3);
let key = str.substring(get(-2, 0, STATEMENTS), top[OFFSET]) + "''"; let key = str.substring(get(-2, 0, STATEMENTS), top[OFFSET]) + "''";
@@ -574,7 +624,7 @@ function Completion() //{{{
if (!completer) if (!completer)
completer = this.completers[func]; completer = this.completers[func];
if (!completer) if (!completer)
return [0, []]; return;
// Split up the arguments // Split up the arguments
let prev = get(-2)[OFFSET]; let prev = get(-2)[OFFSET];
@@ -590,11 +640,11 @@ function Completion() //{{{
if (!(compl instanceof Array)) if (!(compl instanceof Array))
compl = [v for (v in compl)]; compl = [v for (v in compl)];
key = this.eval(key); key = this.eval(key);
return [top[OFFSET], this.filter(compl, key + string, last, key.length)]; return complete.call(this, obj, key, compl, string, last);
} }
// Nothing to do. // Nothing to do.
return [0, []]; return;
} }
/* /*
@@ -603,23 +653,28 @@ function Completion() //{{{
* key = "baz" * key = "baz"
* *
* str = "foo" * str = "foo"
* obj = [liberator, window] * obj = [modules, window]
* key = "foo" * key = "foo"
*/ */
let [offset, obj, key] = getObjKey(-1); let [offset, obj, key] = getObjKey(-1);
if (!/^(?:\w[\w\d]*)?$/.test(key)) if (!/^(?:\w[\w\d]*)?$/.test(key))
return [0, []]; /* Not a word. Forget it. Can this even happen? */ return; /* Not a word. Forget it. Can this even happen? */
let compl = this.objectKeys(obj); top[OFFSET] = offset;
return [offset, this.filter(compl, key)]; return complete.call(this, obj, key);
} }
}; };
let javascript = new Javascript(); let javascript = new Javascript();
function buildSubstrings(str, filter) function buildSubstrings(str, filter)
{ {
if (substrings.length)
{
substrings = substrings.filter(function strIndex(s) str.indexOf(s) >= 0);
return;
}
if (filter == "") if (filter == "")
return; return;
let length = filter.length; let length = filter.length;
@@ -662,12 +717,7 @@ function Completion() //{{{
filtered.push([compitem, item[1], favicon ? item[2] : null]); filtered.push([compitem, item[1], favicon ? item[2] : null]);
if (longest) if (longest)
{
if (substrings.length == 0)
buildSubstrings(str, filter); buildSubstrings(str, filter);
else
substrings = substrings.filter(function strIndex(s) str.indexOf(s) >= 0);
}
break; break;
} }
} }
@@ -740,19 +790,9 @@ function Completion() //{{{
// returns the longest common substring // returns the longest common substring
// used for the 'longest' setting for wildmode // used for the 'longest' setting for wildmode
getLongestSubstring: function getLongestSubstring() get longestSubstring () substrings.reduce(function (a, b) a.length > b.length ? a : b, ""),
{
if (substrings.length == 0)
return "";
var longest = substrings[0]; get substrings() substrings.slice(),
for (let i = 1; i < substrings.length; i++)
{
if (substrings[i].length > longest.length)
longest = substrings[i];
}
return longest;
},
// generic filter function, also builds substrings needed // generic filter function, also builds substrings needed
// for :set wildmode=list:longest, if necessary // for :set wildmode=list:longest, if necessary
@@ -853,10 +893,7 @@ function Completion() //{{{
filtered.push(elem); filtered.push(elem);
} }
filtered = filtered.concat(additionalCompletions); return filtered.concat(additionalCompletions);
if (options.get("wildoptions").has("sort"))
filtered = filtered.sort(function (a, b) util.compareIgnoreCase(a[0], b[0]));;
return filtered;
}, },
// generic helper function which checks if the given "items" array pass "filter" // generic helper function which checks if the given "items" array pass "filter"
@@ -963,6 +1000,7 @@ function Completion() //{{{
let rtp = options["runtimepath"].split(","); let rtp = options["runtimepath"].split(",");
rtp.forEach(function (path) { rtp.forEach(function (path) {
// FIXME: Now! Very, very broken.
schemes = schemes.concat([[c[0].replace(/\.vimp$/, ""), ""] schemes = schemes.concat([[c[0].replace(/\.vimp$/, ""), ""]
for each (c in completion.file(path + "/colors/", true)[1])]); for each (c in completion.file(path + "/colors/", true)[1])]);
}); });
@@ -972,15 +1010,20 @@ function Completion() //{{{
command: function command(context) command: function command(context)
{ {
context.title = ["Command"];
if (!context.filter) if (!context.filter)
return { start: 0, items: [[c.name, c.description] for (c in commands)] }; context.items = [[c.name, c.description] for (c in commands)];
else else
return { start: 0, items: this.filter([[c.longNames, c.description] for (c in commands)], context.filter, true) }; context.items = this.filter([[c.longNames, c.description] for (c in commands)], context.filter, true);
}, },
dialog: function dialog(filter) [0, this.filter(config.dialogs, filter)], dialog: function dialog(filter) [0, this.filter(config.dialogs, filter)],
directory: function (filter, tail) [0, this.file(filter, tail)[1].filter(function (f) f[1] == "Directory")], directory: function (context, tail)
{
this.file(context, tail);
context.items = context.items.filter(function (i) i[1] == "Directory");
},
environment: function environment(filter) environment: function environment(filter)
{ {
@@ -1014,7 +1057,8 @@ function Completion() //{{{
// then get completions of the command name // then get completions of the command name
let [count, cmd, special, args] = commands.parseCommand(context.filter); let [count, cmd, special, args] = commands.parseCommand(context.filter);
let [, prefix, junk] = context.filter.match(/^(:*\d*)\w*(.?)/) || []; let [, prefix, junk] = context.filter.match(/^(:*\d*)\w*(.?)/) || [];
context.advance(junk.length) context.advance(prefix.length)
context.items = []; // XXX
if (!junk) if (!junk)
return this.command(context); return this.command(context);
@@ -1023,40 +1067,38 @@ function Completion() //{{{
let compObject = { start: 0, items: [] }; let compObject = { start: 0, items: [] };
if (command && command.completer) if (command && command.completer)
{ {
[prefix] = context.filter.match(/^(?:\w+[\s!]|!)\s*/); [prefix] = context.filter.match(/^(?:\w*[\s!]|!)\s*/);
context.advance((prefix || "").length); context = context.fork(cmd, prefix.length);
this.filterString = context.filter;
args = command.parseArgs(context.filter, true); args = command.parseArgs(context.filter, true);
liberator.dump(args);
if (args) if (args)
{ {
// XXX, XXX, XXX // XXX, XXX, XXX
compObject = command.completer.call(command, args.string, special, args, context); compObject = command.completer.call(command, args.string, special, args, context);
liberator.dump(compObject);
if (compObject instanceof Array) // for now at least, let completion functions return arrays instead of objects if (compObject instanceof Array) // for now at least, let completion functions return arrays instead of objects
compObject = { start: compObject[0], items: compObject[1] }; compObject = { start: compObject[0], items: compObject[1] };
if (compObject == null)
compObject = { start: context.offset, items: context.items };
else
compObject.start += context.offset;
if (args.completions) if (args.completions)
{ {
if (!compObject.items.length) let argContext = context.fork("args", args.completionStart);
compObject.start = args.completeStart + context.offset; argContext.title = [args.completeOpt || "Options"];
if (args.completeStart + context.offset == compObject.start) argContext.items = args.completions;
compObject.items = args.completions.concat(compObject.items);
} }
liberator.dump(compObject); if (compObject != null)
liberator.dump("\n"); {
context.advance(compObject.start);
context.title = ["Completions"];
context.items = compObject.items;
} }
} }
return compObject; //liberator.dump([[v.name, v.offset, v.items.length, v.hasItems] for each (v in context.contexts)]);
}
}, },
// TODO: support file:// and \ or / path separators on both platforms // TODO: support file:// and \ or / path separators on both platforms
// if "tail" is true, only return names without any directory components // if "tail" is true, only return names without any directory components
file: function file(filter, tail) file: function file(context, tail)
{ {
let [, dir, compl] = filter.match(/^((?:.*[\/\\])?)(.*?)$/); let [dir] = context.filter.match(/^(?:.*[\/\\])?/);
// dir == "" is expanded inside readDirectory to the current dir // dir == "" is expanded inside readDirectory to the current dir
let generate = function () let generate = function ()
@@ -1085,37 +1127,35 @@ function Completion() //{{{
return mapped; return mapped;
}; };
context.title = ["Path", "Type"];
if (tail) if (tail)
return [dir.length, this.cached("file-" + dir, compl, generate, "filter", [true])]; context.advance(dir.length);
else context.items = this.cached("file-" + dir, context.filter, generate, "filter", true);
return [0, this.cached("file-" + dir, filter, generate, "filter", [true])];
}, },
help: function help(filter) help: function help(filter)
{ {
var files = config.helpFiles; let res = [];
var res = [];
for (let i = 0; i < files.length; i++) for (let [, file] in Iterator(config.helpFiles))
{ {
try try
{ {
var xmlhttp = new XMLHttpRequest(); var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "chrome://liberator/locale/" + files[i], false); xmlhttp.open("GET", "chrome://liberator/locale/" + file, false);
xmlhttp.send(null); xmlhttp.send(null);
} }
catch (e) catch (e)
{ {
liberator.log("Error opening chrome://liberator/locale/" + files[i], 1); liberator.log("Error opening chrome://liberator/locale/" + file, 1);
continue; continue;
} }
var doc = xmlhttp.responseXML; let doc = xmlhttp.responseXML;
var elems = doc.getElementsByClassName("tag"); res.push(Array.map(doc.getElementsByClassName("tag"),
for (let j = 0; j < elems.length; j++) function (elem) [elem.textContent, file]));
res.push([elems[j].textContent, files[i]]);
} }
return [0, this.filter(res, filter)]; return [0, this.filter(util.Array.flatten(res), filter)];
}, },
highlightGroup: function highlightGroup(filter) commands.get("highlight").completer(filter), // XXX highlightGroup: function highlightGroup(filter) commands.get("highlight").completer(filter), // XXX
@@ -1140,11 +1180,14 @@ function Completion() //{{{
preference: function preference(filter) commands.get("set").completer(filter, true), // XXX preference: function preference(filter) commands.get("set").completer(filter, true), // XXX
search: function search(filter) search: function search(context)
{ {
let [, keyword, args] = filter.match(/^\s*(\S*)\s*(.*)/); let [, keyword, args] = context.filter.match(/^\s*(\S*)\s*(.*)/);
let keywords = bookmarks.getKeywords(); let keywords = bookmarks.getKeywords();
let engines = this.filter(keywords.concat(bookmarks.getSearchEngines()), filter, false, true); let engines = this.filter(keywords.concat(bookmarks.getSearchEngines()), context.filter, false, true);
context.title = ["Search Keywords"];
context.items = engines;
// NOTE: While i like the result of the code, due to the History simplification // NOTE: While i like the result of the code, due to the History simplification
// that code is too slow to be here. We might use a direct Places History query instead for better speed // that code is too slow to be here. We might use a direct Places History query instead for better speed
@@ -1172,58 +1215,50 @@ function Completion() //{{{
// let searches = this.cached("searches-" + keyword, args, generate, "filter", [false, true]); // let searches = this.cached("searches-" + keyword, args, generate, "filter", [false, true]);
// searches = searches.map(function (a) (a = a.concat(), a[0] = keyword + " " + a[0], a)); // searches = searches.map(function (a) (a = a.concat(), a[0] = keyword + " " + a[0], a));
// return [0, searches.concat(engines)]; // return [0, searches.concat(engines)];
return [0, engines];
}, },
// XXX: Move to bookmarks.js? // XXX: Move to bookmarks.js?
searchEngineSuggest: function searchEngineSuggest(filter, engineAliases) searchEngineSuggest: function (context, engineAliases)
{ {
this.filterString = filter; this.filterString = context.filter;
if (!filter) if (!filter)
return [0, []]; return [0, []];
var engineList = (engineAliases || options["suggestengines"]).split(","); let engineList = (engineAliases || options["suggestengines"] || "google").split(",");
var responseType = "application/x-suggestions+json"; let responseType = "application/x-suggestions+json";
var ss = Components.classes["@mozilla.org/browser/search-service;1"] let ss = Components.classes["@mozilla.org/browser/search-service;1"]
.getService(Components.interfaces.nsIBrowserSearchService); .getService(Components.interfaces.nsIBrowserSearchService);
let matches = query.match(RegExp("^\s*(" + name + "\\s+)(.*)$")) || [];
if (matches[1])
context.advance(matches[1].length);
query = context.filter;
var completions = []; let completions = [];
engineList.forEach(function (name) { engineList.forEach(function (name) {
var query = filter; let engine = ss.getEngineByAlias(name);
var queryURI;
var engine = ss.getEngineByAlias(name);
var reg = new RegExp("^\s*(" + name + "\\s+)(.*)$");
var matches = query.match(reg);
if (matches)
query = matches[2];
if (engine && engine.supportsResponseType(responseType)) if (engine && engine.supportsResponseType(responseType))
queryURI = engine.getSubmission(query, responseType).uri.asciiSpec; var queryURI = engine.getSubmission(query, responseType).uri.asciiSpec;
else else
return [0, []]; return;
var xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open("GET", queryURI, false); xhr.open("GET", queryURI, false);
xhr.send(null); xhr.send(null);
var json = Components.classes["@mozilla.org/dom/json;1"] let json = Components.classes["@mozilla.org/dom/json;1"]
.createInstance(Components.interfaces.nsIJSON); .createInstance(Components.interfaces.nsIJSON);
var results = json.decode(xhr.responseText)[1]; let results = json.decode(xhr.responseText)[1];
if (!results) if (!results)
return [0, []]; return;
results.forEach(function (item) { let ctxt = context.fork(engine.name, (matches[1] || "").length);
ctxt.title = [engine.name + " Suggestions"];
// make sure we receive strings, otherwise a man-in-the-middle attack // make sure we receive strings, otherwise a man-in-the-middle attack
// could return objects which toString() method could be called to // could return objects which toString() method could be called to
// execute untrusted code // execute untrusted code
if (typeof item != "string") ctxt.items = [[item, ""] for ([k, item] in results) if (typeof item == "string")];
return [0, []];
completions.push([(matches ? matches[1] : "") + item, engine.name + " suggestion"]);
}); });
});
return [0, completions];
}, },
shellCommand: function shellCommand(filter) shellCommand: function shellCommand(filter)
@@ -1288,84 +1323,67 @@ function Completion() //{{{
// may consist of search engines, filenames, bookmarks and history, // may consist of search engines, filenames, bookmarks and history,
// depending on the 'complete' option // depending on the 'complete' option
// if the 'complete' argument is passed like "h", it temporarily overrides the complete option // if the 'complete' argument is passed like "h", it temporarily overrides the complete option
url: function url(filter, complete) url: function url(context, complete)
{ {
function getMoreItems(count, maxTime) this.filterString = context.filter;
{
maxTime = maxTime || 5000; // maximum time to wait, default 5 sec
count = count || 10;
var completions = [];
historyResult = null;
let then = new Date().getTime();
for (let now = then; now - then < maxTime; now = new Date().getTime())
{
liberator.threadYield();
if (!historyResult)
continue;
if (historyResult.searchResult == historyResult.RESULT_SUCCESS ||
historyResult.searchResult == historyResult.RESULT_NOMATCH ||
(historyResult.searchResult == historyResult.RESULT_SUCCESS_ONGOING &&
historyResult.matchCount >= count + numLocationCompletions))
{
//liberator.dump("Got " + historyResult.matchCount + " more results after " + (now - then) + " ms with result: " + historyResult.searchResult);
//completionService.stopSearch();
for (let i in util.range(numLocationCompletions, historyResult.matchCount))
completions.push([historyResult.getValueAt(i),
historyResult.getCommentAt(i),
historyResult.getImageAt(i)]);
numLocationCompletions = historyResult.matchCount;
break;
}
}
return completions;
}
this.filterString = filter;
var completions = [];
var numLocationCompletions = 0; // how many async completions did we already return to the caller? var numLocationCompletions = 0; // how many async completions did we already return to the caller?
var start = 0; var start = 0;
var skip = filter.match("^(.*" + options["urlseparator"] + ")(.*)"); // start after the last 'urlseparator' var skip = context.filter.match("^.*" + options["urlseparator"]); // start after the last 'urlseparator'
if (skip) if (skip)
{ context.advance(skip[0].length);
start += skip[1].length;
filter = skip[2];
}
var cpt = complete || options["complete"]; let opts = {
var suggestEngineAlias = options["suggestengines"] || "google"; s: this.search,
// join all completion arrays together f: this.file,
for (let c in util.Array.iterator(cpt)) S: this.searchEngineSuggest,
b: function (context)
{ {
if (c == "s") context.title = ["Bookmark", "Title"];
completions = completions.concat(this.search(filter)[1]); context.items = bookmarks.get(context.filter)
else if (c == "f") },
completions = completions.concat(this.file(filter, false)[1]); l: function (context)
else if (c == "S")
completions = completions.concat(this.searchEngineSuggest(filter, suggestEngineAlias)[1]);
else if (c == "b")
completions = completions.concat(bookmarks.get(filter));
else if (c == "l" && completionService) // add completions like Firefox's smart location bar
{ {
historyCache = []; if (!completionService)
completionCache = completions.slice(); // make copy of current results return
context.title = ["Smart Completions"];
context.incomplete = true;
if (context.items.length)
context.hasItems = true; // XXX
let timer = new util.Timer(50, 100, function () {
let result = context.result;
context.items = [
[result.getValueAt(i), result.getCommentAt(i), result.getImageAt(i)]
for (i in util.range(0, result.matchCount))
];
context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING;
let filter = context.filter;
context.items.forEach(function ([item]) buildSubstrings(item, filter));
});
completionService.stopSearch(); completionService.stopSearch();
completionService.startSearch(filter, "", historyResult, { completionService.startSearch(context.filter, "", context.result, {
onSearchResult: function onSearchResult(search, result) { onSearchResult: function onSearchResult(search, result) {
historyResult = result; context.result = result;
//liberator.dump("Search result in " + historyResult.matchCount + " results with retval: " + historyResult.searchResult); timer.tell();
historyTimer.tell();
if (result.searchResult <= result.RESULT_SUCCESS) if (result.searchResult <= result.RESULT_SUCCESS)
historyTimer.flush(); timer.flush();
} }
}); });
} }
} };
Array.forEach(complete || options["complete"],
function (c) context.fork(c, 0, opts[c], completion));
},
// TODO: incomplete result should be set conditionally // FIXME: Temporary
return { start: start, items: completions, getMoreItems: getMoreItems, incompleteResult: true }; _url: function (filter, complete)
{
let context = new CompletionContext({
selection: { getRangeAt: function () ({ startOffset: filter.length }) },
rootElement: { textContent: filter }
});
this.url(context, complete);
return context.allItems;
}, },
userCommand: function userCommand(filter) userCommand: function userCommand(filter)

View File

@@ -218,13 +218,13 @@ function AutoCommands() //{{{
var list = template.generic( var list = template.generic(
<table> <table>
<tr> <tr class="hl-Title">
<td class="hl-Title" colspan="2">----- Auto Commands -----</td> <td colspan="2">----- Auto Commands -----</td>
</tr> </tr>
{ {
template.map(cmds, function ([event, items]) template.map(cmds, function ([event, items])
<tr> <tr class="hl-Title">
<td class="hl-Title" colspan="2">{event}</td> <td colspan="2">{event}</td>
</tr> </tr>
+ +
template.map(items, function (item) template.map(items, function (item)
@@ -562,9 +562,7 @@ function Events() //{{{
liberator.echoerr("Interrupted"); liberator.echoerr("Interrupted");
else else
liberator.echoerr("Processing " + event.type + " event: " + (e.echoerr || e)); liberator.echoerr("Processing " + event.type + " event: " + (e.echoerr || e));
liberator.dump(e); liberator.reportError(e);
if (Components.utils.reportError)
Components.utils.reportError(e);
} }
}; };
} }

View File

@@ -208,7 +208,7 @@ function IO() //{{{
}, },
{ {
argCount: "?", argCount: "?",
completer: function (filter) completion.file(filter, true), completer: function (filter, bang, args, context) completion.file(context, true),
literal: true literal: true
}); });
@@ -268,7 +268,7 @@ function IO() //{{{
{ {
argCount: "?", argCount: "?",
bang: true, bang: true,
completer: function (filter) completion.file(filter, true) completer: function (filter, bang, args, context) completion.file(context, true)
}); });
commands.add(["runt[ime]"], commands.add(["runt[ime]"],
@@ -301,7 +301,7 @@ function IO() //{{{
{ {
argCount: "1", argCount: "1",
bang: true, bang: true,
completer: function (filter) completion.file(filter, true) completer: function (filter, bang, args, context) completion.file(context, true)
}); });
commands.add(["!", "run"], commands.add(["!", "run"],
@@ -893,8 +893,7 @@ lookup:
catch (e) catch (e)
{ {
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e); let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
if (Components.utils.reportError) liberator.reportError(e);
Components.utils.reportError(e);
if (!silent) if (!silent)
liberator.echoerr(message); liberator.echoerr(message);
} }

View File

@@ -57,9 +57,7 @@ const liberator = (function () //{{{
catch (e) catch (e)
{ {
toJavaScriptConsole(); toJavaScriptConsole();
if (Components.utils.reportError) liberator.reportError(e);
Components.utils.reportError(e);
liberator.dump(e);
} }
} }
@@ -1087,6 +1085,23 @@ const liberator = (function () //{{{
goQuitApplication(); goQuitApplication();
}, },
reportError: function (error)
{
if (Components.utils.reportError)
Components.utils.reportError(error);
let obj = {
toString: function () error.toString(),
stack: { toString: function () "\n" + error.stack.replace(/^/mg, "\t") }
};
for (let [k, v] in Iterator(error))
{
if (!(k in obj))
obj[k] = v;
}
liberator.dump(obj);
liberator.dump("");
},
restart: function () restart: function ()
{ {
const nsIAppStartup = Components.interfaces.nsIAppStartup; const nsIAppStartup = Components.interfaces.nsIAppStartup;

View File

@@ -548,7 +548,7 @@ function Tabs() //{{{
}, },
{ {
bang: true, bang: true,
completer: function (filter) completion.url(filter) completer: function (filter, bang, args, context) completion.url(context)
}); });
commands.add(["tabde[tach]"], commands.add(["tabde[tach]"],

View File

@@ -62,7 +62,7 @@ const template = {
// that we cannot even try/catch it // that we cannot even try/catch it
if (/^\[JavaPackage.*\]$/.test(arg)) if (/^\[JavaPackage.*\]$/.test(arg))
return <>[JavaPackage]</>; return <>[JavaPackage]</>;
if (processStrings) if (processStrings && false)
arg = String(arg).replace("\n", "\\n", "g"); arg = String(arg).replace("\n", "\\n", "g");
return <span class="hl-Object">{arg}</span>; return <span class="hl-Object">{arg}</span>;
default: default:

View File

@@ -117,7 +117,8 @@ function CommandLine() //{{{
if (events.feedingKeys) if (events.feedingKeys)
return; return;
completionContext.reset(); completionContext.reset();
commandline.setCompletions(completion.ex(completionContext)); completionContext.fork("ex", 0, completion.ex, completion);
commandline.setCompletions(completionContext.allItems);
}); });
// the containing box for the promptWidget and commandWidget // the containing box for the promptWidget and commandWidget
@@ -156,7 +157,8 @@ function CommandLine() //{{{
liberator.registerCallback("complete", modes.EX, function (str) { liberator.registerCallback("complete", modes.EX, function (str) {
completionContext.reset(); completionContext.reset();
completionContext.tabPressed = true; completionContext.tabPressed = true;
return completion.ex(completionContext); completionContext.fork("ex", 0, completion.ex, completion);
return completionContext.allItems;
}); });
liberator.registerCallback("change", modes.EX, function (command) { liberator.registerCallback("change", modes.EX, function (command) {
completion.cancel(); // cancel any previous completion function completion.cancel(); // cancel any previous completion function
@@ -584,6 +586,10 @@ function CommandLine() //{{{
commandWidget.focus(); commandWidget.focus();
completionContext = new CompletionContext(commandWidget.inputField.editor); completionContext = new CompletionContext(commandWidget.inputField.editor);
completionContext.onUpdate = function ()
{
commandline.setCompletions(this.allItems);
};
// open the completion list automatically if wanted // open the completion list automatically if wanted
if (/\s/.test(cmd) && if (/\s/.test(cmd) &&
options.get("wildoptions").has("auto") && options.get("wildoptions").has("auto") &&
@@ -815,17 +821,12 @@ function CommandLine() //{{{
historyIndex = UNINITIALIZED; historyIndex = UNINITIALIZED;
// TODO: call just once, and not on each <Tab> // TODO: call just once, and not on each <Tab>
var wim = options["wildmode"].split(","); let wildmode = options["wildmode"].split(",");
var hasList = false; let wildType = wildmode[Math.min(wildIndex++, wildmode.length - 1)];
var longest = false;
var full = false; let hasList = /^list(:|$)/.test(wildType);
var wildType = wim[wildIndex++] || wim[wim.length - 1]; let longest = /(^|:)longest$/.test(wildType);
if (wildType == "list" || wildType == "list:full" || wildType == "list:longest") let full = !longest && /(^|:)full/.test(wildType);
hasList = true;
if (wildType == "longest" || wildType == "list:longest")
longest = true;
else if (wildType == "full" || wildType == "list:full")
full = true;
// we need to build our completion list first // we need to build our completion list first
if (completionIndex == UNINITIALIZED) if (completionIndex == UNINITIALIZED)
@@ -837,20 +838,22 @@ function CommandLine() //{{{
// sort the completion list // sort the completion list
// TODO: might not make sense anymore with our advanced completions, we should just sort when necessary // TODO: might not make sense anymore with our advanced completions, we should just sort when necessary
if (options.get("wildoptions").has("sort")) // FIXME: CompletionContext
completions.items.sort(function (a, b) String.localeCompare(a[0], b[0])); //if (options.get("wildoptions").has("sort"))
// completions.items.sort(function (a, b) String.localeCompare(a[0], b[0]));
completionList.setItems(completions.items); completionList.setItems(completionContext.allItems);
} }
if (completions.items.length == 0) if (completions.items.length == 0)
{ {
// try to fetch more items, if possible // Wait for items to come available
// TODO: also use that code when we DO have completions but too few // TODO: also use that code when we DO have completions but too few
if (completions.getMoreItems) let end = Date.now() + 5000;
while (completionContext.incomplete && completions.items.length == 0 && Date.now() < end)
{ {
completions.items = completions.items.concat(completions.getMoreItems(1)); liberator.threadYield();
completionList.setItems(completions.items); completions = completionContext.allItems;
} }
if (completions.items.length == 0) // still not more matches if (completions.items.length == 0) // still not more matches
@@ -895,11 +898,11 @@ function CommandLine() //{{{
{ {
var compl = null; var compl = null;
if (longest && completions.items.length > 1) if (longest && completions.items.length > 1)
compl = completion.getLongestSubstring(); compl = completion.longestSubstring;
else if (full) else if (full)
compl = completions.items[completionIndex][0]; compl = completions.items[completionIndex].text;
else if (completions.items.length == 1) else if (completions.items.length == 1)
compl = completions.items[0][0]; compl = completions.items[0].text;
if (compl) if (compl)
{ {
@@ -1236,8 +1239,9 @@ function CommandLine() //{{{
return; return;
// don't show an empty result, if we are just waiting for data to arrive // don't show an empty result, if we are just waiting for data to arrive
if (newCompletions.incompleteResult && newCompletions.items.length == 0) // FIXME: Maybe. CompletionContext
return; //if (newCompletions.incompleteResult && newCompletions.items.length == 0)
// return;
completionList.setItems(newCompletions.items); completionList.setItems(newCompletions.items);
@@ -1332,32 +1336,30 @@ function ItemList(id) //{{{
// TODO: move to completions? // TODO: move to completions?
function createDefaultRow(item, dom) function createDefaultRow(item, dom)
{ {
if (item instanceof Array) let { text: text, description: description, icon: icon } = item;
item = { text: item[0], description: item[1], icon: item[2] };
/* Kludge until we have completion contexts. */ /* Kludge until we have completion contexts. */
let map = completion.filterMap; let map = completion.filterMap;
if (map) if (map)
{ {
item.text = map[0] ? map[0](item.text) : item.text; text = map[0] ? map[0](text) : text;
item.description = map[1] ? map[1](item.description) : item.description; description = map[1] ? map[1](description) : description;
} }
/* Obviously, ItemList shouldn't know or care about this. */ /* Obviously, ItemList shouldn't know or care about this. */
let filter = completion.filterString; let filter = completion.filterString;
if (filter) if (filter)
{ {
item.text = template.highlightFilter(item.text, filter); text = template.highlightFilter(text, filter);
item.description = template.highlightFilter(item.description, filter); description = template.highlightFilter(description, filter);
} }
if (typeof item.icon == "function") if (typeof icon == "function")
item.icon = item.icon(); icon = icon();
let row = let row =
<ul class="hl-CompItem"> <ul class="hl-CompItem">
<li class="hl-CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</li> <li class="hl-CompIcon">{icon ? <img src={icon}/> : <></>}</li>
<li class="hl-CompResult">{item.text}</li> <li class="hl-CompResult">{text}</li>
<li class="hl-CompDesc">{item.description}</li> <li class="hl-CompDesc">{description}</li>
</ul>; </ul>;
if (dom) if (dom)

View File

@@ -130,10 +130,7 @@ const util = { //{{{
}; };
}, },
compareIgnoreCase: function (a, b) compareIgnoreCase: function (a, b) String.localeCompare(a.toLowerCase(), b.toLowerCase()),
{
return String.localeCompare(a.toLowerCase(), b.toLowerCase());
},
clip: function (str, length) clip: function (str, length)
{ {

View File

@@ -293,7 +293,7 @@ const config = { //{{{
}, },
{ {
bang: true, bang: true,
completer: function (filter) completion.url(filter) completer: function (filter, args, bang, context) completion.url(context)
}); });
commands.add(["redr[aw]"], commands.add(["redr[aw]"],
@@ -364,7 +364,7 @@ const config = { //{{{
else else
liberator.open("about:blank", liberator.NEW_WINDOW); liberator.open("about:blank", liberator.NEW_WINDOW);
}, },
{ completer: function (filter) completion.url(filter) }); { completer: function (filter, bang, args, context) completion.url(context) });
/////////////////////////////////////////////////////////////////////////////}}} /////////////////////////////////////////////////////////////////////////////}}}
////////////////////// OPTIONS ///////////////////////////////////////////////// ////////////////////// OPTIONS /////////////////////////////////////////////////