diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js
index effa9663..657e9c06 100644
--- a/common/content/bookmarks.js
+++ b/common/content/bookmarks.js
@@ -399,6 +399,93 @@ function Bookmarks() //{{{
literal: 0
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ completion.bookmark = function bookmark(context, tags, extra) {
+ context.title = ["Bookmark", "Title"];
+ context.format = bookmarks.format;
+ for (let val in Iterator(extra || []))
+ {
+ let [k, v] = val; // Need block scope here for the closure
+ if (v)
+ context.filters.push(function (item) this._match(v, item[k]));
+ }
+ // Need to make a copy because set completions() checks instanceof Array,
+ // and this may be an Array from another window.
+ context.completions = Array.slice(storage["bookmark-cache"].bookmarks);
+ completion.urls(context, tags);
+ };
+
+ completion.search = function search(context, noSuggest) {
+ let [, keyword, space, args] = context.filter.match(/^\s*(\S*)(\s*)(.*)$/);
+ let keywords = bookmarks.getKeywords();
+ let engines = bookmarks.getSearchEngines();
+
+ context.title = ["Search Keywords"];
+ context.completions = keywords.concat(engines);
+ context.keys = { text: 0, description: 1, icon: 2 };
+
+ if (!space || noSuggest)
+ return;
+
+ context.fork("suggest", keyword.length + space.length, this, "searchEngineSuggest",
+ keyword, true);
+
+ let item = keywords.filter(function (k) k.keyword == keyword)[0];
+ if (item && item.url.indexOf("%s") > -1)
+ context.fork("keyword/" + keyword, keyword.length + space.length, null, function (context) {
+ context.format = history.format;
+ context.title = [keyword + " Quick Search"];
+ // context.background = true;
+ context.compare = CompletionContext.Sort.unsorted;
+ context.generate = function () {
+ let [begin, end] = item.url.split("%s");
+
+ return history.get({ uri: window.makeURI(begin), uriIsPrefix: true }).map(function (item) {
+ let rest = item.url.length - end.length;
+ let query = item.url.substring(begin.length, rest);
+ if (item.url.substr(rest) == end && query.indexOf("&") == -1)
+ {
+ item.url = decodeURIComponent(query);
+ return item;
+ }
+ }).filter(util.identity);
+ };
+ });
+ };
+
+ completion.searchEngineSuggest = function searchEngineSuggest(context, engineAliases, kludge) {
+ if (!context.filter)
+ return;
+
+ let engineList = (engineAliases || options["suggestengines"] || "google").split(",");
+
+ let completions = [];
+ engineList.forEach(function (name) {
+ let engine = services.get("browserSearch").getEngineByAlias(name);
+ if (!engine)
+ return;
+ let [,word] = /^\s*(\S+)/.exec(context.filter) || [];
+ if (!kludge && word == name) // FIXME: Check for matching keywords
+ return;
+ let ctxt = context.fork(name, 0);
+
+ ctxt.title = [engine.description + " Suggestions"];
+ ctxt.compare = CompletionContext.Sort.unsorted;
+ ctxt.incomplete = true;
+ bookmarks.getSuggestions(name, ctxt.filter, function (compl) {
+ ctxt.incomplete = false;
+ ctxt.completions = compl;
+ });
+ });
+ };
+
+ completion.addUrlCompleter("S", "Suggest engines", completion.searchEngineSuggest);
+ completion.addUrlCompleter("b", "Bookmarks", completion.bookmark);
+ completion.addUrlCompleter("s", "Search engines and keyword URLs", completion.search);
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
@@ -840,6 +927,23 @@ function History() //{{{
options: [[["-max", "-m"], options.OPTION_INT]]
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ completion.history = function _history(context, maxItems) {
+ context.format = history.format;
+ context.title = ["History"]
+ context.compare = CompletionContext.Sort.unsorted;
+ //context.background = true;
+ if (context.maxItems == null)
+ context.maxItems = 100;
+ context.regenerate = true;
+ context.generate = function () history.get(context.filter, this.maxItems);
+ };
+
+ completion.addUrlCompleter("h", "History", completion.history);
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
diff --git a/common/content/buffer.js b/common/content/buffer.js
index 25f1df71..63321fbb 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -673,6 +673,61 @@ function Buffer() //{{{
bang: true
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ liberator.registerObserver("load_completion", function () {
+ completion.alternateStyleSheet = function alternateStylesheet(context) {
+ context.title = ["Stylesheet", "Location"];
+
+ // unify split style sheets
+ let styles = {};
+
+ buffer.alternateStyleSheets.forEach(function (style) {
+ if (!(style.title in styles))
+ styles[style.title] = [];
+
+ styles[style.title].push(style.href || "inline");
+ });
+
+ context.completions = [[s, styles[s].join(", ")] for (s in styles)];
+ };
+
+ completion.buffer = function buffer(context) {
+ filter = context.filter.toLowerCase();
+ context.anchored = false;
+ context.title = ["Buffer", "URL"];
+ context.keys = { text: "text", description: "url", icon: "icon" };
+ context.compare = CompletionContext.Sort.number;
+ let process = context.process[0];
+ context.process = [function (item, text)
+ <>
+ {item.item.indicator}
+ { process.call(this, item, text) }
+ >];
+
+ context.completions = util.map(tabs.browsers, function ([i, browser]) {
+ let indicator = " ";
+ if (i == tabs.index())
+ indicator = "%"
+ else if (i == tabs.index(tabs.alternate))
+ indicator = "#";
+
+ let tab = tabs.getTab(i);
+ let url = browser.contentDocument.location.href;
+ i = i + 1;
+
+ return {
+ text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url],
+ url: template.highlightURL(url),
+ indicator: indicator,
+ icon: tab.image || DEFAULT_FAVICON
+ };
+ });
+ };
+ });
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PAGE INFO ///////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
@@ -1769,6 +1824,19 @@ function Marks() //{{{
marks.list(filter);
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ completion.mark = function mark(context) {
+ function percent(i) Math.round(i * 100);
+
+ // FIXME: Line/Column doesn't make sense with %
+ context.title = ["Mark", "Line Column File"];
+ context.keys.description = function ([,m]) percent(m.position.y) + "% " + percent(m.position.x) + "% " + m.location;
+ context.completions = marks.all;
+ };
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
diff --git a/common/content/commands.js b/common/content/commands.js
index 5d7707b2..10554283 100644
--- a/common/content/commands.js
+++ b/common/content/commands.js
@@ -392,10 +392,6 @@ function Commands() //{{{
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
- liberator.registerObserver("load_completion", function () {
- completion.setFunctionCompleter(commands.get, [function () ([c.name, c.description] for (c in commands))]);
- });
-
const self = {
// FIXME: remove later, when our option handler is better
@@ -1156,6 +1152,77 @@ function Commands() //{{{
completer: function (context) completion.userCommand(context)
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ liberator.registerObserver("load_completion", function () {
+ completion.setFunctionCompleter(commands.get, [function () ([c.name, c.description] for (c in commands))]);
+
+ completion.command = function command(context) {
+ context.title = ["Command"];
+ context.keys = { text: "longNames", description: "description" };
+ context.completions = [k for (k in commands)];
+ };
+
+ // provides completions for ex commands, including their arguments
+ completion.ex = function ex(context) {
+ // if there is no space between the command name and the cursor
+ // then get completions of the command name
+ let [count, cmd, bang, args] = commands.parseCommand(context.filter);
+ let [, prefix, junk] = context.filter.match(/^(:*\d*)\w*(.?)/) || [];
+ context.advance(prefix.length)
+ if (!junk)
+ return context.fork("", 0, this, "command");
+
+ // dynamically get completions as specified with the command's completer function
+ let command = commands.get(cmd);
+ if (!command)
+ {
+ context.highlight(0, cmd.length, "SPELLCHECK");
+ return;
+ }
+
+ [prefix] = context.filter.match(/^(?:\w*[\s!]|!)\s*/);
+ let cmdContext = context.fork(cmd, prefix.length);
+ let argContext = context.fork("args", prefix.length);
+ args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang });
+ if (args)
+ {
+ // FIXME: Move to parseCommand
+ args.count = count;
+ args.bang = bang;
+ if (!args.completeOpt && command.completer)
+ {
+ cmdContext.advance(args.completeStart);
+ cmdContext.quote = args.quote;
+ cmdContext.filter = args.completeFilter;
+ try
+ {
+ let compObject = command.completer.call(command, cmdContext, args);
+ 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)
+ {
+ cmdContext.advance(compObject.start);
+ cmdContext.filterFunc = null;
+ cmdContext.completions = compObject.items;
+ }
+ }
+ catch (e)
+ {
+ liberator.reportError(e);
+ }
+ }
+ }
+ };
+
+ completion.userCommand = function userCommand(context) {
+ context.title = ["User Command", "Definition"];
+ context.keys = { text: "name", description: "replacementText" };
+ context.completions = commands.getUserCommands();
+ };
+ });
//}}}
return self;
diff --git a/common/content/completion.js b/common/content/completion.js
index e8308e44..0a3f796b 100644
--- a/common/content/completion.js
+++ b/common/content/completion.js
@@ -1340,533 +1340,10 @@ function Completion() //{{{
////////////////////// COMPLETION TYPES ////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
- // TODO: shouldn't all of these have a standard signature (context, args, ...)? --djk
- abbreviation: function abbreviation(context, args, mode)
- {
- mode = mode || "!";
-
- if (args.completeArg == 0)
- {
- let abbreviations = editor.getAbbreviations(mode);
- context.completions = [[lhs, ""] for ([, [, lhs,]] in Iterator(abbreviations))];
- }
- },
-
- alternateStyleSheet: function alternateStylesheet(context)
- {
- context.title = ["Stylesheet", "Location"];
-
- // unify split style sheets
- let styles = {};
-
- buffer.alternateStyleSheets.forEach(function (style) {
- if (!(style.title in styles))
- styles[style.title] = [];
-
- styles[style.title].push(style.href || "inline");
- });
-
- context.completions = [[s, styles[s].join(", ")] for (s in styles)];
- },
-
- autocmdEvent: function autocmdEvent(context)
- {
- context.completions = config.autocommands;
- },
-
- // TODO: shouldn't these app-specific completers be moved to config.js? --djk
- bookmark: function bookmark(context, tags, extra)
- {
- context.title = ["Bookmark", "Title"];
- context.format = bookmarks.format;
- for (let val in Iterator(extra || []))
- {
- let [k, v] = val; // Need block scope here for the closure
- if (v)
- context.filters.push(function (item) this._match(v, item[k]));
- }
- // Need to make a copy because set completions() checks instanceof Array,
- // and this may be an Array from another window.
- context.completions = Array.slice(storage["bookmark-cache"].bookmarks);
- completion.urls(context, tags);
- },
-
- song: function song(context, args)
- {
- // TODO: useful descriptions?
- function map(list) list.map(function (i) [i, ""]);
- let [artist, album] = [args[0], args[1]];
-
- if (args.completeArg == 0)
- {
- context.title = ["Artists"];
- context.completions = map(library.getArtists());
- }
- else if (args.completeArg == 1)
- {
- context.title = ["Albums by " + artist];
- context.completions = map(library.getAlbums(artist));
- }
- else if (args.completeArg == 2)
- {
- context.title = ["Tracks from " + album + " by " + artist];
- context.completions = map(library.getTracks(artist, album));
- }
- },
-
- playlist: function playlist(context, args)
- {
- context.title = ["Playlist", "Type"];
- context.keys = { text: "name", description: "type" };
- context.completions = player.getPlaylists();
- },
-
- buffer: function buffer(context)
- {
- filter = context.filter.toLowerCase();
- context.anchored = false;
- context.title = ["Buffer", "URL"];
- context.keys = { text: "text", description: "url", icon: "icon" };
- context.compare = CompletionContext.Sort.number;
- let process = context.process[0];
- context.process = [function (item, text)
- <>
- {item.item.indicator}
- { process.call(this, item, text) }
- >];
-
- context.completions = util.map(tabs.browsers, function ([i, browser]) {
- let indicator = " ";
- if (i == tabs.index())
- indicator = "%"
- else if (i == tabs.index(tabs.alternate))
- indicator = "#";
-
- let tab = tabs.getTab(i);
- let url = browser.contentDocument.location.href;
- i = i + 1;
-
- return {
- text: [i + ": " + (tab.label || "(Untitled)"), i + ": " + url],
- url: template.highlightURL(url),
- indicator: indicator,
- icon: tab.image || DEFAULT_FAVICON
- };
- });
- },
-
- charset: function (context)
- {
- context.anchored = false;
- context.generate = function () {
- let names = util.Array(
- 'more1 more2 more3 more4 more5 unicode'.split(' ').map(function (key)
- options.getPref('intl.charsetmenu.browser.' + key).split(', ')))
- .flatten().uniq();
- let bundle = document.getElementById('liberator-charset-bundle');
- return names.map(function (name) [name, bundle.getString(name.toLowerCase() + '.title')]);
- };
- },
-
- colorScheme: function colorScheme(context)
- {
- let colors = [];
-
- io.getRuntimeDirectories("colors").forEach(function (dir) {
- io.readDirectory(dir).forEach(function (file) {
- if (/\.vimp$/.test(file.leafName) && !colors.some(function (c) c.leafName == file.leafName))
- colors.push(file);
- });
- });
-
- context.title = ["Color Scheme", "Runtime Path"];
- context.completions = [[c.leafName.replace(/\.vimp$/, ""), c.parent.path] for ([,c] in Iterator(colors))]
- },
-
- command: function command(context)
- {
- context.title = ["Command"];
- context.keys = { text: "longNames", description: "description" };
- context.completions = [k for (k in commands)];
- },
-
- dialog: function dialog(context)
- {
- context.title = ["Dialog"];
- context.completions = config.dialogs;
- },
-
- directory: function directory(context, full)
- {
- this.file(context, full);
- context.filters.push(function ({ item: f }) f.isDirectory());
- },
-
- environment: function environment(context)
- {
- let command = liberator.has("Win32") ? "set" : "env";
- let lines = io.system(command).split("\n");
- lines.pop();
-
- context.title = ["Environment Variable", "Value"];
- context.generate = function () lines.map(function (line) (line.match(/([^=]+)=(.+)/) || []).slice(1));
- },
-
- // provides completions for ex commands, including their arguments
- ex: function ex(context)
- {
- // if there is no space between the command name and the cursor
- // then get completions of the command name
- let [count, cmd, bang, args] = commands.parseCommand(context.filter);
- let [, prefix, junk] = context.filter.match(/^(:*\d*)\w*(.?)/) || [];
- context.advance(prefix.length)
- if (!junk)
- return context.fork("", 0, this, "command");
-
- // dynamically get completions as specified with the command's completer function
- let command = commands.get(cmd);
- if (!command)
- {
- context.highlight(0, cmd.length, "SPELLCHECK");
- return;
- }
-
- [prefix] = context.filter.match(/^(?:\w*[\s!]|!)\s*/);
- let cmdContext = context.fork(cmd, prefix.length);
- let argContext = context.fork("args", prefix.length);
- args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang });
- if (args)
- {
- // FIXME: Move to parseCommand
- args.count = count;
- args.bang = bang;
- if (!args.completeOpt && command.completer)
- {
- cmdContext.advance(args.completeStart);
- cmdContext.quote = args.quote;
- cmdContext.filter = args.completeFilter;
- try
- {
- let compObject = command.completer.call(command, cmdContext, args);
- 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)
- {
- cmdContext.advance(compObject.start);
- cmdContext.filterFunc = null;
- cmdContext.completions = compObject.items;
- }
- }
- catch (e)
- {
- liberator.reportError(e);
- }
- }
- }
- },
-
- // TODO: support file:// and \ or / path separators on both platforms
- // if "tail" is true, only return names without any directory components
- file: function file(context, full)
- {
- // dir == "" is expanded inside readDirectory to the current dir
- let [dir] = context.filter.match(/^(?:.*[\/\\])?/);
-
- if (!full)
- context.advance(dir.length);
-
- context.title = [full ? "Path" : "Filename", "Type"];
- context.keys = {
- text: !full ? "leafName" : function (f) dir + f.leafName,
- description: function (f) f.isDirectory() ? "Directory" : "File",
- isdir: function (f) f.isDirectory(),
- icon: function (f) f.isDirectory() ? "resource://gre/res/html/folder.png"
- : "moz-icon://" + f.leafName
- };
- context.compare = function (a, b)
- b.isdir - a.isdir || String.localeCompare(a.text, b.text);
-
- if (options["wildignore"])
- {
- let wigRegexp = RegExp("(^" + options.get("wildignore").values.join("|") + ")$");
- context.filters.push(function ({item: f}) f.isDirectory() || !wigRegexp.test(f.leafName));
- }
-
- // context.background = true;
- context.key = dir;
- context.generate = function generate_file()
- {
- try
- {
- return io.readDirectory(dir);
- }
- catch (e) {}
- };
- },
-
- help: function help(context)
- {
- context.title = ["Help"];
- context.anchored = false;
- context.generate = function ()
- {
- let res = config.helpFiles.map(function (file) {
- let resp = util.httpGet("chrome://liberator/locale/" + file);
- if (!resp)
- return [];
- let doc = resp.responseXML;
- return Array.map(doc.getElementsByClassName("tag"),
- function (elem) [elem.textContent, file]);
- });
- return util.Array.flatten(res);
- }
- },
-
- // XXX
- highlightGroup: function highlightGroup(context, args) commands.get("highlight").completer(context, args),
-
- history: function _history(context, maxItems)
- {
- context.format = history.format;
- context.title = ["History"]
- context.compare = CompletionContext.Sort.unsorted;
- //context.background = true;
- if (context.maxItems == null)
- context.maxItems = 100;
- context.regenerate = true;
- context.generate = function () history.get(context.filter, this.maxItems);
- },
-
get javascriptCompleter() javascript,
javascript: function _javascript(context) javascript.complete(context),
- location: function location(context)
- {
- if (!services.get("autoCompleteSearch"))
- return;
-
- context.anchored = false;
- context.title = ["Smart Completions"];
- context.keys.icon = 2;
- context.incomplete = true;
- context.hasItems = context.completions.length > 0; // XXX
- context.filterFunc = null;
- context.cancel = function () services.get("autoCompleteSearch").stopSearch();
- context.compare = CompletionContext.Sort.unsorted;
- let timer = new Timer(50, 100, function (result) {
- context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING;
- context.completions = [
- [result.getValueAt(i), result.getCommentAt(i), result.getImageAt(i)]
- for (i in util.range(0, result.matchCount))
- ];
- });
- services.get("autoCompleteSearch").stopSearch();
- services.get("autoCompleteSearch").startSearch(context.filter, "", context.result, {
- onSearchResult: function onSearchResult(search, result)
- {
- context.result = result;
- timer.tell(result);
- if (result.searchResult <= result.RESULT_SUCCESS)
- timer.flush();
- }
- });
- },
-
- macro: function macro(context)
- {
- context.title = ["Macro", "Keys"];
- context.completions = [item for (item in events.getMacros())];
- },
-
- mark: function mark(context)
- {
- function percent(i) Math.round(i * 100);
-
- // FIXME: Line/Column doesn't make sense with %
- context.title = ["Mark", "Line Column File"];
- context.keys.description = function ([,m]) percent(m.position.y) + "% " + percent(m.position.x) + "% " + m.location;
- context.completions = marks.all;
- },
-
- mediaView: function mediaView(context)
- {
- context.title = ["Media View", "URL"];
- context.anchored = false;
- context.keys = { text: "contentTitle", description: "contentUrl" };
- context.completions = player.getMediaPages();
- },
-
- menuItem: function menuItem(context)
- {
- context.title = ["Menu Path", "Label"];
- context.anchored = false;
- context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
- context.completions = liberator.menuItems;
- },
-
- option: function option(context, scope)
- {
- context.title = ["Option"];
- context.keys = { text: "names", description: "description" };
- context.completions = options;
- if (scope)
- context.filters.push(function ({ item: opt }) opt.scope & scope);
- },
-
- optionValue: function (context, name, op, curValue)
- {
- let opt = options.get(name);
- let completer = opt.completer;
- if (!completer)
- return;
-
- let curValues = curValue != null ? opt.parseValues(curValue) : opt.values;
- let newValues = opt.parseValues(context.filter);
-
- let len = context.filter.length;
- switch (opt.type)
- {
- case "boolean":
- if (!completer)
- completer = function () [["true", ""], ["false", ""]];
- break;
- case "stringlist":
- let target = newValues.pop();
- len = target ? target.length : 0;
- break;
- case "charlist":
- len = 0;
- break;
- }
- // TODO: Highlight when invalid
- context.advance(context.filter.length - len);
-
- context.title = ["Option Value"];
- let completions = completer(context);
- if (!completions)
- return;
- // Not Vim compatible, but is a significant enough improvement
- // that it's worth breaking compatibility.
- if (newValues instanceof Array)
- {
- completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1);
- switch (op)
- {
- case "+":
- completions = completions.filter(function (val) curValues.indexOf(val[0]) == -1);
- break;
- case "-":
- completions = completions.filter(function (val) curValues.indexOf(val[0]) > -1);
- break;
- }
- }
- context.completions = completions;
- },
-
- preference: function preference(context)
- {
- context.anchored = false;
- context.title = ["Firefox Preference", "Value"];
- context.keys = { text: function (item) item, description: function (item) options.getPref(item) };
- context.completions = services.get("pref").getChildList("", { value: 0 });
- },
-
- search: function search(context, noSuggest)
- {
- let [, keyword, space, args] = context.filter.match(/^\s*(\S*)(\s*)(.*)$/);
- let keywords = bookmarks.getKeywords();
- let engines = bookmarks.getSearchEngines();
-
- context.title = ["Search Keywords"];
- context.completions = keywords.concat(engines);
- context.keys = { text: 0, description: 1, icon: 2 };
-
- if (!space || noSuggest)
- return;
-
- context.fork("suggest", keyword.length + space.length, this, "searchEngineSuggest",
- keyword, true);
-
- let item = keywords.filter(function (k) k.keyword == keyword)[0];
- if (item && item.url.indexOf("%s") > -1)
- context.fork("keyword/" + keyword, keyword.length + space.length, null, function (context) {
- context.format = history.format;
- context.title = [keyword + " Quick Search"];
- // context.background = true;
- context.compare = CompletionContext.Sort.unsorted;
- context.generate = function () {
- let [begin, end] = item.url.split("%s");
-
- return history.get({ uri: window.makeURI(begin), uriIsPrefix: true }).map(function (item) {
- let rest = item.url.length - end.length;
- let query = item.url.substring(begin.length, rest);
- if (item.url.substr(rest) == end && query.indexOf("&") == -1)
- {
- item.url = decodeURIComponent(query);
- return item;
- }
- }).filter(util.identity);
- };
- });
- },
-
- searchEngineSuggest: function searchEngineSuggest(context, engineAliases, kludge)
- {
- if (!context.filter)
- return;
-
- let engineList = (engineAliases || options["suggestengines"] || "google").split(",");
-
- let completions = [];
- engineList.forEach(function (name) {
- let engine = services.get("browserSearch").getEngineByAlias(name);
- if (!engine)
- return;
- let [,word] = /^\s*(\S+)/.exec(context.filter) || [];
- if (!kludge && word == name) // FIXME: Check for matching keywords
- return;
- let ctxt = context.fork(name, 0);
-
- ctxt.title = [engine.description + " Suggestions"];
- ctxt.compare = CompletionContext.Sort.unsorted;
- ctxt.incomplete = true;
- bookmarks.getSuggestions(name, ctxt.filter, function (compl) {
- ctxt.incomplete = false;
- ctxt.completions = compl;
- });
- });
- },
-
- shellCommand: function shellCommand(context)
- {
- context.title = ["Shell Command", "Path"];
- context.generate = function ()
- {
- let dirNames = services.get("environment").get("PATH").split(RegExp(liberator.has("Win32") ? ";" : ":"));
- let commands = [];
-
- for (let [,dirName] in Iterator(dirNames))
- {
- let dir = io.getFile(dirName);
- if (dir.exists() && dir.isDirectory())
- {
- commands.push([[file.leafName, dir.path] for ([i, file] in Iterator(io.readDirectory(dir)))
- if (file.isFile() && file.isExecutable())]);
- }
- }
-
- return util.Array.flatten(commands);
- }
- },
-
- sidebar: function sidebar(context)
- {
- let menu = document.getElementById("viewSidebarMenu");
- context.title = ["Sidebar Panel"];
- context.completions = Array.map(menu.childNodes, function (n) [n.label, ""]);
- },
-
// filter a list of urls
//
// may consist of search engines, filenames, bookmarks and history,
@@ -1938,36 +1415,11 @@ function Completion() //{{{
function (item, text) highlight.call(this, item, text, 1)
];
});
- },
-
- userCommand: function userCommand(context)
- {
- context.title = ["User Command", "Definition"];
- context.keys = { text: "name", description: "replacementText" };
- context.completions = commands.getUserCommands();
- },
-
- userMapping: function userMapping(context, args, modes)
- {
- // FIXME: have we decided on a 'standard' way to handle this clash? --djk
- modes = modes || [modules.modes.NORMAL];
-
- if (args.completeArg == 0)
- {
- let maps = [[m.names[0], ""] for (m in mappings.getUserIterator(modes))];
- context.completions = maps;
- }
}
//}}}
};
const UrlCompleter = new Struct("name", "description", "completer");
- self.addUrlCompleter("S", "Suggest engines", self.searchEngineSuggest);
- self.addUrlCompleter("b", "Bookmarks", self.bookmark);
- self.addUrlCompleter("h", "History", self.history);
- self.addUrlCompleter("f", "Local files", self.file);
- self.addUrlCompleter("l", "Firefox location bar entries (bookmarks and history sorted in an intelligent way)", self.location);
- self.addUrlCompleter("s", "Search engines and keyword URLs", self.search);
return self;
//}}}
diff --git a/common/content/editor.js b/common/content/editor.js
index 1f722d81..bccf70eb 100644
--- a/common/content/editor.js
+++ b/common/content/editor.js
@@ -565,6 +565,23 @@ function Editor() //{{{
addAbbreviationCommands("i", "insert");
addAbbreviationCommands("c", "command line");
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ liberator.registerObserver("load_completion", function () {
+ // TODO: shouldn't all of these have a standard signature (context, args, ...)? --djk
+ completion.abbreviation = function abbreviation(context, args, mode) {
+ mode = mode || "!";
+
+ if (args.completeArg == 0)
+ {
+ let abbreviations = editor.getAbbreviations(mode);
+ context.completions = [[lhs, ""] for ([, [, lhs,]] in Iterator(abbreviations))];
+ }
+ };
+ });
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
diff --git a/common/content/events.js b/common/content/events.js
index 7a0e7a4e..f170d3ca 100644
--- a/common/content/events.js
+++ b/common/content/events.js
@@ -161,13 +161,26 @@ function AutoCommands() //{{{
);
/////////////////////////////////////////////////////////////////////////////}}}
- ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () {
completion.setFunctionCompleter(autocommands.get, [function () config.autocommands]);
+
+ completion.autocmdEvent = function autocmdEvent(context) {
+ context.completions = config.autocommands;
+ };
+
+ completion.macro = function macro(context) {
+ context.title = ["Macro", "Keys"];
+ context.completions = [item for (item in events.getMacros())];
+ };
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
return {
__iterator__: function () util.Array.itervalues(store),
diff --git a/common/content/io.js b/common/content/io.js
index 7101ed20..9feee498 100644
--- a/common/content/io.js
+++ b/common/content/io.js
@@ -386,7 +386,7 @@ function IO() //{{{
});
/////////////////////////////////////////////////////////////////////////////}}}
- ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () {
@@ -395,8 +395,100 @@ function IO() //{{{
context.quote[2] = "";
completion.file(context, true);
}]);
+
+ completion.charset = function (context) {
+ context.anchored = false;
+ context.generate = function () {
+ let names = util.Array(
+ "more1 more2 more3 more4 more5 unicode".split(" ").map(function (key)
+ options.getPref("intl.charsetmenu.browser." + key).split(', '))
+ ).flatten().uniq();
+ let bundle = document.getElementById("liberator-charset-bundle");
+ return names.map(function (name) [name, bundle.getString(name.toLowerCase() + ".title")]);
+ };
+ };
+
+ completion.directory = function directory(context, full) {
+ this.file(context, full);
+ context.filters.push(function ({ item: f }) f.isDirectory());
+ };
+
+ completion.environment = function environment(context) {
+ let command = liberator.has("Win32") ? "set" : "env";
+ let lines = io.system(command).split("\n");
+ lines.pop();
+
+ context.title = ["Environment Variable", "Value"];
+ context.generate = function () lines.map(function (line) (line.match(/([^=]+)=(.+)/) || []).slice(1));
+ };
+
+ // TODO: support file:// and \ or / path separators on both platforms
+ // if "tail" is true, only return names without any directory components
+ completion.file = function file(context, full) {
+ // dir == "" is expanded inside readDirectory to the current dir
+ let [dir] = context.filter.match(/^(?:.*[\/\\])?/);
+
+ if (!full)
+ context.advance(dir.length);
+
+ context.title = [full ? "Path" : "Filename", "Type"];
+ context.keys = {
+ text: !full ? "leafName" : function (f) dir + f.leafName,
+ description: function (f) f.isDirectory() ? "Directory" : "File",
+ isdir: function (f) f.isDirectory(),
+ icon: function (f) f.isDirectory() ? "resource://gre/res/html/folder.png"
+ : "moz-icon://" + f.leafName
+ };
+ context.compare = function (a, b)
+ b.isdir - a.isdir || String.localeCompare(a.text, b.text);
+
+ if (options["wildignore"])
+ {
+ let wigRegexp = RegExp("(^" + options.get("wildignore").values.join("|") + ")$");
+ context.filters.push(function ({item: f}) f.isDirectory() || !wigRegexp.test(f.leafName));
+ }
+
+ // context.background = true;
+ context.key = dir;
+ context.generate = function generate_file()
+ {
+ try
+ {
+ return io.readDirectory(dir);
+ }
+ catch (e) {}
+ };
+ };
+
+ completion.shellCommand = function shellCommand(context) {
+ context.title = ["Shell Command", "Path"];
+ context.generate = function ()
+ {
+ let dirNames = services.get("environment").get("PATH").split(RegExp(liberator.has("Win32") ? ";" : ":"));
+ let commands = [];
+
+ for (let [,dirName] in Iterator(dirNames))
+ {
+ let dir = io.getFile(dirName);
+ if (dir.exists() && dir.isDirectory())
+ {
+ commands.push([[file.leafName, dir.path] for ([i, file] in Iterator(io.readDirectory(dir)))
+ if (file.isFile() && file.isExecutable())]);
+ }
+ }
+
+ return util.Array.flatten(commands);
+ }
+ };
+
+ completion.addUrlCompleter("f", "Local files", completion.file);
});
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
const self = {
/**
diff --git a/common/content/liberator.js b/common/content/liberator.js
index 855004d4..74c982ce 100644
--- a/common/content/liberator.js
+++ b/common/content/liberator.js
@@ -87,6 +87,72 @@ const liberator = (function () //{{{
}
}
+ // initially hide all GUI, it is later restored unless the user has :set go= or something
+ // similar in his config
+ function hideGUI()
+ {
+ let guioptions = config.guioptions;
+ for (let option in guioptions)
+ {
+ guioptions[option].forEach(function (elem) {
+ try
+ {
+ document.getElementById(elem).collapsed = true;
+ }
+ catch (e) {}
+ });
+ }
+ }
+
+ // return the platform normalized to Vim values
+ function getPlatformFeature()
+ {
+ let platform = navigator.platform;
+
+ return /^Mac/.test(platform) ? "MacUnix" : platform == "Win32" ? "Win32" : "Unix";
+ }
+
+ // TODO: move this
+ function getMenuItems()
+ {
+ function addChildren(node, parent)
+ {
+ for (let [,item] in Iterator(node.childNodes))
+ {
+ if (item.childNodes.length == 0 && item.localName == "menuitem"
+ && !/rdf:http:/.test(item.getAttribute("label"))) // FIXME
+ {
+ item.fullMenuPath = parent + item.getAttribute("label");
+ items.push(item);
+ }
+ else
+ {
+ let path = parent;
+ if (item.localName == "menu")
+ path += item.getAttribute("label") + ".";
+ addChildren(item, path);
+ }
+ }
+ }
+
+ let items = [];
+ addChildren(document.getElementById(config.guioptions["m"][1]), "");
+ return items;
+ }
+
+ // show a usage index either in the MOW or as a full help page
+ function showHelpIndex(tag, items, inMow)
+ {
+ if (inMow)
+ liberator.echo(template.usage(items), commandline.FORCE_MULTILINE);
+ else
+ liberator.help(tag);
+ }
+
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// OPTIONS /////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
// Only general options are added here, which are valid for all Vimperator like extensions
registerObserver("load_options", function () {
@@ -191,6 +257,10 @@ const liberator = (function () //{{{
});
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// MAPPINGS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
registerObserver("load_mappings", function () {
mappings.add(modes.all, [""],
@@ -209,33 +279,9 @@ const liberator = (function () //{{{
function () { liberator.quit(true); });
});
- // TODO: move this
- function getMenuItems()
- {
- function addChildren(node, parent)
- {
- for (let [,item] in Iterator(node.childNodes))
- {
- if (item.childNodes.length == 0 && item.localName == "menuitem"
- && !/rdf:http:/.test(item.getAttribute("label"))) // FIXME
- {
- item.fullMenuPath = parent + item.getAttribute("label");
- items.push(item);
- }
- else
- {
- let path = parent;
- if (item.localName == "menu")
- path += item.getAttribute("label") + ".";
- addChildren(item, path);
- }
- }
- }
-
- let items = [];
- addChildren(document.getElementById(config.guioptions["m"][1]), "");
- return items;
- }
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMMANDS ////////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
registerObserver("load_commands", function () {
@@ -544,39 +590,40 @@ const liberator = (function () //{{{
});
});
- // initially hide all GUI, it is later restored unless the user has :set go= or something
- // similar in his config
- function hideGUI()
- {
- let guioptions = config.guioptions;
- for (let option in guioptions)
- {
- guioptions[option].forEach(function (elem) {
- try
- {
- document.getElementById(elem).collapsed = true;
- }
- catch (e) {}
- });
- }
- }
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
- // return the platform normalized to Vim values
- function getPlatformFeature()
- {
- let platform = navigator.platform;
+ registerObserver("load_completion", function () {
+ completion.dialog = function dialog(context) {
+ context.title = ["Dialog"];
+ context.completions = config.dialogs;
+ };
- return /^Mac/.test(platform) ? "MacUnix" : platform == "Win32" ? "Win32" : "Unix";
- }
+ completion.help = function help(context) {
+ context.title = ["Help"];
+ context.anchored = false;
+ context.generate = function ()
+ {
+ let res = config.helpFiles.map(function (file) {
+ let resp = util.httpGet("chrome://liberator/locale/" + file);
+ if (!resp)
+ return [];
+ let doc = resp.responseXML;
+ return Array.map(doc.getElementsByClassName("tag"),
+ function (elem) [elem.textContent, file]);
+ });
+ return util.Array.flatten(res);
+ }
+ };
- // show a usage index either in the MOW or as a full help page
- function showHelpIndex(tag, items, inMow)
- {
- if (inMow)
- liberator.echo(template.usage(items), commandline.FORCE_MULTILINE);
- else
- liberator.help(tag);
- }
+ completion.menuItem = function menuItem(context) {
+ context.title = ["Menu Path", "Label"];
+ context.anchored = false;
+ context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
+ context.completions = liberator.menuItems;
+ };
+ });
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
diff --git a/common/content/mappings.js b/common/content/mappings.js
index 59cf3df0..1db6f5f3 100644
--- a/common/content/mappings.js
+++ b/common/content/mappings.js
@@ -297,6 +297,23 @@ function Mappings() //{{{
[m.mask for (m in modes.mainModes) if (m.char == mode.char)],
[mode.disp.toLowerCase()]);
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ liberator.registerObserver("load_completion", function () {
+ completion.userMapping = function userMapping(context, args, modes) {
+ // FIXME: have we decided on a 'standard' way to handle this clash? --djk
+ modes = modes || [modules.modes.NORMAL];
+
+ if (args.completeArg == 0)
+ {
+ let maps = [[m.names[0], ""] for (m in mappings.getUserIterator(modes))];
+ context.completions = maps;
+ }
+ };
+ });
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
diff --git a/common/content/options.js b/common/content/options.js
index 35b9ad36..719c3107 100644
--- a/common/content/options.js
+++ b/common/content/options.js
@@ -913,18 +913,85 @@ function Options() //{{{
});
/////////////////////////////////////////////////////////////////////////////}}}
- ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
- // TODO: Does this belong elsewhere?
liberator.registerObserver("load_completion", function () {
completion.setFunctionCompleter(options.get, [function () ([o.name, o.description] for (o in options))]);
completion.setFunctionCompleter([options.getPref, options.safeSetPref, options.setPref, options.resetPref, options.invertPref],
[function () services.get("pref")
.getChildList("", { value: 0 })
.map(function (pref) [pref, ""])]);
+
+ completion.option = function option(context, scope) {
+ context.title = ["Option"];
+ context.keys = { text: "names", description: "description" };
+ context.completions = options;
+ if (scope)
+ context.filters.push(function ({ item: opt }) opt.scope & scope);
+ };
+
+ completion.optionValue = function (context, name, op, curValue) {
+ let opt = options.get(name);
+ let completer = opt.completer;
+ if (!completer)
+ return;
+
+ let curValues = curValue != null ? opt.parseValues(curValue) : opt.values;
+ let newValues = opt.parseValues(context.filter);
+
+ let len = context.filter.length;
+ switch (opt.type)
+ {
+ case "boolean":
+ if (!completer)
+ completer = function () [["true", ""], ["false", ""]];
+ break;
+ case "stringlist":
+ let target = newValues.pop();
+ len = target ? target.length : 0;
+ break;
+ case "charlist":
+ len = 0;
+ break;
+ }
+ // TODO: Highlight when invalid
+ context.advance(context.filter.length - len);
+
+ context.title = ["Option Value"];
+ let completions = completer(context);
+ if (!completions)
+ return;
+ // Not Vim compatible, but is a significant enough improvement
+ // that it's worth breaking compatibility.
+ if (newValues instanceof Array)
+ {
+ completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1);
+ switch (op)
+ {
+ case "+":
+ completions = completions.filter(function (val) curValues.indexOf(val[0]) == -1);
+ break;
+ case "-":
+ completions = completions.filter(function (val) curValues.indexOf(val[0]) > -1);
+ break;
+ }
+ }
+ context.completions = completions;
+ };
+
+ completion.preference = function preference(context) {
+ context.anchored = false;
+ context.title = ["Firefox Preference", "Value"];
+ context.keys = { text: function (item) item, description: function (item) options.getPref(item) };
+ context.completions = services.get("pref").getChildList("", { value: 0 });
+ };
});
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// PUBLIC SECTION //////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
const self = {
/**
diff --git a/common/content/style.js b/common/content/style.js
index 0e260395..7ffad6ab 100644
--- a/common/content/style.js
+++ b/common/content/style.js
@@ -534,16 +534,9 @@ if (highlight.CSS != Highlights.prototype.CSS)
liberator.triggerObserver("load_styles", "styles");
liberator.triggerObserver("load_highlight", "highlight");
-liberator.registerObserver("load_completion", function () {
- completion.setFunctionCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]),
- [ // Prototype: (system, name, filter, css, index)
- null,
- function (context, obj, args) args[0] ? styles.systemNames : styles.userNames,
- function (context, obj, args) styles.completeSite(context, content),
- null,
- function (context, obj, args) args[0] ? styles.systemSheets : styles.userSheets
- ]);
-});
+/////////////////////////////////////////////////////////////////////////////}}}
+////////////////////// COMMANDS ////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_commands", function () {
@@ -711,5 +704,40 @@ liberator.registerObserver("load_commands", function () {
]
});
});
+/////////////////////////////////////////////////////////////////////////////}}}
+////////////////////// COMPLETIONS /////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////{{{
+
+liberator.registerObserver("load_completion", function () {
+ completion.setFunctionCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]),
+ [ // Prototype: (system, name, filter, css, index)
+ null,
+ function (context, obj, args) args[0] ? styles.systemNames : styles.userNames,
+ function (context, obj, args) styles.completeSite(context, content),
+ null,
+ function (context, obj, args) args[0] ? styles.systemSheets : styles.userSheets
+ ]);
+
+ completion.colorScheme = function colorScheme(context) {
+ let colors = [];
+
+ io.getRuntimeDirectories("colors").forEach(function (dir) {
+ io.readDirectory(dir).forEach(function (file) {
+ if (/\.vimp$/.test(file.leafName) && !colors.some(function (c) c.leafName == file.leafName))
+ colors.push(file);
+ });
+ });
+
+ context.title = ["Color Scheme", "Runtime Path"];
+ context.completions = [[c.leafName.replace(/\.vimp$/, ""), c.parent.path] for ([,c] in Iterator(colors))]
+ };
+
+ // FIXME: extract from :highlight
+ completion.highlightGroup = function highlightGroup(context, args) {
+ return commands.get("highlight").completer(context, args);
+ };
+
+});
+//}}}
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/template.js b/common/content/template.js
index ee149ab2..eb3090fa 100644
--- a/common/content/template.js
+++ b/common/content/template.js
@@ -8,7 +8,7 @@
/** @scope modules */
-const template = {
+const template = { //{{{
add: function add(a, b) a + b,
join: function join(c) function (a, b) a + c + b,
@@ -332,6 +332,6 @@ const template = {
);
//
}
-};
+}; //}}}
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/vimperator/content/config.js b/vimperator/content/config.js
index 65ea883f..e319fd43 100644
--- a/vimperator/content/config.js
+++ b/vimperator/content/config.js
@@ -505,6 +505,50 @@ const config = { //{{{
"Set the separator regexp used to separate multiple URL args",
"string", ",\\s");
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ completion.location = function location(context) {
+ if (!services.get("autoCompleteSearch"))
+ return;
+
+ context.anchored = false;
+ context.title = ["Smart Completions"];
+ context.keys.icon = 2;
+ context.incomplete = true;
+ context.hasItems = context.completions.length > 0; // XXX
+ context.filterFunc = null;
+ context.cancel = function () services.get("autoCompleteSearch").stopSearch();
+ context.compare = CompletionContext.Sort.unsorted;
+ let timer = new Timer(50, 100, function (result) {
+ context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING;
+ context.completions = [
+ [result.getValueAt(i), result.getCommentAt(i), result.getImageAt(i)]
+ for (i in util.range(0, result.matchCount))
+ ];
+ });
+ services.get("autoCompleteSearch").stopSearch();
+ services.get("autoCompleteSearch").startSearch(context.filter, "", context.result, {
+ onSearchResult: function onSearchResult(search, result) {
+ context.result = result;
+ timer.tell(result);
+ if (result.searchResult <= result.RESULT_SUCCESS)
+ timer.flush();
+ }
+ });
+ };
+
+ completion.sidebar = function sidebar(context) {
+ let menu = document.getElementById("viewSidebarMenu");
+ context.title = ["Sidebar Panel"];
+ context.completions = Array.map(menu.childNodes, function (n) [n.label, ""]);
+ };
+
+ completion.addUrlCompleter("l",
+ "Firefox location bar entries (bookmarks and history sorted in an intelligent way)",
+ completion.location);
+
//}}}
}
}; //}}}
diff --git a/xulmus/content/player.js b/xulmus/content/player.js
index 5ced3f57..5a986693 100644
--- a/xulmus/content/player.js
+++ b/xulmus/content/player.js
@@ -406,6 +406,45 @@ function Player() // {{{
},
{ argCount: "1" });
+ /////////////////////////////////////////////////////////////////////////////}}}
+ ////////////////////// COMPLETIONS /////////////////////////////////////////////
+ /////////////////////////////////////////////////////////////////////////////{{{
+
+ completion.song = function song(context, args) {
+ // TODO: useful descriptions?
+ function map(list) list.map(function (i) [i, ""]);
+ let [artist, album] = [args[0], args[1]];
+
+ if (args.completeArg == 0)
+ {
+ context.title = ["Artists"];
+ context.completions = map(library.getArtists());
+ }
+ else if (args.completeArg == 1)
+ {
+ context.title = ["Albums by " + artist];
+ context.completions = map(library.getAlbums(artist));
+ }
+ else if (args.completeArg == 2)
+ {
+ context.title = ["Tracks from " + album + " by " + artist];
+ context.completions = map(library.getTracks(artist, album));
+ }
+ };
+
+ completion.playlist = function playlist(context, args) {
+ context.title = ["Playlist", "Type"];
+ context.keys = { text: "name", description: "type" };
+ context.completions = player.getPlaylists();
+ };
+
+ completion.mediaView = function mediaView(context) {
+ context.title = ["Media View", "URL"];
+ context.anchored = false;
+ context.keys = { text: "contentTitle", description: "contentUrl" };
+ context.completions = player.getMediaPages();
+ };
+
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{