From 36ca0c98a88a66bc437aeb069f3f950ee9858a3f Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 3 Oct 2008 03:08:51 +0000 Subject: [PATCH] Make completion generally niftier (add a rate limiting timer) --- content/buffer.js | 12 ++-- content/completion.js | 65 ++++++++++---------- content/find.js | 9 ++- content/options.js | 6 +- content/ui.js | 135 ++++++++++++++++-------------------------- content/util.js | 63 +++++++++++++++++++- 6 files changed, 160 insertions(+), 130 deletions(-) diff --git a/content/buffer.js b/content/buffer.js index 64825420..c261d2db 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -1246,13 +1246,11 @@ liberator.Buffer = function () //{{{ // TODO: make this an XBL element rather than messing with the content // document var doc = frames[next].document; - var indicator = doc.createElement("div"); - indicator.id = "liberator-frame-indicator"; - // NOTE: need to set a high z-index - it's a crapshoot! - var style = "background-color: red; opacity: 0.5; z-index: 999;" + - "position: fixed; top: 0; bottom: 0; left: 0; right: 0;"; - indicator.setAttribute("style", style); - doc.body.appendChild(indicator); + var indicator = +
; + doc.body.appendChild(liberator.util.xmlToDom(indicator)); // remove the frame indicator setTimeout(function () doc.body.removeChild(indicator), 500); diff --git a/content/completion.js b/content/completion.js index 038ab5ea..d65cb40a 100644 --- a/content/completion.js +++ b/content/completion.js @@ -32,10 +32,26 @@ liberator.Completion = function () //{{{ ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ + var completionService = Components.classes["@mozilla.org/browser/global-history;2"] + .getService(Components.interfaces.nsIAutoCompleteSearch); + // the completion substrings, used for showing the longest common match var substrings = []; - var urlResultsCache = null; - var urlCompletionCache = []; + var historyCache = []; + var historyResult = null; + var completionCache = []; + + var historyTimer = new liberator.util.Timer(50, 100, function () { + let comp = []; + for (let i in liberator.util.range(0, historyResult.matchCount)) + comp.push([historyResult.getValueAt(i), + historyResult.getCommentAt(i)]); + + //let foo = ["", "IGNORED", "FAILURE", "NOMATCH", "SUCCESS", "NOMATCH_ONGOING", "SUCCESS_ONGOING"]; + + liberator.commandline.setCompletions(completionCache.concat(comp)); + historyCache = comp; + }); // function uses smartcase // list = [ [['com1', 'com2'], 'text'], [['com3', 'com4'], 'text'] ] @@ -439,46 +455,33 @@ liberator.Completion = function () //{{{ var autoCompletions = liberator.options["wildoptions"].indexOf("auto") >= 0; var suggestEngineAlias = liberator.options["suggestengines"] || "google"; // join all completion arrays together - for (let i = 0; i < cpt.length; i++) + for (let c in liberator.util.arrayIter(cpt)) { - if (cpt[i] == "s") + if (c == "s") completions = completions.concat(this.search(filter)[1]); - else if (cpt[i] == "f") + else if (c == "f") completions = completions.concat(this.file(filter, false)[1]); - else if (!autoCompletions && cpt[i] == "b") - completions = completions.concat(liberator.bookmarks.get(filter)); - else if (!autoCompletions && cpt[i] == "h") - completions = completions.concat(liberator.history.get(filter)); - else if (cpt[i] == "S") + else if (c == "S") completions = completions.concat(this.searchEngineSuggest(filter, suggestEngineAlias)[1]); - else if (autoCompletions && cpt[i] == "l") // add completions like Firefox's smart location bar + else if (c == "b" && !autoCompletions) + completions = completions.concat(liberator.bookmarks.get(filter)); + else if (c == "h" && !autoCompletions) + completions = completions.concat(liberator.history.get(filter)); + else if (c == "l") // add completions like Firefox's smart location bar { - var completionService = Components.classes["@mozilla.org/browser/global-history;2"] - .getService(Components.interfaces.nsIAutoCompleteSearch); - completionService.startSearch(filter, "", urlResultsCache, { + completionCache = completions; + completionService.startSearch(filter, "", historyResult, { onSearchResult: function (search, result) { - //if (result.searchResult != result.RESULT_SUCCESS) - // return; - //liberator.log(result.searchResult); - //var res = "";// + util.objectToString(result) + "\n---\n"; - //liberator.log(result.matchCount + " matches: " + result.searchResult); - var comp = []; - //if (result.searchResult == result.RESULT_SUCCESS) - // urlResultsCache = result; - - for (let i = 0; i < result.matchCount; i++) - { - comp.push([result.getValueAt(i), result.getCommentAt(i)]); - } - urlCompletionCache = comp; - if (comp.length > 0 || result.searchResult == result.RESULT_SUCCESS) - liberator.commandline.setCompletions(completions.concat(comp)); + historyResult = result; + historyTimer.tell(); + if (result.searchResult <= result.RESULT_SUCCESS) + historyTimer.force(); } }); } } - return [start, completions.concat(urlCompletionCache)]; + return [start, completions.concat(historyCache)]; }, userCommand: function (filter) diff --git a/content/find.js b/content/find.js index f948c770..e01146b0 100644 --- a/content/find.js +++ b/content/find.js @@ -176,11 +176,10 @@ liberator.Search = function () //{{{ } var baseNode = doc.createElementNS("http://www.w3.org/1999/xhtml", "span"); - baseNode.setAttribute("style", liberator.options["hlsearchstyle"]); - baseNode.style.display = "inline"; - baseNode.style.fontSize = "inherit"; - baseNode.style.padding = "0"; - baseNode.className = "__liberator-search"; + + baseNode = liberator.util.xmlToDom(baseNode); var body = doc.body; var count = body.childNodes.length; diff --git a/content/options.js b/content/options.js index 9a8b33cc..a6747cb6 100644 --- a/content/options.js +++ b/content/options.js @@ -742,15 +742,15 @@ liberator.Options = function () //{{{ if (!filter) { - let options = [[prefix + option.name, option.description] + let opts = [[prefix + option.name, option.description] for (option in options)]; - return [0, options]; + return [0, opts]; } else if (filter.indexOf("=") == -1) { for (let option in options) optionCompletions.push([[prefix + name, option.description] - for (name in option.names) + for each (name in option.names) if (name.indexOf(filter) == 0)]); // Flatten array. optionCompletions = Array.concat.apply(Array, optionCompletions); diff --git a/content/ui.js b/content/ui.js index 31e78159..b95435f6 100644 --- a/content/ui.js +++ b/content/ui.js @@ -105,6 +105,13 @@ liberator.CommandLine = function () //{{{ var wildIndex = 0; // keep track how often we press in a row var startHints = false; // whether we're waiting to start hints mode + var statusTimer = new liberator.util.Timer(50, 100, function () + liberator.statusline.updateProgress("match " + (completionIndex + 1) + " of " + completions.length)); + var autocompleteTimer = new liberator.util.Timer(50, 100, function (command) { + var res = liberator.completion.ex(command); + liberator.commandline.setCompletions(res[1], res[0]); + }); + // the containing box for the promptWidget and commandWidget var commandlineWidget = document.getElementById("liberator-commandline"); // the prompt for the current command, for example : or /. Can be blank @@ -115,11 +122,13 @@ liberator.CommandLine = function () //{{{ // the widget used for multiline output var multilineOutputWidget = document.getElementById("liberator-multiline-output"); - var stylesheet = multilineOutputWidget.contentDocument.createElement("link"); - stylesheet.setAttribute("rel", "stylesheet"); - stylesheet.setAttribute("type", "text/css"); - stylesheet.setAttribute("href", "chrome://" + liberator.config.name.toLowerCase() + "/skin/vimperator.css"); - multilineOutputWidget.contentDocument.getElementsByTagName("head")[0].appendChild(stylesheet); + var stylesheet = + ; + + stylesheet = liberator.util.xmlToDom(stylesheet, multilineOutputWidget.contentDocument); + multilineOutputWidget.contentDocument.getElementsByTagName("head")[0] + .appendChild(stylesheet); multilineOutputWidget.contentDocument.body.id = "liberator-multiline-output-content"; @@ -152,10 +161,7 @@ liberator.CommandLine = function () //{{{ liberator.registerCallback("change", liberator.modes.EX, function (command) { if (liberator.options["wildoptions"].indexOf("auto") >= 0) - { - var res = liberator.completion.ex(command); - liberator.commandline.setCompletions(res[1], res[0]); - } + autocompleteTimer.tell(command); else completionIndex = UNINITIALIZED; }); @@ -558,6 +564,7 @@ liberator.CommandLine = function () //{{{ historyIndex = UNINITIALIZED; completionIndex = UNINITIALIZED; + autocompleteTimer.reset(); liberator.modes.push(liberator.modes.COMMAND_LINE, currentExtendedMode); setHighlightGroup(this.HL_NORMAL); @@ -805,6 +812,7 @@ liberator.CommandLine = function () //{{{ completions.sort(function (a, b) String.localeCompare(a[0], b[0])); completionList.setItems(completions); + statusTimer.reset(); } if (completions.length == 0) @@ -833,7 +841,7 @@ liberator.CommandLine = function () //{{{ // FIXME: this innocent looking line is the source of a big performance // problem, when keeping pressed down, so disable it for now - // liberator.statusline.updateProgress("match " + (completionIndex + 1) + " of " + completions.length); + statusTimer.tell(); } // the following line is not inside if (hasList) for list:longest,full @@ -1200,10 +1208,9 @@ liberator.ItemList = function (id) //{{{ var doc = iframe.contentDocument; var container = iframe.parentNode; - var stylesheet = doc.createElement("link"); - stylesheet.setAttribute("rel", "stylesheet"); - stylesheet.setAttribute("type", "text/css"); - stylesheet.setAttribute("href", "chrome://" + liberator.config.name.toLowerCase() + "/skin/vimperator.css"); + var stylesheet = liberator.util.xmlToDom( + , doc); doc.getElementsByTagName("head")[0].appendChild(stylesheet); doc.body.id = id + "-content"; @@ -1227,44 +1234,20 @@ liberator.ItemList = function (id) //{{{ catch (e) {} // for muttator! // TODO: temporary, to be changed/removed - function createRow(a, b, c) + function createRow(a, b, c, dom) { - var row = doc.createElement("tr"); - row.setAttribute("class", "liberator-compitem"); - var icon = doc.createElement("td"); - icon.setAttribute("style", "width: 16px"); + let row = + + + {b} + {c} + + if (a) - { - var img = doc.createElement("img"); - img.setAttribute("width", "16px"); - img.setAttribute("height", "16px"); - img.setAttribute("src", a); - icon.appendChild(img); - } - row.appendChild(icon); - - var title = doc.createElement("td"); - title.appendChild(doc.createTextNode(b)); - title.setAttribute("style", "width: 45%; overflow:hidden"); - row.appendChild(title); - - var url = doc.createElement("td"); - url.setAttribute("style", "color: gray"); - url.appendChild(doc.createTextNode(c)); - row.appendChild(url); - - return row; - - var row = doc.createElement("tr"); - row.setAttribute("class", "liberator-compitem"); - var icon = doc.createElement("td"); - XML.prettyPrinting = false; - icon.innerHTML = <> - - {b}
{c}; - ; - row.appendChild(icon); + row.td[0].* = ; + if (dom) + return liberator.util.xmlToDom(row, doc); return row; } @@ -1296,7 +1279,7 @@ liberator.ItemList = function (id) //{{{ */ function fill(offset) { - if (listOffset == offset || offset < 0 || offset >= completions.length) + if (listOffset == offset || offset < 0 || offset >= completions.length && completions.length) return; if (listIndex > -1 && offset == listOffset + 1) @@ -1310,10 +1293,11 @@ liberator.ItemList = function (id) //{{{ } catch (e) {} - var row = createRow(icon, completions[offset + maxItems - 1][0], completions[offset + maxItems - 1][1]); + var row = createRow(icon, completions[offset + maxItems - 1][0], completions[offset + maxItems - 1][1], true); var e = doc.getElementsByTagName("tbody"); - e[e.length - 1].removeChild(e[e.length - 1].firstChild); - e[e.length - 1].appendChild(row); + e = e[e.length - 1]; + e.removeChild(e.firstChild); + e.appendChild(row); completionElements = doc.getElementsByClassName("liberator-compitem"); // TODO: make faster return; } @@ -1327,58 +1311,43 @@ liberator.ItemList = function (id) //{{{ icon = faviconService.getFaviconImageForPage(uri).spec; } catch (e) {} - var row = createRow(icon, completions[offset][0], completions[offset][1]); + var row = createRow(icon, completions[offset][0], completions[offset][1], true); var e = doc.getElementsByTagName("tbody"); - e[e.length - 1].removeChild(e[e.length - 1].lastChild); - e[e.length - 1].insertBefore(row, e[e.length - 1].firstChild); + e = e[e.length - 1]; + e.removeChild(e.lastChild); + e.insertBefore(row, e.firstChild); completionElements = doc.getElementsByClassName("liberator-compitem"); // TODO: make faster return; } + listOffset = offset; // do a full refill of the list: doc.body.innerHTML = ""; - var div = doc.createElement("div"); - div.setAttribute("class", "ex-command-output hl-Normal"); - div.innerHTML = "Completions:"; - var table = doc.createElement("table"); - table.setAttribute("width", "100%"); - table.setAttribute("style", "table-layout: fixed; width: 100%"); - //div.appendChild(table); - var tbody = doc.createElement("tbody"); - table.appendChild(tbody); + let div =
+ Completions: +
+
; + let tbody = div..tbody; - for (let i = 0; i < completions.length; i++) + for (let [i, elem] in Iterator(completions)) { - var elem = completions[i]; if (i >= listOffset && i - listOffset < maxItems) { - var icon = ""; + let icon = ""; try { - var uri = ioService.newURI(elem[0], null, null); + let uri = ioService.newURI(elem[0], null, null); icon = faviconService.getFaviconImageForPage(uri).spec; - //dump(icon + "\n"); } catch (e) {} - if (i == -132434) - { - var row = doc.createElement("tr"); - row.setAttribute("align", "center"); - row.innerHTML = 'Bookmarks'; - tbody.appendChild(row); - } - else - { - tbody.appendChild(createRow(icon, elem[0], elem[1])); - } + tbody.* += createRow(icon, elem[0], elem[1]); } }; - doc.body.appendChild(div); - doc.body.appendChild(table); + doc.body.appendChild(liberator.util.xmlToDom(div, doc)); completionElements = doc.getElementsByClassName("liberator-compitem"); autoSize(); diff --git a/content/util.js b/content/util.js index 42198ca9..c183972f 100644 --- a/content/util.js +++ b/content/util.js @@ -28,6 +28,51 @@ the terms of any one of the MPL, the GPL or the LGPL. liberator.util = { //{{{ + Timer: function Timer(minInterval, maxInterval, callback) + { + let self = this; + let timer = Components.classes["@mozilla.org/timer;1"] + .createInstance(Components.interfaces.nsITimer); + this.first = true; /* When set, trigger immediately. */ + this.latest = 0; + this.notify = function (aTimer) + { + timer.cancel(); + if (this.latest || this.first) + callback(this.arg); + else /* Don't trigger. Set this.first after the minimum interval. */ + timer.initWithCallback(this, minInterval, timer.TYPE_ONE_SHOT); + this.first = (this.latest == 0); + this.latest = 0; + } + this.tell = function (arg) + { + if (arg != undefined) + this.arg = arg; + if (this.first || this.latest > Date.now()) + return this.notify(); + if (this.latest) + timer.cancel(); + + let timeout = minInterval; + if (this.latest) + timeout = Math.min(minInterval, this.latest - Date.now()); + else + this.latest = Date.now() + maxInterval; + this.timer = timer.initWithCallback(this, timeout, timer.TYPE_ONE_SHOT); + } + this.reset = function () + { + timer.cancel(); + this.first = true; + } + this.flush = function () + { + if (this.latest) + this.notify(); + } + }, + arrayIter: function (ary) { let length = ary.length; @@ -353,7 +398,23 @@ liberator.util = { //{{{ } return urls; - } + }, + + xmlToDom: function (node, doc) + { + switch (node.nodeKind()) + { + case "text": + return doc.createTextNode(node); + case "element": + let domnode = doc.createElement(node.name()); + for each (let attr in node.@*) + domnode.setAttribute(attr.name(), String(attr)); + for each (let child in node.*) + domnode.appendChild(arguments.callee(child, doc)); + return domnode; + } + }, }; //}}} // vim: set fdm=marker sw=4 ts=4 et: