diff --git a/content/completion.js b/content/completion.js
index 159af919..e1c8fc72 100644
--- a/content/completion.js
+++ b/content/completion.js
@@ -32,6 +32,82 @@ modules._cleanEval = function (__liberator_eval_arg, __liberator_eval_tmp)
return window.eval(__liberator_eval_arg);
}
+function CompletionContext(editor, offset)
+{
+ if (editor instanceof arguments.callee)
+ {
+ let parent = editor;
+ this.parent = parent;
+ this.editor = parent.editor;
+ this.offset = parent.offset + (offset || 0);
+ this.__defineGetter__("tabPressed", function () this.parent.tabPressed);
+ this.contexts = this.parent.contexts;
+ }
+ else
+ {
+ this.editor = editor;
+ this.offset = offset || 0;
+ this.tabPressed = false;
+ this.contexts = {};
+ }
+ this.selectionTypes = {};
+}
+CompletionContext.prototype = {
+ get caret() this.editor.selection.getRangeAt(0).startOffset - this.offset,
+
+ get filter() this.value.substr(this.offset, this.caret),
+
+ get value() this.editor.rootElement.textContent,
+
+ advance: function (count)
+ {
+ this.offset += count;
+ },
+
+ fork: function (name, offset)
+ {
+ if (!(name in this.contexts))
+ this.contexts[name] = new CompletionContext(this);
+ this.contexts[name].offset = this.offset + offset;
+ return this.contexts[name];
+ },
+
+ highlight: function (start, length, type)
+ {
+ try // Firefox <3.1 doesn't have repaintSelection
+ {
+ this.selectionTypes[type] = null;
+ const selType = Components.interfaces.nsISelectionController["SELECTION_" + type];
+ const editor = this.editor;
+ let sel = editor.selectionController.getSelection(selType);
+ if (length == 0)
+ {
+ sel.removeAllRanges();
+ editor.selectionController.repaintSelection(selType);
+ return;
+ }
+
+ let range = editor.selection.getRangeAt(0).cloneRange();
+ range.setStart(range.startContainer, this.offset + start);
+ range.setEnd(range.startContainer, this.offset + start + length);
+ sel.addRange(range);
+ editor.selectionController.repaintSelection(selType);
+ }
+ catch (e) {}
+ },
+
+ reset: function ()
+ {
+ // Not ideal.
+ for (let type in this.selectionTypes)
+ this.highlight(0, 0, type);
+ this.selectionTypes = {};
+ this.tabPressed = false;
+ this.offset = 0;
+ },
+
+}
+
function Completion() //{{{
{
////////////////////////////////////////////////////////////////////////////////
@@ -235,6 +311,7 @@ function Completion() //{{{
function buildStack(start)
{
+ let self = this;
/* Push and pop the stack, maintaining references to 'top' and 'last'. */
let push = function push(arg)
{
@@ -246,12 +323,12 @@ function Completion() //{{{
{
if (top[CHAR] != arg)
{
- commandline.highlight(top[OFFSET] + 1, i + 1, "SPELLCHECK");
- commandline.highlight(top[OFFSET], top[OFFSET] + 1, "FIND");
+ self.context.highlight(top[OFFSET] + 1, i - top[OFFSET], "SPELLCHECK");
+ self.context.highlight(top[OFFSET], 1, "FIND");
throw new Error("Invalid JS");
}
if (i == str.length - 1)
- commandline.highlight(top[OFFSET], top[OFFSET] + 1, "FIND");
+ self.context.highlight(top[OFFSET], 1, "FIND");
// The closing character of this stack frame will have pushed a new
// statement, leaving us with an empty statement. This doesn't matter,
// now, as we simply throw away the frame when we pop it, but it may later.
@@ -354,20 +431,22 @@ function Completion() //{{{
lastIdx = i;
}
- this.complete = function complete(string)
+ this.complete = function complete(context)
{
- commandline.highlight(0, 0, "SPELLCHECK");
- commandline.highlight(0, 0, "FIND");
+ this.context = context;
+ let string = context.filter;
let self = this;
try
{
continuing = lastIdx && string.indexOf(str) == 0;
str = string;
- buildStack(continuing ? lastIdx : 0);
+ buildStack.call(this, continuing ? lastIdx : 0);
}
catch (e)
{
+ if (e.message != "Invalid JS")
+ Components.utils.reportError(e);
// liberator.dump(util.escapeString(string) + ": " + e + "\n" + e.stack);
lastIdx = 0;
return [0, []];
@@ -891,12 +970,12 @@ function Completion() //{{{
return [0, completion.filter(schemes, filter)];
},
- command: function command(filter)
+ command: function command(context)
{
- if (!filter)
- return [0, [[c.name, c.description] for (c in commands)]];
+ if (!context.filter)
+ return { start: 0, items: [[c.name, c.description] for (c in commands)] };
else
- return [0, this.filter([[c.longNames, c.description] for (c in commands)], filter, true)];
+ return { start: 0, items: this.filter([[c.longNames, c.description] for (c in commands)], context.filter, true) };
},
dialog: function dialog(filter) [0, this.filter(config.dialogs, filter)],
@@ -919,39 +998,57 @@ function Completion() //{{{
},
// provides completions for ex commands, including their arguments
- ex: function ex(str)
+ ex: function ex(context)
{
this.filterMap = null;
this.filterString = "";
- this.parenMatch = null;
substrings = [];
- if (str.indexOf(cacheFilter["ex"]) != 0)
+ if (context.filter.indexOf(cacheFilter["ex"]) != 0)
{
cacheFilter = {};
cacheResults = {};
}
- cacheFilter["ex"] = str;
+ cacheFilter["ex"] = context.filter;
// if there is no space between the command name and the cursor
// then get completions of the command name
- var [count, cmd, special, args] = commands.parseCommand(str);
- var matches = str.match(/^(:*\d*)\w*$/);
- if (matches)
- return { start: matches[1].length, items: this.command(cmd)[1] };
+ let [count, cmd, special, args] = commands.parseCommand(context.filter);
+ let [, prefix, junk] = context.filter.match(/^(:*\d*)\w*(.?)/) || [];
+ context.advance(junk.length)
+ if (!junk)
+ return this.command(context);
// dynamically get completions as specified with the command's completer function
- var compObject = { start: 0, completions: [] };
- var exLength = 0;
- var command = commands.get(cmd);
+ let command = commands.get(cmd);
+ let compObject = { start: 0, items: [] };
if (command && command.completer)
{
- matches = str.match(/^:*\d*(?:\w+[\s!]|!)\s*/);
- exLength = matches ? matches[0].length : 0;
- compObject = command.completer.call(command, args, special);
- if (compObject instanceof Array) // for now at least, let completion functions return arrays instead of objects
- compObject = { start: compObject[0], items: compObject[1] };
+ [prefix] = context.filter.match(/^(?:\w+[\s!]|!)\s*/);
+ context.advance((prefix || "").length);
+ args = command.parseArgs(context.filter, true);
+ liberator.dump(args);
+ if (args)
+ {
+ // XXX, XXX, XXX
+ 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
+ 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 (!compObject.items.length)
+ compObject.start = args.completeStart + context.offset;
+ if (args.completeStart + context.offset == compObject.start)
+ compObject.items = args.completions.concat(compObject.items);
+ }
+ liberator.dump(compObject);
+ liberator.dump("\n");
+ }
}
- compObject.start += exLength;
return compObject;
},
@@ -1025,9 +1122,9 @@ function Completion() //{{{
get javascriptCompleter() javascript,
- javascript: function _javascript(str)
+ javascript: function (context)
{
- return javascript.complete(str);
+ return javascript.complete(context);
},
macro: function macro(filter)
diff --git a/content/liberator.js b/content/liberator.js
index 4afd8c74..b9636e71 100644
--- a/content/liberator.js
+++ b/content/liberator.js
@@ -336,7 +336,7 @@ const liberator = (function () //{{{
},
{
bang: true,
- completer: function (filter) completion.javascript(filter),
+ completer: function (filter, bang, args, context) completion.javascript(context),
hereDoc: true
});
@@ -1161,10 +1161,6 @@ const liberator = (function () //{{{
liberator.log("All modules loaded", 3);
- // TODO: move elsewhere
- liberator.registerCallback("submit", modes.EX, function (command) { liberator.execute(command); });
- liberator.registerCallback("complete", modes.EX, function (str) { return completion.ex(str); });
-
// first time intro message
const firstTime = "extensions." + config.name.toLowerCase() + ".firsttime";
if (options.getPref(firstTime, true))
diff --git a/content/style.js b/content/style.js
index 666fdd38..aabb000b 100644
--- a/content/style.js
+++ b/content/style.js
@@ -429,20 +429,20 @@ liberator.registerObserver("load_commands", function ()
{
argCount: "2",
bang: true,
- completer: function (filter) {
- let args = this.parseArgs(filter, true);
- let compl = args ? args.completions : [];
- if (args && args.completeOpt)
- return [args.completeStart, compl];
-
- try
+ completer: function (filter, bang, args) {
+ let compl = [];
+ if (args.completeArg == 0)
{
- compl.push([content.location.host, "Current Host"]);
- compl.push([content.location.href, "Current URL"]);
+ try
+ {
+ compl.push([content.location.host, "Current Host"]);
+ compl.push([content.location.href, "Current URL"]);
+ }
+ catch (e) {}
+ comp = compl.concat([[s, ""] for each (s in styles.sites)])
+ return [0, completion.filter(compl, args.arguments[0])];
}
- catch (e) {}
- comp = compl.concat([[s, ""] for each (s in styles.sites)])
- return [0, completion.filter(compl, filter)];
+ return [0, []];
},
hereDoc: true,
literal: true,
@@ -473,8 +473,8 @@ liberator.registerObserver("load_commands", function ()
.concat([[s, ""] for each (s in styles.sites)])
, filter)],
literal: true,
- options: [[["-index", "-i"], commands.OPTION_INT],
- [["-name", "-n"], commands.OPTION_STRING]]
+ options: [[["-index", "-i"], commands.OPTION_INT, null, function () [[k, v.name || v.sites.join(",") + " " + v.css] for ([k, v] in Iterator(styles.userNames))]],
+ [["-name", "-n"], commands.OPTION_STRING, null, function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))]]]
});
commands.add(["hi[ghlight]"],
diff --git a/content/ui.js b/content/ui.js
index 7b4e5e62..481d7631 100644
--- a/content/ui.js
+++ b/content/ui.js
@@ -98,6 +98,7 @@ function CommandLine() //{{{
var completionList = new ItemList("liberator-completions");
var completions = { start: 0, items: [] };
+ var completionContext = null;
// for the example command "open sometext| othertext" (| is the cursor pos):
var completionPrefix = ""; // will be: "open sometext"
var completionPostfix = ""; // will be: " othertext"
@@ -112,11 +113,11 @@ function CommandLine() //{{{
else
statusline.updateProgress("match " + (completionIndex + 1) + " of " + completions.items.length);
});
- var autocompleteTimer = new util.Timer(201, 300, function (command) {
+ var autocompleteTimer = new util.Timer(201, 300, function (tabPressed) {
if (events.feedingKeys)
return;
-
- commandline.setCompletions(completion.ex(command));
+ completionContext.reset();
+ commandline.setCompletions(completion.ex(completionContext));
});
// the containing box for the promptWidget and commandWidget
@@ -151,10 +152,16 @@ function CommandLine() //{{{
var promptChangeCallback = null;
var promptCompleter = null;
+ liberator.registerCallback("submit", modes.EX, function (command) { liberator.execute(command); });
+ liberator.registerCallback("complete", modes.EX, function (str) {
+ completionContext.reset();
+ completionContext.tabPressed = true;
+ return completion.ex(completionContext);
+ });
liberator.registerCallback("change", modes.EX, function (command) {
completion.cancel(); // cancel any previous completion function
if (options.get("wildoptions").has("auto"))
- autocompleteTimer.tell(command);
+ autocompleteTimer.tell(false);
else
completionIndex = UNINITIALIZED;
});
@@ -209,9 +216,7 @@ function CommandLine() //{{{
setPrompt("");
setCommand(str);
if (!forceSingle &&
- commandWidget.inputField.editor
- .selection.getRangeAt(0)
- .startContainer.parentNode
+ commandWidget.inputField.editor.rootElement
.scrollWidth > commandWidget.inputField.scrollWidth)
{
setCommand("");
@@ -493,7 +498,7 @@ function CommandLine() //{{{
if (str != null)
command.action(str);
},
- { completer: function (filter) completion.javascript(filter) });
+ { completer: function (filter, bang, args, context) completion.javascript(context) });
});
commands.add(["mes[sages]"],
@@ -542,6 +547,8 @@ function CommandLine() //{{{
// FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence
APPEND_TO_MESSAGES : 1 << 3, // add the string to the message history
+ get completionContext() completionContext,
+
get mode() (modes.extended == modes.EX) ? "cmd" : "search",
get silent() silent,
@@ -576,11 +583,12 @@ function CommandLine() //{{{
commandWidget.focus();
+ completionContext = new CompletionContext(commandWidget.inputField.editor);
// open the completion list automatically if wanted
if (/\s/.test(cmd) &&
options.get("wildoptions").has("auto") &&
extendedMode == modes.EX)
- autocompleteTimer.tell(cmd);
+ autocompleteTimer.tell(false);
},
// normally used when pressing esc, does not execute a command
diff --git a/content/util.js b/content/util.js
index 64ee3570..015cde54 100644
--- a/content/util.js
+++ b/content/util.js
@@ -336,7 +336,7 @@ const util = { //{{{
i = parseInt(i);
else if (/^[A-Z_]+$/.test(i))
i = "";
- keys.push([i, <>{key}{noVal ? "" : <>:{value}>}
>]);
+ keys.push([i, <>{key}{noVal ? "" : <>: {value}>}
>]);
}
}
catch (e) {}