1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-23 15:52:29 +01:00

fixed :open foo<tab> completion when writing very fast and when not using wop=auto. Many other small architectural completion improvements, more to come...

This commit is contained in:
Martin Stubenschrott
2008-11-20 17:29:20 +00:00
parent a044098e1b
commit 069a8e57f1
3 changed files with 123 additions and 74 deletions

4
TODO
View File

@@ -1,4 +1,3 @@
<pre>
Priority list: Priority list:
1-9 as in Vim (9 = required for next release, 5 = would be nice, 1 = probably not) 1-9 as in Vim (9 = required for next release, 5 = would be nice, 1 = probably not)
@@ -23,6 +22,7 @@ BUGS:
- :set! browser.urlbar.clickSelectsAll=true -> clicking in the location bar unfocuses it immediately - :set! browser.urlbar.clickSelectsAll=true -> clicking in the location bar unfocuses it immediately
- http://apcmag.com/why_os_x_106_snow_leopard_should_leave_powerpc_in_the_cold.htm - scrolls the wrong - http://apcmag.com/why_os_x_106_snow_leopard_should_leave_powerpc_in_the_cold.htm - scrolls the wrong
window with j/k (the green slashdot thing for me). window with j/k (the green slashdot thing for me).
- http://cgiirc.blitzed.org?chan=%23debug is unusable after login in
(recent CVS regressions): (recent CVS regressions):
- A cached "awesomebar completion" result is still displayed, after the user removes "l" from the 'cpt' and - A cached "awesomebar completion" result is still displayed, after the user removes "l" from the 'cpt' and
@@ -34,6 +34,7 @@ BUGS:
:b a<tab> changes the (Untitled) title to "about:blank" :b a<tab> changes the (Untitled) title to "about:blank"
- visual caret mode is broken - visual caret mode is broken
- Tabbing between form elems triggers NORMAL mode - Tabbing between form elems triggers NORMAL mode
- :bmarks, :hs output and 'cpt'=bh completions are broken
FEATURES: FEATURES:
9 adaptive timeout for auto-completions, :set completions can be updated more often than 9 adaptive timeout for auto-completions, :set completions can be updated more often than
@@ -87,4 +88,3 @@ FEATURES:
RANDOM IDEAS: RANDOM IDEAS:
* hide scrollbars: (window.content.document.body.style.overflow = "hidden" has problems with the mouse wheel) * hide scrollbars: (window.content.document.body.style.overflow = "hidden" has problems with the mouse wheel)
</pre>

View File

