diff --git a/content/bookmarks.js b/content/bookmarks.js index 2c1dc1b1..980cde44 100644 --- a/content/bookmarks.js +++ b/content/bookmarks.js @@ -283,7 +283,7 @@ liberator.Bookmarks = function () //{{{ }, { bang: true, - completer: function (filter) [0, liberator.bookmarks.get(filter)], + completer: function (filter) liberator.completion.bookmark(filter), options: [[["-tags", "-T"], liberator.commands.OPTION_LIST]] }); @@ -296,7 +296,7 @@ liberator.Bookmarks = function () //{{{ liberator.echo(deletedCount + " bookmark(s) with url `" + url + "' deleted", liberator.commandline.FORCE_SINGLELINE); }, - { completer: function (filter) [0, liberator.bookmarks.get(filter)] }); + { completer: function (filter) liberator.completion.bookmark(filter) }); /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// @@ -696,7 +696,7 @@ liberator.History = function () //{{{ function (args, special) { liberator.history.list(args, special); }, { bang: true, - completer: function (filter) [0, liberator.history.get(filter)] + completer: function (filter) liberator.completion.history(filter) }); /////////////////////////////////////////////////////////////////////////////}}} diff --git a/content/buffer.js b/content/buffer.js index 8ccfcf79..ae5da47d 100644 --- a/content/buffer.js +++ b/content/buffer.js @@ -380,10 +380,10 @@ liberator.Buffer = function () //{{{ "number", 1, { completer: function (filter) [0, [ - ["0", "Don't show link destination"], - ["1", "Show the link in the status line"], - ["2", "Show the link in the command line"] - ]], + ["0", "Don't show link destination"], + ["1", "Show the link in the status line"], + ["2", "Show the link in the command line"] + ]], validator: function (value) value >= 0 && value <= 2 }); @@ -805,6 +805,7 @@ liberator.Buffer = function () //{{{ liberator.buffer.highlight(key, css, special); }, { + // TODO: add this as a standard highlight completion function? completer: function (filter) [0, liberator.completion.filter([[v, ""] for ([k, v] in Iterator(highlightClasses))], filter)], hereDoc: true, bang: true, diff --git a/content/completion.js b/content/completion.js index 8de03f77..7239c199 100644 --- a/content/completion.js +++ b/content/completion.js @@ -320,7 +320,7 @@ liberator.Completion = function () //{{{ || get(-3, 0, STATEMENTS) == get(-2)[OFFSET]) /* Okay. Is it an array literal? */ return [0, []]; /* No. Nothing to do. */ - /* + /* * str = "foo[bar + 'baz" * obj = "foo" * key = "bar + ''" @@ -510,267 +510,6 @@ liberator.Completion = function () //{{{ return cacheResults[key]; }, - autocommand: function (filter) - { - let autoCmds = liberator.config.autocommands; - return [0, this.filter(autoCmds, filter)]; - }, - - // FIXME: items shouldn't be [[[a], b]], but [[a, b]] and only mapped if at all for bLCS --mst - buffer: function (filter) - { - var items = []; - var num = getBrowser().browsers.length; - var title, url; - - for (let i = 0; i < num; i++) - { - try - { - title = getBrowser().getBrowserAtIndex(i).contentDocument.title; - } - catch (e) - { - title = ""; - } - - url = getBrowser().getBrowserAtIndex(i).contentDocument.location.href; - - if (title.indexOf(filter) == -1 && url.indexOf(filter) == -1 && - (i + 1).toString().indexOf(filter) == -1) - continue; - - if (title.indexOf(filter) != -1 || url.indexOf(filter) != -1 || - (i + 1).toString().indexOf(filter) != -1) - { - if (title == "") - title = "(Untitled)"; - items.push([[(i + 1) + ": " + title, (i + 1) + ": " + url], url]); - } - } - - if (!filter) - return [0, items.map(function (i) [i[0][0], i[1]])]; - - return [0, buildLongestCommonSubstring(items, filter)]; - }, - - command: function (filter) - { - var completions = []; - - if (!filter) - { - for (let command in liberator.commands) - completions.push([command.name, command.description]); - return [0, completions]; - } - - for (let command in liberator.commands) - completions.push([command.longNames, command.description]); - - return [0, buildLongestStartingSubstring(completions, filter)]; - }, - - // TODO: support file:// and \ or / path separators on both platforms - // if "tail" is true, only return names without any directory components - file: function (filter, tail) - { - var dir = "", compl = ""; - var matches = filter.match(/^(.*[\/\\])?(.*?)$/); - - if (matches) - { - dir = matches[1] || ""; // "" is expanded inside readDirectory to the current dir - compl = matches[2] || ""; - } - - var files = [], mapped = []; - - try - { - files = liberator.io.readDirectory(dir, true); - - if (liberator.options["wildignore"]) - { - var wigRegexp = new RegExp("(^" + liberator.options["wildignore"].replace(",", "|", "g") + ")$"); - - files = files.filter(function (f) f.isDirectory() || !wigRegexp.test(f.leafName)) - } - - mapped = files.map(function (file) [tail ? file.leafName : (dir + file.leafName), - file.isDirectory() ? "Directory" : "File"]); - } - catch (e) - { - return [0, []]; - } - - if (tail) - return [dir.length, buildLongestStartingSubstring(mapped, compl, true)]; - else - return [0, buildLongestStartingSubstring(mapped, filter, true)]; - }, - - javascript: function (str) - { - return javascript.complete(str); - }, - - macro: function (filter) - { - var macros = [item for (item in liberator.events.getMacros())]; - - return [0, liberator.completion.filter(macros, filter)]; - }, - - search: function (filter) - { - let results = this.cached("search", filter, - function () Array.concat(liberator.bookmarks.getKeywords().map(function (k) [k[0], k[1], k[3]]), - liberator.bookmarks.getSearchEngines()), - "filter", false, true); - return [0, results]; - }, - - // XXX: Move to bookmarks.js? - searchEngineSuggest: function (filter, engineAliases) - { - if (!filter) - return [0, []]; - - var engineList = (engineAliases || liberator.options["suggestengines"]).split(","); - var responseType = "application/x-suggestions+json"; - var ss = Components.classes["@mozilla.org/browser/search-service;1"] - .getService(Components.interfaces.nsIBrowserSearchService); - - var completions = []; - engineList.forEach(function (name) { - var query = filter; - 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)) - queryURI = engine.getSubmission(query, responseType).uri.asciiSpec; - else - return [0, []]; - - var xhr = new XMLHttpRequest(); - xhr.open("GET", queryURI, false); - xhr.send(null); - - var json = Components.classes["@mozilla.org/dom/json;1"] - .createInstance(Components.interfaces.nsIJSON); - var results = json.decode(xhr.responseText)[1]; - if (!results) - return [0, []]; - - results.forEach(function (item) { - // make sure we receive strings, otherwise a man-in-the-middle attack - // could return objects which toString() method could be called to - // execute untrusted code - if (typeof item != "string") - return [0, []]; - - completions.push([(matches ? matches[1] : "") + item, engine.name + " suggestion"]); - }); - }); - - return [0, completions]; - }, - - stylesheet: function (filter) - { - var completions = liberator.buffer.alternateStyleSheets.map( - function (stylesheet) [stylesheet.title, stylesheet.href || "inline"] - ); - - // unify split style sheets - completions.forEach(function (stylesheet) { - for (let i = 0; i < completions.length; i++) - { - if (stylesheet[0] == completions[i][0] && stylesheet[1] != completions[i][1]) - { - stylesheet[1] += ", " + completions[i][1]; - completions.splice(i, 1); - } - } - }); - - return [0, this.filter(completions, filter)]; - }, - - // filter a list of urls - // - // may consist of search engines, filenames, bookmarks and history, - // depending on the 'complete' option - // if the 'complete' argument is passed like "h", it temporarily overrides the complete option - url: function (filter, complete) - { - var completions = []; - var start = 0; - var skip = filter.match("^(.*" + liberator.options["urlseparator"] + ")(.*)"); // start after the last 'urlseparator' - if (skip) - { - start += skip[1].length; - filter = skip[2]; - } - - var cpt = complete || liberator.options["complete"]; - var suggestEngineAlias = liberator.options["suggestengines"] || "google"; - // join all completion arrays together - for (let c in liberator.util.arrayIter(cpt)) - { - if (c == "s") - completions.push(this.search(filter)[1]); - else if (c == "f") - completions.push(this.file(filter, false)[1]); - else if (c == "S") - completions.push(this.searchEngineSuggest(filter, suggestEngineAlias)[1]); - else if (c == "b") - completions.push(liberator.bookmarks.get(filter).map(function (a) [a[0], a[1], a[5]])); - else if (c == "h") - completions.push(liberator.history.get(filter)); - else if (c == "l" && completionService) // add completions like Firefox's smart location bar - { - completionService.stopSearch(); - completionService.startSearch(filter, "", historyResult, { - onSearchResult: function (search, result) { - historyResult = result; - historyTimer.tell(); - if (result.searchResult <= result.RESULT_SUCCESS) - historyTimer.flush(); - } - }); - } - } - - completionCache = liberator.util.flatten(completions); - return [start, completionCache.concat(historyCache)]; - }, - - userCommand: function (filter) - { - var commands = liberator.commands.getUserCommands(); - commands = commands.map(function (command) [command.name, ""]); - return [0, this.filter(commands, filter)]; - }, - - userMapping: function (filter, modes) - { - // TODO: add appropriate getters to l.mappings - var mappings = []; - - for (let map in liberator.mappings.getUserIterator(modes)) - mappings.push([map.names[0], ""]); - - return [0, this.filter(mappings, filter)]; - }, - // discard all entries in the 'urls' array, which don't match 'filter // urls must be of type [["url", "title"], [...]] or optionally // [["url", "title", keyword, [tags]], [...]] @@ -865,6 +604,72 @@ liberator.Completion = function () //{{{ return false; }, + //////////////////////////////////////////////////////////////////////////////// + ////////////////////// COMPLETION TYPES //////////////////////////////////////// + /////////////////////////////////////////////////////////////////////////////{{{ + + bookmark: function (filter) [0, liberator.bookmarks.get(filter)], + + // FIXME: items shouldn't be [[[a], b]], but [[a, b]] and only mapped if at all for bLCS --mst + buffer: function (filter) + { + var items = []; + var num = getBrowser().browsers.length; + var title, url; + + for (let i = 0; i < num; i++) + { + try + { + title = getBrowser().getBrowserAtIndex(i).contentDocument.title; + } + catch (e) + { + title = ""; + } + + url = getBrowser().getBrowserAtIndex(i).contentDocument.location.href; + + if (title.indexOf(filter) == -1 && url.indexOf(filter) == -1 && + (i + 1).toString().indexOf(filter) == -1) + continue; + + if (title.indexOf(filter) != -1 || url.indexOf(filter) != -1 || + (i + 1).toString().indexOf(filter) != -1) + { + if (title == "") + title = "(Untitled)"; + items.push([[(i + 1) + ": " + title, (i + 1) + ": " + url], url]); + } + } + + if (!filter) + return [0, items.map(function (i) [i[0][0], i[1]])]; + + return [0, buildLongestCommonSubstring(items, filter)]; + }, + + command: function (filter) + { + var completions = []; + + if (!filter) + { + for (let command in liberator.commands) + completions.push([command.name, command.description]); + return [0, completions]; + } + + for (let command in liberator.commands) + completions.push([command.longNames, command.description]); + + return [0, buildLongestStartingSubstring(completions, filter)]; + }, + + dialog: function (filter) [0, this.filter(liberator.config.dialogs || [], filter)], + + event: function (filter) [0, this.filter(liberator.config.autocommands, filter)], + // provides completions for ex commands, including their arguments ex: function (str) { @@ -889,7 +694,247 @@ liberator.Completion = function () //{{{ [start, completions] = command.completer.call(this, args, special); } return [exLength + start, completions]; + }, + + // TODO: support file:// and \ or / path separators on both platforms + // if "tail" is true, only return names without any directory components + file: function (filter, tail) + { + var dir = "", compl = ""; + var matches = filter.match(/^(.*[\/\\])?(.*?)$/); + + if (matches) + { + dir = matches[1] || ""; // "" is expanded inside readDirectory to the current dir + compl = matches[2] || ""; + } + + var files = [], mapped = []; + + try + { + files = liberator.io.readDirectory(dir, true); + + if (liberator.options["wildignore"]) + { + var wigRegexp = new RegExp("(^" + liberator.options["wildignore"].replace(",", "|", "g") + ")$"); + + files = files.filter(function (f) f.isDirectory() || !wigRegexp.test(f.leafName)) + } + + mapped = files.map(function (file) [tail ? file.leafName : (dir + file.leafName), + file.isDirectory() ? "Directory" : "File"]); + } + catch (e) + { + return [0, []]; + } + + if (tail) + return [dir.length, buildLongestStartingSubstring(mapped, compl, true)]; + else + return [0, buildLongestStartingSubstring(mapped, filter, true)]; + }, + + help: function (filter) + { + var files = liberator.config.helpFiles || []; + var res = []; + + for (let i = 0; i < files.length; i++) + { + try + { + var xmlhttp = new XMLHttpRequest(); + xmlhttp.open("GET", "chrome://" + liberator.config.name.toLowerCase() + "/locale/" + files[i], false); + xmlhttp.send(null); + } + catch (e) + { + liberator.log("Error opening chrome://" + liberator.config.name.toLowerCase() + "/locale/" + files[i], 1); + continue; + } + var doc = xmlhttp.responseXML; + var elems = doc.getElementsByClassName("tag"); + for (let j = 0; j < elems.length; j++) + res.push([elems[j].textContent, files[i]]); + } + + return [0, this.filter(res, filter)]; + }, + + history: function (filter) [0, liberator.history.get(filter)], + + javascript: function (str) + { + return javascript.complete(str); + }, + + macro: function (filter) + { + var macros = [item for (item in liberator.events.getMacros())]; + + return [0, this.filter(macros, filter)]; + }, + + search: function (filter) + { + let results = this.cached("search", filter, + function () Array.concat(liberator.bookmarks.getKeywords().map(function (k) [k[0], k[1], k[3]]), + liberator.bookmarks.getSearchEngines()), + "filter", false, true); + return [0, results]; + }, + + // XXX: Move to bookmarks.js? + searchEngineSuggest: function (filter, engineAliases) + { + if (!filter) + return [0, []]; + + var engineList = (engineAliases || liberator.options["suggestengines"]).split(","); + var responseType = "application/x-suggestions+json"; + var ss = Components.classes["@mozilla.org/browser/search-service;1"] + .getService(Components.interfaces.nsIBrowserSearchService); + + var completions = []; + engineList.forEach(function (name) { + var query = filter; + 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)) + queryURI = engine.getSubmission(query, responseType).uri.asciiSpec; + else + return [0, []]; + + var xhr = new XMLHttpRequest(); + xhr.open("GET", queryURI, false); + xhr.send(null); + + var json = Components.classes["@mozilla.org/dom/json;1"] + .createInstance(Components.interfaces.nsIJSON); + var results = json.decode(xhr.responseText)[1]; + if (!results) + return [0, []]; + + results.forEach(function (item) { + // make sure we receive strings, otherwise a man-in-the-middle attack + // could return objects which toString() method could be called to + // execute untrusted code + if (typeof item != "string") + return [0, []]; + + completions.push([(matches ? matches[1] : "") + item, engine.name + " suggestion"]); + }); + }); + + return [0, completions]; + }, + + sidebar: function (filter) + { + var menu = document.getElementById("viewSidebarMenu"); + var nodes = []; + + for (let i = 0; i < menu.childNodes.length; i++) + nodes.push([menu.childNodes[i].label, ""]); + + return [0, this.filter(nodes, filter)]; + }, + + stylesheet: function (filter) + { + var completions = liberator.buffer.alternateStyleSheets.map( + function (stylesheet) [stylesheet.title, stylesheet.href || "inline"] + ); + + // unify split style sheets + completions.forEach(function (stylesheet) { + for (let i = 0; i < completions.length; i++) + { + if (stylesheet[0] == completions[i][0] && stylesheet[1] != completions[i][1]) + { + stylesheet[1] += ", " + completions[i][1]; + completions.splice(i, 1); + } + } + }); + + return [0, this.filter(completions, filter)]; + }, + + // filter a list of urls + // + // may consist of search engines, filenames, bookmarks and history, + // depending on the 'complete' option + // if the 'complete' argument is passed like "h", it temporarily overrides the complete option + url: function (filter, complete) + { + var completions = []; + var start = 0; + var skip = filter.match("^(.*" + liberator.options["urlseparator"] + ")(.*)"); // start after the last 'urlseparator' + if (skip) + { + start += skip[1].length; + filter = skip[2]; + } + + var cpt = complete || liberator.options["complete"]; + var suggestEngineAlias = liberator.options["suggestengines"] || "google"; + // join all completion arrays together + for (let c in liberator.util.arrayIter(cpt)) + { + if (c == "s") + completions.push(this.search(filter)[1]); + else if (c == "f") + completions.push(this.file(filter, false)[1]); + else if (c == "S") + completions.push(this.searchEngineSuggest(filter, suggestEngineAlias)[1]); + else if (c == "b") + completions.push(liberator.bookmarks.get(filter).map(function (a) [a[0], a[1], a[5]])); + else if (c == "h") + completions.push(liberator.history.get(filter)); + else if (c == "l" && completionService) // add completions like Firefox's smart location bar + { + completionService.stopSearch(); + completionService.startSearch(filter, "", historyResult, { + onSearchResult: function (search, result) { + historyResult = result; + historyTimer.tell(); + if (result.searchResult <= result.RESULT_SUCCESS) + historyTimer.flush(); + } + }); + } + } + + completionCache = liberator.util.flatten(completions); + return [start, completionCache.concat(historyCache)]; + }, + + userCommand: function (filter) + { + var commands = liberator.commands.getUserCommands(); + commands = commands.map(function (command) [command.name, ""]); + return [0, this.filter(commands, filter)]; + }, + + userMapping: function (filter, modes) + { + // TODO: add appropriate getters to l.mappings + var mappings = []; + + for (let map in liberator.mappings.getUserIterator(modes)) + mappings.push([map.names[0], ""]); + + return [0, this.filter(mappings, filter)]; } + // }}} }; //}}} }; //}}} diff --git a/content/events.js b/content/events.js index 8fd23ea0..6242be7d 100644 --- a/content/events.js +++ b/content/events.js @@ -127,7 +127,7 @@ liberator.AutoCommands = function () //{{{ }, { bang: true, - completer: function (filter) liberator.completion.autocommand(filter) + completer: function (filter) liberator.completion.event(filter) }); // TODO: expand target to all buffers @@ -139,7 +139,7 @@ liberator.AutoCommands = function () //{{{ }, { argCount: "+", - completer: function (filter) liberator.completion.autocommand(filter) + completer: function (filter) liberator.completion.event(filter) } ); @@ -175,7 +175,7 @@ liberator.AutoCommands = function () //{{{ { // TODO: Vim actually just displays "No matching autocommands" when no arg is specified argCount: "+", - completer: function (filter) liberator.completion.autocommand(filter) + completer: function (filter) liberator.completion.event(filter) } ); diff --git a/content/liberator.js b/content/liberator.js index b264034c..a5ee41b5 100644 --- a/content/liberator.js +++ b/content/liberator.js @@ -241,11 +241,10 @@ const liberator = (function () //{{{ }, { argCount: "+", // NOTE: single arg may contain unescaped whitespace + // TODO: add this as a standard menu completion function completer: function (filter) { - var completions = getMenuItems().map( - function (item) [item.fullMenuPath, item.label] - ); + let completions = getMenuItems().map(function (item) [item.fullMenuPath, item.label]); return [0, liberator.completion.filter(completions, filter)]; } }); @@ -313,7 +312,7 @@ const liberator = (function () //{{{ }, { bang: true, - completer: function (filter) getHelpCompletions(filter) + completer: function (filter) liberator.completion.help(filter) }); liberator.commands.add(["javas[cript]", "js"], @@ -543,33 +542,6 @@ const liberator = (function () //{{{ }); } - function getHelpCompletions(filter) - { - var files = liberator.config.helpFiles || []; - var res = []; - - for (let i = 0; i < files.length; i++) - { - try - { - var xmlhttp = new XMLHttpRequest(); - xmlhttp.open("GET", "chrome://" + liberator.config.name.toLowerCase() + "/locale/" + files[i], false); - xmlhttp.send(null); - } - catch (e) - { - liberator.log("Error opening chrome://" + liberator.config.name.toLowerCase() + "/locale/" + files[i], 1); - continue; - } - var doc = xmlhttp.responseXML; - var elems = doc.getElementsByClassName("tag"); - for (let j = 0; j < elems.length; j++) - res.push([elems[j].textContent, files[i]]); - } - - return [0, liberator.completion.filter(res, filter)]; - } - // initially hide all GUI, it is later restored unless the user has :set go= or something // similar in his config function hideGUI() @@ -905,7 +877,7 @@ const liberator = (function () //{{{ }, 500); } - var [, items] = getHelpCompletions(topic); + var [, items] = liberator.completion.help(topic); var partialMatch = -1; for (let i = 0; i < items.length; i++) diff --git a/content/tabs.js b/content/tabs.js index 6000a3b6..41fb09da 100644 --- a/content/tabs.js +++ b/content/tabs.js @@ -145,9 +145,9 @@ liberator.Tabs = function () //{{{ completer: function (filter) { return [ - ["0", "Never show tab bar"], - ["1", "Show tab bar only if more than one tab is open"], - ["2", "Always show tab bar"] + ["0", "Never show tab bar"], + ["1", "Show tab bar only if more than one tab is open"], + ["2", "Always show tab bar"] ]; }, validator: function (value) value >= 0 && value <= 2 diff --git a/content/ui.js b/content/ui.js index 07d4b60e..4534def7 100644 --- a/content/ui.js +++ b/content/ui.js @@ -1430,9 +1430,9 @@ liberator.StatusLine = function () //{{{ completer: function (filter) { return [ - ["0", "Never display status line"], - ["1", "Display status line only if there are multiple windows"], - ["2", "Always display status line"] + ["0", "Never display status line"], + ["1", "Display status line only if there are multiple windows"], + ["2", "Always display status line"] ]; }, validator: function (value) value >= 0 && value <= 2 diff --git a/content/vimperator.js b/content/vimperator.js index 08169b21..dbb7a120 100644 --- a/content/vimperator.js +++ b/content/vimperator.js @@ -331,16 +331,7 @@ liberator.config = { //{{{ }, { argCount: "+", - completer: function (filter) - { - var menu = document.getElementById("viewSidebarMenu"); - var nodes = []; - - for (let i = 0; i < menu.childNodes.length; i++) - nodes.push([menu.childNodes[i].label, ""]); - - return [0, liberator.completion.filter(nodes, filter)]; - } + completer: function (filter) liberator.completion.sidebar(filter) }); liberator.commands.add(["winc[lose]", "wc[lose]"],