1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-22 23:47:57 +01:00

Recfactoring:

* Standard module format. All modules are explicitly declared
   as modules, they're created via a constructor and
   instantiated automatically. They're dependency aware. They
   stringify properly.

 * Classes are declared the same way (rather like Structs
   already were). They also stringify properly. Plus, each
   instance has a rather nifty closure member that closes all
   of its methods around 'this', so you can pass them to map,
   forEach, setTimeout, etc. Modules are themselves classes,
   with a special metaclass, as it were.

 * Doug Crockford is dead, metaphorically speaking.
   Closure-based classes just don't fit into any of the common
   JavaScript frameworks, and they're inefficient and
   confusing. Now, all class and module members are accessed
   explicitly via 'this', which makes it very clear that
   they're class members and not (e.g.) local variables,
   without anything nasty like Hungarian notation.

 * Strictly one module per file. Classes that belong to a
   module live in the same file.

 * For the moment, there are quite a few utility functions
   sitting in base.c, because my class implementation used
   them, and I haven't had the time or inclination to sort them
   out. I plan to reconcile them with the current mess that is
   the util namespace.

 * Changed bracing style.
This commit is contained in:
Kris Maglione
2009-11-08 20:54:31 -05:00
parent 46b7a29fb7
commit 6a25312c7d
46 changed files with 15950 additions and 17909 deletions

64
HACKING
View File

@@ -18,16 +18,15 @@ important, please ask.
== Coding Style ==
In general: Just look at the existing source code!
We try to be quite consistent, but of course, that's not always possible.
Also we try to target experienced JavaScript developers which do not
necessarily need to have a good understanding of Vimperator's source code, nor
do they probably know in-depth concepts of other languages like Lisp or Python.
Therefore, the coding style should feel natural to any JavaScript developer
so it is easy to read and understand. Of course, this does not mean, you have
to avoid all new JavaScript features like list comprehension or generators.
Use them, when they make sense, but don't use them, when the resulting code
is hard to read.
We try to target experienced JavaScript developers who do not
necessarily need to have a good understanding of Vimperator's source
code, nor necessarily understand in-depth concepts of other
languages like Lisp or Python. Therefore, the coding style should
feel natural to any JavaScript developer. Of course, this does not
mean, you have to avoid all new JavaScript features like list
comprehension or generators. Use them, when they make sense, but
don't use them when the resulting code is hard to read.
=== The most important style issues are: ===
@@ -40,30 +39,42 @@ is hard to read.
* Use " for enclosing strings instead of ', unless using ' avoids escaping of lots of "
Example: alert("foo") instead of alert('foo');
* Use // regexp literals rather than RegExp constructors, unless
you're constructing an expression on the fly, or RegExp
constructors allow you to escape less /s than the additional
escaping of special characters required by string quoting.
Good: /application\/xhtml\+xml/
Bad: RegExp("application/xhtml\\+xml")
Good: RegExp("http://(www\\.)vimperator.org/(.*)/(.*)")
Bad: /http:\/\/(www\.)vimperator.org\/(.*)\/(.*)/
* Exactly one space after if/for/while/catch etc. and after a comma, but none
after a parenthesis or after a function call:
for (pre; condition; post)
but:
alert("foo");
* Opening curly brackets { must be on a new line, unless it is used in a closure:
function myFunction ()
{
* Bracing is formatted as follows:
function myFunction () {
if (foo)
{
baz = false;
return bar;
}
else
{
else {
baz = false;
return baz;
}
}
but:
setTimeout(function () {
...
var quux = frob("you",
{
a: 1,
b: 42,
c: {
hoopy: "frood"
}
});
When in doubt, look for similar code.
* No braces for one-line conditional statements:
Right:
if (foo)
@@ -97,9 +108,9 @@ is hard to read.
* Use UNIX new lines (\n), not windows (\r\n) or old Mac ones (\r)
* Use Iterators, Array#forEach, or for (let i = 0; i < ary.length; i++)
to iterate over arrays. for (let i in ary) and for each (let i in ary)
include members in an Array.prototype, which some extensions alter.
* Use Iterators or Array#forEach to iterate over arrays. for (let i
in ary) and for each (let i in ary) include members in an
Array.prototype, which some extensions alter.
Right:
for (let [,elem] in Iterator(ary))
for (let [k, v] in Iterator(obj))
@@ -137,11 +148,4 @@ Additionally, maybe there should be some benchmark information here --
something to let a developer know what's "too" slow...? Or general
guidelines about optimization?
== Source Code Management ==
TODO: Document the existence of remote branches and discuss when and how
to push to them. At least provide an index so that devs know where
to look if an old branch needs to be maintained or a feature needs
to be added to a new branch.
// vim: set ft=asciidoc fdm=marker sw=4 ts=4 et ai:

View File

@@ -0,0 +1,275 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
/** @scope modules */
const AutoCommand = new Struct("event", "pattern", "command");
/**
* @instance autocommands
*/
const AutoCommands = Module("autocommands", {
init: function () {
this._store = [];
},
__iterator__: function () util.Array.itervalues(this._store),
/**
* Adds a new autocommand. <b>cmd</b> will be executed when one of the
* specified <b>events</b> occurs and the URL of the applicable buffer
* matches <b>regex</b>.
*
* @param {Array} events The array of event names for which this
* autocommand should be executed.
* @param {string} regex The URL pattern to match against the buffer URL.
* @param {string} cmd The Ex command to run.
*/
add: function (events, regex, cmd) {
if (typeof events == "string") {
events = events.split(",");
liberator.log("DEPRECATED: the events list arg to autocommands.add() should be an array of event names");
}
events.forEach(function (event) {
this._store.push(new AutoCommand(event, RegExp(regex), cmd));
});
},
/**
* Returns all autocommands with a matching <b>event</b> and
* <b>regex</b>.
*
* @param {string} event The event name filter.
* @param {string} regex The URL pattern filter.
* @returns {AutoCommand[]}
*/
get: function (event, regex) {
return this._store.filter(function (autoCmd) matchAutoCmd(autoCmd, event, regex));
},
/**
* Deletes all autocommands with a matching <b>event</b> and
* <b>regex</b>.
*
* @param {string} event The event name filter.
* @param {string} regex The URL pattern filter.
*/
remove: function (event, regex) {
this._store = this._store.filter(function (autoCmd) !matchAutoCmd(autoCmd, event, regex));
},
/**
* Lists all autocommands with a matching <b>event</b> and
* <b>regex</b>.
*
* @param {string} event The event name filter.
* @param {string} regex The URL pattern filter.
*/
list: function (event, regex) {
let cmds = {};
// XXX
this._store.forEach(function (autoCmd) {
if (matchAutoCmd(autoCmd, event, regex)) {
cmds[autoCmd.event] = cmds[autoCmd.event] || [];
cmds[autoCmd.event].push(autoCmd);
}
});
let list = template.commandOutput(
<table>
<tr highlight="Title">
<td colspan="2">----- Auto Commands -----</td>
</tr>
{
template.map(cmds, function ([event, items])
<tr highlight="Title">
<td colspan="2">{event}</td>
</tr>
+
template.map(items, function (item)
<tr>
<td>&#160;{item.pattern.source}</td>
<td>{item.command}</td>
</tr>))
}
</table>);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
},
/**
* Triggers the execution of all autocommands registered for
* <b>event</b>. A map of <b>args</b> is passed to each autocommand
* when it is being executed.
*
* @param {string} event The event to fire.
* @param {Object} args The args to pass to each autocommand.
*/
trigger: function (event, args) {
if (options.get("eventignore").has("all", event))
return;
let autoCmds = this._store.filter(function (autoCmd) autoCmd.event == event);
liberator.echomsg("Executing " + event + " Auto commands for \"*\"", 8);
let lastPattern = null;
let url = args.url || "";
for (let [, autoCmd] in Iterator(autoCmds)) {
if (autoCmd.pattern.test(url)) {
if (!lastPattern || lastPattern.source != autoCmd.pattern.source)
liberator.echomsg("Executing " + event + " Auto commands for \"" + autoCmd.pattern.source + "\"", 8);
lastPattern = autoCmd.pattern;
liberator.echomsg("autocommand " + autoCmd.command, 9);
if (typeof autoCmd.command == "function") {
try {
autoCmd.command.call(autoCmd, args);
}
catch (e) {
liberator.reportError(e);
liberator.echoerr(e);
}
}
else
liberator.execute(commands.replaceTokens(autoCmd.command, args), null, true);
}
}
}
}, {
matchAutoCmd: function (autoCmd, event, regex) {
return (!event || autoCmd.event == event) && (!regex || autoCmd.pattern.source == regex);
},
}, {
commands: function () {
commands.add(["au[tocmd]"],
"Execute commands automatically on events",
function (args) {
let [event, regex, cmd] = args;
let events = [];
try {
RegExp(regex);
}
catch (e) {
liberator.assert(false, "E475: Invalid argument: " + regex);
}
if (event) {
// NOTE: event can only be a comma separated list for |:au {event} {pat} {cmd}|
let validEvents = config.autocommands.map(function (event) event[0]);
validEvents.push("*");
events = event.split(",");
liberator.assert(events.every(function (event) validEvents.indexOf(event) >= 0),
"E216: No such group or event: " + event);
}
if (cmd) { // add new command, possibly removing all others with the same event/pattern
if (args.bang)
autocommands.remove(event, regex);
if (args["-javascript"])
cmd = eval("(function (args) { with(args) {" + cmd + "} })");
autocommands.add(events, regex, cmd);
}
else {
if (event == "*")
event = null;
if (args.bang) {
// TODO: "*" only appears to work in Vim when there is a {group} specified
if (args[0] != "*" || regex)
autocommands.remove(event, regex); // remove all
}
else
autocommands.list(event, regex); // list all
}
}, {
bang: true,
completer: function (context) completion.autocmdEvent(context),
literal: 2,
options: [[["-javascript", "-js"], commands.OPTION_NOARG]]
});
[
{
name: "do[autocmd]",
description: "Apply the autocommands matching the specified URL pattern to the current buffer"
}, {
name: "doautoa[ll]",
description: "Apply the autocommands matching the specified URL pattern to all buffers"
}
].forEach(function (command) {
commands.add([command.name],
command.description,
// TODO: Perhaps this should take -args to pass to the command?
function (args) {
// Vim compatible
if (args.length == 0)
return void liberator.echomsg("No matching autocommands");
let [event, url] = args;
let defaultURL = url || buffer.URL;
let validEvents = config.autocommands.map(function (e) e[0]);
// TODO: add command validators
liberator.assert(event != "*",
"E217: Can't execute autocommands for ALL events");
liberator.assert(validEvents.indexOf(event) >= 0,
"E216: No such group or event: " + args);
liberator.assert(autocommands.get(event).some(function (c) c.pattern.test(defaultURL)),
"No matching autocommands");
if (this.name == "doautoall" && liberator.has("tabs")) {
let current = tabs.index();
for (let i = 0; i < tabs.count; i++) {
tabs.select(i);
// if no url arg is specified use the current buffer's URL
autocommands.trigger(event, { url: url || buffer.URL });
}
tabs.select(current);
}
else
autocommands.trigger(event, { url: defaultURL });
}, {
argCount: "*", // FIXME: kludged for proper error message should be "1".
completer: function (context) completion.autocmdEvent(context)
});
});
},
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())];
};
},
options: function () {
options.add(["eventignore", "ei"],
"List of autocommand event names which should be ignored",
"stringlist", "",
{
completer: function () config.autocommands.concat([["all", "All events"]]),
validator: Option.validateCompleter
});
options.add(["focuscontent", "fc"],
"Try to stay in normal mode after loading a web page",
"boolean", false);
},
});
// vim: set fdm=marker sw=4 ts=4 et:

293
common/content/base.js Normal file
View File

