1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 07:07:59 +01:00

Move the standard type completers to appropriate modules.

This commit is contained in:
Doug Kearns
2009-06-18 20:46:09 +10:00
parent 561ed5fc3e
commit ec8d7686fc
14 changed files with 680 additions and 625 deletions

View File

@@ -399,6 +399,93 @@ function Bookmarks() //{{{
literal: 0 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 ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{
@@ -840,6 +927,23 @@ function History() //{{{
options: [[["-max", "-m"], options.OPTION_INT]] 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 ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{

View File

@@ -673,6 +673,61 @@ function Buffer() //{{{
bang: true 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)
<>
<span highlight="Indicator" style="display: inline-block; width: 1.5em; text-align: center">{item.item.indicator}</span>
{ 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 /////////////////////////////////////////////// ////////////////////// PAGE INFO ///////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{
@@ -1769,6 +1824,19 @@ function Marks() //{{{
marks.list(filter); 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 ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{

View File

@@ -392,10 +392,6 @@ function Commands() //{{{
////////////////////// PUBLIC SECTION ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () {
completion.setFunctionCompleter(commands.get, [function () ([c.name, c.description] for (c in commands))]);
});
const self = { const self = {
// FIXME: remove later, when our option handler is better // FIXME: remove later, when our option handler is better
@@ -1156,6 +1152,77 @@ function Commands() //{{{
completer: function (context) completion.userCommand(context) 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; return self;

View File

@@ -1340,533 +1340,10 @@ function Completion() //{{{
////////////////////// COMPLETION TYPES //////////////////////////////////////// ////////////////////// 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)
<>
<span highlight="Indicator" style="display: inline-block; width: 1.5em; text-align: center">{item.item.indicator}</span>
{ 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, get javascriptCompleter() javascript,
javascript: function _javascript(context) javascript.complete(context), 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 // filter a list of urls
// //
// may consist of search engines, filenames, bookmarks and history, // may consist of search engines, filenames, bookmarks and history,
@@ -1938,36 +1415,11 @@ function Completion() //{{{
function (item, text) highlight.call(this, item, text, 1) 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"); 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; return self;
//}}} //}}}

View File

@@ -565,6 +565,23 @@ function Editor() //{{{
addAbbreviationCommands("i", "insert"); addAbbreviationCommands("i", "insert");
addAbbreviationCommands("c", "command line"); 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 ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{

View File

@@ -161,13 +161,26 @@ function AutoCommands() //{{{
); );
/////////////////////////////////////////////////////////////////////////////}}} /////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION ////////////////////////////////////////// ////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () { liberator.registerObserver("load_completion", function () {
completion.setFunctionCompleter(autocommands.get, [function () config.autocommands]); 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 { return {
__iterator__: function () util.Array.itervalues(store), __iterator__: function () util.Array.itervalues(store),

View File

@@ -386,7 +386,7 @@ function IO() //{{{
}); });
/////////////////////////////////////////////////////////////////////////////}}} /////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION ////////////////////////////////////////// ////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () { liberator.registerObserver("load_completion", function () {
@@ -395,8 +395,100 @@ function IO() //{{{
context.quote[2] = ""; context.quote[2] = "";
completion.file(context, true); 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 = { const self = {
/** /**

View File

@@ -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 // Only general options are added here, which are valid for all Vimperator like extensions
registerObserver("load_options", function () { registerObserver("load_options", function () {
@@ -191,6 +257,10 @@ const liberator = (function () //{{{
}); });
}); });
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// MAPPINGS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
registerObserver("load_mappings", function () { registerObserver("load_mappings", function () {
mappings.add(modes.all, ["<F1>"], mappings.add(modes.all, ["<F1>"],
@@ -209,33 +279,9 @@ const liberator = (function () //{{{
function () { liberator.quit(true); }); function () { liberator.quit(true); });
}); });
// TODO: move this /////////////////////////////////////////////////////////////////////////////}}}
function getMenuItems() ////////////////////// COMMANDS ////////////////////////////////////////////////
{ /////////////////////////////////////////////////////////////////////////////{{{
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;
}
registerObserver("load_commands", function () { 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 ////////////////////// COMPLETIONS /////////////////////////////////////////////
function hideGUI() /////////////////////////////////////////////////////////////////////////////{{{
registerObserver("load_completion", function () {
completion.dialog = function dialog(context) {
context.title = ["Dialog"];
context.completions = config.dialogs;
};
completion.help = function help(context) {
context.title = ["Help"];
context.anchored = false;
context.generate = function ()
{ {
let guioptions = config.guioptions; let res = config.helpFiles.map(function (file) {
for (let option in guioptions) let resp = util.httpGet("chrome://liberator/locale/" + file);
{ if (!resp)
guioptions[option].forEach(function (elem) { return [];
try let doc = resp.responseXML;
{ return Array.map(doc.getElementsByClassName("tag"),
document.getElementById(elem).collapsed = true; function (elem) [elem.textContent, file]);
}
catch (e) {}
}); });
return util.Array.flatten(res);
} }
} };
// return the platform normalized to Vim values completion.menuItem = function menuItem(context) {
function getPlatformFeature() context.title = ["Menu Path", "Label"];
{ context.anchored = false;
let platform = navigator.platform; context.keys = { text: "fullMenuPath", description: function (item) item.getAttribute("label") };
context.completions = liberator.menuItems;
return /^Mac/.test(platform) ? "MacUnix" : platform == "Win32" ? "Win32" : "Unix"; };
} });
// 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);
}
/////////////////////////////////////////////////////////////////////////////}}} /////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////

View File

@@ -297,6 +297,23 @@ function Mappings() //{{{
[m.mask for (m in modes.mainModes) if (m.char == mode.char)], [m.mask for (m in modes.mainModes) if (m.char == mode.char)],
[mode.disp.toLowerCase()]); [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 ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{

View File

@@ -913,18 +913,85 @@ function Options() //{{{
}); });
/////////////////////////////////////////////////////////////////////////////}}} /////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION ////////////////////////////////////////// ////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{
// TODO: Does this belong elsewhere?
liberator.registerObserver("load_completion", function () { liberator.registerObserver("load_completion", function () {
completion.setFunctionCompleter(options.get, [function () ([o.name, o.description] for (o in options))]); 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], completion.setFunctionCompleter([options.getPref, options.safeSetPref, options.setPref, options.resetPref, options.invertPref],
[function () services.get("pref") [function () services.get("pref")
.getChildList("", { value: 0 }) .getChildList("", { value: 0 })
.map(function (pref) [pref, ""])]); .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 = { const self = {
/** /**

View File

@@ -534,16 +534,9 @@ if (highlight.CSS != Highlights.prototype.CSS)
liberator.triggerObserver("load_styles", "styles"); liberator.triggerObserver("load_styles", "styles");
liberator.triggerObserver("load_highlight", "highlight"); liberator.triggerObserver("load_highlight", "highlight");
liberator.registerObserver("load_completion", function () { /////////////////////////////////////////////////////////////////////////////}}}
completion.setFunctionCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]), ////////////////////// COMMANDS ////////////////////////////////////////////////
[ // 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
]);
});
liberator.registerObserver("load_commands", function () { 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: // vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -8,7 +8,7 @@
/** @scope modules */ /** @scope modules */
const template = { const template = { //{{{
add: function add(a, b) a + b, add: function add(a, b) a + b,
join: function join(c) function (a, b) a + c + b, join: function join(c) function (a, b) a + c + b,
@@ -332,6 +332,6 @@ const template = {
</table>); </table>);
// </e4x> // </e4x>
} }
}; }; //}}}
// vim: set fdm=marker sw=4 ts=4 et: // vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -505,6 +505,50 @@ const config = { //{{{
"Set the separator regexp used to separate multiple URL args", "Set the separator regexp used to separate multiple URL args",
"string", ",\\s"); "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);
//}}} //}}}
} }
}; //}}} }; //}}}

View File

@@ -406,6 +406,45 @@ function Player() // {{{
}, },
{ argCount: "1" }); { 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 ////////////////////////////////////////// ////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{ /////////////////////////////////////////////////////////////////////////////{{{