mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2026-01-07 21:04:12 +01:00
Clean up completion tabbing.
This commit is contained in:
@@ -225,13 +225,14 @@ CompletionContext.prototype = {
|
||||
{
|
||||
this.hasItems = true;
|
||||
this._generate = arg;
|
||||
liberator.dump(this.name + ": set generate()");
|
||||
//**/ liberator.dump(this.name + ": set generate()");
|
||||
if (this.background && this.regenerate)
|
||||
{
|
||||
//**/ this.__i = (this.__i || 0) + 1;
|
||||
//**/ let self = this;
|
||||
//**/ function dump(msg) liberator.callInMainThread(function () liberator.dump(self.name + ":" + self.__i + ": " + msg));
|
||||
//**/ dump("set generate() regenerating");
|
||||
|
||||
let lock = {};
|
||||
this.cache.backgroundLock = lock;
|
||||
this.incomplete = true;
|
||||
@@ -837,8 +838,6 @@ function Completion() //{{{
|
||||
let res = functions.some(function (idx) idx >= start && idx < end);
|
||||
if (!res || self.context.tabPressed || key in cache.eval)
|
||||
return false;
|
||||
liberator.dump(cache.eval[key]);
|
||||
liberator.dumpStack();
|
||||
self.context.waitingForTab = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -676,9 +676,12 @@ const liberator = (function () //{{{
|
||||
window.dump(("config" in modules && config.name.toLowerCase()) + ": " + message);
|
||||
},
|
||||
|
||||
dumpStack: function (msg)
|
||||
dumpStack: function (msg, frames)
|
||||
{
|
||||
liberator.dump((msg || "") + (new Error()).stack);
|
||||
let stack = Error().stack.replace(/(?:.*\n){2}/, "");
|
||||
if (frames != null)
|
||||
[stack] = stack.match(RegExp("(?:.*\n){0," + frames + "}"));
|
||||
liberator.dump((msg || "Stack") + "\n" + stack);
|
||||
},
|
||||
|
||||
echo: function (str, flags)
|
||||
@@ -965,7 +968,10 @@ const liberator = (function () //{{{
|
||||
io.source(file.path, false);
|
||||
liberator.pluginFiles[file.path] = true;
|
||||
}
|
||||
catch (e) {};
|
||||
catch (e)
|
||||
{
|
||||
liberator.reportError(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -96,133 +96,196 @@ function CommandLine() //{{{
|
||||
|
||||
var silent = false;
|
||||
|
||||
function Completions(context)
|
||||
{
|
||||
let self = this;
|
||||
context.onUpdate = function ()
|
||||
{
|
||||
self.reset();
|
||||
};
|
||||
this.context = context;
|
||||
this.selected = null;
|
||||
this.wildmode = options.get("wildmode");
|
||||
this.itemList = completionList;
|
||||
this.itemList.setItems(context);
|
||||
this.reset();
|
||||
}
|
||||
Completions.prototype = {
|
||||
UP: {},
|
||||
DOWN: {},
|
||||
PAGE_UP: {},
|
||||
PAGE_DOWN: {},
|
||||
RESET: null,
|
||||
|
||||
get completion()
|
||||
{
|
||||
let str = commandline.getCommand();
|
||||
return str.substring(this.prefix.length, str.length - this.suffix.length);
|
||||
},
|
||||
set completion set_completion(completion)
|
||||
{
|
||||
previewClear();
|
||||
let editor = commandWidget.inputField.editor;
|
||||
|
||||
// Change the completion text.
|
||||
// The second line is a hack to deal with some substring
|
||||
// preview corner cases.
|
||||
commandWidget.value = this.prefix + completion + this.suffix;
|
||||
editor.selection.focusNode.textContent = commandWidget.value;
|
||||
|
||||
// Reset the caret to one position after the completion.
|
||||
let range = editor.selection.getRangeAt(0);
|
||||
range.setStart(range.startContainer, this.prefix.length + completion.length);
|
||||
range.collapse(true);
|
||||
},
|
||||
|
||||
get start() this.context.allItems.start,
|
||||
|
||||
get items() this.context.allItems.items,
|
||||
|
||||
get substring() this.context.longestAllSubstring,
|
||||
|
||||
get wildtype() this.wildtypes[this.wildIndex] || "",
|
||||
|
||||
get type() ({
|
||||
list: this.wildmode.checkHas(this.wildtype, "list"),
|
||||
longest: this.wildmode.checkHas(this.wildtype, "longest"),
|
||||
first: this.wildmode.checkHas(this.wildtype, ""),
|
||||
full: this.wildmode.checkHas(this.wildtype, "full")
|
||||
}),
|
||||
|
||||
reset: function reset(show)
|
||||
{
|
||||
this.wildtypes = this.wildmode.values;
|
||||
this.wildIndex = -1;
|
||||
|
||||
this.prefix = this.context.value.substr(0, this.start);
|
||||
this.value = this.context.value.substr(this.start, this.context.caret);
|
||||
this.suffix = this.context.value.substr(this.context.caret);
|
||||
|
||||
if (show)
|
||||
{
|
||||
this.itemList.reset();
|
||||
this.select(this.RESET);
|
||||
this.itemList.show();
|
||||
this.wildIndex = 0;
|
||||
}
|
||||
|
||||
previewSubstring();
|
||||
},
|
||||
|
||||
select: function select(idx)
|
||||
{
|
||||
switch (idx)
|
||||
{
|
||||
case this.UP:
|
||||
if (this.selected == null)
|
||||
idx = this.items.length - 1;
|
||||
else
|
||||
idx = this.selected - 1;
|
||||
break;
|
||||
case this.DOWN:
|
||||
if (this.selected == null)
|
||||
idx = 0;
|
||||
else
|
||||
idx = this.selected + 1;
|
||||
break;
|
||||
case this.RESET:
|
||||
idx = null;
|
||||
break;
|
||||
default: idx = Math.max(0, Math.max(this.items.length - 1, idx));
|
||||
}
|
||||
this.itemList.selectItem(idx);
|
||||
if (idx < 0 || idx >= this.items.length || idx == null)
|
||||
{
|
||||
// Wrapped. Start again.
|
||||
this.selected = null;
|
||||
this.completion = this.value;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.selected = idx;
|
||||
this.completion = this.items[idx].text;
|
||||
}
|
||||
},
|
||||
|
||||
tab: function tab(reverse)
|
||||
{
|
||||
// Check if we need to run the completer.
|
||||
if (this.context.waitingForTab || this.wildIndex == -1)
|
||||
{
|
||||
this.context.reset();
|
||||
this.context.tabPressed = true;
|
||||
liberator.triggerCallback("complete", currentExtendedMode, this.context);
|
||||
this.reset(true);
|
||||
}
|
||||
|
||||
if (this.items.length == 0)
|
||||
{
|
||||
// No items. Wait for any unfinished completers.
|
||||
let end = Date.now() + 5000;
|
||||
while (this.context.incomplete && this.items.length == 0 && Date.now() < end)
|
||||
liberator.threadYield();
|
||||
|
||||
if (this.items.length == 0)
|
||||
return liberator.beep();
|
||||
}
|
||||
|
||||
switch (this.wildtype.replace(/.*:/, ""))
|
||||
{
|
||||
case "":
|
||||
this.select(0);
|
||||
break;
|
||||
case "longest":
|
||||
if (this.items.length > 1)
|
||||
{
|
||||
if (this.substring && this.substring != this.completion)
|
||||
{
|
||||
this.completion = this.substring;
|
||||
liberator.triggerCallback("change", currentExtendedMode, commandline.getCommand());
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Fallthrough
|
||||
case "full":
|
||||
this.select(reverse ? this.UP : this.DOWN)
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.type.list)
|
||||
completionList.show();
|
||||
|
||||
this.wildIndex = Math.max(0, Math.min(this.wildtypes.length - 1, this.wildIndex + 1));
|
||||
|
||||
statusTimer.tell();
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
var completionIndex = UNINITIALIZED;
|
||||
var completions = null;
|
||||
|
||||
var wildIndex = 0; // keep track how often we press <Tab> in a row
|
||||
var startHints = false; // whether we're waiting to start hints mode
|
||||
var lastSubstring = "";
|
||||
|
||||
var statusTimer = new util.Timer(5, 100, function statusTell() {
|
||||
if (completionIndex >= completions.items.length)
|
||||
if (completions.selected == null)
|
||||
statusline.updateProgress("");
|
||||
else
|
||||
statusline.updateProgress("match " + (completionIndex + 1) + " of " + completions.items.length);
|
||||
statusline.updateProgress("match " + (completions.selected + 1) + " of " + completions.items.length);
|
||||
});
|
||||
var autocompleteTimer = new util.Timer(201, 300, function autocompleteTell(tabPressed) {
|
||||
if (events.feedingKeys)
|
||||
if (events.feedingKeys || !completions)
|
||||
return;
|
||||
completionContext.reset();
|
||||
completionContext.fork("ex", 0, completion, "ex");
|
||||
commandline.setCompletions(completionContext.allItems);
|
||||
completions.context.reset();
|
||||
liberator.triggerCallback("complete", currentExtendedMode, completions.context);
|
||||
completions.reset(true);
|
||||
completions.itemList.show();
|
||||
});
|
||||
|
||||
var tabTimer = new util.Timer(10, 10, function tabTell(event) {
|
||||
|
||||
let command = commandline.getCommand();
|
||||
|
||||
// always reset our completion history so up/down keys will start with new values
|
||||
historyIndex = UNINITIALIZED;
|
||||
|
||||
// TODO: call just once, and not on each <Tab>
|
||||
let wildmode = options.get("wildmode");
|
||||
let wildType = wildmode.values[Math.min(wildIndex++, wildmode.values.length - 1)];
|
||||
|
||||
let first = wildmode.value == "";
|
||||
let hasList = wildmode.checkHas(wildType, "list");
|
||||
let longest = wildmode.checkHas(wildType, "longest");
|
||||
let full = first || !longest && wildmode.checkHas(wildType, "full");
|
||||
|
||||
// we need to build our completion list first
|
||||
if (completionIndex == UNINITIALIZED || completionContext.waitingForTab)
|
||||
{
|
||||
completionIndex = -1;
|
||||
completionPrefix = command.substring(0, commandWidget.selectionStart);
|
||||
completionPostfix = command.substring(commandWidget.selectionStart);
|
||||
completions = liberator.triggerCallback("complete", currentExtendedMode, completionPrefix);
|
||||
|
||||
completionList.setItems(completionContext);
|
||||
}
|
||||
|
||||
if (completions.items.length == 0)
|
||||
{
|
||||
// Wait for items to come available
|
||||
// TODO: also use that code when we DO have completions but too few
|
||||
let end = Date.now() + 5000;
|
||||
while (completionContext.incomplete && completions.items.length == 0 && Date.now() < end)
|
||||
{
|
||||
liberator.threadYield();
|
||||
completions = completionContext.allItems;
|
||||
}
|
||||
|
||||
if (completions.items.length == 0) // still not more matches
|
||||
{
|
||||
liberator.beep();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (first)
|
||||
completionIndex = 0;
|
||||
else if (full)
|
||||
{
|
||||
if (event.shiftKey)
|
||||
completionIndex--;
|
||||
else
|
||||
completionIndex++;
|
||||
completionIndex = Math.max(0, Math.min(completions.items.length - 1, completionIndex));
|
||||
|
||||
statusTimer.tell();
|
||||
}
|
||||
|
||||
// the following line is not inside if (hasList) for list:longest,full
|
||||
completionList.selectItem(completionIndex);
|
||||
if (hasList)
|
||||
completionList.show();
|
||||
|
||||
if ((completionIndex == -1 || completionIndex >= completions.items.length) && !longest) // wrapped around matches, reset command line
|
||||
{
|
||||
if (full)
|
||||
setCommand(completionPrefix + completionPostfix);
|
||||
}
|
||||
else
|
||||
{
|
||||
let compl = null;
|
||||
if (longest && completions.items.length > 1)
|
||||
compl = completions.longestSubstring;
|
||||
else if (full)
|
||||
compl = completions.items[completionIndex].text;
|
||||
else if (completions.items.length == 1)
|
||||
compl = completions.items[0].text;
|
||||
|
||||
if (compl)
|
||||
{
|
||||
previewClear();
|
||||
let editor = commandWidget.inputField.editor;
|
||||
|
||||
commandWidget.value = command.substring(0, completions.start) + compl + completionPostfix;
|
||||
editor.selection.focusNode.textContent = commandWidget.value;
|
||||
|
||||
let range = editor.selection.getRangeAt(0);
|
||||
range.setStart(range.startContainer, completions.start + compl.length);
|
||||
range.collapse(true);
|
||||
|
||||
if (longest)
|
||||
liberator.triggerCallback("change", currentExtendedMode, commandline.getCommand());
|
||||
|
||||
// Start a new completion in the next iteration. Useful for commands like :source
|
||||
// RFC: perhaps the command can indicate whether the completion should be restarted
|
||||
// -> should be doable now, since the completion items are objects
|
||||
// Needed for :source to grab another set of completions after a file/directory has been filled out
|
||||
// if (completions.length == 1 && !full)
|
||||
// completionIndex = UNINITIALIZED;
|
||||
}
|
||||
}
|
||||
if (completions)
|
||||
completions.tab(event.shiftKey);
|
||||
});
|
||||
|
||||
// the containing box for the promptWidget and commandWidget
|
||||
@@ -258,18 +321,14 @@ function CommandLine() //{{{
|
||||
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;
|
||||
completionContext.fork("ex", 0, completion, "ex");
|
||||
return completionContext.allItems;
|
||||
liberator.registerCallback("complete", modes.EX, function (context) {
|
||||
context.fork("ex", 0, completion, "ex");
|
||||
});
|
||||
liberator.registerCallback("change", modes.EX, function (command) {
|
||||
completion.cancel(); // cancel any previous completion function
|
||||
if (options.get("wildoptions").has("auto"))
|
||||
autocompleteTimer.tell(false);
|
||||
else
|
||||
completionIndex = UNINITIALIZED;
|
||||
completions.selected = completions.RESET;
|
||||
});
|
||||
|
||||
function closePrompt(value)
|
||||
@@ -286,7 +345,7 @@ function CommandLine() //{{{
|
||||
liberator.registerCallback("change", modes.PROMPT,
|
||||
function (str) { if (promptChangeCallback) return promptChangeCallback(str); });
|
||||
liberator.registerCallback("complete", modes.PROMPT,
|
||||
function (str) { if (promptCompleter) return promptCompleter(str); });
|
||||
function (context) { if (promptCompleter) promptCompleter(context); });
|
||||
|
||||
function setHighlightGroup(group)
|
||||
{
|
||||
@@ -322,21 +381,28 @@ function CommandLine() //{{{
|
||||
}
|
||||
function previewSubstring()
|
||||
{
|
||||
if (!options.get("wildoptions").has("auto") || !completionContext)
|
||||
// This will only work with autocomplete.
|
||||
if (!options.get("wildoptions").has("auto") || !completions)
|
||||
return;
|
||||
|
||||
let editor = commandWidget.inputField.editor;
|
||||
let wildmode = options.get("wildmode");
|
||||
let wildType = wildmode.values[Math.min(wildIndex, wildmode.values.length - 1)];
|
||||
if (wildmode.checkHas(wildType, "longest") && commandWidget.selectionStart == commandWidget.value.length)
|
||||
|
||||
if (completions.type.longest && !completions.suffix)
|
||||
{
|
||||
// highlight= won't work here.
|
||||
let start = commandWidget.selectionStart;
|
||||
let substring = completionContext.longestAllSubstring.substr(start - completionContext.allItems.start);
|
||||
if (substring.length < 2 && substring != lastSubstring.substr(Math.max(0, lastSubstring.length - substring.length)))
|
||||
let substring = completions.substring;
|
||||
|
||||
// Don't show 1-character substrings unless we've just hit backspace
|
||||
if (substring.length < 2 && lastSubstring.indexOf(substring) != 0)
|
||||
return;
|
||||
lastSubstring = substring;
|
||||
let node = <span style={highlight.get("Preview").value}>{substring}</span>
|
||||
editor.insertNode(util.xmlToDom(node, document), editor.rootElement, 1);
|
||||
// Chop off the bits we already have.
|
||||
substring = substring.substr(completions.value.length);
|
||||
|
||||
// highlight="Preview" won't work in the editor.
|
||||
let node = util.xmlToDom(<span style={highlight.get("Preview").value}>{substring}</span>,
|
||||
document);
|
||||
editor.insertNode(node, editor.rootElement, 1);
|
||||
commandWidget.selectionStart = commandWidget.selectionEnd = start;
|
||||
}
|
||||
}
|
||||
@@ -527,6 +593,7 @@ function CommandLine() //{{{
|
||||
completer: function completer(filter)
|
||||
{
|
||||
return [
|
||||
// Why do we need ""?
|
||||
["", "Complete only the first match"],
|
||||
["full", "Complete the next full match"],
|
||||
["longest", "Complete to longest common string"],
|
||||
@@ -677,7 +744,7 @@ 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 completionContext() completions.context,
|
||||
|
||||
get mode() (modes.extended == modes.EX) ? "cmd" : "search",
|
||||
|
||||
@@ -692,7 +759,12 @@ function CommandLine() //{{{
|
||||
|
||||
getCommand: function getCommand()
|
||||
{
|
||||
return commandWidget.inputField.editor.rootElement.firstChild.textContent;
|
||||
try
|
||||
{
|
||||
return commandWidget.inputField.editor.rootElement.firstChild.textContent;
|
||||
}
|
||||
catch (e) {}
|
||||
return commandWidget.value;
|
||||
},
|
||||
|
||||
open: function open(prompt, cmd, extendedMode)
|
||||
@@ -704,7 +776,6 @@ function CommandLine() //{{{
|
||||
currentExtendedMode = extendedMode || null;
|
||||
|
||||
historyIndex = UNINITIALIZED;
|
||||
completionIndex = UNINITIALIZED;
|
||||
|
||||
modes.set(modes.COMMAND_LINE, currentExtendedMode);
|
||||
setHighlightGroup(this.HL_NORMAL);
|
||||
@@ -713,11 +784,8 @@ function CommandLine() //{{{
|
||||
|
||||
commandWidget.focus();
|
||||
|
||||
completionContext = CompletionContext(commandWidget.inputField.editor);
|
||||
completionContext.onUpdate = function ()
|
||||
{
|
||||
commandline.setCompletions(this.allItems);
|
||||
};
|
||||
completions = new Completions(CompletionContext(commandWidget.inputField.editor));
|
||||
|
||||
// open the completion list automatically if wanted
|
||||
if (/\s/.test(cmd) &&
|
||||
options.get("wildoptions").has("auto") &&
|
||||
@@ -889,7 +957,7 @@ function CommandLine() //{{{
|
||||
event.stopPropagation();
|
||||
|
||||
// always reset the tab completion if we use up/down keys
|
||||
completionIndex = UNINITIALIZED;
|
||||
completions.select(completions.RESET);
|
||||
|
||||
// save 'start' position for iterating through the history
|
||||
if (historyIndex == UNINITIALIZED)
|
||||
@@ -954,6 +1022,7 @@ function CommandLine() //{{{
|
||||
{
|
||||
// reset the tab completion
|
||||
completionIndex = historyIndex = UNINITIALIZED;
|
||||
completions.reset();
|
||||
|
||||
// and blur the command line if there is no text left
|
||||
if (command.length == 0)
|
||||
@@ -1239,55 +1308,16 @@ function CommandLine() //{{{
|
||||
outputContainer.collapsed = false;
|
||||
},
|
||||
|
||||
// to allow asynchronous adding of completions
|
||||
setCompletions: function setCompletions(newCompletions)
|
||||
{
|
||||
if (liberator.mode != modes.COMMAND_LINE)
|
||||
return;
|
||||
|
||||
// don't show an empty result, if we are just waiting for data to arrive
|
||||
// FIXME: Maybe. CompletionContext
|
||||
//if (newCompletions.incompleteResult && newCompletions.items.length == 0)
|
||||
// return;
|
||||
|
||||
completionList.setItems(completionContext);
|
||||
|
||||
// try to keep the old item selected
|
||||
if (completionIndex >= 0 && completionIndex < newCompletions.items.length && completionIndex < completions.items.length)
|
||||
{
|
||||
if (newCompletions.items[completionIndex][0] != completions.items[completionIndex][0])
|
||||
completionIndex = -1;
|
||||
}
|
||||
else
|
||||
completionIndex = -1;
|
||||
|
||||
let oldStart = completions.start;
|
||||
completions = newCompletions;
|
||||
if (typeof completions.start != "number")
|
||||
completions.start = oldStart;
|
||||
|
||||
completionList.selectItem(completionIndex);
|
||||
if (options.get("wildoptions").has("auto"))
|
||||
completionList.show();
|
||||
|
||||
// why do we have to set that here? Without that, we lose the
|
||||
// prefix when wrapping around searches
|
||||
// with that, we SOMETIMES have problems with <tab> followed by <s-tab> in :open completions
|
||||
|
||||
previewSubstring();
|
||||
let command = this.getCommand();
|
||||
completionPrefix = command.substring(0, commandWidget.selectionStart);
|
||||
completionPostfix = command.substring(commandWidget.selectionStart);
|
||||
},
|
||||
|
||||
// TODO: does that function need to be public?
|
||||
resetCompletions: function resetCompletions()
|
||||
{
|
||||
autocompleteTimer.reset();
|
||||
completion.cancel();
|
||||
completions = { start: completions.start, items: [] };
|
||||
completionIndex = historyIndex = UNINITIALIZED;
|
||||
wildIndex = 0;
|
||||
if (completions)
|
||||
{
|
||||
completions.context.reset();
|
||||
completions.reset();
|
||||
}
|
||||
historyIndex = UNINITIALIZED;
|
||||
removeSuffix = "";
|
||||
}
|
||||
};
|
||||
@@ -1509,12 +1539,19 @@ function ItemList(id) //{{{
|
||||
},
|
||||
visible: function visible() !container.collapsed,
|
||||
|
||||
reset: function ()
|
||||
{
|
||||
startIndex = endIndex = selIndex = -1;
|
||||
div = null;
|
||||
this.selectItem(-1);
|
||||
},
|
||||
|
||||
// if @param selectedItem is given, show the list and select that item
|
||||
setItems: function setItems(newItems, selectedItem)
|
||||
{
|
||||
startIndex = endIndex = selIndex = -1;
|
||||
items = newItems;
|
||||
init();
|
||||
this.reset();
|
||||
if (typeof selectedItem == "number")
|
||||
{
|
||||
this.selectItem(selectedItem);
|
||||
@@ -1530,11 +1567,14 @@ function ItemList(id) //{{{
|
||||
|
||||
//let now = Date.now();
|
||||
|
||||
if (div == null)
|
||||
init();
|
||||
|
||||
let sel = selIndex;
|
||||
let len = items.allItems.items.length;
|
||||
let newOffset = startIndex;
|
||||
|
||||
if (index == -1 || index == len) // wrapped around
|
||||
if (index == -1 || index == null || index == len) // wrapped around
|
||||
{
|
||||
if (selIndex < 0)
|
||||
newOffset = 0;
|
||||
|
||||
Reference in New Issue
Block a user