@@ -0,0 +1,293 @@
function array(obj) {
if (isgenerator(obj))
obj = [k for (k in obj)];
else if (obj.length)
obj = Array.slice(obj);
return util.Array(obj);
}
function keys(obj) {
if ('__iterator__' in obj) {
var iter = obj.__iterator__;
yield '__iterator__';
// This is dangerous, but necessary.
delete obj.__iterator__;
}
for (var k in obj)
if (obj.hasOwnProperty(k))
yield k;
if (iter !== undefined)
obj.__iterator__ = iter;
}
function values(obj) {
for (var k in obj)
if (obj.hasOwnProperty(k))
yield obj[k];
}
function foreach(iter, fn, self) {
for (let val in iter)
fn.call(self, val);
}
function dict(ary) {
var obj = {};
for (var i=0; i < ary.length; i++) {
var val = ary[i];
obj[val[0]] = val[1];
}
return obj;
}
function set(ary) {
var obj = {}
if (ary)
for (var i=0; i < ary.length; i++)
obj[ary[i]] = true;
return obj;
}
set.add = function(set, key) { set[key] = true }
set.remove = function(set, key) { delete set[key] }
function iter(obj) {
if (obj instanceof Ci.nsISimpleEnumerator)
return (function () {
while (obj.hasMoreElements())
yield obj.getNext();
})()
if (isinstance(obj, [Ci.nsIStringEnumerator, Ci.nsIUTF8StringEnumerator]))
return (function () {
while (obj.hasMore())
yield obj.getNext();
})();
if (isinstance(obj, Ci.nsIDOMNodeIterator))
return (function () {
try {
while (true)
yield obj.nextNode()
}
catch (e) {}
})();
if (isinstance(obj, [HTMLCollection, NodeList]))
return util.Array.iteritems(obj);
if (obj instanceof NamedNodeMap)
return (function () {
for (let i=0; i < obj.length; i++)
yield [obj.name, obj]
})();
return Iterator(obj);
}
function issubclass(targ, src) {
return src === targ ||
targ && typeof targ === "function" && targ.prototype instanceof src;
}
function isinstance(targ, src) {
const types = {
boolean: Boolean,
string: String,
function: Function,
number: Number,
}
src = Array.concat(src);
for (var i=0; i < src.length; i++) {
if (targ instanceof src[i])
return true;
var type = types[typeof targ];
if (type && issubclass(src[i], type))
return true;
}
return false;
}
function isobject(obj) {
return typeof obj === "object" && obj != null;
}
function isarray(obj) {
return Object.prototype.toString(obj) == "[object Array]";
}
function isgenerator(val) {
return Object.prototype.toString(obj) == "[object Generator]";
}
function isstring(val) {
return typeof val === "string" || val instanceof String;
}
function callable(val) {
return typeof val === "function";
}
function call(fn) {
fn.apply(arguments[1], Array.slice(arguments, 2));
return fn;
}
function curry(fn, length, acc) {
if (length == null)
length = fn.length;
if (length == 0)
return fn;
/* Close over function with 'this' */
function close(self, fn) function () fn.apply(self, Array.slice(arguments));
let first = (arguments.length < 3);
if (acc == null)
acc = [];
return function() {
let args = acc.concat(Array.slice(arguments));
/* The curried result should preserve 'this' */
if (arguments.length == 0)
return close(this, arguments.callee);
if (args.length >= length)
return fn.apply(this, args);
if (first)
fn = close(this, fn);
return curry(fn, length, args);
}
}
function update(targ) {
for (let i=1; i < arguments.length; i++) {
let src = arguments[i];
foreach(keys(src || {}), function(k) {
var get = src.__lookupGetter__(k),
set = src.__lookupSetter__(k);
if (!get && !set) {
var v = src[k];
targ[k] = v;
if (targ.__proto__ && callable(v)) {
v.superapply = function(self, args) {
return targ.__proto__[k].apply(self, args);
}
v.supercall = function(self) {
return v.superapply(self, Array.slice(arguments, 1));
}
}
}
if (get)
targ.__defineGetter__(k, get);
if (set)
targ.__defineSetter__(k, set);
});
}
return targ;
}
function extend(subc, superc, overrides) {
subc.prototype = { __proto__: superc.prototype };
update(subc.prototype, overrides);
subc.superclass = superc.prototype;
subc.prototype.constructor = subc;
subc.prototype.__class__ = subc;
if (superc.prototype.constructor === Object.prototype.constructor)
superc.prototype.constructor = superc;
}
function Class() {
function constructor() {
let self = {
__proto__: Constructor.prototype,
constructor: Constructor,
get closure() {
delete this.closure;
const self = this;
return this.closure = dict([k for (k in this) if (!self.__lookupGetter__(k) && callable(self[k]))].map(
function (k) [k, function () self[k].apply(self, arguments)]));
}
};
var res = self.init.apply(self, arguments);
return res !== undefined ? res : self
}
var args = Array.slice(arguments);
if (isstring(args[0]))
var name = args.shift();
var superclass = Class;
if (callable(args[0]))
superclass = args.shift();
var Constructor = eval("(function " + (name || superclass.name) +
String.substr(constructor, 20) + ")");
if (!('init' in superclass.prototype)) {
var superc = superclass;
superclass = function Shim() {}
extend(superclass, superc, {
init: superc,
});
}
extend(Constructor, superclass, args[0]);
update(Constructor, args[1]);
args = args.slice(2);
Array.forEach(args, function(obj) {
if (callable(obj))
obj = obj.prototype;
update(Constructor.prototype, obj);
});
return Constructor;
}
Class.toString = function () "[class " + this.constructor.name + "]",
Class.prototype = {
init: function() {},
toString: function () "[instance " + this.constructor.name + "]",
};
const Struct = Class("Struct", {
init: function () {
let args = Array.slice(arguments);
this.__defineGetter__("length", function () args.length);
this.__defineGetter__("members", function () args.slice());
for (let arg in Iterator(args)) {
let [i, name] = arg;
this.__defineGetter__(name, function () this[i]);
this.__defineSetter__(name, function (val) { this[i] = val; });
}
function Struct() {
let self = this instanceof arguments.callee ? this : new arguments.callee();
//for (let [k, v] in Iterator(Array.slice(arguments))) // That is makes using struct twice as slow as the following code:
for (let i = 0; i < arguments.length; i++) {
if (arguments[i] != undefined)
self[i] = arguments[i];
}
return self;
}
Struct.prototype = this;
Struct.defaultValue = function (key, val) {
let i = args.indexOf(key);
Struct.prototype.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0
Struct.prototype.__defineSetter__(i, function (val) {
let value = val;
this.__defineGetter__(i, function () value);
this.__defineSetter__(i, function (val) { value = val });
});
};
return this.constructor = Struct;
},
clone: function clone() {
return this.constructor.apply(null, this.slice());
},
// Iterator over our named members
__iterator__: function () {
let self = this;
return ([v, self[v]] for ([k, v] in Iterator(self.members)))
}
});
// Add no-sideeffect array methods. Can't set new Array() as the prototype or
// get length() won't work.
for (let [, k] in Iterator(["concat", "every", "filter", "forEach", "indexOf", "join", "lastIndexOf",
"map", "reduce", "reduceRight", "reverse", "slice", "some", "sort"]))
Struct.prototype[k] = Array.prototype[k];
// vim: set fdm=marker sw=4 ts=4 et:

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,10 @@
/**
* @instance browser
*/
function Browser() //{{{
{
////////////////////////////////////////////////////////////////////////////////
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const Browser = Module("browser", {
}, {
// TODO: support 'nrformats'? -> probably not worth it --mst
function incrementURL(count)
{
incrementURL: function (count) {
let matches = buffer.URL.match(/(.*?)(\d+)(\D*)$/);
if (!matches)
return void liberator.beep();
@@ -25,33 +20,27 @@ function Browser() //{{{
let [, pre, number, post] = matches;
let newNumber = parseInt(number, 10) + count;
let newNumberStr = String(newNumber > 0 ? newNumber : 0);
if (number.match(/^0/)) // add 0009<C-a> should become 0010
{
if (number.match(/^0/)) { // add 0009<C-a> should become 0010
while (newNumberStr.length < number.length)
newNumberStr = "0" + newNumberStr;
}
liberator.open(pre + newNumberStr + post);
}
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// OPTIONS /////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
}, {
options: function () {
options.add(["encoding", "enc"],
"Sets the current buffer's character encoding",
"string", "UTF-8",
{
scope: options.OPTION_SCOPE_LOCAL,
getter: function () getBrowser().docShell.QueryInterface(Ci.nsIDocCharset).charset,
setter: function (val)
{
setter: function (val) {
if (options["encoding"] == val)
return val;
// Stolen from browser.jar/content/browser/browser.js, more or less.
try
{
try {
getBrowser().docShell.QueryInterface(Ci.nsIDocCharset).charset = val;
PlacesUtils.history.setCharsetForURI(getWebNavigation().currentURI, val);
getWebNavigation().reload(Ci.nsIWebNavigation.LOAD_FLAGS_CHARSET_CHANGE);
@@ -64,8 +53,7 @@ function Browser() //{{{
// only available in FF 3.5
services.add("privateBrowsing", "@mozilla.org/privatebrowsing;1", Ci.nsIPrivateBrowsingService);
if (services.get("privateBrowsing"))
{
if (services.get("privateBrowsing")) {
options.add(["private", "pornmode"],
"Set the 'private browsing' option",
"boolean", false,
@@ -76,24 +64,20 @@ function Browser() //{{{
let services = modules.services; // Storage objects are global to all windows, 'modules' isn't.
storage.newObject("private-mode", function () {
({
init: function ()
{
init: function () {
services.get("observer").addObserver(this, "private-browsing", false);
services.get("observer").addObserver(this, "quit-application", false);
this.private = services.get("privateBrowsing").privateBrowsingEnabled;
},
observe: function (subject, topic, data)
{
if (topic == "private-browsing")
{
observe: function (subject, topic, data) {
if (topic == "private-browsing") {
if (data == "enter")
storage.privateMode = true;
else if (data == "exit")
storage.privateMode = false;
storage.fireEvent("private-mode", "change", storage.privateMode);
}
else if (topic == "quit-application")
{
else if (topic == "quit-application") {
services.get("observer").removeObserver(this, "quit-application");
services.get("observer").removeObserver(this, "private-browsing");
}
@@ -109,11 +93,9 @@ function Browser() //{{{
options.add(["urlseparator"],
"Set the separator regex used to separate multiple URL args",
"string", ",\\s");
},
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// MAPPINGS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
mappings: function () {
mappings.add([modes.NORMAL],
["y"], "Yank current location to the clipboard",
function () { util.copyToClipboard(buffer.URL, true); });
@@ -145,12 +127,12 @@ function Browser() //{{{
mappings.add([modes.NORMAL],
["<C-a>"], "Increment last number in URL",
function (count) { incrementURL(Math.max(count, 1)); },
function (count) { Browser.incrementURL(Math.max(count, 1)); },
{ count: true });
mappings.add([modes.NORMAL],
["<C-x>"], "Decrement last number in URL",
function (count) { incrementURL(-Math.max(count, 1)); },
function (count) { Browser.incrementURL(-Math.max(count, 1)); },
{ count: true });
mappings.add([modes.NORMAL], ["~"],
@@ -163,25 +145,20 @@ function Browser() //{{{
mappings.add([modes.NORMAL], ["gH"],
"Open homepage in a new tab",
function ()
{
function () {
let homepages = gHomeButton.getHomePage();
liberator.open(homepages, { from: "homepage", where: liberator.NEW_TAB });
});
mappings.add([modes.NORMAL], ["gu"],
"Go to parent directory",
function (count)
{
function isDirectory(url)
{
if (/^file:\/|^\//.test(url))
{
function (count) {
function isDirectory(url) {
if (/^file:\/|^\//.test(url)) {
let file = io.File(url);
return file.exists() && file.isDirectory();
}
else
{
else {
// for all other locations just check if the URL ends with /
return /\/$/.test(url);
}
@@ -192,8 +169,7 @@ function Browser() //{{{
// XXX
let url = buffer.URL;
for (let i = 0; i < count; i++)
{
for (let i = 0; i < count; i++) {
if (isDirectory(url))
url = url.replace(/^(.*?:)(.*?)([^\/]+\/*)$/, "$1$2/");
else
@@ -210,8 +186,7 @@ function Browser() //{{{
mappings.add([modes.NORMAL], ["gU"],
"Go to the root of the website",
function ()
{
function () {
let uri = content.document.location;
if (/(about|mailto):/.test(uri.protocol)) // exclude these special protocols for now
return void liberator.beep();
@@ -221,15 +196,12 @@ function Browser() //{{{
mappings.add([modes.NORMAL], ["<C-l>"],
"Redraw the screen",
function () { commands.get("redraw").execute("", false); });
},
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
commands: function () {
commands.add(["downl[oads]", "dl"],
"Show progress of current downloads",
function ()
{
function () {
liberator.open("chrome://mozapps/content/downloads/downloads.xul",
options.get("newtab").has("all", "downloads")
? liberator.NEW_TAB : liberator.CURRENT_TAB);
@@ -238,16 +210,14 @@ function Browser() //{{{
commands.add(["o[pen]", "e[dit]"],
"Open one or more URLs in the current tab",
function (args)
{
function (args) {
args = args.string;
if (args)
liberator.open(args);
else
liberator.open("about:blank");
},
{
}, {
completer: function (context) completion.url(context),
literal: 0,
privateData: true,
@@ -255,23 +225,14 @@ function Browser() //{{{
commands.add(["redr[aw]"],
"Redraw the screen",
function ()
{
function () {
let wu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
wu.redraw();
modes.show();
},
{ argCount: "0" });
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
return {
// TODO: extract browser-specific functionality from liberator
};
//}}}
} //}}}
}
});
// vim: set fdm=marker sw=4 ts=4 et:

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,6 @@
try { __liberator_eval_result = eval(__liberator_eval_string);
}
catch (e)
{
catch (e) {
__liberator_eval_error = e;
}
// IMPORTANT: The eval statement *must* remain on the first line

File diff suppressed because it is too large Load Diff

View File

@@ -13,79 +13,25 @@
// : 'linksearch' searches should highlight link matches only
// : changing any search settings should also update the search state including highlighting
// : incremental searches shouldn't permanently update search modifiers
//
// TODO: Clean up this rat's nest. --Kris
/**
* @instance finder
*/
function Finder() //{{{
{
////////////////////////////////////////////////////////////////////////////////
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const Finder = Module("finder", {
init: function () {
const self = this;
var found = false; // true if the last search was successful
var backwards = false; // currently searching backwards
var searchString = ""; // current search string (without modifiers)
var searchPattern = ""; // current search string (includes modifiers)
var lastSearchPattern = ""; // the last searched pattern (includes modifiers)
var lastSearchString = ""; // the last searched string (without modifiers)
var lastSearchBackwards = false; // like "backwards", but for the last search, so if you cancel a search with <esc> this is not set
var caseSensitive = false; // search string is case sensitive
var linksOnly = false; // search is limited to link text only
// Event handlers for search - closure is needed
commandline.registerCallback("change", modes.SEARCH_FORWARD, function (str) { finder.onKeyPress(str); });
commandline.registerCallback("submit", modes.SEARCH_FORWARD, function (str) { finder.onSubmit(str); });
commandline.registerCallback("cancel", modes.SEARCH_FORWARD, function () { finder.onCancel(); });
// TODO: allow advanced myModes in register/triggerCallback
commandline.registerCallback("change", modes.SEARCH_BACKWARD, function (str) { finder.onKeyPress(str); });
commandline.registerCallback("submit", modes.SEARCH_BACKWARD, function (str) { finder.onSubmit(str); });
commandline.registerCallback("cancel", modes.SEARCH_BACKWARD, function () { finder.onCancel(); });
// set searchString, searchPattern, caseSensitive, linksOnly
function processUserPattern(pattern)
{
//// strip off pattern terminator and offset
//if (backwards)
// pattern = pattern.replace(/\?.*/, "");
//else
// pattern = pattern.replace(/\/.*/, "");
searchPattern = pattern;
// links only search - \l wins if both modifiers specified
if (/\\l/.test(pattern))
linksOnly = true;
else if (/\L/.test(pattern))
linksOnly = false;
else if (options["linksearch"])
linksOnly = true;
else
linksOnly = false;
// strip links-only modifiers
pattern = pattern.replace(/(\\)?\\[lL]/g, function ($0, $1) { return $1 ? $0 : ""; });
// case sensitivity - \c wins if both modifiers specified
if (/\c/.test(pattern))
caseSensitive = false;
else if (/\C/.test(pattern))
caseSensitive = true;
else if (options["ignorecase"] && options["smartcase"] && /[A-Z]/.test(pattern))
caseSensitive = true;
else if (options["ignorecase"])
caseSensitive = false;
else
caseSensitive = true;
// strip case-sensitive modifiers
pattern = pattern.replace(/(\\)?\\[cC]/g, function ($0, $1) { return $1 ? $0 : ""; });
// remove any modifier escape \
pattern = pattern.replace(/\\(\\[cClL])/g, "$1");
searchString = pattern;
}
this._found = false; // true if the last search was successful
this._backwards = false; // currently searching backwards
this._searchString = ""; // current search string (without modifiers)
this._searchPattern = ""; // current search string (includes modifiers)
this._lastSearchPattern = ""; // the last searched pattern (includes modifiers)
this._lastSearchString = ""; // the last searched string (without modifiers)
this._lastSearchBackwards = false; // like "backwards", but for the last search, so if you cancel a search with <esc> this is not set
this._caseSensitive = false; // search string is case sensitive
this._linksOnly = false; // search is limited to link text only
/* Stolen from toolkit.jar in Firefox, for the time being. The private
* methods were unstable, and changed. The new version is not remotely
@@ -108,37 +54,33 @@ function Finder() //{{{
* Ehsan Akhgari <ehsan.akhgari@gmail.com>
* Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
*/
var highlighter = {
this._highlighter = {
doc: null,
spans: [],
search: function (aWord, matchCase)
{
search: function (aWord, matchCase) {
var finder = services.create("find");
if (matchCase !== undefined)
finder.caseSensitive = matchCase;
self._caseSensitive = matchCase;
var range;
while ((range = finder.Find(aWord, this.searchRange, this.startPt, this.endPt)))
yield range;
},
highlightDoc: function highlightDoc(win, aWord)
{
highlightDoc: function highlightDoc(win, aWord) {
this.doc = content.document; // XXX
Array.forEach(win.frames, function (frame) highlighter.highlightDoc(frame, aWord));
Array.forEach(win.frames, function (frame) this._highlighter.highlightDoc(frame, aWord));
var doc = win.document;
if (!doc || !(doc instanceof HTMLDocument))
return;
if (!aWord)
{
let elems = highlighter.spans;
for (let i = elems.length; --i >= 0;)
{
if (!aWord) {
let elems = this._highlighter.spans;
for (let i = elems.length; --i >= 0;) {
let elem = elems[i];
let docfrag = doc.createDocumentFragment();
let next = elem.nextSibling;
@@ -174,8 +116,7 @@ function Finder() //{{{
liberator.interrupted = false;
let n = 0;
for (let retRange in this.search(aWord, caseSensitive))
{
for (let retRange in this.search(aWord, this._caseSensitive)) {
// Highlight
var nodeSurround = baseNode.cloneNode(true);
var node = this.highlight(retRange, nodeSurround);
@@ -189,8 +130,7 @@ function Finder() //{{{
}
},
highlight: function highlight(aRange, aNode)
{
highlight: function highlight(aRange, aNode) {
var startContainer = aRange.startContainer;
var startOffset = aRange.startOffset;
var endOffset = aRange.endOffset;
@@ -206,15 +146,11 @@ function Finder() //{{{
/**
* Clears all search highlighting.
*/
clear: function ()
{
this.spans.forEach(function (span)
{
if (span.parentNode)
{
clear: function () {
this.spans.forEach(function (span) {
if (span.parentNode) {
let el = span.firstChild;
while (el)
{
while (el) {
span.removeChild(el);
span.parentNode.insertBefore(el, span);
el = span.firstChild;
@@ -227,17 +163,271 @@ function Finder() //{{{
isHighlighted: function (doc) this.doc == doc && this.spans.length > 0
};
},
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// OPTIONS /////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
// set searchString, searchPattern, caseSensitive, linksOnly
processUserPattern: function (pattern) {
//// strip off pattern terminator and offset
//if (backwards)
// pattern = pattern.replace(/\?.*/, "");
//else
// pattern = pattern.replace(/\/.*/, "");
this._searchPattern = pattern;
// links only search - \l wins if both modifiers specified
if (/\\l/.test(pattern))
this._linksOnly = true;
else if (/\L/.test(pattern))
this._linksOnly = false;
else if (options["linksearch"])
this._linksOnly = true;
else
this._linksOnly = false;
// strip links-only modifiers
pattern = pattern.replace(/(\\)?\\[lL]/g, function ($0, $1) { return $1 ? $0 : ""; });
// case sensitivity - \c wins if both modifiers specified
if (/\c/.test(pattern))
this._caseSensitive = false;
else if (/\C/.test(pattern))
this._caseSensitive = true;
else if (options["ignorecase"] && options["smartcase"] && /[A-Z]/.test(pattern))
this._caseSensitive = true;
else if (options["ignorecase"])
this._caseSensitive = false;
else
this._caseSensitive = true;
// strip case-sensitive modifiers
pattern = pattern.replace(/(\\)?\\[cC]/g, function ($0, $1) { return $1 ? $0 : ""; });
// remove any modifier escape \
pattern = pattern.replace(/\\(\\[cClL])/g, "$1");
this._searchString = pattern;
},
/**
* Called when the search dialog is requested.
*
* @param {number} mode The search mode, either modes.SEARCH_FORWARD or
* modes.SEARCH_BACKWARD.
* @default modes.SEARCH_FORWARD
*/
openPrompt: function (mode) {
this._backwards = mode == modes.SEARCH_BACKWARD;
commandline.open(this._backwards ? "?" : "/", "", mode);
// TODO: focus the top of the currently visible screen
},
// TODO: backwards seems impossible i fear :(
/**
* Searches the current buffer for <b>str</b>.
*
* @param {string} str The string to find.
*/
find: function (str) {
let fastFind = getBrowser().fastFind;
this._processUserPattern(str);
fastFind.caseSensitive = this._caseSensitive;
this._found = fastFind.find(this._searchString, this._linksOnly) != Ci.nsITypeAheadFind.FIND_NOTFOUND;
if (!this._found)
setTimeout(function () liberator.echoerr("E486: Pattern not found: " + this._searchPattern, commandline.FORCE_SINGLELINE), 0);
},
/**
* Searches the current buffer again for the most recently used search
* string.
*
* @param {boolean} reverse Whether to search forwards or backwards.
* @default false
*/
findAgain: function (reverse) {
// This hack is needed to make n/N work with the correct string, if
// we typed /foo<esc> after the original search. Since searchString is
// readonly we have to call find() again to update it.
if (getBrowser().fastFind.searchString != this._lastSearchString)
this.find(this._lastSearchString);
let up = reverse ? !this._lastSearchBackwards : this._lastSearchBackwards;
let result = getBrowser().fastFind.findAgain(up, this._linksOnly);
if (result == Ci.nsITypeAheadFind.FIND_NOTFOUND)
liberator.echoerr("E486: Pattern not found: " + this._lastSearchPattern, commandline.FORCE_SINGLELINE);
else if (result == Ci.nsITypeAheadFind.FIND_WRAPPED) {
// hack needed, because wrapping causes a "scroll" event which clears
// our command line
setTimeout(function () {
let msg = up ? "search hit TOP, continuing at BOTTOM" : "search hit BOTTOM, continuing at TOP";
commandline.echo(msg, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES | commandline.FORCE_SINGLELINE);
}, 0);
}
else {
commandline.echo((up ? "?" : "/") + this._lastSearchPattern, null, commandline.FORCE_SINGLELINE);
if (options["hlsearch"])
this.highlight(this._lastSearchString);
}
},
/**
* Called when the user types a key in the search dialog. Triggers a
* search attempt if 'incsearch' is set.
*
* @param {string} str The search string.
*/
onKeyPress: function (str) {
if (options["incsearch"])
this.find(str);
},
/**
* Called when the <Enter> key is pressed to trigger a search.
*
* @param {string} str The search string.
* @param {boolean} forcedBackward Whether to search forwards or
* backwards. This overrides the direction set in
* (@link #openPrompt).
* @default false
*/
onSubmit: function (str, forcedBackward) {
if (typeof forcedBackward === "boolean")
this._backwards = forcedBackward;
if (str)
var pattern = str;
else {
liberator.assert(this._lastSearchPattern, "E35: No previous search pattern");
pattern = this._lastSearchPattern;
}
this.clear();
if (!options["incsearch"] || !str || !this._found) {
// prevent any current match from matching again
if (!window.content.getSelection().isCollapsed)
window.content.getSelection().getRangeAt(0).collapse(this._backwards);
this.find(pattern);
}
this._lastSearchBackwards = this._backwards;
//lastSearchPattern = pattern.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX
this._lastSearchPattern = pattern;
this._lastSearchString = this._searchString;
// TODO: move to find() when reverse incremental searching is kludged in
// need to find again for reverse searching
if (this._backwards)
setTimeout(function () { finder.findAgain(false); }, 0);
if (options["hlsearch"])
this.highlight(this._searchString);
modes.reset();
},
/**
* Called when the search is canceled. For example, if someone presses
* <Esc> while typing a search.
*/
onCancel: function () {
// TODO: code to reposition the document to the place before search started
},
/**
* Highlights all occurances of <b>str</b> in the buffer.
*
* @param {string} str The string to highlight.
*/
highlight: function (str) {
// FIXME: Thunderbird incompatible
if (config.name == "Muttator")
return;
if (this._highlighter.isHighlighted(content.document))
return;
if (!str)
str = this._lastSearchString;
this._highlighter.highlightDoc(window.content, str);
// recreate selection since highlightDoc collapses the selection
if (window.content.getSelection().isCollapsed)
getBrowser().fastFind.findAgain(this._backwards, this._linksOnly);
// TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"])
},
/**
* Clears all search highlighting.
*/
clear: function () {
this._highlighter.clear();
}
}, {
commandline: function () {
const self = this;
// Event handlers for search - closure is needed
commandline.registerCallback("change", modes.SEARCH_FORWARD, function (str) { self.onKeyPress(str); });
commandline.registerCallback("submit", modes.SEARCH_FORWARD, function (str) { self.onSubmit(str); });
commandline.registerCallback("cancel", modes.SEARCH_FORWARD, function () { self.onCancel(); });
// TODO: allow advanced myModes in register/triggerCallback
commandline.registerCallback("change", modes.SEARCH_BACKWARD, function (str) { self.onKeyPress(str); });
commandline.registerCallback("submit", modes.SEARCH_BACKWARD, function (str) { self.onSubmit(str); });
commandline.registerCallback("cancel", modes.SEARCH_BACKWARD, function () { self.onCancel(); });
},
commands: function () {
commands.add(["noh[lsearch]"],
"Remove the search highlighting",
function () { finder.clear(); },
{ argCount: "0" });
},
mappings: function () {
var myModes = config.browserModes;
myModes = myModes.concat([modes.CARET]);
mappings.add(myModes,
["/"], "Search forward for a pattern",
function () { finder.openPrompt(modes.SEARCH_FORWARD); });
mappings.add(myModes,
["?"], "Search backwards for a pattern",
function () { finder.openPrompt(modes.SEARCH_BACKWARD); });
mappings.add(myModes,
["n"], "Find next",
function () { finder.findAgain(false); });
mappings.add(myModes,
["N"], "Find previous",
function () { finder.findAgain(true); });
mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["*"],
"Find word under cursor",
function () {
this._found = false;
finder.onSubmit(buffer.getCurrentWord(), false);
});
mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["#"],
"Find word under cursor backwards",
function () {
this._found = false;
finder.onSubmit(buffer.getCurrentWord(), true);
});
},
options: function () {
options.add(["hlsearch", "hls"],
"Highlight previous search pattern matches",
"boolean", "false",
{
setter: function (value)
{
"boolean", "false", {
setter: function (value) {
if (value)
finder.highlight();
else
@@ -262,236 +452,7 @@ function Finder() //{{{
options.add(["smartcase", "scs"],
"Override the 'ignorecase' option if the pattern contains uppercase characters",
"boolean", true);
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// MAPPINGS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
var myModes = config.browserModes;
myModes = myModes.concat([modes.CARET]);
mappings.add(myModes,
["/"], "Search forward for a pattern",
function () { finder.openPrompt(modes.SEARCH_FORWARD); });
mappings.add(myModes,
["?"], "Search backwards for a pattern",
function () { finder.openPrompt(modes.SEARCH_BACKWARD); });
mappings.add(myModes,
["n"], "Find next",
function () { finder.findAgain(false); });
mappings.add(myModes,
["N"], "Find previous",
function () { finder.findAgain(true); });
mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["*"],
"Find word under cursor",
function ()
{
found = false;
finder.onSubmit(buffer.getCurrentWord(), false);
});
mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["#"],
"Find word under cursor backwards",
function ()
{
found = false;
finder.onSubmit(buffer.getCurrentWord(), true);
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
commands.add(["noh[lsearch]"],
"Remove the search highlighting",
function () { finder.clear(); },
{ argCount: "0" });
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
return {
/**
* Called when the search dialog is requested.
*
* @param {number} mode The search mode, either modes.SEARCH_FORWARD or
* modes.SEARCH_BACKWARD.
* @default modes.SEARCH_FORWARD
*/
openPrompt: function (mode)
{
backwards = mode == modes.SEARCH_BACKWARD;
commandline.open(backwards ? "?" : "/", "", mode);
// TODO: focus the top of the currently visible screen
},
// TODO: backwards seems impossible i fear :(
/**
* Searches the current buffer for <b>str</b>.
*
* @param {string} str The string to find.
*/
find: function (str)
{
let fastFind = getBrowser().fastFind;
processUserPattern(str);
fastFind.caseSensitive = caseSensitive;
found = fastFind.find(searchString, linksOnly) != Ci.nsITypeAheadFind.FIND_NOTFOUND;
if (!found)
setTimeout(function () liberator.echoerr("E486: Pattern not found: " + searchPattern, commandline.FORCE_SINGLELINE), 0);
},
/**
* Searches the current buffer again for the most recently used search
* string.
*
* @param {boolean} reverse Whether to search forwards or backwards.
* @default false
*/
findAgain: function (reverse)
{
// This hack is needed to make n/N work with the correct string, if
// we typed /foo<esc> after the original search. Since searchString is
// readonly we have to call find() again to update it.
if (getBrowser().fastFind.searchString != lastSearchString)
this.find(lastSearchString);
let up = reverse ? !lastSearchBackwards : lastSearchBackwards;
let result = getBrowser().fastFind.findAgain(up, linksOnly);
if (result == Ci.nsITypeAheadFind.FIND_NOTFOUND)
liberator.echoerr("E486: Pattern not found: " + lastSearchPattern, commandline.FORCE_SINGLELINE);
else if (result == Ci.nsITypeAheadFind.FIND_WRAPPED)
{
// hack needed, because wrapping causes a "scroll" event which clears
// our command line
setTimeout(function () {
let msg = up ? "search hit TOP, continuing at BOTTOM" : "search hit BOTTOM, continuing at TOP";
commandline.echo(msg, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES | commandline.FORCE_SINGLELINE);
}, 0);
}
else
{
commandline.echo((up ? "?" : "/") + lastSearchPattern, null, commandline.FORCE_SINGLELINE);
if (options["hlsearch"])
this.highlight(lastSearchString);
}
},
/**
* Called when the user types a key in the search dialog. Triggers a
* search attempt if 'incsearch' is set.
*
* @param {string} str The search string.
*/
onKeyPress: function (str)
{
if (options["incsearch"])
this.find(str);
},
/**
* Called when the <Enter> key is pressed to trigger a search.
*
* @param {string} str The search string.
* @param {boolean} forcedBackward Whether to search forwards or
* backwards. This overrides the direction set in
* (@link #openPrompt).
* @default false
*/
onSubmit: function (str, forcedBackward)
{
if (typeof forcedBackward === "boolean")
backwards = forcedBackward;
if (str)
var pattern = str;
else
{
liberator.assert(lastSearchPattern, "E35: No previous search pattern");
pattern = lastSearchPattern;
}
this.clear();
if (!options["incsearch"] || !str || !found)
{
// prevent any current match from matching again
if (!window.content.getSelection().isCollapsed)
window.content.getSelection().getRangeAt(0).collapse(backwards);
this.find(pattern);
}
lastSearchBackwards = backwards;
//lastSearchPattern = pattern.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX
lastSearchPattern = pattern;
lastSearchString = searchString;
// TODO: move to find() when reverse incremental searching is kludged in
// need to find again for reverse searching
if (backwards)
setTimeout(function () { finder.findAgain(false); }, 0);
if (options["hlsearch"])
this.highlight(searchString);
modes.reset();
},
/**
* Called when the search is canceled. For example, if someone presses
* <Esc> while typing a search.
*/
onCancel: function ()
{
// TODO: code to reposition the document to the place before search started
},
/**
* Highlights all occurances of <b>str</b> in the buffer.
*
* @param {string} str The string to highlight.
*/
highlight: function (str)
{
// FIXME: Thunderbird incompatible
if (config.name == "Muttator")
return;
if (highlighter.isHighlighted(content.document))
return;
if (!str)
str = lastSearchString;
highlighter.highlightDoc(window.content, str);
// recreate selection since highlightDoc collapses the selection
if (window.content.getSelection().isCollapsed)
getBrowser().fastFind.findAgain(backwards, linksOnly);
// TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"])
},
/**
* Clears all search highlighting.
*/
clear: function ()
{
highlighter.clear();
}
};
//}}}
} //}}}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -4,8 +4,7 @@
// given in the LICENSE.txt file included with this file.
function checkFragment()
{
function checkFragment() {
document.title = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "title")[0].textContent;
var frag = document.location.hash.substr(1);
var elem = document.getElementById(frag);

File diff suppressed because it is too large Load Diff

233
common/content/history.js Normal file
View File

@@ -0,0 +1,233 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
const History = Module("history", {
get format() bookmarks.format,
get service() services.get("history"),
get: function get(filter, maxItems) {
// no query parameters will get all history
let query = services.get("history").getNewQuery();
let options = services.get("history").getNewQueryOptions();
if (typeof filter == "string")
filter = { searchTerms: filter };
for (let [k, v] in Iterator(filter))
query[k] = v;
options.sortingMode = options.SORT_BY_DATE_DESCENDING;
options.resultType = options.RESULTS_AS_URI;
if (maxItems > 0)
options.maxResults = maxItems;
// execute the query
let root = services.get("history").executeQuery(query, options).root;
root.containerOpen = true;
let items = util.map(util.range(0, root.childCount), function (i) {
let node = root.getChild(i);
return {
url: node.uri,
title: node.title,
icon: node.icon ? node.icon.spec : DEFAULT_FAVICON
};
});
root.containerOpen = false; // close a container after using it!
return items;
},
get session() {
let sh = window.getWebNavigation().sessionHistory;
let obj = [];
obj.index = sh.index;
obj.__iterator__ = function () util.Array.iteritems(this);
for (let i in util.range(0, sh.count)) {
obj[i] = { index: i, __proto__: sh.getEntryAtIndex(i, false) };
util.memoize(obj[i], "icon",
function (obj) services.get("favicon").getFaviconImageForPage(obj.URI).spec);
}
return obj;
},
// TODO: better names
stepTo: function stepTo(steps) {
let start = 0;
let end = window.getWebNavigation().sessionHistory.count - 1;
let current = window.getWebNavigation().sessionHistory.index;
if (current == start && steps < 0 || current == end && steps > 0)
liberator.beep();
else {
let index = util.Math.constrain(current + steps, start, end);
window.getWebNavigation().gotoIndex(index);
}
},
goToStart: function goToStart() {
let index = window.getWebNavigation().sessionHistory.index;
if (index > 0)
window.getWebNavigation().gotoIndex(0);
else
liberator.beep();
},
goToEnd: function goToEnd() {
let sh = window.getWebNavigation().sessionHistory;
let max = sh.count - 1;
if (sh.index < max)
window.getWebNavigation().gotoIndex(max);
else
liberator.beep();
},
// if openItems is true, open the matching history items in tabs rather than display
list: function list(filter, openItems, maxItems) {
// FIXME: returning here doesn't make sense
// Why the hell doesn't it make sense? --Kris
// See comment at bookmarks.list --djk
if (!openItems)
return completion.listCompleter("history", filter, maxItems);
let items = completion.runCompleter("history", filter, maxItems);
if (items.length)
return liberator.open(items.map(function (i) i.url), liberator.NEW_TAB);
if (filter.length > 0)
liberator.echoerr("E283: No history matching \"" + filter + "\"");
else
liberator.echoerr("No history set");
}
}, {
}, {
commands: function () {
commands.add(["ba[ck]"],
"Go back in the browser history",
function (args) {
let url = args.literalArg;
if (args.bang)
history.goToStart();
else {
if (url) {
let sh = history.session;
if (/^\d+(:|$)/.test(url) && sh.index - parseInt(url) in sh)
return void window.getWebNavigation().gotoIndex(sh.index - parseInt(url));
for (let [i, ent] in Iterator(sh.slice(0, sh.index).reverse()))
if (ent.URI.spec == url)
return void window.getWebNavigation().gotoIndex(i);
liberator.echoerr("Exxx: URL not found in history");
}
else
history.stepTo(-Math.max(args.count, 1));
}
},
{
argCount: "?",
bang: true,
completer: function completer(context) {
let sh = history.session;
context.anchored = false;
context.compare = CompletionContext.Sort.unsorted;
context.filters = [CompletionContext.Filter.textDescription];
context.completions = sh.slice(0, sh.index).reverse();
context.keys = { text: function (item) (sh.index - item.index) + ": " + item.URI.spec, description: "title", icon: "icon" };
},
count: true,
literal: 0
});
commands.add(["fo[rward]", "fw"],
"Go forward in the browser history",
function (args) {
let url = args.literalArg;
if (args.bang)
history.goToEnd();
else {
if (url) {
let sh = history.session;
if (/^\d+(:|$)/.test(url) && sh.index + parseInt(url) in sh)
return void window.getWebNavigation().gotoIndex(sh.index + parseInt(url));
for (let [i, ent] in Iterator(sh.slice(sh.index + 1)))
if (ent.URI.spec == url)
return void window.getWebNavigation().gotoIndex(i);
liberator.echoerr("Exxx: URL not found in history");
}
else
history.stepTo(Math.max(args.count, 1));
}
},
{
argCount: "?",
bang: true,
completer: function completer(context) {
let sh = history.session;
context.anchored = false;
context.compare = CompletionContext.Sort.unsorted;
context.filters = [CompletionContext.Filter.textDescription];
context.completions = sh.slice(sh.index + 1);
context.keys = { text: function (item) (item.index - sh.index) + ": " + item.URI.spec, description: "title", icon: "icon" };
},
count: true,
literal: 0
});
commands.add(["hist[ory]", "hs"],
"Show recently visited URLs",
function (args) { history.list(args.join(" "), args.bang, args["-max"] || 1000); }, {
bang: true,
completer: function (context) { context.quote = null; completion.history(context); },
// completer: function (filter) completion.history(filter)
options: [[["-max", "-m"], commands.OPTION_INT]]
});
},
completion: function () {
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);
},
mappings: function () {
var myModes = config.browserModes;
mappings.add(myModes,
["<C-o>"], "Go to an older position in the jump list",
function (count) { history.stepTo(-Math.max(count, 1)); },
{ count: true });
mappings.add(myModes,
["<C-i>"], "Go to a newer position in the jump list",
function (count) { history.stepTo(Math.max(count, 1)); },
{ count: true });
mappings.add(myModes,
["H", "<A-Left>", "<M-Left>"], "Go back in the browser history",
function (count) { history.stepTo(-Math.max(count, 1)); },
{ count: true });
mappings.add(myModes,
["L", "<A-Right>", "<M-Right>"], "Go forward in the browser history",
function (count) { history.stepTo(Math.max(count, 1)); },
{ count: true });
},
});
// vim: set fdm=marker sw=4 ts=4 et:

File diff suppressed because it is too large Load Diff

View File

@@ -11,51 +11,51 @@
const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
function load(script)
{
for (let [i, base] in Iterator(prefix))
{
try
{
function load(script) {
for (let [i, base] in Iterator(prefix)) {
try {
loader.loadSubScript(base + script, modules);
return;
}
catch (e)
{
catch (e) {
if (i + 1 < prefix.length)
continue;
if (Components.utils.reportError)
Components.utils.reportError(e);
dump("liberator: Loading script " + script + ": " + e + "\n");
dump(e.stack + "\n");
}
}
}
Components.utils.import("resource://liberator/storage.jsm", modules);
let prefix = [BASE];
["services.js",
"liberator.js",
"configbase.js",
"config.js"].forEach(load);
modules.config.__proto__ = modules.configbase;
["util.js",
"style.js",
["base.js",
"modules.js",
"autocommands.js",
"buffer.js",
"commandline.js",
"commands.js",
"completion.js",
"config.js",
"configbase.js",
"liberator.js",
"editor.js",
"events.js",
"finder.js",
"hints.js",
"io.js",
"mappings.js",
"marks.js",
"modes.js",
"options.js",
"services.js",
"statusline.js",
"style.js",
"template.js",
"ui.js"].forEach(load);
"util.js",
].forEach(load);
modules.config.__proto__ = modules.configbase;
prefix.unshift("chrome://" + modules.config.name.toLowerCase() + "/content/");
modules.config.scripts.forEach(load);

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,7 @@
<?xml-stylesheet href="chrome://liberator/skin/liberator.css" type="text/css"?>
<!DOCTYPE overlay SYSTEM "liberator.dtd" [
<!ENTITY liberator.content "chrome://liberator/content/">
<!ENTITY and "&amp;&amp;">
]>
<overlay id="liberator"
@@ -28,8 +29,8 @@
</stringbundleset>
<keyset id="mainKeyset">
<key id="key_open_vimbar" key=":" oncommand="liberator.modules.commandline.open(':', '', liberator.modules.modes.EX);" modifiers=""/>
<key id="key_stop" keycode="VK_ESCAPE" oncommand="liberator.modules.events.onEscape();"/>
<key id="key_open_vimbar" key=":" oncommand="window.liberator &and; liberator.modules.commandline.open(':', '', liberator.modules.modes.EX);" modifiers=""/>
<key id="key_stop" keycode="VK_ESCAPE" oncommand="window.liberator &and; liberator.modules.events.onEscape();"/>
<!-- other keys are handled inside the event loop in events.js -->
</keyset>
@@ -42,24 +43,24 @@
<commandset id="onVimperatorFocus"
commandupdater="true"
events="focus"
oncommandupdate="if (liberator.modules.events != undefined) liberator.modules.events.onFocusChange(event);"/>
oncommandupdate="if (window.liberator &and; liberator.modules.events != undefined) liberator.modules.events.onFocusChange(event);"/>
<commandset id="onVimperatorSelect"
commandupdater="true"
events="select"
oncommandupdate="if (liberator.modules.events != undefined) liberator.modules.events.onSelectionChange(event);"/>
oncommandupdate="if (window.liberator &and; liberator.modules.events != undefined) liberator.modules.events.onSelectionChange(event);"/>
<!-- As of Firefox 3.1pre, <iframe>.height changes do not seem to have immediate effect,
therefore we need to put them into a <vbox> for which that works just fine -->
<vbox class="liberator-container" hidden="false" collapsed="true">
<iframe id="liberator-multiline-output" src="chrome://liberator/content/buffer.xhtml"
flex="1" hidden="false" collapsed="false"
onclick="liberator.modules.commandline.onMultilineOutputEvent(event)"/>
onclick="window.liberator &and; liberator.modules.commandline.onMultilineOutputEvent(event)"/>
</vbox>
<vbox class="liberator-container" hidden="false" collapsed="true">
<iframe id="liberator-completions" src="chrome://liberator/content/buffer.xhtml"
flex="1" hidden="false" collapsed="false"
onclick="liberator.modules.commandline.onMultilineOutputEvent(event)"/>
onclick="window.liberator &and; liberator.modules.commandline.onMultilineOutputEvent(event)"/>
</vbox>
<stack orient="horizontal" align="stretch" class="liberator-container" liberator:highlight="CmdLine">
@@ -67,18 +68,18 @@
<hbox id="liberator-commandline" hidden="false" collapsed="true" class="liberator-container" liberator:highlight="Normal">
<label class="plain" id="liberator-commandline-prompt" flex="0" crop="end" value="" collapsed="true"/>
<textbox class="plain" id="liberator-commandline-command" flex="1" type="timed" timeout="100"
oninput="liberator.modules.commandline.onEvent(event);"
onkeyup="liberator.modules.commandline.onEvent(event);"
onfocus="liberator.modules.commandline.onEvent(event);"
onblur="liberator.modules.commandline.onEvent(event);"/>
oninput="window.liberator &and; liberator.modules.commandline.onEvent(event);"
onkeyup="window.liberator &and; liberator.modules.commandline.onEvent(event);"
onfocus="window.liberator &and; liberator.modules.commandline.onEvent(event);"
onblur="window.liberator &and; liberator.modules.commandline.onEvent(event);"/>
</hbox>
</stack>
<vbox class="liberator-container" hidden="false" collapsed="false" liberator:highlight="CmdLine">
<textbox id="liberator-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true" liberator:highlight="Normal"
onkeypress="liberator.modules.commandline.onMultilineInputEvent(event);"
oninput="liberator.modules.commandline.onMultilineInputEvent(event);"
onblur="liberator.modules.commandline.onMultilineInputEvent(event);"/>
onkeypress="window.liberator &and; liberator.modules.commandline.onMultilineInputEvent(event);"
oninput="window.liberator &and; liberator.modules.commandline.onMultilineInputEvent(event);"
onblur="window.liberator &and; liberator.modules.commandline.onMultilineInputEvent(event);"/>
</vbox>
</window>

View File

@@ -27,8 +27,8 @@
* @optional
* @private
*/
function Map(modes, keys, description, action, extraInfo) //{{{
{
const Map = Class("Map", {
init: function (modes, keys, description, action, extraInfo) {
modes = Array.concat(modes);
if (!extraInfo)
@@ -70,9 +70,7 @@ function Map(modes, keys, description, action, extraInfo) //{{{
* plugin authors should create only user mappings.
*/
this.user = extraInfo.user || false;
}
Map.prototype = {
},
/**
* Returns whether this mapping can be invoked by a key sequence matching
@@ -93,8 +91,7 @@ Map.prototype = {
* @param {string} argument The normal argument if accepted by this
* mapping. E.g. "a" for "ma"
*/
execute: function (motion, count, argument)
{
execute: function (motion, count, argument) {
let args = [];
if (this.motion)
@@ -112,60 +109,46 @@ Map.prototype = {
return liberator.trapErrors(repeat);
}
}; //}}}
});
/**
* @instance mappings
*/
function Mappings() //{{{
{
////////////////////////////////////////////////////////////////////////////////
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const Mappings = Module("mappings", {
requires: ["modes"],
var main = []; // default mappings
var user = []; // user created mappings
init: function () {
this._main = []; // default mappings
this._user = []; // user created mappings
},
for (let mode in modes)
{
main[mode] = [];
user[mode] = [];
}
function addMap(map)
{
let where = map.user ? user : main;
_addMap: function (map) {
let where = map.user ? this._user : this._main;
map.modes.forEach(function (mode) {
if (!(mode in where))
where[mode] = [];
where[mode].push(map);
});
}
},
function getMap(mode, cmd, stack)
{
_getMap: function (mode, cmd, stack) {
let maps = stack[mode] || [];
for (let [, map] in Iterator(maps))
{
for (let [, map] in Iterator(maps)) {
if (map.hasName(cmd))
return map;
}
return null;
}
},
function removeMap(mode, cmd)
{
let maps = user[mode] || [];
_removeMap: function (mode, cmd) {
let maps = this._user[mode] || [];
let names;
for (let [i, map] in Iterator(maps))
{
for (let [j, name] in Iterator(map.names))
{
if (name == cmd)
{
for (let [i, map] in Iterator(maps)) {
for (let [j, name] in Iterator(map.names)) {
if (name == cmd) {
map.names.splice(j, 1);
if (map.names.length == 0)
maps.splice(i, 1);
@@ -173,28 +156,221 @@ function Mappings() //{{{
}
}
}
}
},
function expandLeader(keyString) keyString.replace(/<Leader>/i, mappings.getMapLeader())
_expandLeader: function (keyString) keyString.replace(/<Leader>/i, mappings.getMapLeader()),
// Return all mappings present in all @modes
function mappingsIterator(modes, stack)
{
_mappingsIterator: function (modes, stack) {
modes = modes.slice();
return (map for ([i, map] in Iterator(stack[modes.shift()]))
if (modes.every(function (mode) stack[mode].some(
function (m) m.rhs == map.rhs && m.names[0] == map.names[0]))))
},
// NOTE: just normal mode for now
/** @property {Iterator(Map)} @private */
__iterator__: function () this._mappingsIterator([modes.NORMAL], this._main),
// used by :mkvimperatorrc to save mappings
/**
* Returns a user-defined mappings iterator for the specified
* <b>mode</b>.
*
* @param {number} mode The mode to return mappings from.
* @returns {Iterator(Map)}
*/
getUserIterator: function (mode) this._mappingsIterator(mode, this._user),
addMode: function (mode) {
if (!(mode in this._user || mode in this._main)) {
this._main[mode] = [];
this._user[mode] = [];
}
},
/**
* Adds a new default key mapping.
*
* @param {number[]} modes The modes that this mapping applies to.
* @param {string[]} keys The key sequences which are bound to
* <b>action</b>.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash.
* @optional
*/
add: function (modes, keys, description, action, extra) {
this._addMap(new Map(modes, keys, description, action, extra));
},
/**
* Adds a new user-defined key mapping.
*
* @param {number[]} modes The modes that this mapping applies to.
* @param {string[]} keys The key sequences which are bound to
* <b>action</b>.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash (see
* {@link Map#extraInfo}).
* @optional
*/
addUserMap: function (modes, keys, description, action, extra) {
keys = keys.map(this._expandLeader);
extra = extra || {};
extra.user = true;
let map = new Map(modes, keys, description || "User defined mapping", action, extra);
// remove all old mappings to this key sequence
for (let [, name] in Iterator(map.names)) {
for (let [, mode] in Iterator(map.modes))
this._removeMap(mode, name);
}
function addMapCommands(ch, modes, modeDescription)
this._addMap(map);
},
/**
* Returns the map from <b>mode</b> named <b>cmd</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The map name to match.
* @returns {Map}
*/
get: function (mode, cmd) {
mode = mode || modes.NORMAL;
return this._getMap(mode, cmd, this._user) || this._getMap(mode, cmd, this._main);
},
/**
* Returns the default map from <b>mode</b> named <b>cmd</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The map name to match.
* @returns {Map}
*/
getDefault: function (mode, cmd) {
mode = mode || modes.NORMAL;
return this._getMap(mode, cmd, this._main);
},
/**
* Returns an array of maps with names starting with but not equal to
* <b>prefix</b>.
*
* @param {number} mode The mode to search.
* @param {string} prefix The map prefix string to match.
* @returns {Map[]}
*/
getCandidates: function (mode, prefix) {
let mappings = this._user[mode].concat(this._main[mode]);
let matches = [];
for (let [, map] in Iterator(mappings)) {
for (let [, name] in Iterator(map.names)) {
if (name.indexOf(prefix) == 0 && name.length > prefix.length) {
// for < only return a candidate if it doesn't look like a <c-x> mapping
if (prefix != "<" || !/^<.+>/.test(name))
matches.push(map);
}
}
}
return matches;
},
/*
* Returns the map leader string used to replace the special token
* "<Leader>" when user mappings are defined.
*
* @returns {string}
*/
// FIXME: property
getMapLeader: function () {
let leaderRef = liberator.variableReference("mapleader");
return leaderRef[0] ? leaderRef[0][leaderRef[1]] : "\\";
},
/**
* Returns whether there is a user-defined mapping <b>cmd</b> for the
* specified <b>mode</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The candidate key mapping.
* @returns {boolean}
*/
hasMap: function (mode, cmd) this._user[mode].some(function (map) map.hasName(cmd)),
/**
* Remove the user-defined mapping named <b>cmd</b> for <b>mode</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The map name to match.
*/
remove: function (mode, cmd) {
this._removeMap(mode, cmd);
},
/**
* Remove all user-defined mappings for <b>mode</b>.
*
* @param {number} mode The mode to remove all mappings from.
*/
removeAll: function (mode) {
this._user[mode] = [];
},
/**
* Lists all user-defined mappings matching <b>filter</b> for the
* specified <b>modes</b>.
*
* @param {number[]} modes An array of modes to search.
* @param {string} filter The filter string to match.
*/
list: function (modes, filter) {
let modeSign = "";
// TODO: Vim hides "nv" in a :map and "v" and "n" in :vmap and
// :nmap respectively if the map is not exclusive.
modes.forEach(function (mode) {
for (let m in modules.modes.mainModes)
if (mode == m.mask && modeSign.indexOf(m.char) == -1)
modeSign += m.char;
});
let maps = this._mappingsIterator(modes, this._user);
if (filter)
maps = [map for (map in maps) if (map.names[0] == filter)];
let list = <table>
{
template.map(maps, function (map)
template.map(map.names, function (name)
<tr>
<td>{modeSign} {name}</td>
<td>{map.noremap ? "*" : " "}</td>
<td>{map.rhs || "function () { ... }"}</td>
</tr>))
}
</table>;
// TODO: Move this to an ItemList to show this automatically
if (list.*.length() == list.text().length()) {
liberator.echomsg("No mapping found");
return;
}
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
}, {
}, {
commands: function () {
function addMapCommands(ch, modes, modeDescription) {
// 0 args -> list all maps
// 1 arg -> list the maps starting with args
// 2 args -> map arg1 to arg*
function map(args, modes, noremap)
{
if (!args.length)
{
function map(args, modes, noremap) {
if (!args.length) {
mappings.list(modes);
return;
}
@@ -202,17 +378,15 @@ function Mappings() //{{{
let [lhs, rhs] = args;
if (!rhs) // list the mapping
mappings.list(modes, expandLeader(lhs));
else
{
mappings.list(modes, this._expandLeader(lhs));
else {
// this matches Vim's behaviour
if (/^<Nop>$/i.test(rhs))
noremap = true;
mappings.addUserMap(modes, [lhs],
"User defined mapping",
function (count) { events.feedkeys((count > -1 ? count : "") + this.rhs, this.noremap, this.silent); },
{
function (count) { events.feedkeys((count > -1 ? count : "") + this.rhs, this.noremap, this.silent); }, {
count: true,
rhs: events.canonicalKeys(rhs),
noremap: !!noremap,
@@ -224,8 +398,7 @@ function Mappings() //{{{
modeDescription = modeDescription ? " in " + modeDescription + " mode" : "";
// :map, :noremap => NORMAL + VISUAL modes
function isMultiMode(map, cmd)
{
function isMultiMode(map, cmd) {
return map.modes.indexOf(modules.modes.NORMAL) >= 0
&& map.modes.indexOf(modules.modes.VISUAL) >= 0
&& /^[nv](nore)?map$/.test(cmd);
@@ -237,8 +410,7 @@ function Mappings() //{{{
[["<silent>", "<Silent>"], commands.OPTION_NOARG]
],
literal: 1,
serial: function ()
{
serial: function () {
let noremap = this.name.indexOf("noremap") > -1;
return [
{
@@ -247,7 +419,7 @@ function Mappings() //{{{
arguments: [map.names[0]],
literalArg: map.rhs
}
for (map in mappingsIterator(modes, user))
for (map in this._mappingsIterator(modes, this._user))
if (map.rhs && map.noremap == noremap && !isMultiMode(map, this.name))
];
}
@@ -270,15 +442,12 @@ function Mappings() //{{{
commands.add([ch + "unm[ap]"],
"Remove a mapping" + modeDescription,
function (args)
{
function (args) {
args = args[0];
let found = false;
for (let [, mode] in Iterator(modes))
{
if (mappings.hasMap(mode, args))
{
for (let [, mode] in Iterator(modes)) {
if (mappings.hasMap(mode, args)) {
mappings.remove(mode, args);
found = true;
}
@@ -292,10 +461,6 @@ function Mappings() //{{{
});
}
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
addMapCommands("", [modes.NORMAL, modes.VISUAL], "");
for (let mode in modes.mainModes)
@@ -303,22 +468,17 @@ function Mappings() //{{{
addMapCommands(mode.char,
[m.mask for (m in modes.mainModes) if (m.char == mode.char)],
[mode.disp.toLowerCase()]);
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMPLETIONS /////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () {
},
completion: function () {
completion.setFunctionCompleter(mappings.get,
[
null,
function (context, obj, args)
{
function (context, obj, args) {
let mode = args[0];
return util.Array.flatten(
[
[[name, map.description] for ([i, name] in Iterator(map.names))]
for ([i, map] in Iterator(user[mode].concat(main[mode])))
for ([i, map] in Iterator(this._user[mode].concat(this._main[mode])))
]);
}
]);
@@ -327,232 +487,18 @@ function Mappings() //{{{
// FIXME: have we decided on a 'standard' way to handle this clash? --djk
modes = modes || [modules.modes.NORMAL];
if (args.completeArg == 0)
{
if (args.completeArg == 0) {
let maps = [[m.names[0], ""] for (m in mappings.getUserIterator(modes))];
context.completions = maps;
}
};
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
return {
// NOTE: just normal mode for now
/** @property {Iterator(Map)} @private */
__iterator__: function () mappingsIterator([modes.NORMAL], main),
// used by :mkvimperatorrc to save mappings
/**
* Returns a user-defined mappings iterator for the specified
* <b>mode</b>.
*
* @param {number} mode The mode to return mappings from.
* @returns {Iterator(Map)}
*/
getUserIterator: function (mode) mappingsIterator(mode, user),
addMode: function (mode)
{
if (!(mode in user || mode in main))
{
main[mode] = [];
user[mode] = [];
},
modes: function () {
for (let mode in modes) {
this._main[mode] = [];
this._user[mode] = [];
}
},
/**
* Adds a new default key mapping.
*
* @param {number[]} modes The modes that this mapping applies to.
* @param {string[]} keys The key sequences which are bound to
* <b>action</b>.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash.
* @optional
*/
add: function (modes, keys, description, action, extra)
{
addMap(new Map(modes, keys, description, action, extra));
},
/**
* Adds a new user-defined key mapping.
*
* @param {number[]} modes The modes that this mapping applies to.
* @param {string[]} keys The key sequences which are bound to
* <b>action</b>.
* @param {string} description A description of the key mapping.
* @param {function} action The action invoked by each key sequence.
* @param {Object} extra An optional extra configuration hash (see
* {@link Map#extraInfo}).
* @optional
*/
addUserMap: function (modes, keys, description, action, extra)
{
keys = keys.map(expandLeader);
extra = extra || {};
extra.user = true;
let map = new Map(modes, keys, description || "User defined mapping", action, extra);
// remove all old mappings to this key sequence
for (let [, name] in Iterator(map.names))
{
for (let [, mode] in Iterator(map.modes))
removeMap(mode, name);
}
addMap(map);
},
/**
* Returns the map from <b>mode</b> named <b>cmd</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The map name to match.
* @returns {Map}
*/
get: function (mode, cmd)
{
mode = mode || modes.NORMAL;
return getMap(mode, cmd, user) || getMap(mode, cmd, main);
},
/**
* Returns the default map from <b>mode</b> named <b>cmd</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The map name to match.
* @returns {Map}
*/
getDefault: function (mode, cmd)
{
mode = mode || modes.NORMAL;
return getMap(mode, cmd, main);
},
/**
* Returns an array of maps with names starting with but not equal to
* <b>prefix</b>.
*
* @param {number} mode The mode to search.
* @param {string} prefix The map prefix string to match.
* @returns {Map[]}
*/
getCandidates: function (mode, prefix)
{
let mappings = user[mode].concat(main[mode]);
let matches = [];
for (let [, map] in Iterator(mappings))
{
for (let [, name] in Iterator(map.names))
{
if (name.indexOf(prefix) == 0 && name.length > prefix.length)
{
// for < only return a candidate if it doesn't look like a <c-x> mapping
if (prefix != "<" || !/^<.+>/.test(name))
matches.push(map);
}
}
}
return matches;
},
/*
* Returns the map leader string used to replace the special token
* "<Leader>" when user mappings are defined.
*
* @returns {string}
*/
// FIXME: property
getMapLeader: function ()
{
let leaderRef = liberator.variableReference("mapleader");
return leaderRef[0] ? leaderRef[0][leaderRef[1]] : "\\";
},
/**
* Returns whether there is a user-defined mapping <b>cmd</b> for the
* specified <b>mode</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The candidate key mapping.
* @returns {boolean}
*/
hasMap: function (mode, cmd) user[mode].some(function (map) map.hasName(cmd)),
/**
* Remove the user-defined mapping named <b>cmd</b> for <b>mode</b>.
*
* @param {number} mode The mode to search.
* @param {string} cmd The map name to match.
*/
remove: function (mode, cmd)
{
removeMap(mode, cmd);
},
/**
* Remove all user-defined mappings for <b>mode</b>.
*
* @param {number} mode The mode to remove all mappings from.
*/
removeAll: function (mode)
{
user[mode] = [];
},
/**
* Lists all user-defined mappings matching <b>filter</b> for the
* specified <b>modes</b>.
*
* @param {number[]} modes An array of modes to search.
* @param {string} filter The filter string to match.
*/
list: function (modes, filter)
{
let modeSign = "";
// TODO: Vim hides "nv" in a :map and "v" and "n" in :vmap and
// :nmap respectively if the map is not exclusive.
modes.forEach(function (mode) {
for (let m in modules.modes.mainModes)
if (mode == m.mask && modeSign.indexOf(m.char) == -1)
modeSign += m.char;
});
let maps = mappingsIterator(modes, user);
if (filter)
maps = [map for (map in maps) if (map.names[0] == filter)];
let list = <table>
{
template.map(maps, function (map)
template.map(map.names, function (name)
<tr>
<td>{modeSign} {name}</td>
<td>{map.noremap ? "*" : " "}</td>
<td>{map.rhs || "function () { ... }"}</td>
</tr>))
}
</table>;
// TODO: Move this to an ItemList to show this automatically
if (list.*.length() == list.text().length())
{
liberator.echomsg("No mapping found");
return;
}
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
};
//}}}
} //}}}
});
// vim: set fdm=marker sw=4 ts=4 et:

343
common/content/marks.js Normal file
View File

@@ -0,0 +1,343 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
/**
* @scope modules
* @instance marks
*/
const Marks = Module("marks", {
requires: ["storage"],
init: function init() {
this._localMarks = storage.newMap("local-marks", { store: true, privateData: true });
this._urlMarks = storage.newMap("url-marks", { store: true, privateData: true });
this._pendingJumps = [];
var appContent = document.getElementById("appcontent");
if (appContent)
appContent.addEventListener("load", this.closure._onPageLoad, true);
},
/**
* @property {Array} Returns all marks, both local and URL, in a sorted
* array.
*/
get all() {
// local marks
let location = window.content.location.href;
let lmarks = [i for (i in this._localMarkIter()) if (i[1].location == location)];
lmarks.sort();
// URL marks
// FIXME: why does umarks.sort() cause a "Component is not available =
// NS_ERROR_NOT_AVAILABLE" exception when used here?
let umarks = [i for (i in this._urlMarks)];
umarks.sort(function (a, b) a[0].localeCompare(b[0]));
return lmarks.concat(umarks);
},
/**
* Add a named mark for the current buffer, at its current position.
* If mark matches [A-Z], it's considered a URL mark, and will jump to
* the same position at the same URL no matter what buffer it's
* selected from. If it matches [a-z'"], it's a local mark, and can
* only be recalled from a buffer with a matching URL.
*
* @param {string} mark The mark name.
* @param {boolean} silent Whether to output error messages.
*/
// TODO: add support for frameset pages
add: function (mark, silent) {
let win = window.content;
let doc = win.document;
if (!doc.body)
return;
if (doc.body instanceof HTMLFrameSetElement) {
if (!silent)
liberator.echoerr("Marks support for frameset pages not implemented yet");
return;
}
let x = win.scrollMaxX ? win.pageXOffset / win.scrollMaxX : 0;
let y = win.scrollMaxY ? win.pageYOffset / win.scrollMaxY : 0;
let position = { x: x, y: y };
if (Marks.isURLMark(mark)) {
this._urlMarks.set(mark, { location: win.location.href, position: position, tab: tabs.getTab() });
if (!silent)
liberator.log("Adding URL mark: " + Marks.markToString(mark, this._urlMarks.get(mark)), 5);
}
else if (Marks.isLocalMark(mark)) {
// remove any previous mark of the same name for this location
this._removeLocalMark(mark);
if (!this._localMarks.get(mark))
this._localMarks.set(mark, []);
let vals = { location: win.location.href, position: position };
this._localMarks.get(mark).push(vals);
if (!silent)
liberator.log("Adding local mark: " + Marks.markToString(mark, vals), 5);
}
},
/**
* Remove all marks matching <b>filter</b>. If <b>special</b> is
* given, removes all local marks.
*
* @param {string} filter A string containing one character for each
* mark to be removed.
* @param {boolean} special Whether to delete all local marks.
*/
// FIXME: Shouldn't special be replaced with a null filter?
remove: function (filter, special) {
if (special) {
// :delmarks! only deletes a-z marks
for (let [mark, ] in this._localMarks)
this._removeLocalMark(mark);
}
else {
for (let [mark, ] in this._urlMarks) {
if (filter.indexOf(mark) >= 0)
this._removeURLMark(mark);
}
for (let [mark, ] in this._localMarks) {
if (filter.indexOf(mark) >= 0)
this._removeLocalMark(mark);
}
}
},
/**
* Jumps to the named mark. See {@link #add}
*
* @param {string} mark The mark to jump to.
*/
jumpTo: function (mark) {
let ok = false;
if (Marks.isURLMark(mark)) {
let slice = this._urlMarks.get(mark);
if (slice && slice.tab && slice.tab.linkedBrowser) {
if (slice.tab.parentNode != getBrowser().tabContainer) {
this._pendingJumps.push(slice);
// NOTE: this obviously won't work on generated pages using
// non-unique URLs :(
liberator.open(slice.location, liberator.NEW_TAB);
return;
}
let index = tabs.index(slice.tab);
if (index != -1) {
tabs.select(index);
let win = slice.tab.linkedBrowser.contentWindow;
if (win.location.href != slice.location) {
this._pendingJumps.push(slice);
win.location.href = slice.location;
return;
}
liberator.log("Jumping to URL mark: " + Marks.markToString(mark, slice), 5);
buffer.scrollToPercent(slice.position.x * 100, slice.position.y * 100);
ok = true;
}
}
}
else if (Marks.isLocalMark(mark)) {
let win = window.content;
let slice = this._localMarks.get(mark) || [];
for (let [, lmark] in Iterator(slice)) {
if (win.location.href == lmark.location) {
liberator.log("Jumping to local mark: " + Marks.markToString(mark, lmark), 5);
buffer.scrollToPercent(lmark.position.x * 100, lmark.position.y * 100);
ok = true;
break;
}
}
}
if (!ok)
liberator.echoerr("E20: Mark not set");
},
/**
* List all marks matching <b>filter</b>.
*
* @param {string} filter
*/
list: function (filter) {
let marks = this.all;
liberator.assert(marks.length > 0, "No marks set");
if (filter.length > 0) {
marks = marks.filter(function (mark) filter.indexOf(mark[0]) >= 0);
liberator.assert(marks.length > 0, "E283: No marks matching " + filter.quote());
}
let list = template.tabular(
["Mark", "Line", "Column", "File"],
["", "text-align: right", "text-align: right", "color: green"],
([mark[0],
Math.round(mark[1].position.x * 100) + "%",
Math.round(mark[1].position.y * 100) + "%",
mark[1].location]
for ([, mark] in Iterator(marks))));
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
},
_onPageLoad: function _onPageLoad(event) {
let win = event.originalTarget.defaultView;
for (let [i, mark] in Iterator(this._pendingJumps)) {
if (win && win.location.href == mark.location) {
buffer.scrollToPercent(mark.position.x * 100, mark.position.y * 100);
this._pendingJumps.splice(i, 1);
return;
}
}
},
_removeLocalMark: function _removeLocalMark(mark) {
let localmark = this._localMarks.get(mark);
if (localmark) {
let win = window.content;
for (let [i, ] in Iterator(localmark)) {
if (localmark[i].location == win.location.href) {
liberator.log("Deleting local mark: " + Marks.markToString(mark, localmark[i]), 5);
localmark.splice(i, 1);
if (localmark.length == 0)
this._localMarks.remove(mark);
break;
}
}
}
},
_removeURLMark: function _removeURLMark(mark) {
let urlmark = this._urlMarks.get(mark);
if (urlmark) {
liberator.log("Deleting URL mark: " + Marks.markToString(mark, urlmark), 5);
this._urlMarks.remove(mark);
}
},
_localMarkIter: function _localMarkIter() {
for (let [mark, value] in this._localMarks)
for (let [, val] in Iterator(value))
yield [mark, val];
},
}, {
markToString: function markToString(name, mark) {
return name + ", " + mark.location +
", (" + Math.round(mark.position.x * 100) +
"%, " + Math.round(mark.position.y * 100) + "%)" +
(("tab" in mark) ? ", tab: " + tabs.index(mark.tab) : "");
},
isLocalMark: function isLocalMark(mark) /^['`a-z]$/.test(mark),
isURLMark: function isURLMark(mark) /^[A-Z0-9]$/.test(mark),
}, {
mappings: function () {
var myModes = config.browserModes;
mappings.add(myModes,
["m"], "Set mark at the cursor position",
function (arg) {
if (/[^a-zA-Z]/.test(arg))
return void liberator.beep();
marks.add(arg);
},
{ arg: true });
mappings.add(myModes,
["'", "`"], "Jump to the mark in the current buffer",
function (arg) { marks.jumpTo(arg); },
{ arg: true });
},
commands: function () {
commands.add(["delm[arks]"],
"Delete the specified marks",
function (args) {
let special = args.bang;
args = args.string;
// assert(special ^ args)
liberator.assert( special || args, "E471: Argument required");
liberator.assert(!special || !args, "E474: Invalid argument");
let matches;
if (matches = args.match(/(?:(?:^|[^a-zA-Z0-9])-|-(?:$|[^a-zA-Z0-9])|[^a-zA-Z0-9 -]).*/)) {
// NOTE: this currently differs from Vim's behavior which
// deletes any valid marks in the arg list, up to the first
// invalid arg, as well as giving the error message.
liberator.echoerr("E475: Invalid argument: " + matches[0]);
return;
}
// check for illegal ranges - only allow a-z A-Z 0-9
if (matches = args.match(/[a-zA-Z0-9]-[a-zA-Z0-9]/g)) {
for (let i = 0; i < matches.length; i++) {
let start = matches[i][0];
let end = matches[i][2];
if (/[a-z]/.test(start) != /[a-z]/.test(end) ||
/[A-Z]/.test(start) != /[A-Z]/.test(end) ||
/[0-9]/.test(start) != /[0-9]/.test(end) ||
start > end)
{
liberator.echoerr("E475: Invalid argument: " + args.match(matches[i] + ".*")[0]);
return;
}
}
}
marks.remove(args, special);
},
{
bang: true,
completer: function (context) completion.mark(context)
});
commands.add(["ma[rk]"],
"Mark current location within the web page",
function (args) {
let mark = args[0];
liberator.assert(mark.length <= 1, "E488: Trailing characters");
liberator.assert(/[a-zA-Z]/.test(mark),
"E191: Argument must be a letter or forward/backward quote");
marks.add(mark);
},
{ argCount: "1" });
commands.add(["marks"],
"Show all location marks of current web page",
function (args) {
args = args.string;
// ignore invalid mark characters unless there are no valid mark chars
liberator.assert(!args || /[a-zA-Z]/.test(args),
"E283: No marks matching " + args.quote());
let filter = args.replace(/[^a-zA-Z]/g, "");
marks.list(filter);
});
},
completion: function () {
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;
};
},
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -5,29 +5,55 @@
/** @scope modules */
const modes = (function () //{{{
{
////////////////////////////////////////////////////////////////////////////////
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const Modes = Module("modes", {
requires: ["util"],
var main = 1; // NORMAL
var extended = 0; // NONE
init: function () {
this._main = 1; // NORMAL
this._extended = 0; // NONE
var lastShown = null;
this._lastShown = null;
var passNextKey = false;
var passAllKeys = false;
var isRecording = false;
var isReplaying = false; // playing a macro
this._passNextKey = false;
this._passAllKeys = false;
this._isRecording = false;
this._isReplaying = false; // playing a macro
var modeStack = [];
this._modeStack = [];
function getModeMessage()
{
if (passNextKey && !passAllKeys)
this._mainModes = [self.NONE];
this._lastMode = 0;
this._modeMap = {};
// main modes, only one should ever be active
this.addMode("NORMAL", { char: "n", display: -1 });
this.addMode("INSERT", { char: "i", input: true });
this.addMode("VISUAL", { char: "v", display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : "") });
this.addMode("COMMAND_LINE", { char: "c", input: true });
this.addMode("CARET"); // text cursor is visible
this.addMode("TEXTAREA", { char: "i" });
this.addMode("EMBED", { input: true });
this.addMode("CUSTOM", { display: function () plugins.mode });
// this._extended modes, can include multiple modes, and even main modes
this.addMode("EX", true);
this.addMode("HINTS", true);
this.addMode("INPUT_MULTILINE", true);
this.addMode("OUTPUT_MULTILINE", true);
this.addMode("SEARCH_FORWARD", true);
this.addMode("SEARCH_BACKWARD", true);
this.addMode("SEARCH_VIEW_FORWARD", true);
this.addMode("SEARCH_VIEW_BACKWARD", true);
this.addMode("MENU", true); // a popupmenu is active
this.addMode("LINE", true); // linewise visual mode
this.addMode("PROMPT", true);
config.modes.forEach(function (mode) { this.addMode.apply(this, mode); }, this);
},
_getModeMessage: function () {
if (this._passNextKey && !this._passAllKeys)
return "-- PASS THROUGH (next) --";
else if (passAllKeys && !passNextKey)
else if (this._passAllKeys && !this._passNextKey)
return "-- PASS THROUGH --";
// when recording a macro
@@ -38,34 +64,30 @@ const modes = (function () //{{{
macromode = "replaying";
let ext = "";
if (extended & modes.MENU) // TODO: desirable?
if (this._extended & modes.MENU) // TODO: desirable?
ext += " (menu)";
ext += " --" + macromode;
if (main in modeMap && typeof modeMap[main].display == "function")
return "-- " + modeMap[main].display() + ext;
if (this._main in this._modeMap && typeof this._modeMap[this._main].display == "function")
return "-- " + this._modeMap[this._main].display() + ext;
return macromode;
}
},
// NOTE: Pay attention that you don't run into endless loops
// Usually you should only indicate to leave a special mode like HINTS
// by calling modes.reset() and adding the stuff which is needed
// for its cleanup here
function handleModeChange(oldMode, newMode, oldExtended)
{
_handleModeChange: function (oldMode, newMode, oldExtended) {
switch (oldMode)
{
switch (oldMode) {
case modes.TEXTAREA:
case modes.INSERT:
editor.unselectText();
break;
case modes.VISUAL:
if (newMode == modes.CARET)
{
try
{ // clear any selection made; a simple if (selection) does not work
if (newMode == modes.CARET) {
try { // clear any selection made; a simple if (selection) does not work
let selection = window.content.getSelection();
selection.collapseToStart();
}
@@ -87,8 +109,7 @@ const modes = (function () //{{{
break;
}
if (newMode == modes.NORMAL)
{
if (newMode == modes.NORMAL) {
// disable caret mode when we want to switch to normal mode
if (options.getPref("accessibility.browsewithcaret"))
options.setPref("accessibility.browsewithcaret", false);
@@ -96,34 +117,26 @@ const modes = (function () //{{{
statusline.updateUrl();
liberator.focusContent(true);
}
}
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const self = {
},
NONE: 0,
__iterator__: function () util.Array.itervalues(this.all),
get all() mainModes.slice(),
get all() this._mainModes.slice(),
get mainModes() (mode for ([k, mode] in Iterator(modeMap)) if (!mode.extended && mode.name == k)),
get mainModes() (mode for ([k, mode] in Iterator(modes._modeMap)) if (!mode.extended && mode.name == k)),
get mainMode() modeMap[main],
get mainMode() this._modeMap[this._main],
addMode: function (name, extended, options)
{
addMode: function (name, extended, options) {
let disp = name.replace("_", " ", "g");
this[name] = 1 << lastMode++;
if (typeof extended == "object")
{
this[name] = 1 << this._lastMode++;
if (typeof extended == "object") {
options = extended;
extended = false;
}
modeMap[name] = modeMap[this[name]] = util.extend({
this._modeMap[name] = this._modeMap[this[name]] = util.extend({
extended: extended,
count: true,
input: false,
@@ -131,65 +144,59 @@ const modes = (function () //{{{
name: name,
disp: disp
}, options);
modeMap[name].display = modeMap[name].display || function () disp;
this._modeMap[name].display = this._modeMap[name].display || function () disp;
if (!extended)
mainModes.push(this[name]);
this._mainModes.push(this[name]);
if ("mappings" in modules)
mappings.addMode(this[name]);
},
getMode: function (name) modeMap[name],
getMode: function (name) this._modeMap[name],
// show the current mode string in the command line
show: function ()
{
show: function () {
let msg = "";
if (options["showmode"])
msg = getModeMessage();
msg = this._getModeMessage();
commandline.echo(msg, "ModeMsg", commandline.FORCE_SINGLELINE);
},
// add/remove always work on the extended mode only
add: function (mode)
{
extended |= mode;
// add/remove always work on the this._extended mode only
add: function (mode) {
this._extended |= mode;
this.show();
},
// helper function to set both modes in one go
// if silent == true, you also need to take care of the mode handling changes yourself
set: function (mainMode, extendedMode, silent, stack)
{
silent = (silent || main == mainMode && extended == extendedMode);
// if a main mode is set, the extended is always cleared
let oldMain = main, oldExtended = extended;
set: function (mainMode, extendedMode, silent, stack) {
silent = (silent || this._main == mainMode && this._extended == extendedMode);
// if a this._main mode is set, the this._extended is always cleared
let oldMain = this._main, oldExtended = this._extended;
if (typeof extendedMode === "number")
extended = extendedMode;
if (typeof mainMode === "number")
{
main = mainMode;
this._extended = extendedMode;
if (typeof mainMode === "number") {
this._main = mainMode;
if (!extendedMode)
extended = modes.NONE;
this._extended = modes.NONE;
if (main != oldMain)
handleModeChange(oldMain, mainMode, oldExtended);
if (this._main != oldMain)
this._handleModeChange(oldMain, mainMode, oldExtended);
}
liberator.triggerObserver("modeChange", [oldMain, oldExtended], [main, extended], stack);
liberator.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack);
if (!silent)
this.show();
},
push: function (mainMode, extendedMode, silent)
{
modeStack.push([main, extended]);
this.set(mainMode, extendedMode, silent, { push: modeStack[modeStack.length - 1] });
push: function (mainMode, extendedMode, silent) {
this._modeStack.push([this._main, this._extended]);
this.set(mainMode, extendedMode, silent, { push: this._modeStack[this._modeStack.length - 1] });
},
pop: function (silent)
{
let a = modeStack.pop();
pop: function (silent) {
let a = this._modeStack.pop();
if (a)
this.set(a[0], a[1], silent, { pop: a });
else
@@ -198,8 +205,7 @@ const modes = (function () //{{{
// TODO: Deprecate this in favor of addMode? --Kris
// Ya --djk
setCustomMode: function (modestr, oneventfunc, stopfunc)
{
setCustomMode: function (modestr, oneventfunc, stopfunc) {
// TODO this.plugin[id]... ('id' maybe submode or what..)
plugins.mode = modestr;
plugins.onEvent = oneventfunc;
@@ -207,74 +213,38 @@ const modes = (function () //{{{
},
// keeps recording state
reset: function (silent)
{
modeStack = [];
reset: function (silent) {
this._modeStack = [];
if (config.isComposeWindow)
this.set(modes.COMPOSE, modes.NONE, silent);
else
this.set(modes.NORMAL, modes.NONE, silent);
},
remove: function (mode)
{
if (extended & mode)
{
extended &= ~mode;
remove: function (mode) {
if (this._extended & mode) {
this._extended &= ~mode;
this.show();
}
},
get passNextKey() passNextKey,
set passNextKey(value) { passNextKey = value; this.show(); },
get passNextKey() this._passNextKey,
set passNextKey(value) { this._passNextKey = value; this.show(); },
get passAllKeys() passAllKeys,
set passAllKeys(value) { passAllKeys = value; this.show(); },
get passAllKeys() this._passAllKeys,
set passAllKeys(value) { this._passAllKeys = value; this.show(); },
get isRecording() isRecording,
set isRecording(value) { isRecording = value; this.show(); },
get isRecording() this._isRecording,
set isRecording(value) { this._isRecording = value; this.show(); },
get isReplaying() isReplaying,
set isReplaying(value) { isReplaying = value; this.show(); },
get isReplaying() this._isReplaying,
set isReplaying(value) { this._isReplaying = value; this.show(); },
get main() main,
get main() this._main,
set main(value) { this.set(value); },
get extended() extended,
get extended() this._extended,
set extended(value) { this.set(null, value); }
};
var mainModes = [self.NONE];
var lastMode = 0;
var modeMap = {};
// main modes, only one should ever be active
self.addMode("NORMAL", { char: "n", display: -1 });
self.addMode("INSERT", { char: "i", input: true });
self.addMode("VISUAL", { char: "v", display: function () "VISUAL" + (extended & modes.LINE ? " LINE" : "") });
self.addMode("COMMAND_LINE", { char: "c", input: true });
self.addMode("CARET"); // text cursor is visible
self.addMode("TEXTAREA", { char: "i" });
self.addMode("EMBED", { input: true });
self.addMode("CUSTOM", { display: function () plugins.mode });
// extended modes, can include multiple modes, and even main modes
self.addMode("EX", true);
self.addMode("HINTS", true);
self.addMode("INPUT_MULTILINE", true);
self.addMode("OUTPUT_MULTILINE", true);
self.addMode("SEARCH_FORWARD", true);
self.addMode("SEARCH_BACKWARD", true);
self.addMode("SEARCH_VIEW_FORWARD", true);
self.addMode("SEARCH_VIEW_BACKWARD", true);
self.addMode("MENU", true); // a popupmenu is active
self.addMode("LINE", true); // linewise visual mode
self.addMode("PROMPT", true);
config.modes.forEach(function (mode) { self.addMode.apply(self, mode); });
return self;
//}}}
})(); //}}}
});
// vim: set fdm=marker sw=4 ts=4 et:

73
common/content/modules.js Normal file
View File

@@ -0,0 +1,73 @@
const ModuleBase = Class("ModuleBase", { requires: [] });
function Module(name, inst, clas, moduleInit) {
const module = Class(name, ModuleBase, inst, clas);
module.INIT = moduleInit || {};
module.requires = inst.requires || [];
Module.list.push(module);
Module.constructors[name] = module;
return module;
}
Module.list = [];
Module.constructors = {};
window.addEventListener("load", function () {
function dump(str) window.dump(String.replace(str, /\n?$/, "\n").replace(/^/m, config.name.toLowerCase() + ": "));
const start = Date.now();
const deferredInit = { load: [] };
const seen = set();
function load(module, prereq) {
try {
if (module.name in modules)
return;
if (module.name in seen)
throw Error("Module dependency loop.");
set.add(seen, module.name);
for (let dep in values(module.requires))
load(Module.constructors[dep], module.name);
dump("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name);
modules[module.name] = module();
function init(mod, module)
function () module.INIT[mod].call(modules[module.name], modules[mod]);
for (let [mod, ] in iter(module.INIT))
try {
if (mod in modules)
init(mod, module)();
else {
deferredInit[mod] = deferredInit[mod] || [];
deferredInit[mod].push(init(mod, module));
}
}
catch(e) {
if (modules.liberator)
liberator.reportError(e);
}
for (let [, fn] in iter(deferredInit[module.name] || []))
fn();
}
catch (e) {
dump("Loading " + (module && module.name) + ": " + e);
if (e.stack)
dump(e.stack);
}
}
Module.list.forEach(load);
deferredInit['load'].forEach(call)
for (let module in values(Module.list))
delete module.INIT;
dump("Loaded in " + (Date.now() - start) + "ms\n");
}, false);
window.addEventListener("unload", function () {
for (let [, mod] in iter(modules))
if (mod instanceof ModuleBase && 'destroy' in mod)
mod.destroy();
}, false);
// vim: set fdm=marker sw=4 ts=4 et:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,173 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
/** @scope modules */
/**
* @instance quickmarks
*/
const QuickMarks = Module("quickmarks", {
requires: ["storage"],
init: function () {
this._qmarks = storage.newMap("quickmarks", { store: true });
},
/**
* Adds a new quickmark with name <b>qmark</b> referencing
* the URL <b>location</b>. Any existing quickmark with the same name
* will be replaced.
*
* @param {string} qmark The name of the quickmark {A-Z}.
* @param {string} location The URL accessed by this quickmark.
*/
add: function add(qmark, location) {
this._qmarks.set(qmark, location);
liberator.echomsg("Added Quick Mark '" + qmark + "': " + location, 1);
},
/**
* Deletes the specified quickmarks. The <b>filter</b> is a list of
* quickmarks and ranges are supported. Eg. "ab c d e-k".
*
* @param {string} filter The list of quickmarks to delete.
*
*/
remove: function remove(filter) {
let pattern = RegExp("[" + filter.replace(/\s+/g, "") + "]");
for (let [qmark, ] in this._qmarks) {
if (pattern.test(qmark))
this._qmarks.remove(qmark);
}
},
/**
* Removes all quickmarks.
*/
removeAll: function removeAll() {
this._qmarks.clear();
},
/**
* Opens the URL referenced by the specified <b>qmark</b>.
*
* @param {string} qmark The quickmark to open.
* @param {number} where A constant describing where to open the page.
* See {@link Liberator#open}.
*/
jumpTo: function jumpTo(qmark, where) {
let url = this._qmarks.get(qmark);
if (url)
liberator.open(url, where);
else
liberator.echoerr("E20: QuickMark not set");
},
/**
* Lists all quickmarks matching <b>filter</b> in the message window.
*
* @param {string} filter The list of quickmarks to display. Eg. "abc"
* Ranges are not supported.
*/
// FIXME: filter should match that of quickmarks.remove or vice versa
list: function list(filter) {
let marks = [k for ([k, v] in this._qmarks)];
let lowercaseMarks = marks.filter(function (x) /[a-z]/.test(x)).sort();
let uppercaseMarks = marks.filter(function (x) /[A-Z]/.test(x)).sort();
let numberMarks = marks.filter(function (x) /[0-9]/.test(x)).sort();
marks = Array.concat(lowercaseMarks, uppercaseMarks, numberMarks);
liberator.assert(marks.length > 0, "No QuickMarks set");
if (filter.length > 0) {
marks = marks.filter(function (qmark) filter.indexOf(qmark) >= 0);
liberator.assert(marks.length >= 0, "E283: No QuickMarks matching \"" + filter + "\"");
}
let items = [[mark, this._qmarks.get(mark)] for ([k, mark] in Iterator(marks))];
template.genericTable(items, { title: ["QuickMark", "URL"] });
}
}, {
}, {
commands: function () {
commands.add(["delqm[arks]"],
"Delete the specified QuickMarks",
function (args) {
// TODO: finish arg parsing - we really need a proper way to do this. :)
// assert(args.bang ^ args.string)
liberator.assert( args.bang || args.string, "E471: Argument required");
liberator.assert(!args.bang || !args.string, "E474: Invalid argument");
if (args.bang)
quickmarks.removeAll();
else
quickmarks.remove(args.string);
},
{
bang: true,
completer: function (context) {
context.title = ["QuickMark", "URL"];
context.completions = this._qmarks;
}
});
commands.add(["qma[rk]"],
"Mark a URL with a letter for quick access",
function (args) {
let matches = args.string.match(/^([a-zA-Z0-9])(?:\s+(.+))?$/);
if (!matches)
liberator.echoerr("E488: Trailing characters");
else if (!matches[2])
quickmarks.add(matches[1], buffer.URL);
else
quickmarks.add(matches[1], matches[2]);
},
{ argCount: "+" });
commands.add(["qmarks"],
"Show all QuickMarks",
function (args) {
args = args.string;
// ignore invalid qmark characters unless there are no valid qmark chars
liberator.assert(!args || /[a-zA-Z0-9]/.test(args), "E283: No QuickMarks matching \"" + args + "\"");
let filter = args.replace(/[^a-zA-Z0-9]/g, "");
quickmarks.list(filter);
});
},
mappings: function () {
var myModes = config.browserModes;
mappings.add(myModes,
["go"], "Jump to a QuickMark",
function (arg) { quickmarks.jumpTo(arg, liberator.CURRENT_TAB); },
{ arg: true });
mappings.add(myModes,
["gn"], "Jump to a QuickMark in a new tab",
function (arg) {
quickmarks.jumpTo(arg,
/\bquickmark\b/.test(options["activate"]) ?
liberator.NEW_TAB : liberator.NEW_BACKGROUND_TAB);
},
{ arg: true });
mappings.add(myModes,
["M"], "Add new QuickMark for current URL",
function (arg) {
if (/[^a-zA-Z0-9]/.test(arg))
return void liberator.beep();
quickmarks.add(arg, buffer.URL);
},
{ arg: true });
},
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -13,100 +13,88 @@
// FIXME:
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
function Sanitizer() //{{{
{
////////////////////////////////////////////////////////////////////////////////
////////////////////// PRIVATE SECTION /////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const Sanitizer = Module("sanitizer", {
requires: ["liberator"],
const local = {}; // XXX: is there some reason liberator.loadModule doesn't create modules with new?
services.get("subscriptLoader").loadSubScript("chrome://browser/content/sanitize.js", local);
const Sanitizer = local.Sanitizer;
init: function () {
const self = this;
liberator.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
this.__proto__.__proto__ = new Sanitizer.Sanitizer; // Good enough.
var prefArgList = [["commandLine", "commandline"],
// TODO: remove this version test
if (/^1.9.1/.test(services.get("xulAppInfo").platformVersion))
self.prefDomain = "privacy.cpd.";
else
self.prefDomain = "privacy.item.";
self.prefDomain2 = "extensions.liberator.privacy.cpd.";
},
// Largely ripped from from browser/base/content/sanitize.js so we can override
// the pref strategy without stepping on the global prefs namespace.
sanitize: function () {
const prefService = services.get("pref");
let branch = prefService.getBranch(this.prefDomain);
let branch2 = prefService.getBranch(this.prefDomain2);
let errors = null;
function prefSet(name) {
try {
return branch.getBoolPref(name);
}
catch (e) {
return branch2.getBoolPref(name);
}
}
// Cache the range of times to clear
if (this.ignoreTimespan)
var range = null; // If we ignore timespan, clear everything
else
range = this.range || Sanitizer.getClearRange();
for (let itemName in this.items) {
let item = this.items[itemName];
item.range = range;
if ("clear" in item && item.canClear && prefSet(itemName)) {
liberator.log("Sanitizing " + itemName + " items...");
// Some of these clear() may raise exceptions (see bug #265028)
// to sanitize as much as possible, we catch and store them,
// rather than fail fast.
// Callers should check returned errors and give user feedback
// about items that could not be sanitized
try {
item.clear();
}
catch (e) {
if (!errors)
errors = {};
errors[itemName] = e;
dump("Error sanitizing " + itemName + ": " + e + "\n");
}
}
}
return errors;
},
get prefNames() util.Array.flatten([this.prefDomain, this.prefDomain2].map(options.allPrefs)),
}, {
prefArgList: [["commandLine", "commandline"],
["offlineApps", "offlineapps"],
["siteSettings", "sitesettings"]];
function prefToArg(pref)
{
["siteSettings", "sitesettings"]],
prefToArg: function (pref) {
let pref = pref.replace(/.*\./, "");
return util.Array.toObject(prefArgList)[pref] || pref;
}
function argToPref(arg) [k for ([, [k, v]] in Iterator(prefArgList)) if (v == arg)][0] || arg
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// OPTIONS /////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
options.add(["sanitizeitems", "si"],
"The default list of private items to sanitize",
"stringlist", "cache,commandline,cookies,formdata,history,marks,sessions",
{
setter: function (values)
{
for (let [, pref] in Iterator(sanitizer.prefNames))
{
options.setPref(pref, false);
for (let [, value] in Iterator(this.parseValues(values)))
{
if (prefToArg(pref) == value)
{
options.setPref(pref, true);
break;
}
}
}
return values;
return util.Array.toObject(Sanitizer.prefArgList)[pref] || pref;
},
getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(prefToArg).join(","),
completer: function (value) [
["cache", "Cache"],
["commandline", "Command-line history"],
["cookies", "Cookies"],
["downloads", "Download history"],
["formdata", "Saved form and search history"],
["history", "Browsing history"],
["macros", "Saved macros"],
["marks", "Local and URL marks"],
["offlineapps", "Offline website data"],
["passwords", "Saved passwords"],
["sessions", "Authenticated sessions"],
["sitesettings", "Site preferences"],
],
validator: Option.validateCompleter
});
options.add(["sanitizetimespan", "sts"],
"The default sanitizer time span",
"number", 1,
{
setter: function (value)
{
options.setPref("privacy.sanitize.timeSpan", value);
return value;
},
getter: function () options.getPref("privacy.sanitize.timeSpan", this.defaultValue),
completer: function (value) [
["0", "Everything"],
["1", "Last hour"],
["2", "Last two hours"],
["3", "Last four hours"],
["4", "Today"]
],
validator: Option.validateCompleter
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
argToPref: function (arg) [k for ([k, v] in values(Sanitizer.prefArgList)) if (v == arg)][0] || arg,
}, {
commands: function () {
commands.add(["sa[nitize]"],
"Clear private data",
function (args)
{
function (args) {
if (options['private'])
return void liberator.echomsg("Cannot sanitize items in private mode");
@@ -115,37 +103,30 @@ function Sanitizer() //{{{
sanitizer.range = Sanitizer.getClearRange(timespan);
sanitizer.ignoreTimespan = !sanitizer.range;
if (args.bang)
{
if (args.bang) {
liberator.assert(args.length == 0, "E488: Trailing characters");
liberator.log("Sanitizing all items in 'sanitizeitems'...");
let errors = sanitizer.sanitize();
if (errors)
{
if (errors) {
for (let item in errors)
liberator.echoerr("Error sanitizing " + item + ": " + errors[item]);
}
}
else
{
else {
liberator.assert(args.length > 0, "E471: Argument required");
for (let [, item] in Iterator(args.map(argToPref)))
{
for (let [, item] in Iterator(args.map(Sanitizer.argToPref))) {
liberator.log("Sanitizing " + item + " items...");
if (sanitizer.canClearItem(item))
{
try
{
if (sanitizer.canClearItem(item)) {
try {
sanitizer.items[item].range = sanitizer.range;
sanitizer.clearItem(item);
}
catch (e)
{
catch (e) {
liberator.echoerr("Error sanitizing " + item + ": " + e);
}
}
@@ -168,31 +149,18 @@ function Sanitizer() //{{{
function () options.get("sanitizetimespan").completer()]
]
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
var self = new Sanitizer();
// TODO: remove this version test
if (/^1.9.1/.test(services.get("xulAppInfo").platformVersion))
self.prefDomain = "privacy.cpd.";
else
self.prefDomain = "privacy.item.";
self.prefDomain2 = "extensions.liberator.privacy.cpd.";
},
options: function () {
const self = this;
// add liberator-specific private items
[
{
name: "commandLine",
action: function ()
{
action: function () {
let stores = ["command", "search"];
if (self.range)
{
if (self.range) {
stores.forEach(function (store) {
storage["history-" + store].mutate("filter", function (item) {
let timestamp = item.timestamp * 1000;
@@ -210,8 +178,7 @@ function Sanitizer() //{{{
},
{
name: "marks",
action: function ()
{
action: function () {
storage["local-marks"].clear();
storage["url-marks"].clear();
}
@@ -229,12 +196,10 @@ function Sanitizer() //{{{
});
// call Sanitize autocommand
for (let [name, item] in Iterator(self.items))
{
let arg = prefToArg(name);
for (let [name, item] in Iterator(self.items)) {
let arg = Sanitizer.prefToArg(name);
if (item.clear)
{
if (item.clear) {
let func = item.clear;
item.clear = function () {
autocommands.trigger("Sanitize", { name: arg })
@@ -243,70 +208,62 @@ function Sanitizer() //{{{
}
}
self.getClearRange = Sanitizer.getClearRange;
options.add(["sanitizeitems", "si"],
"The default list of private items to sanitize",
"stringlist", "cache,commandline,cookies,formdata,history,marks,sessions",
{
setter: function (values) {
for (let [, pref] in Iterator(sanitizer.prefNames)) {
continue;
options.setPref(pref, false);
// Largely ripped from from browser/base/content/sanitize.js so we can override
// the pref strategy without stepping on the global prefs namespace.
self.sanitize = function () {
const prefService = services.get("pref");
let branch = prefService.getBranch(this.prefDomain);
let branch2 = prefService.getBranch(this.prefDomain2);
let errors = null;
function prefSet(name)
{
try
{
return branch.getBoolPref(name);
}
catch (e)
{
return branch2.getBoolPref(name);
}
}
// Cache the range of times to clear
if (this.ignoreTimespan)
var range = null; // If we ignore timespan, clear everything
else
range = this.range || Sanitizer.getClearRange();
for (let itemName in this.items)
{
let item = this.items[itemName];
item.range = range;
if ("clear" in item && item.canClear && prefSet(itemName))
{
liberator.log("Sanitizing " + itemName + " items...");
// Some of these clear() may raise exceptions (see bug #265028)
// to sanitize as much as possible, we catch and store them,
// rather than fail fast.
// Callers should check returned errors and give user feedback
// about items that could not be sanitized
try
{
item.clear();
}
catch (e)
{
if (!errors)
errors = {};
errors[itemName] = e;
dump("Error sanitizing " + itemName + ": " + e + "\n");
for (let [, value] in Iterator(this.parseValues(values))) {
if (Sanitizer.prefToArg(pref) == value) {
options.setPref(pref, true);
break;
}
}
}
return errors;
};
return values;
},
getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(Sanitizer.prefToArg).join(","),
completer: function (value) [
["cache", "Cache"],
["commandline", "Command-line history"],
["cookies", "Cookies"],
["downloads", "Download history"],
["formdata", "Saved form and search history"],
["history", "Browsing history"],
["macros", "Saved macros"],
["marks", "Local and URL marks"],
["offlineapps", "Offline website data"],
["passwords", "Saved passwords"],
["sessions", "Authenticated sessions"],
["sitesettings", "Site preferences"],
],
validator: Option.validateCompleter
});
self.__defineGetter__("prefNames",
function () util.Array.flatten([self.prefDomain, self.prefDomain2].map(options.allPrefs)));
//}}}
return self;
} //}}}
options.add(["sanitizetimespan", "sts"],
"The default sanitizer time span",
"number", 1,
{
setter: function (value) {
options.setPref("privacy.sanitize.timeSpan", value);
return value;
},
getter: function () options.getPref("privacy.sanitize.timeSpan", this.defaultValue),
completer: function (value) [
["0", "Everything"],
["1", "Last hour"],
["2", "Last two hours"],
["3", "Last four hours"],
["4", "Today"]
],
validator: Option.validateCompleter
});
},
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -15,15 +15,46 @@ const Cu = Components.utils;
*
* @constructor
*/
function Services()
{
const classes = {};
const services = {};
const Services = Module("services", {
init: function () {
this.classes = {};
this.services = {};
function create(classes, ifaces, meth)
{
try
{
this.add("appStartup", "@mozilla.org/toolkit/app-startup;1", Ci.nsIAppStartup);
this.add("autoCompleteSearch", "@mozilla.org/autocomplete/search;1?name=history", Ci.nsIAutoCompleteSearch);
this.add("bookmarks", "@mozilla.org/browser/nav-bookmarks-service;1", Ci.nsINavBookmarksService);
this.add("browserSearch", "@mozilla.org/browser/search-service;1", Ci.nsIBrowserSearchService);
this.add("cache", "@mozilla.org/network/cache-service;1", Ci.nsICacheService);
this.add("console", "@mozilla.org/consoleservice;1", Ci.nsIConsoleService);
this.add("liberator:", "@mozilla.org/network/protocol;1?name=liberator");
this.add("directory", "@mozilla.org/file/directory_service;1", Ci.nsIProperties);
this.add("downloadManager", "@mozilla.org/download-manager;1", Ci.nsIDownloadManager);
this.add("environment", "@mozilla.org/process/environment;1", Ci.nsIEnvironment);
this.add("extensionManager", "@mozilla.org/extensions/manager;1", Ci.nsIExtensionManager);
this.add("favicon", "@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService);
this.add("history", "@mozilla.org/browser/global-history;2", [Ci.nsIGlobalHistory3, Ci.nsINavHistoryService, Ci.nsIBrowserHistory]);
this.add("io", "@mozilla.org/network/io-service;1", Ci.nsIIOService);
this.add("json", "@mozilla.org/dom/json;1", Ci.nsIJSON, "createInstance");
this.add("livemark", "@mozilla.org/browser/livemark-service;2", Ci.nsILivemarkService);
this.add("observer", "@mozilla.org/observer-service;1", Ci.nsIObserverService);
this.add("pref", "@mozilla.org/preferences-service;1", [Ci.nsIPrefService, Ci.nsIPrefBranch, Ci.nsIPrefBranch2]);
this.add("profile", "@mozilla.org/toolkit/profile-service;1", Ci.nsIToolkitProfileService);
this.add("rdf", "@mozilla.org/rdf/rdf-service;1", Ci.nsIRDFService);
this.add("sessionStore", "@mozilla.org/browser/sessionstore;1", Ci.nsISessionStore);
this.add("subscriptLoader", "@mozilla.org/moz/jssubscript-loader;1", Ci.mozIJSSubScriptLoader);
this.add("threadManager", "@mozilla.org/thread-manager;1", Ci.nsIThreadManager);
this.add("windowMediator", "@mozilla.org/appshell/window-mediator;1", Ci.nsIWindowMediator);
this.add("windowWatcher", "@mozilla.org/embedcomp/window-watcher;1", Ci.nsIWindowWatcher);
this.add("xulAppInfo", "@mozilla.org/xre/app-info;1", Ci.nsIXULAppInfo);
this.addClass("file", "@mozilla.org/file/local;1", Ci.nsILocalFile);
this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler);
this.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind);
this.addClass("process", "@mozilla.org/process/util;1", Ci.nsIProcess);
},
_create: function (classes, ifaces, meth) {
try {
let res = Cc[classes][meth || "getService"]();
if (!ifaces)
return res.wrappedJSObject;
@@ -31,19 +62,11 @@ function Services()
ifaces.forEach(function (iface) res.QueryInterface(iface));
return res;
}
catch (e)
{
catch (e) {
// liberator.log() is not defined at this time, so just dump any error
dump("Service creation failed for '" + classes + "': " + e + "\n");
}
}
const self = {
/* @property {Object} A map of all cached services. */
get services() services,
/* @property {Object} A map of all cached classes. */
get classes() classes,
},
/**
* Adds a new XPCOM service to the cache.
@@ -55,9 +78,8 @@ function Services()
* @param {string} meth The name of the function used to instanciate
* the service.
*/
add: function (name, class, ifaces, meth)
{
return services[name] = create(class, ifaces, meth);
add: function (name, class, ifaces, meth) {
return this.services[name] = this._create(class, ifaces, meth);
},
/**
@@ -68,9 +90,9 @@ function Services()
* @param {nsISupports|nsISupports[]} ifaces The interface or array of
* interfaces implemented by this class.
*/
addClass: function (name, class, ifaces)
{
return classes[name] = function () create(class, ifaces, "createInstance");
addClass: function (name, class, ifaces) {
const self = this;
return this.classes[name] = function () self._create(class, ifaces, "createInstance");
},
/**
@@ -78,50 +100,14 @@ function Services()
*
* @param {string} name The service's cache key.
*/
get: function (name) services[name],
get: function (name) this.services[name],
/**
* Returns a new instance of the cached class with the specified name.
*
* @param {string} name The class's cache key.
*/
create: function (name) classes[name]()
};
self.add("appStartup", "@mozilla.org/toolkit/app-startup;1", Ci.nsIAppStartup);
self.add("autoCompleteSearch", "@mozilla.org/autocomplete/search;1?name=history", Ci.nsIAutoCompleteSearch);
self.add("bookmarks", "@mozilla.org/browser/nav-bookmarks-service;1", Ci.nsINavBookmarksService);
self.add("browserSearch", "@mozilla.org/browser/search-service;1", Ci.nsIBrowserSearchService);
self.add("cache", "@mozilla.org/network/cache-service;1", Ci.nsICacheService);
self.add("console", "@mozilla.org/consoleservice;1", Ci.nsIConsoleService);
self.add("directory", "@mozilla.org/file/directory_service;1", Ci.nsIProperties);
self.add("environment", "@mozilla.org/process/environment;1", Ci.nsIEnvironment);
self.add("extensionManager", "@mozilla.org/extensions/manager;1", Ci.nsIExtensionManager);
self.add("favicon", "@mozilla.org/browser/favicon-service;1", Ci.nsIFaviconService);
self.add("json", "@mozilla.org/dom/json;1", Ci.nsIJSON, "createInstance");
self.add("liberator:", "@mozilla.org/network/protocol;1?name=liberator");
self.add("livemark", "@mozilla.org/browser/livemark-service;2", Ci.nsILivemarkService);
self.add("observer", "@mozilla.org/observer-service;1", Ci.nsIObserverService);
self.add("io", "@mozilla.org/network/io-service;1", Ci.nsIIOService);
self.add("pref", "@mozilla.org/preferences-service;1", [Ci.nsIPrefService, Ci.nsIPrefBranch, Ci.nsIPrefBranch2]);
self.add("profile", "@mozilla.org/toolkit/profile-service;1", Ci.nsIToolkitProfileService);
self.add("rdf", "@mozilla.org/rdf/rdf-service;1", Ci.nsIRDFService);
self.add("sessionStore", "@mozilla.org/browser/sessionstore;1", Ci.nsISessionStore);
self.add("subscriptLoader", "@mozilla.org/moz/jssubscript-loader;1", Ci.mozIJSSubScriptLoader);
self.add("threadManager", "@mozilla.org/thread-manager;1", Ci.nsIThreadManager);
self.add("windowMediator", "@mozilla.org/appshell/window-mediator;1", Ci.nsIWindowMediator);
self.add("windowWatcher", "@mozilla.org/embedcomp/window-watcher;1", Ci.nsIWindowWatcher);
self.add("xulAppInfo", "@mozilla.org/xre/app-info;1", Ci.nsIXULAppInfo);
self.addClass("file", "@mozilla.org/file/local;1", Ci.nsILocalFile);
self.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler);
self.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind);
self.addClass("process", "@mozilla.org/process/util;1", Ci.nsIProcess);
self.addClass("zipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter);
return self;
}
var services = Services();
create: function (name) this.classes[name]()
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -0,0 +1,245 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
/** @scope modules */
const StatusLine = Module("statusline", {
init: function () {
this._statusBar = document.getElementById("status-bar");
this._statusBar.collapsed = true; // it is later restored unless the user sets laststatus=0
// our status bar fields
this._statuslineWidget = document.getElementById("liberator-statusline");
this._urlWidget = document.getElementById("liberator-statusline-field-url");
this._inputBufferWidget = document.getElementById("liberator-statusline-field-inputbuffer");
this._progressWidget = document.getElementById("liberator-statusline-field-progress");
this._tabCountWidget = document.getElementById("liberator-statusline-field-tabcount");
this._bufferPositionWidget = document.getElementById("liberator-statusline-field-bufferposition");
},
/**
* Update the status bar to indicate how secure the website is:
* extended - Secure connection with Extended Validation(EV) certificate.
* secure - Secure connection with valid certificate.
* broken - Secure connection with invalid certificate, or
* mixed content.
* insecure - Insecure connection.
*
* @param {'extended'|'secure'|'broken'|'insecure'} type
*/
setClass: function setClass(type) {
const highlightGroup = {
extended: "StatusLineExtended",
secure: "StatusLineSecure",
broken: "StatusLineBroken",
insecure: "StatusLine"
};
this._statusBar.setAttributeNS(NS.uri, "highlight", highlightGroup[type]);
},
// update all fields of the statusline
update: function update() {
this.updateUrl();
this.updateInputBuffer();
this.updateProgress();
this.updateTabCount();
this.updateBufferPosition();
},
/**
* Update the URL displayed in the status line. Also displays status
* icons, [+-♥], when there are next and previous pages in the
* current tab's history, and when the current URL is bookmarked,
* respectively.
*
* @param {string} url The URL to display.
* @default buffer.URL
*/
updateUrl: function updateUrl(url) {
// ripped from Firefox; modified
function losslessDecodeURI(url) {
// 1. decodeURI decodes %25 to %, which creates unintended
// encoding sequences.
url = url.split("%25").map(decodeURI).join("%25");
// 2. Re-encode whitespace so that it doesn't get eaten away
// by the location bar (bug 410726).
url = url.replace(/[\r\n\t]/g, encodeURIComponent);
// Encode invisible characters (soft hyphen, zero-width space, BOM,
// line and paragraph separator, word joiner, invisible times,
// invisible separator, object replacement character) (bug 452979)
url = url.replace(/[\v\x0c\x1c\x1d\x1e\x1f\u00ad\u200b\ufeff\u2028\u2029\u2060\u2062\u2063\ufffc]/g,
encodeURIComponent);
// Encode bidirectional formatting characters.
// (RFC 3987 sections 3.2 and 4.1 paragraph 6)
url = url.replace(/[\u200e\u200f\u202a\u202b\u202c\u202d\u202e]/g,
encodeURIComponent);
return url;
};
if (url == null)
// TODO: this probably needs a more general solution.
url = losslessDecodeURI(buffer.URL);
// make it even more Vim-like
if (url == "about:blank") {
if (!buffer.title)
url = "[No Name]";
}
else {
url = url.replace(RegExp("^liberator://help/(\\S+)#(.*)"), function (m, n1, n2) n1 + " " + decodeURIComponent(n2) + " [Help]")
.replace(RegExp("^liberator://help/(\\S+)"), "$1 [Help]");
}
// when session information is available, add [+] when we can go
// backwards, [-] when we can go forwards
let modified = "";
if (window.getWebNavigation) {
let sh = window.getWebNavigation().sessionHistory;
if (sh && sh.index > 0)
modified += "+";
if (sh && sh.index < sh.count -1)
modified += "-";
}
if (modules.bookmarks) {
if (bookmarks.isBookmarked(buffer.URL))
modified += "\u2764"; // a heart symbol: ❤
//modified += "\u2665"; // a heart symbol: ♥
}
if (modified)
url += " [" + modified + "]";
this._urlWidget.value = url;
},
/**
* Set the contents of the status line's input buffer to the given
* string. Used primarily when a key press requires further input
* before being processed, including mapping counts and arguments,
* along with multi-key mappings.
*
* @param {string} buffer
*/
updateInputBuffer: function updateInputBuffer(buffer) {
if (!buffer || typeof buffer != "string")
buffer = "";
this._inputBufferWidget.value = buffer;
},
/**
* Update the page load progress bar.
*
* @param {string|number} progress The current progress, as follows:
* A string - Displayed literally.
* A ratio 0 < n < 1 - Displayed as a progress bar.
* A number n <= 0 - Displayed as a "Loading" message.
* Any other number - The progress is cleared.
*/
updateProgress: function updateProgress(progress) {
if (!progress)
progress = "";
if (typeof progress == "string")
this._progressWidget.value = progress;
else if (typeof progress == "number") {
let progressStr = "";
if (progress <= 0)
progressStr = "[ Loading... ]";
else if (progress < 1) {
progress = Math.floor(progress * 20);
progressStr = "["
+ "====================".substr(0, progress)
+ ">"
+ " ".substr(0, 19 - progress)
+ "]";
}
this._progressWidget.value = progressStr;
}
},
/**
* Display the correct tabcount (e.g., [1/5]) on the status bar.
*
* @param {bool} delayed When true, update count after a
* brief timeout. Useful in the many cases when an
* event that triggers an update is broadcast before
* the tab state is fully updated.
*/
updateTabCount: function updateTabCount(delayed) {
if (liberator.has("tabs")) {
if (delayed)
return void setTimeout(function () statusline.updateTabCount(false), 0);
// update the ordinal which is used for numbered tabs
if (options.get("guioptions").has("n", "N"))
for (let [i, tab] in util.Array.iteritems(getBrowser().mTabs))
tab.setAttribute("ordinal", i + 1);
this._tabCountWidget.value = "[" + (tabs.index() + 1) + "/" + tabs.count + "]";
}
},
/**
* Display the main content's vertical scroll position in the status
* bar.
*
* @param {number} percent The position, as a percentage. @optional
*/
updateBufferPosition: function updateBufferPosition(percent) {
if (!percent || typeof percent != "number") {
let win = document.commandDispatcher.focusedWindow;
if (!win)
return;
percent = win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY;
}
let bufferPositionStr = "";
percent = Math.round(percent * 100);
if (percent < 0)
bufferPositionStr = "All";
else if (percent == 0)
bufferPositionStr = "Top";
else if (percent < 10)
bufferPositionStr = " " + percent + "%";
else if (percent >= 100)
bufferPositionStr = "Bot";
else
bufferPositionStr = percent + "%";
this._bufferPositionWidget.value = bufferPositionStr;
}
}, {
}, {
options: function () {
options.add(["laststatus", "ls"],
"Show the status line",
"number", 2,
{
setter: function setter(value) {
if (value == 0)
document.getElementById("status-bar").collapsed = true;
else if (value == 1)
liberator.echoerr("show status line only with > 1 window not implemented yet");
else
document.getElementById("status-bar").collapsed = false;
return value;
},
completer: function completer(context) [
["0", "Never display status line"],
["1", "Display status line only if there are multiple windows"],
["2", "Always display status line"]
],
validator: Option.validateCompleter
});
},
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -231,8 +231,7 @@ Highlights.prototype.CSS = <![CDATA[
*
* @author Kris Maglione <maglione.k@gmail.com>
*/
function Highlights(name, store)
{
function Highlights(name, store) {
let self = this;
let highlight = {};
let styles = storage.styles;
@@ -256,8 +255,7 @@ function Highlights(name, store)
this.__iterator__ = function () (highlight[v] for ([k, v] in Iterator(keys())));
this.get = function (k) highlight[k];
this.set = function (key, newStyle, force, append)
{
this.set = function (key, newStyle, force, append) {
let [, class, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/);
if (!(class in highlight))
@@ -270,10 +268,8 @@ function Highlights(name, store)
newStyle = (style.value || "").replace(/;?\s*$/, "; " + newStyle);
if (/^\s*$/.test(newStyle))
newStyle = null;
if (newStyle == null)
{
if (style.default == null)
{
if (newStyle == null) {
if (style.default == null) {
delete highlight[style.class];
styles.removeSheet(true, style.selector);
return null;
@@ -284,8 +280,7 @@ function Highlights(name, store)
let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
.replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css))
{
if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css)) {
css = style.selector + " { " + css + " }";
let error = styles.addSheet(true, "highlight:" + style.class, style.filter, css, true);
@@ -301,8 +296,7 @@ function Highlights(name, store)
*
* @param {string} class
*/
this.selector = function (class)
{
this.selector = function (class) {
let [, hl, rest] = class.match(/^(\w*)(.*)/);
let pattern = "[liberator|highlight~=" + hl + "]"
if (highlight[hl] && highlight[hl].class != class)
@@ -314,8 +308,7 @@ function Highlights(name, store)
* Clears all highlighting rules. Rules with default values are
* reset.
*/
this.clear = function ()
{
this.clear = function () {
for (let [k, v] in Iterator(highlight))
this.set(k, null, true);
};
@@ -325,8 +318,7 @@ function Highlights(name, store)
*
* @param {string} css The rules to load. See {@link Highlights#css}.
*/
this.loadCSS = function (css)
{
this.loadCSS = function (css) {
css.replace(/^(\s*\S*\s+)\{((?:.|\n)*?)\}\s*$/gm, function (_, _1, _2) _1 + " " + _2.replace(/\n\s*/g, " "))
.split("\n").filter(function (s) /\S/.test(s))
.forEach(function (style)
@@ -340,8 +332,7 @@ function Highlights(name, store)
if (old && old.value != old.default)
style.value = old.value;
});
for (let [class, hl] in Iterator(highlight))
{
for (let [class, hl] in Iterator(highlight)) {
if (hl.value == hl.default)
this.set(class);
}
@@ -356,8 +347,7 @@ function Highlights(name, store)
*
* @author Kris Maglione <maglione.k@gmail.com>
*/
function Styles(name, store)
{
function Styles(name, store) {
// Can't reference liberator or Components inside Styles --
// they're members of the window object, which disappear
// with this window.
@@ -387,14 +377,12 @@ function Styles(name, store)
Sheet.prototype.__defineGetter__("enabled", function () this._enabled);
Sheet.prototype.__defineSetter__("enabled", function (on) {
this._enabled = Boolean(on);
if (on)
{
if (on) {
self.registerSheet(cssUri(this.fullCSS));
if (this.agent)
self.registerSheet(cssUri(this.fullCSS), true);
}
else
{
else {
self.unregisterSheet(cssUri(this.fullCSS));
self.unregisterSheet(cssUri(this.fullCSS), true);
}
@@ -428,8 +416,7 @@ function Styles(name, store)
* "*" is matched as a prefix.
* @param {string} css The CSS to be applied.
*/
this.addSheet = function (system, name, filter, css, agent)
{
this.addSheet = function (system, name, filter, css, agent) {
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
if (name && name in names)
@@ -437,12 +424,10 @@ function Styles(name, store)
let sheet = Sheet(name, id++, filter.split(",").filter(util.identity), String(css), null, system, agent);
try
{
try {
sheet.enabled = true;
}
catch (e)
{
catch (e) {
return e.echoerr || e;
}
sheets.push(sheet);
@@ -459,8 +444,7 @@ function Styles(name, store)
* @param {string or number} sheet The sheet to retrieve. Strings indicate
* sheet names, while numbers indicate indices.
*/
this.get = function get(system, sheet)
{
this.get = function get(system, sheet) {
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
if (typeof sheet === "number")
@@ -478,8 +462,7 @@ function Styles(name, store)
* @param {string} css
* @param {number} index
*/
this.findSheets = function (system, name, filter, css, index)
{
this.findSheets = function (system, name, filter, css, index) {
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
@@ -508,11 +491,9 @@ function Styles(name, store)
* @param {string} css
* @param {number} index
*/
this.removeSheet = function (system, name, filter, css, index)
{
this.removeSheet = function (system, name, filter, css, index) {
let self = this;
if (arguments.length == 0)
{
if (arguments.length == 0) {
var matches = [system];
system = sheet.system;
}
@@ -531,8 +512,7 @@ function Styles(name, store)
if (matches.length == 0)
return;
for (let [, sheet] in Iterator(matches.reverse()))
{
for (let [, sheet] in Iterator(matches.reverse())) {
sheet.enabled = false;
if (name)
delete names[name];
@@ -540,8 +520,7 @@ function Styles(name, store)
sheets.splice(sheets.indexOf(sheet), 1);
/* Re-add if we're only changing the site filter. */
if (filter)
{
if (filter) {
let sites = sheet.sites.filter(function (f) f != filter);
if (sites.length)
this.addSheet(system, name, sites.join(","), css, sheet.agent);
@@ -558,8 +537,7 @@ function Styles(name, store)
* @param {boolean} reload Whether to reload any sheets that are
* already registered.
*/
this.registerSheet = function (uri, agent, reload)
{
this.registerSheet = function (uri, agent, reload) {
if (reload)
this.unregisterSheet(uri, agent);
uri = ios.newURI(uri, null, null);
@@ -572,22 +550,23 @@ function Styles(name, store)
*
* @param {string} uri The URI of the sheet to unregister.
*/
this.unregisterSheet = function (uri, agent)
{
this.unregisterSheet = function (uri, agent) {
uri = ios.newURI(uri, null, null);
if (sss.sheetRegistered(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET))
sss.unregisterSheet(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET);
};
}
let (array = util.Array)
{
util.extend(Styles.prototype, {
Module("styles", {
requires: ["liberator", "storage", "util"],
init: function () {
let (array = util.Array) {
update(Styles.prototype, {
get sites() array([v.sites for ([k, v] in this.userSheets)]).flatten().uniq().__proto__,
completeSite: function (context, content)
{
completeSite: function (context, content) {
context.anchored = false;
try
{
try {
context.fork("current", 0, this, function (context) {
context.title = ["Current Site"];
context.completions = [
@@ -603,60 +582,19 @@ let (array = util.Array)
});
}
});
}
/**
* @property {Styles}
*/
const styles = storage.newObject("styles", Styles, { store: false });
/**
* @property {Highlights}
*/
const highlight = storage.newObject("highlight", Highlights, { store: false });
if (highlight.CSS != Highlights.prototype.CSS)
{
highlight.CSS = Highlights.prototype.CSS;
highlight.loadCSS(highlight.CSS);
}
liberator.triggerObserver("load_styles", "styles");
liberator.triggerObserver("load_highlight", "highlight");
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_commands", function () {
commands.add(["colo[rscheme]"],
"Load a color scheme",
function (args)
{
let scheme = args[0];
if (scheme == "default")
highlight.clear();
else
liberator.assert(!io.sourceFromRuntimePath(["colors/" + scheme + ".vimp"]),
"E185: Cannot find color scheme " + scheme);
autocommands.trigger("ColorScheme", { name: scheme });
}
return storage.newObject("styles", Styles, { store: false });
},
{
argCount: "1",
completer: function (context) completion.colorScheme(context)
});
}, {
}, {
commands: function () {
commands.add(["sty[le]"],
"Add or list user styles",
function (args)
{
function (args) {
let [filter, css] = args;
let name = args["-name"];
if (!css)
{
if (!css) {
let list = Array.concat([i for (i in styles.userNames)],
[i for (i in styles.userSheets) if (!i[1].name)]);
let str = template.tabular(["", "Name", "Filter", "CSS"],
@@ -671,13 +609,10 @@ liberator.registerObserver("load_commands", function () {
if ((!filter || sheet.sites.indexOf(filter) >= 0) && (!name || sheet.name == name))));
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
else
{
if ("-append" in args)
{
else {
if ("-append" in args) {
let sheet = styles.get(false, name);
if (sheet)
{
if (sheet) {
filter = sheet.sites.concat(filter).join(",");
css = sheet.css + " " + css;
}
@@ -689,13 +624,11 @@ liberator.registerObserver("load_commands", function () {
},
{
bang: true,
completer: function (context, args)
{
completer: function (context, args) {
let compl = [];
if (args.completeArg == 0)
styles.completeSite(context, content);
else if (args.completeArg == 1)
{
else if (args.completeArg == 1) {
let sheet = styles.get(false, args["-name"]);
if (sheet)
context.completions = [[sheet.css, "Current Value"]];
@@ -741,8 +674,7 @@ liberator.registerObserver("load_commands", function () {
}
].forEach(function (cmd) {
commands.add(cmd.name, cmd.desc,
function (args)
{
function (args) {
styles.findSheets(false, args["-name"], args[0], args.literalArg, args["-index"])
.forEach(cmd.action);
},
@@ -762,11 +694,54 @@ liberator.registerObserver("load_commands", function () {
if (!cmd.filter || cmd.filter(sheet))]]]
});
});
},
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
]);
},
});
Module("highlight", {
requires: ["styles"],
init: function () {
const self = storage.newObject("highlight", Highlights, { store: false });
if (self.CSS != Highlights.prototype.CSS) {
self.CSS = Highlights.prototype.CSS;
self.loadCSS(self.CSS);
}
return self;
},
}, {
}, {
commands: function () {
commands.add(["colo[rscheme]"],
"Load a color scheme",
function (args) {
let scheme = args[0];
if (scheme == "default")
highlight.clear();
else
liberator.assert(!io.sourceFromRuntimePath(["colors/" + scheme + ".vimp"]),
"E185: Cannot find color scheme " + scheme);
autocommands.trigger("ColorScheme", { name: scheme });
},
{
argCount: "1",
completer: function (context) completion.colorScheme(context)
});
commands.add(["hi[ghlight]"],
"Set the style of certain display elements",
function (args)
{
function (args) {
let style = <![CDATA[
;
display: inline-block !important;
@@ -784,8 +759,7 @@ liberator.registerObserver("load_commands", function () {
if (clear && css)
return liberator.echo("E488: Trailing characters");
if (!css && !clear)
{
if (!css && !clear) {
// List matching keys
let str = template.tabular(["Key", "Sample", "CSS"],
["padding: 0 1em 0 0; vertical-align: top",
@@ -806,16 +780,14 @@ liberator.registerObserver("load_commands", function () {
},
{
// TODO: add this as a standard highlight completion function?
completer: function (context, args)
{
completer: function (context, args) {
// Complete a highlight group on :hi clear ...
if (args.completeArg > 0 && args[0] == "clear")
args.completeArg = args.completeArg > 1 ? -1 : 0;
if (args.completeArg == 0)
context.completions = [[v.class, v.value] for (v in highlight)];
else if (args.completeArg == 1)
{
else if (args.completeArg == 1) {
let hl = highlight.get(args[0]);
if (hl)
context.completions = [[hl.value, "Current Value"], [hl.default || "", "Default Value"]];
@@ -834,21 +806,8 @@ liberator.registerObserver("load_commands", function () {
if (v.value != v.default)
]
});
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// 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: function () {
completion.colorScheme = function colorScheme(context) {
context.title = ["Color Scheme", "Runtime Path"];
context.keys = { text: function (f) f.leafName.replace(/\.vimp$/, ""), description: ".parent.path" };
@@ -863,7 +822,7 @@ liberator.registerObserver("load_completion", function () {
context.title = ["Highlight Group", "Value"];
context.completions = [[v.class, v.value] for (v in highlight)];
};
},
});
//}}}
// vim: set fdm=marker sw=4 ts=4 et:

File diff suppressed because it is too large Load Diff

View File

@@ -6,18 +6,16 @@
/** @scope modules */
const template = { //{{{
const Template = Module("template", {
add: function add(a, b) a + b,
join: function join(c) function (a, b) a + c + b,
map: function map(iter, func, sep, interruptable)
{
map: function map(iter, func, sep, interruptable) {
if (iter.length) // FIXME: Kludge?
iter = util.Array.itervalues(iter);
let ret = <></>;
let n = 0;
for each (let i in Iterator(iter))
{
for each (let i in Iterator(iter)) {
let val = func(i);
if (val == undefined)
continue;
@@ -30,30 +28,25 @@ const template = { //{{{
return ret;
},
maybeXML: function maybeXML(xml)
{
maybeXML: function maybeXML(xml) {
if (typeof xml == "xml")
return xml;
try
{
try {
return new XMLList(xml);
}
catch (e) {}
return <>{xml}</>;
},
completionRow: function completionRow(item, highlightGroup)
{
completionRow: function completionRow(item, highlightGroup) {
if (typeof icon == "function")
icon = icon();
if (highlightGroup)
{
if (highlightGroup) {
var text = item[0] || "";
var desc = item[1] || "";
}
else
{
else {
var text = this.process[0].call(this, item, item.text);
var desc = this.process[1].call(this, item, item.description);
}
@@ -85,8 +78,7 @@ const template = { //{{{
}
</>,
icon: function (item, text)
{
icon: function (item, text) {
return <><span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}</>
},
@@ -108,14 +100,11 @@ const template = { //{{{
// if "processStrings" is true, any passed strings will be surrounded by " and
// any line breaks are displayed as \n
highlight: function highlight(arg, processStrings, clip)
{
highlight: function highlight(arg, processStrings, clip) {
// some objects like window.JSON or getBrowsers()._browsers need the try/catch
try
{
try {
let str = clip ? util.clip(String(arg), clip) : String(arg);
switch (arg == null ? "undefined" : typeof arg)
{
switch (arg == null ? "undefined" : typeof arg) {
case "number":
return <span highlight="Number">{str}</span>;
case "string":
@@ -146,41 +135,34 @@ const template = { //{{{
return <![CDATA[<unknown type>]]>;
}
}
catch (e)
{
catch (e) {
return<![CDATA[<unknown>]]>;
}
},
highlightFilter: function highlightFilter(str, filter, highlight)
{
return this.highlightSubstrings(str, (function ()
{
highlightFilter: function highlightFilter(str, filter, highlight) {
return this.highlightSubstrings(str, (function () {
if (filter.length == 0)
return;
let lcstr = String.toLowerCase(str);
let lcfilter = filter.toLowerCase();
let start = 0;
while ((start = lcstr.indexOf(lcfilter, start)) > -1)
{
while ((start = lcstr.indexOf(lcfilter, start)) > -1) {
yield [start, filter.length];
start += filter.length;
}
})(), highlight || template.filter);
},
highlightRegexp: function highlightRegexp(str, re, highlight)
{
return this.highlightSubstrings(str, (function ()
{
highlightRegexp: function highlightRegexp(str, re, highlight) {
return this.highlightSubstrings(str, (function () {
let res;
while ((res = re.exec(str)) && res[0].length)
yield [res.index, res[0].length];
})(), highlight || template.filter);
},
highlightSubstrings: function highlightSubstrings(str, iter, highlight)
{
highlightSubstrings: function highlightSubstrings(str, iter, highlight) {
if (typeof str == "xml")
return str;
if (str == "")
@@ -190,8 +172,7 @@ const template = { //{{{
let s = <></>;
let start = 0;
let n = 0;
for (let [i, length] in iter)
{
for (let [i, length] in iter) {
if (n++ > 50) // Prevent infinite loops.
return s + <>{str.substr(start)}</>;
XML.ignoreWhitespace = false;
@@ -202,23 +183,20 @@ const template = { //{{{
return s + <>{str.substr(start)}</>;
},
highlightURL: function highlightURL(str, force)
{
highlightURL: function highlightURL(str, force) {
if (force || /^[a-zA-Z]+:\/\//.test(str))
return <a highlight="URL" href={str}>{str}</a>;
else
return str;
},
commandOutput: function generic(xml)
{
commandOutput: function generic(xml) {
return <>:{commandline.command}<br/>{xml}</>;
},
// every item must have a .xml property which defines how to draw itself
// @param headers is an array of strings, the text for the header columns
genericTable: function genericTable(items, format)
{
genericTable: function genericTable(items, format) {
completion.listCompleter(function (context) {
context.filterFunc = null;
if (format)
@@ -227,8 +205,7 @@ const template = { //{{{
});
},
jumps: function jumps(index, elems)
{
jumps: function jumps(index, elems) {
// <e4x>
return this.commandOutput(
<table>
@@ -248,8 +225,7 @@ const template = { //{{{
// </e4x>
},
options: function options(title, opts)
{
options: function options(title, opts) {
// <e4x>
return this.commandOutput(
<table>
@@ -269,8 +245,7 @@ const template = { //{{{
// </e4x>
},
table: function table(title, data, indent)
{
table: function table(title, data, indent) {
let table =
// <e4x>
<table>
@@ -290,8 +265,7 @@ const template = { //{{{
return table;
},
tabular: function tabular(headings, style, iter)
{
tabular: function tabular(headings, style, iter) {
// TODO: This might be mind-bogglingly slow. We'll see.
// <e4x>
return this.commandOutput(
@@ -315,8 +289,7 @@ const template = { //{{{
// </e4x>
},
usage: function usage(iter)
{
usage: function usage(iter) {
// <e4x>
return this.commandOutput(
<table>
@@ -330,6 +303,6 @@ const template = { //{{{
</table>);
// </e4x>
}
}; //}}}
});
// vim: set fdm=marker sw=4 ts=4 et:

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,11 @@ const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.
const NS = Namespace("liberator", "http://vimperator.org/namespaces/liberator");
default xml namespace = XHTML;
const util = { //{{{
const Util = Module("util", {
init: function () {
this.Array = Util.Array;
},
/**
* Returns true if its argument is an Array object, regardless
* of which context it comes from.
@@ -25,8 +29,7 @@ const util = { //{{{
* @param {Object} obj
* @returns {Object}
*/
cloneObject: function cloneObject(obj)
{
cloneObject: function cloneObject(obj) {
if (obj instanceof Array)
return obj.slice();
let newObj = {};
@@ -43,8 +46,7 @@ const util = { //{{{
* @param {number} length The length of the returned string.
* @returns {string}
*/
clip: function clip(str, length)
{
clip: function clip(str, length) {
return str.length <= length ? str : str.substr(0, length - 3) + "...";
},
@@ -64,8 +66,7 @@ const util = { //{{{
* @param {Node} node
* @returns {Object}
*/
computedStyle: function computedStyle(node)
{
computedStyle: function computedStyle(node) {
while (node instanceof Text && node.parentNode)
node = node.parentNode;
return node.ownerDocument.defaultView.getComputedStyle(node, null);
@@ -78,8 +79,7 @@ const util = { //{{{
* @param {string} str
* @param {boolean} verbose
*/
copyToClipboard: function copyToClipboard(str, verbose)
{
copyToClipboard: function copyToClipboard(str, verbose) {
const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
clipboardHelper.copyString(str);
@@ -94,8 +94,7 @@ const util = { //{{{
* @returns {Object}
*/
// FIXME: newURI needed too?
createURI: function createURI(str)
{
createURI: function createURI(str) {
const fixup = Cc["@mozilla.org/docshell/urifixup;1"].getService(Ci.nsIURIFixup);
return fixup.createFixupURI(str, fixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
},
@@ -107,8 +106,7 @@ const util = { //{{{
* @param {string} str
* @returns {string}
*/
escapeHTML: function escapeHTML(str)
{
escapeHTML: function escapeHTML(str) {
// XXX: the following code is _much_ slower than a simple .replace()
// :history display went down from 2 to 1 second after changing
//
@@ -124,32 +122,28 @@ const util = { //{{{
* @param {string} str
* @returns {string}
*/
escapeRegex: function escapeRegex(str)
{
escapeRegex: function escapeRegex(str) {
return str.replace(/([\\{}()[\].?*+])/g, "\\$1");
},
/**
* Escapes quotes, newline and tab characters in <b>str</b>. The returned
* string is delimited by <b>delimiter</b> or " if <b>delimiter</b> is not
* specified.
* specified. {@see String#quote}.
*
* @param {string} str
* @param {string} delimiter
* @returns {string}
*/
escapeString: function escapeString(str, delimiter)
{
escapeString: function escapeString(str, delimiter) {
if (delimiter == undefined)
delimiter = '"';
return delimiter + str.replace(/([\\'"])/g, "\\$1").replace("\n", "\\n", "g").replace("\t", "\\t", "g") + delimiter;
},
extend: function extend(dest)
{
extend: function extend(dest) {
Array.slice(arguments, 1).filter(util.identity).forEach(function (src) {
for (let [k, v] in Iterator(src))
{
for (let [k, v] in Iterator(src)) {
let get = src.__lookupGetter__(k),
set = src.__lookupSetter__(k);
if (!get && !set)
@@ -171,8 +165,7 @@ const util = { //{{{
* @param nodes {Array(string)}
* @returns {string}
*/
makeXPath: function makeXPath(nodes)
{
makeXPath: function makeXPath(nodes) {
return util.Array(nodes).map(function (node) [node, "xhtml:" + node]).flatten()
.map(function (node) "//" + node).join(" | ");
},
@@ -187,8 +180,7 @@ const util = { //{{{
* passed as the first argument, <b>key</b> as the
* second.
*/
memoize: function memoize(obj, key, getter)
{
memoize: function memoize(obj, key, getter) {
obj.__defineGetter__(key, function () {
delete obj[key];
obj[key] = getter(obj, key);
@@ -209,14 +201,12 @@ const util = { //{{{
* @param {string} str
* @param {RegExp} marker
*/
splitLiteral: function splitLiteral(str, marker)
{
splitLiteral: function splitLiteral(str, marker) {
let results = [];
let resep = RegExp(/^(([^\\'"]|\\.|'([^\\']|\\.)*'|"([^\\"]|\\.)*")*?)/.source + marker.source);
let cont = true;
while (cont)
{
while (cont) {
cont = false;
str = str.replace(resep, function (match, before) {
results.push(before);
@@ -238,17 +228,14 @@ const util = { //{{{
* @param {boolean} humanReadable Use byte multiples.
* @returns {string}
*/
formatBytes: function formatBytes(bytes, decimalPlaces, humanReadable)
{
formatBytes: function formatBytes(bytes, decimalPlaces, humanReadable) {
const unitVal = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
let unitIndex = 0;
let tmpNum = parseInt(bytes, 10) || 0;
let strNum = [tmpNum + ""];
if (humanReadable)
{
while (tmpNum >= 1024)
{
if (humanReadable) {
while (tmpNum >= 1024) {
tmpNum /= 1024;
if (++unitIndex > (unitVal.length - 1))
break;
@@ -273,53 +260,7 @@ const util = { //{{{
return strNum[0] + " " + unitVal[unitIndex];
},
/**
* Generates an Asciidoc help entry.
*
* @param {Command|Map|Option} obj A liberator <b>Command</b>,
* <b>Map</b> or <b>Option</b> object
* @param {XMLList} extraHelp Extra help text beyond the description.
* @returns {string}
*/
generateHelp: function generateHelp(obj, extraHelp)
{
let spec = util.identity;
let tag = util.identity;
if (obj instanceof Command)
tag = spec = function (cmd) <>:{cmd}</>;
else if (obj instanceof Map && obj.count)
spec = function (map) <><oa xmlns="">count</oa>{map}</>;
else if (obj instanceof Option)
{
spec = function (opt) <o xmlns="">{opt}</o>;
tag = function (opt) <>'{opt}'</>;
}
// E4X has its warts.
let br = <>
</>;
default xml namespace = "";
XML.prettyPrinting = false;
XML.ignoreWhitespace = false;
return <></> +
<item>
<tags>{template.map(obj.names, tag, " ")}</tags>
<spec>{spec((obj.specs || obj.names)[0])}</spec>{
!obj.type ? "" : <>
<type>{obj.type}</type>
<default>{obj.defaultValue}</default></>}
<description>{
obj.description ? br+<p>{obj.description.replace(/\.?$/, ".")}</p> : "" }{
extraHelp ? br+extraHelp : "" }{
!(extraHelp || obj.description) ? br+<p>Sorry, no help available.</p> : "" }
</description>
</item>.toXMLString();
},
exportHelp: function (path)
{
exportHelp: function (path) {
const FILE = io.File(path);
const PATH = FILE.leafName.replace(/\..*/, "") + "/";
const TIME = Date.now();
@@ -337,8 +278,7 @@ const util = { //{{{
.split(" ").map(Array.concat));
let chrome = {};
for (let [file,] in Iterator(services.get("liberator:").FILE_MAP))
{
for (let [file,] in Iterator(services.get("liberator:").FILE_MAP)) {
liberator.open("liberator://help/" + file);
events.waitForPageLoad();
let data = [
@@ -346,10 +286,8 @@ const util = { //{{{
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"\n',
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
];
function fix(node)
{
switch(node.nodeType)
{
function fix(node) {
switch(node.nodeType) {
case Node.ELEMENT_NODE:
if (node instanceof HTMLScriptElement)
return;
@@ -358,22 +296,18 @@ const util = { //{{{
if (node instanceof HTMLHtmlElement)
data.push(' xmlns="' + XHTML.uri + '"');
for (let { name: name, value: value } in util.Array.itervalues(node.attributes))
{
if (name == "liberator:highlight")
{
for (let { name: name, value: value } in util.Array.itervalues(node.attributes)) {
if (name == "liberator:highlight") {
name = "class";
value = "hl-" + value;
}
if (name == "href")
{
if (name == "href") {
if (value.indexOf("liberator://help-tag/") == 0)
value = services.get("io").newChannel(value, null, null).originalURI.path.substr(1);
if (!/[#\/]/.test(value))
value += ".xhtml";
}
if (name == "src" && value.indexOf(":") > 0)
{
if (name == "src" && value.indexOf(":") > 0) {
chrome[value] = value.replace(/.*\//, "");;
value = value.replace(/.*\//, "");
}
@@ -385,8 +319,7 @@ const util = { //{{{
}
if (node.localName in empty)
data.push(" />");
else
{
else {
data.push(">");
if (node instanceof HTMLHeadElement)
data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
@@ -429,14 +362,11 @@ const util = { //{{{
* @param {function(XMLHttpRequest)} callback
* @returns {XMLHttpRequest}
*/
httpGet: function httpGet(url, callback)
{
try
{
httpGet: function httpGet(url, callback) {
try {
let xmlhttp = new XMLHttpRequest();
xmlhttp.mozBackgroundRequest = true;
if (callback)
{
if (callback) {
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4)
callback(xmlhttp);
@@ -446,8 +376,7 @@ const util = { //{{{
xmlhttp.send(null);
return xmlhttp;
}
catch (e)
{
catch (e) {
liberator.log("Error opening " + url + ": " + e, 1);
}
},
@@ -465,8 +394,7 @@ const util = { //{{{
* @param {boolean} asIterator Whether to return the results as an
* XPath iterator.
*/
evaluateXPath: function (expression, doc, elem, asIterator)
{
evaluateXPath: function (expression, doc, elem, asIterator) {
if (!doc)
doc = window.content.document;
if (!elem)
@@ -475,11 +403,11 @@ const util = { //{{{
expression = util.makeXPath(expression);
let result = doc.evaluate(expression, elem,
function lookupNamespaceURI(prefix)
{
function lookupNamespaceURI(prefix) {
return {
xhtml: "http://www.w3.org/1999/xhtml",
xhtml2: "http://www.w3.org/2002/06/xhtml2",
liberator: NS.uri,
liberator: NS.uri
}[prefix] || null;
},
@@ -526,8 +454,7 @@ const util = { //{{{
* @param {function} func
* @returns {Array}
*/
map: function map(obj, func)
{
map: function map(obj, func) {
let ary = [];
for (let i in Iterator(obj))
ary.push(func(i));
@@ -558,8 +485,7 @@ const util = { //{{{
* @returns {nsIURI}
*/
// FIXME: createURI needed too?
newURI: function (uri)
{
newURI: function (uri) {
return services.get("io").newURI(uri, null, null);
},
@@ -571,10 +497,9 @@ const util = { //{{{
* @param {boolean} color Whether the output should be colored.
* @returns {string}
*/
objectToString: function objectToString(object, color)
{
objectToString: function objectToString(object, color) {
// Use E4X literals so html is automatically quoted
// only when it's asked for. Noone wants to see &lt;
// only when it's asked for. No one wants to see &lt;
// on their console or :map :foo in their buffer
// when they expect :map <C-f> :foo.
XML.prettyPrinting = false;
@@ -591,20 +516,17 @@ const util = { //{{{
[XHTML, 'html'],
[XUL, 'xul']
]);
if (object instanceof Element)
{
if (object instanceof Element) {
let elem = object;
if (elem.nodeType == elem.TEXT_NODE)
return elem.data;
function namespaced(node)
{
function namespaced(node) {
var ns = NAMESPACES[node.namespaceURI];
if (ns)
return ns + ":" + node.localName;
return node.localName.toLowerCase();
}
try
{
try {
let tag = "<" + [namespaced(elem)].concat(
[namespaced(a) + "=" + template.highlight(a.value, true)
for ([i, a] in util.Array.iteritems(elem.attributes))]).join(" ");
@@ -615,42 +537,34 @@ const util = { //{{{
tag += '>...</' + namespaced(elem) + '>';
return tag;
}
catch (e)
{
catch (e) {
return {}.toString.call(elem);
}
}
try
{ // for window.JSON
try { // for window.JSON
var obj = String(object);
}
catch (e)
{
catch (e) {
obj = "[Object]";
}
obj = template.highlightFilter(util.clip(obj, 150), "\n", !color ? function () "^J" : function () <span highlight="NonText">^J</span>);
let string = <><span highlight="Title Object">{obj}</span>::<br/>&#xa;</>;
let keys = [];
try // window.content often does not want to be queried with "var i in object"
{
try { // window.content often does not want to be queried with "var i in object"
let hasValue = !("__iterator__" in object);
if (modules.isPrototypeOf(object))
{
if (modules.isPrototypeOf(object)) {
object = Iterator(object);
hasValue = false;
}
for (let i in object)
{
for (let i in object) {
let value = <![CDATA[<no value>]]>;
try
{
try {
value = object[i];
}
catch (e) {}
if (!hasValue)
{
if (!hasValue) {
if (i instanceof Array && i.length == 2)
[i, value] = i;
else
@@ -668,8 +582,7 @@ const util = { //{{{
}
catch (e) {}
function compare(a, b)
{
function compare(a, b) {
if (!isNaN(a[0]) && !isNaN(b[0]))
return a[0] - b[0];
return String.localeCompare(a[0], b[0]);
@@ -688,17 +601,14 @@ const util = { //{{{
* negative. @default 1
* @returns {Iterator(Object)}
*/
range: function range(start, end, step)
{
range: function range(start, end, step) {
if (!step)
step = 1;
if (step > 0)
{
if (step > 0) {
for (; start < end; start += step)
yield start;
}
else
{
else {
while (start > end)
yield start += step;
}
@@ -713,13 +623,10 @@ const util = { //{{{
* @param {number} time The time in milliseconds between thread yields.
* @returns {Iterator(Object)}
*/
interruptibleRange: function interruptibleRange(start, end, time)
{
interruptibleRange: function interruptibleRange(start, end, time) {
let endTime = Date.now() + time;
while (start < end)
{
if (Date.now() > endTime)
{
while (start < end) {
if (Date.now() > endTime) {
liberator.threadYield(true, true);
endTime = Date.now() + time;
}
@@ -735,12 +642,10 @@ const util = { //{{{
*
* @returns {string}
*/
readFromClipboard: function readFromClipboard()
{
readFromClipboard: function readFromClipboard() {
let str;
try
{
try {
const clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
const transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
@@ -756,8 +661,7 @@ const util = { //{{{
transferable.getTransferData("text/unicode", data, dataLen);
if (data)
{
if (data) {
data = data.value.QueryInterface(Ci.nsISupportsString);
str = data.data.substring(0, dataLen.value / 2);
}
@@ -776,8 +680,7 @@ const util = { //{{{
* @param {string} str
* @returns {string[]}
*/
stringToURLArray: function stringToURLArray(str)
{
stringToURLArray: function stringToURLArray(str) {
let urls;
if (options["urlseparator"])
@@ -786,8 +689,7 @@ const util = { //{{{
urls = [str];
return urls.map(function (url) {
try
{
try {
// Try to find a matching file.
let file = io.File(url);
if (file.exists() && file.isReadable())
@@ -834,18 +736,15 @@ const util = { //{{{
* stored here, keyed to the value thereof.
* @returns {Node}
*/
xmlToDom: function xmlToDom(node, doc, nodes)
{
xmlToDom: function xmlToDom(node, doc, nodes) {
XML.prettyPrinting = false;
if (node.length() != 1)
{
if (node.length() != 1) {
let domnode = doc.createDocumentFragment();
for each (let child in node)
domnode.appendChild(arguments.callee(child, doc, nodes));
return domnode;
}
switch (node.nodeKind())
{
switch (node.nodeKind()) {
case "text":
return doc.createTextNode(node);
case "element":
@@ -859,18 +758,17 @@ const util = { //{{{
return domnode;
}
}
}; //}}}
// TODO: Why don't we just push all util.BuiltinType up into modules? --djk
/**
}, {
// TODO: Why don't we just push all util.BuiltinType up into modules? --djk
/**
* Array utility methods.
*/
util.Array = function Array_(ary) {
var obj = {
Array: Class("Array", {
init: function (ary) {
return {
__proto__: ary,
__iterator__: function () this.iteritems(),
__noSuchMethod__: function (meth, args)
{
__noSuchMethod__: function (meth, args) {
let res = (util.Array[meth] || Array[meth]).apply(null, [this.__proto__].concat(args));
if (util.Array.isinstance(res))
return util.Array(res);
@@ -879,12 +777,12 @@ util.Array = function Array_(ary) {
concat: function () [].concat.apply(this.__proto__, arguments),
map: function () this.__noSuchMethod__("map", Array.slice(arguments))
};
return obj;
}
util.Array.isinstance = function isinstance(obj) {
},
}, {
isinstance: function isinstance(obj) {
return Object.prototype.toString.call(obj) == "[object Array]";
};
/**
},
/**
* Converts an array to an object. As in lisp, an assoc is an
* array of key-value pairs, which maps directly to an object,
* as such:
@@ -894,14 +792,13 @@ util.Array.isinstance = function isinstance(obj) {
* @... {string} 0 - Key
* @... 1 - Value
*/
util.Array.toObject = function toObject(assoc)
{
toObject: function toObject(assoc) {
let obj = {};
assoc.forEach(function ([k, v]) { obj[k] = v; });
return obj;
};
},
/**
/**
* Flattens an array, such that all elements of the array are
* joined into a single array:
* [["foo", ["bar"]], ["baz"], "quux"] -> ["foo", ["bar"], "baz", "quux"]
@@ -909,35 +806,33 @@ util.Array.toObject = function toObject(assoc)
* @param {Array} ary
* @returns {Array}
*/
util.Array.flatten = function flatten(ary) Array.concat.apply([], ary),
flatten: function flatten(ary) Array.concat.apply([], ary),
/**
/**
* Returns an Iterator for an array's values.
*
* @param {Array} ary
* @returns {Iterator(Object)}
*/
util.Array.itervalues = function itervalues(ary)
{
itervalues: function itervalues(ary) {
let length = ary.length;
for (let i = 0; i < length; i++)
yield ary[i];
};
},
/**
/**
* Returns an Iterator for an array's indices and values.
*
* @param {Array} ary
* @returns {Iterator([{number}, {Object}])}
*/
util.Array.iteritems = function iteritems(ary)
{
iteritems: function iteritems(ary) {
let length = ary.length;
for (let i = 0; i < length; i++)
yield [i, ary[i]];
};
},
/**
/**
* Filters out all duplicates from an array. If
* <b>unsorted</b> is false, the array is sorted before
* duplicates are removed.
@@ -946,85 +841,24 @@ util.Array.iteritems = function iteritems(ary)
* @param {boolean} unsorted
* @returns {Array}
*/
util.Array.uniq = function uniq(ary, unsorted)
{
uniq: function uniq(ary, unsorted) {
let ret = [];
if (unsorted)
{
if (unsorted) {
for (let [, item] in Iterator(ary))
if (ret.indexOf(item) == -1)
ret.push(item);
}
else
{
for (let [, item] in Iterator(ary.sort()))
{
else {
for (let [, item] in Iterator(ary.sort())) {
if (item != last || !ret.length)
ret.push(item);
var last = item;
}
}
return ret;
};
function Struct()
{
let self = this instanceof Struct ? this : new Struct();
if (!arguments.length)
return self;
let args = Array.slice(arguments);
self.__defineGetter__("length", function () args.length);
self.__defineGetter__("members", function () args.slice());
for (let arg in Iterator(args))
{
let [i, name] = arg;
self.__defineGetter__(name, function () this[i]);
self.__defineSetter__(name, function (val) { this[i] = val; });
}
function ConStructor()
{
let self = this instanceof arguments.callee ? this : new arguments.callee();
//for (let [k, v] in Iterator(Array.slice(arguments))) // That is makes using struct twice as slow as the following code:
for (let i = 0; i < arguments.length; i++)
{
if (arguments[i] != undefined)
self[i] = arguments[i];
}
return self;
}
ConStructor.prototype = self;
ConStructor.defaultValue = function (key, val)
{
let i = args.indexOf(key);
ConStructor.prototype.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0
ConStructor.prototype.__defineSetter__(i, function (val) {
let value = val;
this.__defineGetter__(i, function () value);
this.__defineSetter__(i, function (val) { value = val });
});
};
return self.constructor = ConStructor;
}
Struct.prototype = {
clone: function clone()
{
return this.constructor.apply(null, this.slice());
},
// Iterator over our named members
__iterator__: function ()
{
let self = this;
return ([v, self[v]] for ([k, v] in Iterator(self.members)))
}
};
}),
});
// Add no-sideeffect array methods. Can't set new Array() as the prototype or
// get length() won't work.
for (let [, k] in Iterator(["concat", "every", "filter", "forEach", "indexOf", "join", "lastIndexOf",
"map", "reduce", "reduceRight", "reverse", "slice", "some", "sort"]))
Struct.prototype[k] = Array.prototype[k];
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -1,6 +1,6 @@
// Header:
const Name = "Muttator";
// The following doesn't work here, so this module's code is sadly suplicated:
// The following doesn't work here, so this module's code is sadly duplicated:
// Components.utils.import("resource://liberator/about-handler.jsm");
// Copyright (c) 2009 by Doug Kearns

View File

@@ -1,6 +1,6 @@
// Header:
const Name = "Muttator";
// The following doesn't work here, so this module's code is sadly suplicated:
// The following doesn't work here, so this module's code is sadly duplicated:
// Components.utils.import("resource://liberator/commandline-handler.jsm");
// Copyright (c) 2009 by Doug Kearns

View File

@@ -191,23 +191,11 @@ const config = (function () //{{{
liberator.loadModule("hints", Hints);
}
////////////////////////////////////////////////////////////////////////////////
////////////////////// STYLES //////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
commands.add(["pref[erences]", "prefs"],
"Show " + config.hostApplication + " preferences",
function () { window.openOptionsDialog(); },
{ argCount: "0" });
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// OPTIONS /////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
// FIXME: comment obviously incorrect
// 0: never automatically edit externally
// 1: automatically edit externally when message window is shown the first time

View File

@@ -1,6 +1,6 @@
#### configuration
VERSION = 2.2
VERSION = 1.0pre
NAME = vimperator
include ../common/Makefile

View File

@@ -1,6 +1,6 @@
// Header:
const Name = "Vimperator";
// The following doesn't work here, so this module's code is sadly suplicated:
// The following doesn't work here, so this module's code is sadly duplicated:
// Components.utils.import("resource://liberator/about-handler.jsm");
// Copyright (c) 2009 by Doug Kearns

View File

@@ -1,6 +1,6 @@
// Header:
const Name = "Vimperator";
// The following doesn't work here, so this module's code is sadly suplicated:
// The following doesn't work here, so this module's code is sadly duplicated:
// Components.utils.import("resource://liberator/commandline-handler.jsm");
// Copyright (c) 2009 by Doug Kearns

View File

@@ -110,6 +110,8 @@ const config = { //{{{
scripts: [
"browser.js",
"bookmarks.js",
"history.js",
"quickmarks.js",
"sanitizer.js",
"tabs.js"
],
@@ -128,38 +130,6 @@ const config = { //{{{
init: function ()
{
// load Vimperator specific modules
// FIXME: Why aren't these listed in config.scripts?
// FIXME: Why isn't this automatic? -> how would one know which classes to load where? --mst
// Something like:
// liberator.addModule("search", function Search() { ...
// for all modules, or something similar. For modules which
// require other modules, well, there's addObserver("load_foo",
// or we could just make sure that they're all sourced in order.
// The scripts could even just instantiate them themselves.
// --Kris
liberator.loadModule("browser", Browser);
liberator.loadModule("finder", Finder);
liberator.loadModule("bookmarks", Bookmarks);
liberator.loadModule("history", History);
liberator.loadModule("tabs", Tabs);
liberator.loadModule("marks", Marks);
liberator.loadModule("quickmarks", QuickMarks);
liberator.loadModule("hints", Hints);
liberator.loadModule("sanitizer", Sanitizer);
////////////////////////////////////////////////////////////////////////////////
////////////////////// STYLES //////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// MAPPINGS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// COMMANDS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
commands.add(["winon[ly]"],
"Close all other windows",
function ()

View File

@@ -12,7 +12,7 @@
<em:optionsURL>chrome://liberator/content/preferences.xul</em:optionsURL>
<em:file>
<Description about="urn:mozilla:extension:file:vimperator.jar">
<em:package>content/vimperator/</em:package>
<em:package>content/liberator/</em:package>
</Description>
</em:file>
<em:targetApplication>

View File

@@ -1,6 +1,6 @@
// Header:
const Name = "Xulmus";
// The following doesn't work here, so this module's code is sadly suplicated:
// The following doesn't work here, so this module's code is sadly duplicated:
// Components.utils.import("resource://liberator/about-handler.jsm");
// Copyright (c) 2009 by Doug Kearns

View File

@@ -1,6 +1,6 @@
// Header:
const Name = "Xulmus";
// The following doesn't work here, so this module's code is sadly suplicated:
// The following doesn't work here, so this module's code is sadly duplicated:
// Components.utils.import("resource://liberator/commandline-handler.jsm");
// Copyright (c) 2009 by Doug Kearns

View File

@@ -246,6 +246,20 @@ const config = { //{{{
////////////////////// STYLES //////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
let img = Image();
img.src = "chrome://xulmus/content/logo.png";
img.onload = function () {
styles.addSheet(true, "logo", "chrome://liberator/locale/*",
".xulmus-logo {" + <>
display: inline-block;
background: url({img.src});
width: {img.width}px;
height: {img.height}px;
</> + "}",
true);
delete img;
};
////////////////////////////////////////////////////////////////////////////////
////////////////////// MAPPINGS ////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{