@@ -58,6 +58,11 @@ function Completion() //{{{
var completionCache = []; var completionCache = [];
var historyTimer = new util.Timer(50, 100, function histTimer() { var historyTimer = new util.Timer(50, 100, function histTimer() {
// don't set all completions again every time the timer fires, even
// though items might not have changed
if (historyCache.length == historyResult.matchCount)
return;
let comp = []; let comp = [];
for (let i in util.range(0, historyResult.matchCount)) for (let i in util.range(0, historyResult.matchCount))
comp.push([historyResult.getValueAt(i), comp.push([historyResult.getValueAt(i),
@@ -66,8 +71,9 @@ function Completion() //{{{
//let foo = ["", "IGNORED", "FAILURE", "NOMATCH", "SUCCESS", "NOMATCH_ONGOING", "SUCCESS_ONGOING"]; //let foo = ["", "IGNORED", "FAILURE", "NOMATCH", "SUCCESS", "NOMATCH_ONGOING", "SUCCESS_ONGOING"];
// TODO: we need to have a "completionCacheAfter" to allow cpt=slf
historyCache = comp; historyCache = comp;
commandline.setCompletions({ get completions() { return completionCache.concat(historyCache); } }); commandline.setCompletions({ get items() { return completionCache.concat(historyCache); } });
}); });
function Javascript() function Javascript()
@@ -807,7 +813,7 @@ function Completion() //{{{
{ {
return { return {
start: 0, start: 0,
get completions() { return bookmarks.get(filter) }, get items() { return bookmarks.get(filter) },
createRow: function (item) createRow: function (item)
<ul class="hl-CompItem"> <ul class="hl-CompItem">
<li class="hl-CompIcon"><img src={item.icon || ""}/></li> <li class="hl-CompIcon"><img src={item.icon || ""}/></li>
@@ -943,7 +949,7 @@ function Completion() //{{{
var [count, cmd, special, args] = commands.parseCommand(str); var [count, cmd, special, args] = commands.parseCommand(str);
var matches = str.match(/^(:*\d*)\w*$/); var matches = str.match(/^(:*\d*)\w*$/);
if (matches) if (matches)
return { start: matches[1].length, completions: this.command(cmd)[1] }; return { start: matches[1].length, items: this.command(cmd)[1] };
// dynamically get completions as specified with the command's completer function // dynamically get completions as specified with the command's completer function
var compObject = { start: 0, completions: [] }; var compObject = { start: 0, completions: [] };
@@ -955,7 +961,7 @@ function Completion() //{{{
exLength = matches ? matches[0].length : 0; exLength = matches ? matches[0].length : 0;
compObject = command.completer.call(this, args, special); compObject = command.completer.call(this, args, special);
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], completions: compObject[1] }; compObject = { start: compObject[0], items: compObject[1] };
} }
compObject.start += exLength; compObject.start += exLength;
return compObject; return compObject;
@@ -1198,8 +1204,41 @@ function Completion() //{{{
// 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(filter, complete)
{ {
function getMoreItems(count, maxTime)
{
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; this.filterString = filter;
var completions = []; var completions = [];
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 = filter.match("^(.*" + options["urlseparator"] + ")(.*)"); // start after the last 'urlseparator'
if (skip) if (skip)
@@ -1214,32 +1253,34 @@ function Completion() //{{{
for (let c in util.Array.iterator(cpt)) for (let c in util.Array.iterator(cpt))
{ {
if (c == "s") if (c == "s")
completions.push(this.search(filter)[1]); completions = completions.concat(this.search(filter)[1]);
else if (c == "f") else if (c == "f")
completions.push(this.file(filter, false)[1]); completions = completions.concat(this.file(filter, false)[1]);
else if (c == "S") else if (c == "S")
completions.push(this.searchEngineSuggest(filter, suggestEngineAlias)[1]); completions = completions.concat(this.searchEngineSuggest(filter, suggestEngineAlias)[1]);
else if (c == "b") else if (c == "b")
completions.push(bookmarks.get(filter)); completions = completions.concat(bookmarks.get(filter));
else if (c == "h") else if (c == "h")
completions.push(history.get(filter)); completions = completions.concat(history.get(filter));
else if (c == "l" && completionService) // add completions like Firefox's smart location bar else if (c == "l" && completionService) // add completions like Firefox's smart location bar
{ {
historyCache = [];
completionCache = completions.slice(); // make copy of current results
completionService.stopSearch(); completionService.stopSearch();
//dump("searching for " + filter + "\n");
completionService.startSearch(filter, "", historyResult, { completionService.startSearch(filter, "", historyResult, {
onSearchResult: function onSearchResult(search, result) { onSearchResult: function onSearchResult(search, result) {
historyResult = result; historyResult = result;
//liberator.dump("Search result in " + historyResult.matchCount + " results with retval: " + historyResult.searchResult);
historyTimer.tell(); historyTimer.tell();
if (result.searchResult <= result.RESULT_SUCCESS) if (result.searchResult <= result.RESULT_SUCCESS)
historyTimer.flush(); historyTimer.flush();
} }
}); });
} }
} }
completionCache = util.Array.flatten(completions); return { start: start, items: completions, getMoreItems: getMoreItems };
return [start, completionCache.concat(historyCache)];
}, },
userCommand: function userCommand(filter) userCommand: function userCommand(filter)

View File

@@ -97,9 +97,8 @@ function CommandLine() //{{{
var silent = false; var silent = false;
var completionList = new ItemList("liberator-completions"); var completionList = new ItemList("liberator-completions");
var completions = []; var completions = { start: 0, items: [] };
// for the example command "open sometext| othertext" (| is the cursor pos): // for the example command "open sometext| othertext" (| is the cursor pos):
var completionStartIndex = 0; // will be 5 because we want to complete arguments for the :open command
var completionPrefix = ""; // will be: "open sometext" var completionPrefix = ""; // will be: "open sometext"
var completionPostfix = ""; // will be: " othertext" var completionPostfix = ""; // will be: " othertext"
var completionIndex = UNINITIALIZED; var completionIndex = UNINITIALIZED;
@@ -108,10 +107,10 @@ function CommandLine() //{{{
var startHints = false; // whether we're waiting to start hints mode var startHints = false; // whether we're waiting to start hints mode
var statusTimer = new util.Timer(5, 100, function () { var statusTimer = new util.Timer(5, 100, function () {
if (completionIndex >= completions.length) if (completionIndex >= completions.items.length)
statusline.updateProgress(""); statusline.updateProgress("");
else else
statusline.updateProgress("match " + (completionIndex + 1) + " of " + completions.length); 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 (command) {
if (events.feedingKeys) if (events.feedingKeys)
@@ -543,8 +542,6 @@ function CommandLine() //{{{
// FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence // FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence
APPEND_TO_MESSAGES : 1 << 3, // add the string to the message history APPEND_TO_MESSAGES : 1 << 3, // add the string to the message history
get autocompleteTimer() autocompleteTimer,
get mode() (modes.extended == modes.EX) ? "cmd" : "search", get mode() (modes.extended == modes.EX) ? "cmd" : "search",
get silent() silent, get silent() silent,
@@ -581,7 +578,7 @@ function CommandLine() //{{{
// 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") >= 0 && options.get("wildoptions").has("auto") &&
extendedMode == modes.EX) extendedMode == modes.EX)
autocompleteTimer.tell(cmd); autocompleteTimer.tell(cmd);
}, },
@@ -599,9 +596,7 @@ function CommandLine() //{{{
{ {
multilineInputWidget.collapsed = true; multilineInputWidget.collapsed = true;
outputContainer.collapsed = true; outputContainer.collapsed = true;
autocompleteTimer.reset();
completionList.hide(); completionList.hide();
completions = [];
this.resetCompletions(); this.resetCompletions();
setLine("", this.HL_NORMAL); setLine("", this.HL_NORMAL);
@@ -731,7 +726,7 @@ function CommandLine() //{{{
currentExtendedMode = null; /* Don't let modes.pop trigger "cancel" */ currentExtendedMode = null; /* Don't let modes.pop trigger "cancel" */
inputHistory.add(command); inputHistory.add(command);
modes.pop(!commandline.silent); modes.pop(!commandline.silent);
autocompleteTimer.reset(); this.resetCompletions();
completionList.hide(); completionList.hide();
liberator.focusContent(false); liberator.focusContent(false);
statusline.updateProgress(""); // we may have a "match x of y" visible statusline.updateProgress(""); // we may have a "match x of y" visible
@@ -827,25 +822,30 @@ function CommandLine() //{{{
// we need to build our completion list first // we need to build our completion list first
if (completionIndex == UNINITIALIZED) if (completionIndex == UNINITIALIZED)
{ {
completionStartIndex = 0;
completionIndex = -1; completionIndex = -1;
completionPrefix = command.substring(0, commandWidget.selectionStart); completionPrefix = command.substring(0, commandWidget.selectionStart);
completionPostfix = command.substring(commandWidget.selectionStart); completionPostfix = command.substring(commandWidget.selectionStart);
var compObject = liberator.triggerCallback("complete", currentExtendedMode, completionPrefix); completions = liberator.triggerCallback("complete", currentExtendedMode, completionPrefix);
if (compObject)
{
completionStartIndex = compObject.start;
completions = compObject.completions;
}
// sort the completion list // sort the completion list
// TODO: might not make sense anymore with our advanced completions, we should just sort when necessary
if (options.get("wildoptions").has("sort")) if (options.get("wildoptions").has("sort"))
compObject.completions.sort(function (a, b) String.localeCompare(a[0], b[0])); completions.items.sort(function (a, b) String.localeCompare(a[0], b[0]));
completionList.setItems(compObject); completionList.setItems(completions.items);
} }
if (completions.length == 0) if (completions.items.length == 0)
{
// try to fetch more items, if possible
// TODO: also use that code when we DO have completions but too few
if (completions.getMoreItems)
{
completions.items = completions.items.concat(completions.getMoreItems(1));
completionList.setItems(completions.items);
}
if (completions.items.length == 0) // still not more matches
{ {
liberator.beep(); liberator.beep();
// prevent tab from moving to the next field: // prevent tab from moving to the next field:
@@ -853,6 +853,7 @@ function CommandLine() //{{{
event.stopPropagation(); event.stopPropagation();
return false; return false;
} }
}
if (full) if (full)
{ {
@@ -860,12 +861,12 @@ function CommandLine() //{{{
{ {
completionIndex--; completionIndex--;
if (completionIndex < -1) if (completionIndex < -1)
completionIndex = completions.length - 1; completionIndex = completions.items.length - 1;
} }
else else
{ {
completionIndex++; completionIndex++;
if (completionIndex > completions.length) if (completionIndex > completions.items.length)
completionIndex = 0; completionIndex = 0;
} }
@@ -877,30 +878,31 @@ function CommandLine() //{{{
if (hasList) if (hasList)
completionList.show(); completionList.show();
if ((completionIndex == -1 || completionIndex >= completions.length) && !longest) // wrapped around matches, reset command line if ((completionIndex == -1 || completionIndex >= completions.items.length) && !longest) // wrapped around matches, reset command line
{ {
if (full && completions.length > 1) if (full)
setCommand(completionPrefix + completionPostfix); setCommand(completionPrefix + completionPostfix);
} }
else else
{ {
var compl = null; var compl = null;
if (longest && completions.length > 1) if (longest && completions.items.length > 1)
compl = completion.getLongestSubstring(); compl = completion.getLongestSubstring();
else if (full) else if (full)
compl = completions[completionIndex][0]; compl = completions.items[completionIndex][0];
else if (completions.length == 1) else if (completions.items.length == 1)
compl = completions[0][0]; compl = completion.items[0][0];
if (compl) if (compl)
{ {
setCommand(command.substring(0, completionStartIndex) + compl + completionPostfix); setCommand(command.substring(0, completions.start) + compl + completionPostfix);
commandWidget.selectionStart = commandWidget.selectionEnd = completionStartIndex + compl.length; commandWidget.selectionStart = commandWidget.selectionEnd = completions.start + compl.length;
if (longest) if (longest)
liberator.triggerCallback("change", currentExtendedMode, this.getCommand()); liberator.triggerCallback("change", currentExtendedMode, this.getCommand());
// Start a new completion in the next iteration. Useful for commands like :source // 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 // 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 // Needed for :source to grab another set of completions after a file/directory has been filled out
// if (completions.length == 1 && !full) // if (completions.length == 1 && !full)
// completionIndex = UNINITIALIZED; // completionIndex = UNINITIALIZED;
@@ -1220,7 +1222,7 @@ function CommandLine() //{{{
}, },
// to allow asynchronous adding of completions // to allow asynchronous adding of completions
setCompletions: function (completionObject) setCompletions: function (newCompletions)
{ {
if (liberator.mode != modes.COMMAND_LINE) if (liberator.mode != modes.COMMAND_LINE)
return; return;
@@ -1230,32 +1232,41 @@ function CommandLine() //{{{
return completionList.hide(); return completionList.hide();
*/ */
let newCompletions = completionObject.completions; completionList.setItems(newCompletions.items);
completionList.setItems(completionObject);
if (completionIndex >= 0 && completionIndex < newCompletions.length && completionIndex < completions.length) // try to keep the old item selected
if (completionIndex >= 0 && completionIndex < newCompletions.items.length && completionIndex < completions.items.length)
{ {
if (newCompletions[completionIndex][0] != completions[completionIndex][0]) if (newCompletions.items[completionIndex][0] != completions.items[completionIndex][0])
completionIndex = -1; completionIndex = -1;
} }
else else
completionIndex = -1; completionIndex = -1;
let oldStart = completions.start;
completions = newCompletions; completions = newCompletions;
if (typeof completions.start != "number")
completions.start = oldStart;
completionList.selectItem(completionIndex); completionList.selectItem(completionIndex);
if (options.get("wildoptions").has("auto")) if (options.get("wildoptions").has("auto"))
completionList.show(); 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
var command = this.getCommand(); var command = this.getCommand();
completionPrefix = command.substring(0, commandWidget.selectionStart); completionPrefix = command.substring(0, commandWidget.selectionStart);
completionPostfix = command.substring(commandWidget.selectionStart); completionPostfix = command.substring(commandWidget.selectionStart);
if (typeof completionObject.start == "number")
completionStartIndex = completionObject.start;
}, },
// TODO: does that function need to be public?
resetCompletions: function () resetCompletions: function ()
{ {
autocompleteTimer.reset();
completion.cancel();
completions = { start: 0, items: [] };
completionIndex = historyIndex = UNINITIALIZED; completionIndex = historyIndex = UNINITIALIZED;
wildIndex = 0; wildIndex = 0;
} }
@@ -1269,8 +1280,6 @@ function CommandLine() //{{{
* @param id: the id of the the XUL <iframe> which we want to fill * @param id: the id of the the XUL <iframe> which we want to fill
* it MUST be inside a <vbox> (or any other html element, * it MUST be inside a <vbox> (or any other html element,
* because otherwise setting the height does not work properly * because otherwise setting the height does not work properly
*
* TODO: get rid off "completion" variables, we are dealing with variables after all
*/ */
function ItemList(id) //{{{ function ItemList(id) //{{{
{ {
@@ -1295,8 +1304,7 @@ function ItemList(id) //{{{
doc.body.id = id + "-content"; doc.body.id = id + "-content";
doc.body.appendChild(doc.createTextNode("")); doc.body.appendChild(doc.createTextNode(""));
var completions = []; // a reference to the Array of completions var items = [];
var completionObject = {};
var startIndex = -1; // The index of the first displayed item var startIndex = -1; // The index of the first displayed item
var endIndex = -1; // The index one *after* the last displayed item var endIndex = -1; // The index one *after* the last displayed item
var selIndex = -1; // The index of the currently selected element var selIndex = -1; // The index of the currently selected element
@@ -1351,7 +1359,7 @@ function ItemList(id) //{{{
function getCompletion(index) completionElements[index - startIndex]; function getCompletion(index) completionElements[index - startIndex];
/** /**
* uses the entries in completions to fill the listbox * uses the entries in "items" to fill the listbox
* does incremental filling to speed up things * does incremental filling to speed up things
* *
* @param offset: start at this index and show maxItems * @param offset: start at this index and show maxItems
@@ -1359,12 +1367,13 @@ function ItemList(id) //{{{
function fill(offset) function fill(offset)
{ {
let diff = offset - startIndex; let diff = offset - startIndex;
if (offset == null || offset - startIndex == 0 || offset < 0 || completions.length && offset >= completions.length) if (offset == null || offset - startIndex == 0 || offset < 0 || items.length && offset >= items.length)
return; return;
let createRow = ("createRow" in completionObject) ? completionObject.createRow : createDefaultRow; //let createRow = ("createRow" in completionObject) ? completionObject.createRow : createDefaultRow;
let createRow = createDefaultRow;
startIndex = offset; startIndex = offset;
endIndex = Math.min(startIndex + maxItems, completions.length); endIndex = Math.min(startIndex + maxItems, items.length);
if (selIndex > -1 && Math.abs(diff) == 1) /* Scroll one position */ if (selIndex > -1 && Math.abs(diff) == 1) /* Scroll one position */
{ {
@@ -1372,14 +1381,14 @@ function ItemList(id) //{{{
if (diff == 1) /* Scroll down */ if (diff == 1) /* Scroll down */
{ {
let item = completions[endIndex - 1]; let item = items[endIndex - 1];
let row = createRow(item, true); let row = createRow(item, true);
tbody.removeChild(tbody.firstChild); tbody.removeChild(tbody.firstChild);
tbody.appendChild(row); tbody.appendChild(row);
} }
else /* Scroll up */ else /* Scroll up */
{ {
let item = completions[offset]; let item = items[offset];
let row = createRow(item, true); let row = createRow(item, true);
tbody.removeChild(tbody.lastChild); tbody.removeChild(tbody.lastChild);
tbody.insertBefore(row, tbody.firstChild); tbody.insertBefore(row, tbody.firstChild);
@@ -1394,7 +1403,7 @@ function ItemList(id) //{{{
<div class="hl-Completions"> <div class="hl-Completions">
{ {
template.map(util.range(offset, endIndex), function (i) template.map(util.range(offset, endIndex), function (i)
createRow(completions[i])) createRow(items[i]))
} }
</div> </div>
<div class="hl-Completions"> <div class="hl-Completions">
@@ -1439,11 +1448,10 @@ function ItemList(id) //{{{
visible: function () !container.collapsed, visible: function () !container.collapsed,
// if @param selectedItem is given, show the list and select that item // if @param selectedItem is given, show the list and select that item
setItems: function setItems(items, selectedItem) setItems: function setItems(newItems, selectedItem)
{ {
startIndex = endIndex = selIndex = -1; startIndex = endIndex = selIndex = -1;
completionObject = items || { start: 0, completions: [] }; items = newItems || [];
completions = completionObject.completions || []; // TODO: remove?
if (typeof selectedItem == "number") if (typeof selectedItem == "number")
{ {
this.selectItem(selectedItem); this.selectItem(selectedItem);
@@ -1457,7 +1465,7 @@ function ItemList(id) //{{{
//if (container.collapsed) // fixme //if (container.collapsed) // fixme
// return; // return;
if (index == -1 || index == completions.length) // wrapped around if (index == -1 || index == items.length) // wrapped around
{ {
if (selIndex >= 0) if (selIndex >= 0)
getCompletion(selIndex).removeAttribute("selected"); getCompletion(selIndex).removeAttribute("selected");
@@ -1474,7 +1482,7 @@ function ItemList(id) //{{{
else if (index <= startIndex + CONTEXT_LINES) else if (index <= startIndex + CONTEXT_LINES)
newOffset = index - CONTEXT_LINES; newOffset = index - CONTEXT_LINES;
newOffset = Math.min(newOffset, completions.length - maxItems); newOffset = Math.min(newOffset, items.length - maxItems);
newOffset = Math.max(newOffset, 0); newOffset = Math.max(newOffset, 0);
if (selIndex > -1) if (selIndex > -1)