1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-03-31 07:53:39 +02:00

Merge testing.

--HG--
rename : common/content/base.js => common/modules/base.jsm
rename : common/content/services.js => common/modules/services.jsm
rename : common/content/style.js => common/modules/styles.jsm
rename : common/content/template.js => common/modules/template.jsm
rename : common/content/util.js => common/modules/util.jsm
This commit is contained in:
Kris Maglione
2010-08-31 21:09:13 -04:00
parent 5632e14721
commit 8b0d9586b2
39 changed files with 2156 additions and 2197 deletions

View File

@@ -14,8 +14,6 @@ const AutoCommand = Struct("event", "pattern", "command");
* @instance autocommands
*/
const AutoCommands = Module("autocommands", {
requires: ["config"],
init: function () {
this._store = [];
},
@@ -83,7 +81,7 @@ const AutoCommands = Module("autocommands", {
}
});
let list = template.commandOutput(commandline.command,
commandline.commandOutput(
<table>
<tr highlight="Title">
<td colspan="2">----- Auto Commands -----</td>
@@ -101,8 +99,6 @@ const AutoCommands = Module("autocommands", {
</tr>))
}
</table>);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
},
/**
@@ -137,7 +133,6 @@ const AutoCommands = Module("autocommands", {
autoCmd.command.call(autoCmd, args);
}
catch (e) {
dactyl.reportError(e);
dactyl.echoerr(e);
}
}
@@ -203,7 +198,12 @@ const AutoCommands = Module("autocommands", {
return args["-javascript"] ? completion.javascript(context) : completion.ex(context);
},
literal: 2,
options: [[["-javascript", "-js"], commands.OPTION_NOARG]]
options: [
{
names: ["-javascript", "-js"],
description: "Interperate the action as JavaScript code rather than an ex command",
}
]
});
[

View File

@@ -1,624 +0,0 @@
// Copyright (c) 2009 by Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;
function allkeys(obj) {
let ret = {};
try {
for (; obj; obj = obj.__proto__) {
services.get("debugger").wrapValue(obj).getProperties(ret, {});
for (let prop in values(ret.value))
yield prop.name.stringValue;
}
return;
}
catch (e) {}
let __iterator__ = obj.__iterator__;
try {
if ("__iterator__" in obj) {
yield "__iterator__";
delete obj.__iterator__;
}
for (let k in obj)
yield k;
}
finally {
if (__iterator__)
obj.__iterator__ = __iterator__;
}
}
function debuggerProperties(obj) {
if (modules.services && services.get("debugger").isOn) {
let ret = {};
services.get("debugger").wrapValue(obj).getProperties(ret, {});
return ret.value;
}
}
if (!Object.keys)
Object.keys = function keys(obj) [k for (k in obj) if (obj.hasOwnProperty(k))];
if (!Object.getOwnPropertyNames)
Object.getOwnPropertyNames = function getOwnPropertyNames(obj) {
let res = debuggerProperties(obj);
if (res)
return [prop.name.stringValue for (prop in values(res))];
return Object.keys(obj);
}
function properties(obj, prototypes) {
let orig = obj;
let seen = {};
for (; obj; obj = prototypes && obj.__proto__)
for (let key in values(Object.getOwnPropertyNames(obj)))
if (!prototypes || !set.add(seen, key) && obj != orig)
yield key
}
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) {
let obj = {};
if (ary)
for (var i = 0; i < ary.length; i++)
obj[ary[i]] = true;
return obj;
}
set.add = function (set, key) {
let res = this.has(set, key);
set[key] = true;
return res;
}
set.has = function (set, key) Object.prototype.hasOwnProperty.call(set, key);
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, [Ci.nsIDOMHTMLCollection, Ci.nsIDOMNodeList]))
return array.iteritems(obj);
if (obj instanceof Ci.nsIDOMNamedNodeMap)
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;
if (typeof src[i] == "string")
return Object.prototype.toString(targ) == "[object " + src[i] + "]";
var type = types[typeof targ];
if (type && issubclass(src[i], type))
return true;
}
return false;
}
function isobject(obj) {
return typeof obj === "object" && obj != null;
}
/**
* Returns true if and only if its sole argument is an
* instance of the builtin Array type. The array may come from
* any window, frame, namespace, or execution context, which
* is not the case when using (obj instanceof Array).
*/
function isarray(val) {
return Object.prototype.toString.call(val) == "[object Array]";
}
/**
* Returns true if and only if its sole argument is an
* instance of the builtin Generator type. This includes
* functions containing the 'yield' statement and generator
* statements such as (x for (x in obj)).
*/
function isgenerator(val) {
return Object.prototype.toString.call(val) == "[object Generator]";
}
/**
* Returns true if and only if its sole argument is a String,
* as defined by the builtin type. May be constructed via
* String(foo) or new String(foo) from any window, frame,
* namespace, or execution context, which is not the case when
* using (obj instanceof String) or (typeof obj == "string").
*/
function isstring(val) {
return Object.prototype.toString.call(val) == "[object String]";
}
/**
* Returns true if and only if its sole argument may be called
* as a function. This includes classes and function objects.
*/
function callable(val) {
return typeof val === "function";
}
function call(fn) {
fn.apply(arguments[1], Array.slice(arguments, 2));
return fn;
}
function memoize(obj, key, getter) {
obj.__defineGetter__(key, function () {
delete obj[key];
return obj[key] = getter(obj, key);
});
}
/**
* Curries a function to the given number of arguments. Each
* call of the resulting function returns a new function. When
* a call does not contain enough arguments to satisfy the
* required number, the resulting function is another curried
* function with previous arguments accumulated.
*
* function foo(a, b, c) [a, b, c].join(" ");
* curry(foo)(1, 2, 3) -> "1 2 3";
* curry(foo)(4)(5, 6) -> "4 5 6";
* curry(foo)(4)(8)(9) -> "7 8 9";
*
* @param {function} fn The function to curry.
* @param {integer} length The number of arguments expected.
* @default fn.length
* @optional
* @param {object} self The 'this' value for the returned function. When
* omitted, the value of 'this' from the first call to the function is
* preserved.
* @optional
*/
function curry(fn, length, self, 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));
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(self || this, arguments.callee);
if (args.length >= length)
return fn.apply(self || this, args);
return curry(fn, length, self || this, args);
};
}
/**
* Updates an object with the properties of another object. Getters
* and setters are copied as expected. Moreover, any function
* properties receive new 'supercall' and 'superapply' properties,
* which will call the identically named function in target's
* prototype.
*
* let a = { foo: function (arg) "bar " + arg }
* let b = { __proto__: a }
* update(b, { foo: function () arguments.callee.supercall(this, "baz") });
*
* a.foo("foo") -> "bar foo"
* b.foo() -> "bar baz"
*
* @param {Object} target The object to update.
* @param {Object} src The source object from which to update target.
* May be provided multiple times.
* @returns {Object} Returns its updated first argument.
*/
function update(target) {
for (let i = 1; i < arguments.length; i++) {
let src = arguments[i];
Object.getOwnPropertyNames(src || {}).forEach(function (k) {
var get = src.__lookupGetter__(k),
set = src.__lookupSetter__(k);
if (!get && !set) {
var v = src[k];
target[k] = v;
if (target.__proto__ && callable(v)) {
v.superapply = function (self, args) {
return target.__proto__[k].apply(self, args);
};
v.supercall = function (self) {
return v.superapply(self, Array.slice(arguments, 1));
};
}
}
if (get)
target.__defineGetter__(k, get);
if (set)
target.__defineSetter__(k, set);
});
}
return target;
}
/**
* Extends a subclass with a superclass. The subclass's
* prototype is replaced with a new object, which inherits
* from the super class's prototype, {@see update}d with the
* members of 'overrides'.
*
* @param {function} subclass
* @param {function} superclass
* @param {Object} overrides @optional
*/
function extend(subclass, superclass, overrides) {
subclass.prototype = { __proto__: superclass.prototype };
update(subclass.prototype, overrides);
subclass.superclass = superclass.prototype;
subclass.prototype.constructor = subclass;
subclass.prototype.__class__ = subclass;
if (superclass.prototype.constructor === Object.prototype.constructor)
superclass.prototype.constructor = superclass;
}
/**
* @constructor Class
*
* Constructs a new Class. Arguments marked as optional must be
* either entirely elided, or they must have the exact type
* specified.
*
* @param {string} name The class's as it will appear when toString
* is called, as well as in stack traces.
* @optional
* @param {function} base The base class for this module. May be any
* callable object.
* @optional
* @default Class
* @param {Object} prototype The prototype for instances of this
* object. The object itself is copied and not used as a prototype
* directly.
* @param {Object} classProperties The class properties for the new
* module constructor. More than one may be provided.
* @optional
*
* @returns {function} The constructor for the resulting class.
*/
function Class() {
function constructor() {
let self = {
__proto__: Constructor.prototype,
constructor: Constructor,
get closure() {
delete this.closure;
function closure(fn) function () fn.apply(self, arguments);
for (let k in this)
if (!this.__lookupGetter__(k) && callable(this[k]))
closure[k] = closure(self[k]);
return this.closure = closure;
}
};
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).replace(/\W/g, "_") +
String.substr(constructor, 20) + ")");
Constructor.__proto__ = superclass;
Constructor.name = name || superclass.name;
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 = {
/**
* Initializes new instances of this class. Called automatically
* when new instances are created.
*/
init: function () {},
toString: function () "[instance " + this.constructor.name + "]",
/**
* Exactly like {@see nsIDOMWindow#setTimeout}, except that it
* preserves the value of 'this' on invocation of 'callback'.
*
* @param {function} callback The function to call after 'timeout'
* @param {number} timeout The timeout, in seconds, to wait
* before calling 'callback'.
* @returns {integer} The ID of this timeout, to be passed to
* {@see nsIDOMWindow#clearTimeout}.
*/
setTimeout: function (callback, timeout) {
const self = this;
let notify = { notify: function notify(timer) { callback.call(self) } };
let timer = services.create("timer");
timer.initWithCallback(notify, timeout, timer.TYPE_ONE_SHOT);
return timer;
}
};
/**
* @class Struct
*
* Creates a new Struct constructor, used for creating objects with
* a fixed set of named members. Each argument should be the name of
* a member in the resulting objects. These names will correspond to
* the arguments passed to the resultant constructor. Instances of
* the new struct may be treated vary much like arrays, and provide
* many of the same methods.
*
* const Point = Struct("x", "y", "z");
* let p1 = Point(x, y, z);
*
* @returns {function} The constructor for the new Struct.
*/
function Struct() {
let args = Array.slice(arguments);
const Struct = Class("Struct", StructBase, {
length: args.length,
members: args
});
args.forEach(function (name, i) {
Struct.prototype.__defineGetter__(name, function () this[i]);
Struct.prototype.__defineSetter__(name, function (val) { this[i] = val; });
});
return Struct;
}
const StructBase = Class("StructBase", {
init: function () {
for (let i = 0; i < arguments.length; i++)
if (arguments[i] != undefined)
this[i] = arguments[i];
},
clone: function clone() this.constructor.apply(null, this.slice()),
// Iterator over our named members
__iterator__: function () {
let self = this;
return ([k, self[k]] for (k in values(self.members)))
}
}, {
/**
* Sets a lazily constructed default value for a member of
* the struct. The value is constructed once, the first time
* it is accessed and memoized thereafter.
*
* @param {string} key The name of the member for which to
* provide the default value.
* @param {function} val The function which is to generate
* the default value.
*/
defaultValue: function (key, val) {
let i = this.prototype.members.indexOf(key);
this.prototype.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0
this.prototype.__defineSetter__(i, function (value) {
this.__defineGetter__(i, function () value);
this.__defineSetter__(i, function (val) { value = val; });
});
}
});
// Add no-sideeffect array methods. Can't set new Array() as the prototype or
// get length() won't work.
for (let k in values(["concat", "every", "filter", "forEach", "indexOf", "join", "lastIndexOf",
"map", "reduce", "reduceRight", "reverse", "slice", "some", "sort"]))
StructBase.prototype[k] = Array.prototype[k];
/**
* Array utility methods.
*/
const array = Class("util.Array", Array, {
init: function (ary) {
if (isgenerator(ary))
ary = [k for (k in ary)];
else if (ary.length)
ary = Array.slice(ary);
return {
__proto__: ary,
__iterator__: function () this.iteritems(),
__noSuchMethod__: function (meth, args) {
var res = array[meth].apply(null, [this.__proto__].concat(args));
if (array.isinstance(res))
return array(res);
return res;
},
toString: function () this.__proto__.toString(),
concat: function () this.__proto__.concat.apply(this.__proto__, arguments),
map: function () this.__noSuchMethod__("map", Array.slice(arguments))
};
}
}, {
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:
* [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" }
*
* @param {Array[]} assoc
* @... {string} 0 - Key
* @... 1 - Value
*/
toObject: function toObject(assoc) {
let obj = {};
assoc.forEach(function ([k, v]) { obj[k] = v; });
return obj;
},
/**
* Compacts an array, removing all elements that are null or undefined:
* ["foo", null, "bar", undefined] -> ["foo", "bar"]
*
* @param {Array} ary
* @returns {Array}
*/
compact: function compact(ary) ary.filter(function (item) item != null),
/**
* Flattens an array, such that all elements of the array are
* joined into a single array:
* [["foo", ["bar"]], ["baz"], "quux"] -> ["foo", ["bar"], "baz", "quux"]
*
* @param {Array} ary
* @returns {Array}
*/
flatten: function flatten(ary) ary.length ? Array.concat.apply([], ary) : [],
/**
* Returns an Iterator for an array's values.
*
* @param {Array} ary
* @returns {Iterator(Object)}
*/
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}])}
*/
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.
*
* @param {Array} ary
* @param {boolean} unsorted
* @returns {Array}
*/
uniq: function uniq(ary, unsorted) {
let ret = [];
if (unsorted) {
for (let [, item] in Iterator(ary))
if (ret.indexOf(item) == -1)
ret.push(item);
}
else {
for (let [, item] in Iterator(ary.sort())) {
if (item != last || !ret.length)
ret.push(item);
var last = item;
}
}
return ret;
},
/**
* Zips the contents of two arrays. The resulting array is twice the
* length of ary1, with any shortcomings of ary2 replaced with null
* strings.
*
* @param {Array} ary1
* @param {Array} ary2
* @returns {Array}
*/
zip: function zip(ary1, ary2) {
let res = []
for(let [i, item] in Iterator(ary1))
res.push(item, i in ary2 ? ary2[i] : "");
return res;
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -10,193 +10,13 @@ const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
// also includes methods for dealing with keywords and search engines
const Bookmarks = Module("bookmarks", {
requires: ["autocommands", "config", "dactyl", "storage", "services"],
init: function () {
const faviconService = services.get("favicon");
const bookmarksService = services.get("bookmarks");
const historyService = services.get("history");
const tagging = PlacesUtils.tagging;
this.getFavicon = getFavicon;
function getFavicon(uri) {
try {
return faviconService.getFaviconImageForPage(util.newURI(uri)).spec;
}
catch (e) {
return "";
}
}
// Fix for strange Firefox bug:
// Error: [Exception... "Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIObserverService.addObserver]"
// nsresult: "0x8000ffff (NS_ERROR_UNEXPECTED)"
// location: "JS frame :: file://~firefox/components/nsTaggingService.js :: anonymous :: line 89"
// data: no]
// Source file: file://~firefox/components/nsTaggingService.js
tagging.getTagsForURI(window.makeURI("http://mysterious.bug"), {});
const Bookmark = Struct("url", "title", "icon", "keyword", "tags", "id");
const Keyword = Struct("keyword", "title", "icon", "url");
Bookmark.defaultValue("icon", function () getFavicon(this.url));
Bookmark.prototype.__defineGetter__("extra", function () [
["keyword", this.keyword, "Keyword"],
["tags", this.tags.join(", "), "Tag"]
].filter(function (item) item[1]));
const storage = modules.storage;
function Cache(name, store) {
const rootFolders = [bookmarksService.toolbarFolder, bookmarksService.bookmarksMenuFolder, bookmarksService.unfiledBookmarksFolder];
const sleep = dactyl.sleep; // Storage objects are global to all windows, 'dactyl' isn't.
let bookmarks = [];
let self = this;
this.__defineGetter__("name", function () name);
this.__defineGetter__("store", function () store);
this.__defineGetter__("bookmarks", function () this.load());
this.__defineGetter__("keywords",
function () [Keyword(k.keyword, k.title, k.icon, k.url) for ([, k] in Iterator(self.bookmarks)) if (k.keyword)]);
this.__iterator__ = function () (val for ([, val] in Iterator(self.bookmarks)));
function loadBookmark(node) {
if (node.uri == null) // How does this happen?
return false;
let uri = util.newURI(node.uri);
let keyword = bookmarksService.getKeywordForBookmark(node.itemId);
let tags = tagging.getTagsForURI(uri, {}) || [];
let bmark = Bookmark(node.uri, node.title, node.icon && node.icon.spec, keyword, tags, node.itemId);
bookmarks.push(bmark);
return bmark;
}
function readBookmark(id) {
return {
itemId: id,
uri: bookmarksService.getBookmarkURI(id).spec,
title: bookmarksService.getItemTitle(id)
};
}
function deleteBookmark(id) {
let length = bookmarks.length;
bookmarks = bookmarks.filter(function (item) item.id != id);
return bookmarks.length < length;
}
this.findRoot = function findRoot(id) {
do {
var root = id;
id = bookmarksService.getFolderIdForItem(id);
} while (id != bookmarksService.placesRoot && id != root);
return root;
};
this.isBookmark = function (id) rootFolders.indexOf(self.findRoot(id)) >= 0;
this.isRegularBookmark = function findRoot(id) {
do {
var root = id;
if (services.get("livemark") && services.get("livemark").isLivemark(id))
return false;
id = bookmarksService.getFolderIdForItem(id);
} while (id != bookmarksService.placesRoot && id != root);
return rootFolders.indexOf(root) >= 0;
};
// since we don't use a threaded bookmark loading (by set preload)
// anymore, is this loading synchronization still needed? --mst
let loading = false;
this.load = function load() {
if (loading) {
while (loading)
sleep(10);
return bookmarks;
}
// update our bookmark cache
bookmarks = [];
loading = true;
let folders = rootFolders.slice();
let query = historyService.getNewQuery();
let options = historyService.getNewQueryOptions();
while (folders.length > 0) {
query.setFolders(folders, 1);
folders.shift();
let result = historyService.executeQuery(query, options);
let folder = result.root;
folder.containerOpen = true;
// iterate over the immediate children of this folder
for (let i = 0; i < folder.childCount; i++) {
let node = folder.getChild(i);
if (node.type == node.RESULT_TYPE_FOLDER) // folder
folders.push(node.itemId);
else if (node.type == node.RESULT_TYPE_URI) // bookmark
loadBookmark(node);
}
// close a container after using it!
folder.containerOpen = false;
}
this.__defineGetter__("bookmarks", function () bookmarks);
loading = false;
return bookmarks;
};
var observer = {
onBeginUpdateBatch: function onBeginUpdateBatch() {},
onEndUpdateBatch: function onEndUpdateBatch() {},
onItemVisited: function onItemVisited() {},
onItemMoved: function onItemMoved() {},
onItemAdded: function onItemAdded(itemId, folder, index) {
// dactyl.dump("onItemAdded(" + itemId + ", " + folder + ", " + index + ")\n");
if (bookmarksService.getItemType(itemId) == bookmarksService.TYPE_BOOKMARK) {
if (self.isBookmark(itemId)) {
let bmark = loadBookmark(readBookmark(itemId));
storage.fireEvent(name, "add", bmark);
}
}
},
onItemRemoved: function onItemRemoved(itemId, folder, index) {
// dactyl.dump("onItemRemoved(" + itemId + ", " + folder + ", " + index + ")\n");
if (deleteBookmark(itemId))
storage.fireEvent(name, "remove", itemId);
},
onItemChanged: function onItemChanged(itemId, property, isAnnotation, value) {
if (isAnnotation)
return;
// dactyl.dump("onItemChanged(" + itemId + ", " + property + ", " + value + ")\n");
let bookmark = bookmarks.filter(function (item) item.id == itemId)[0];
if (bookmark) {
if (property == "tags")
value = tagging.getTagsForURI(util.newURI(bookmark.url), {});
if (property in bookmark)
bookmark[property] = value;
storage.fireEvent(name, "change", itemId);
}
},
QueryInterface: function QueryInterface(iid) {
if (iid.equals(Ci.nsINavBookmarkObserver) || iid.equals(Ci.nsISupports))
return this;
throw Cr.NS_ERROR_NO_INTERFACE;
}
};
bookmarksService.addObserver(observer, false);
}
let bookmarkObserver = function (key, event, arg) {
if (event == "add")
autocommands.trigger("BookmarkAdd", arg);
statusline.updateUrl();
};
this._cache = storage.newObject("bookmark-cache", Cache, { store: false });
storage.addObserver("bookmark-cache", bookmarkObserver, window);
},
@@ -217,7 +37,7 @@ const Bookmarks = Module("bookmarks", {
try {
let uri = util.createURI(url);
if (!force) {
for (let bmark in this._cache) {
for (let bmark in bookmarkcache) {
if (bmark[0] == uri.spec) {
var id = bmark[5];
if (title)
@@ -268,7 +88,7 @@ const Bookmarks = Module("bookmarks", {
isBookmarked: function isBookmarked(url) {
try {
return services.get("bookmarks").getBookmarkIdsForURI(makeURI(url), {})
.some(this._cache.isRegularBookmark);
.some(bookmarkcache.isRegularBookmark);
}
catch (e) {
return false;
@@ -280,7 +100,7 @@ const Bookmarks = Module("bookmarks", {
try {
let uri = util.newURI(url);
let bmarks = services.get("bookmarks").getBookmarkIdsForURI(uri, {})
.filter(this._cache.isRegularBookmark);
.filter(bookmarkcache.isRegularBookmark);
bmarks.forEach(services.get("bookmarks").removeItem);
return bmarks.length;
}
@@ -350,7 +170,7 @@ const Bookmarks = Module("bookmarks", {
// format of returned array:
// [keyword, helptext, url]
getKeywords: function getKeywords() {
return this._cache.keywords;
return bookmarkcache.keywords;
},
// full search string including engine name as first word in @param text
@@ -451,38 +271,53 @@ const Bookmarks = Module("bookmarks", {
"Show jumplist",
function () {
let sh = history.session;
let list = template.jumps(sh.index, sh);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
commandline.commandOutput(template.jumps(sh.index, sh));
},
{ argCount: "0" });
// TODO: Clean this up.
function tags(context, args) {
let filter = context.filter;
let have = filter.split(",");
const tags = {
names: ["-tags", "-T"],
description: "A comma-separated list of tags",
completer: function tags(context, args) {
let filter = context.filter;
let have = filter.split(",");
args.completeFilter = have.pop();
args.completeFilter = have.pop();
let prefix = filter.substr(0, filter.length - args.completeFilter.length);
let tags = array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(this._cache.bookmarks))]));
let prefix = filter.substr(0, filter.length - args.completeFilter.length);
let tags = array.uniq(util.Array.flatten([b.tags for ([k, b] in Iterator(bookmarkcache.bookmarks))]));
return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)];
}
return [[prefix + tag, tag] for ([i, tag] in Iterator(tags)) if (have.indexOf(tag) < 0)];
},
type: CommandOption.LIST
};
function title(context, args) {
if (!args.bang)
return [[content.document.title, "Current Page Title"]];
context.keys.text = "title";
context.keys.description = "url";
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
}
const title = {
names: ["-title", "-t"],
description: "Bookmark page title or description",
completer: function title(context, args) {
if (!args.bang)
return [[content.document.title, "Current Page Title"]];
context.keys.text = "title";
context.keys.description = "url";
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: args["-keyword"], title: context.filter });
},
type: CommandOption.STRING
};
function keyword(context, args) {
if (!args.bang)
return [];
context.keys.text = "keyword";
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
}
const keyword = {
names: ["-keyword", "-k"],
description: "Keyword by which this bookmark may be opened (:open {keyword})",
completer: function keyword(context, args) {
if (!args.bang)
return [];
context.keys.text = "keyword";
return bookmarks.get(args.join(" "), args["-tags"], null, { keyword: context.filter, title: args["-title"] });
},
type: CommandOption.STRING,
validator: function (arg) /^\S+$/.test(arg)
};
commands.add(["bma[rk]"],
"Add a bookmark",
@@ -503,14 +338,13 @@ const Bookmarks = Module("bookmarks", {
bang: true,
completer: function (context, args) {
if (!args.bang) {
context.title = ["Page URL"];
context.completions = [[content.document.documentURI, "Current Location"]];
return;
}
completion.bookmark(context, args["-tags"], { keyword: args["-keyword"], title: args["-title"] });
},
options: [[["-title", "-t"], commands.OPTION_STRING, null, title],
[["-tags", "-T"], commands.OPTION_LIST, null, tags],
[["-keyword", "-k"], commands.OPTION_STRING, function (arg) /\w/.test(arg)]]
options: [title, tags, keyword]
});
commands.add(["bmarks"],
@@ -525,22 +359,26 @@ const Bookmarks = Module("bookmarks", {
context.filter = args.join(" ");
completion.bookmark(context, args["-tags"]);
},
options: [[["-tags", "-T"], commands.OPTION_LIST, null, tags],
[["-max", "-m"], commands.OPTION_INT]]
options: [tags,
{
names: ["-max", "-m"],
description: "The maximum number of items to list or open",
type: CommandOption.INT
}
]
});
commands.add(["delbm[arks]"],
"Delete a bookmark",
function (args) {
if (args.bang) {
if (args.bang)
commandline.input("This will delete all bookmarks. Would you like to continue? (yes/[no]) ",
function (resp) {
if (resp && resp.match(/^y(es)?$/i)) {
bookmarks._cache.bookmarks.forEach(function (bmark) { services.get("bookmarks").removeItem(bmark.id); });
bookmarkcache.bookmarks.forEach(function (bmark) { services.get("bookmarks").removeItem(bmark.id); });
dactyl.echomsg("All bookmarks deleted", 1, commandline.FORCE_SINGLELINE);
}
});
}
else {
let url = args.string || buffer.URL;
let deletedCount = bookmarks.remove(url);
@@ -621,9 +459,7 @@ const Bookmarks = Module("bookmarks", {
if (v)
context.filters.push(function (item) this._match(v, item[k]));
}
// Need to make a copy because set completions() checks instanceof Array,
// and this may be an Array from another window.
context.completions = Array.slice(storage["bookmark-cache"].bookmarks);
context.completions = bookmarkcache.bookmarks;
completion.urls(context, tags);
};

View File

@@ -8,8 +8,6 @@
/** @scope modules */
Components.utils.import("resource://gre/modules/XPCOMUtils.jsm", modules);
const Point = Struct("x", "y");
/**
@@ -19,8 +17,6 @@ const Point = Struct("x", "y");
* @instance buffer
*/
const Buffer = Module("buffer", {
requires: ["config", "util"],
init: function () {
this.evaluateXPath = util.evaluateXPath;
this.pageInfo = {};
@@ -146,10 +142,6 @@ const Buffer = Module("buffer", {
},
destroy: function () {
try {
config.browser.removeProgressListener(this.progressListener);
}
catch (e) {} // Why? --djk
},
_triggerLoadAutocmd: function _triggerLoadAutocmd(name, doc) {
@@ -214,14 +206,10 @@ const Buffer = Module("buffer", {
/**
* @property {Object} The document loading progress listener.
*/
progressListener: {
QueryInterface: XPCOMUtils.generateQI([
Ci.nsIWebProgressListener,
Ci.nsIXULBrowserWindow
]),
progressListener: update({ __proto__: window.XULBrowserWindow }, {
// XXX: function may later be needed to detect a canceled synchronous openURL()
onStateChange: function (webProgress, request, flags, status) {
onStateChange: function onStateChange(webProgress, request, flags, status) {
onStateChange.superapply(this, arguments);
// STATE_IS_DOCUMENT | STATE_IS_WINDOW is important, because we also
// receive statechange events for loading images and other parts of the web page
if (flags & (Ci.nsIWebProgressListener.STATE_IS_DOCUMENT | Ci.nsIWebProgressListener.STATE_IS_WINDOW)) {
@@ -247,7 +235,8 @@ const Buffer = Module("buffer", {
}
},
// for notifying the user about secure web pages
onSecurityChange: function (webProgress, request, state) {
onSecurityChange: function onSecurityChange(webProgress, request, state) {
onSecurityChange.superapply(this, arguments);
// TODO: do something useful with STATE_SECURE_MED and STATE_SECURE_LOW
if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
statusline.setClass("insecure");
@@ -258,14 +247,17 @@ const Buffer = Module("buffer", {
else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
statusline.setClass("secure");
},
onStatusChange: function (webProgress, request, status, message) {
onStatusChange: function onStatusChange(webProgress, request, status, message) {
onStatusChange.superapply(this, arguments);
statusline.updateUrl(message);
},
onProgressChange: function (webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
onProgressChange: function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
onProgressChange.superapply(this, arguments);
statusline.updateProgress(curTotalProgress/maxTotalProgress);
},
// happens when the users switches tabs
onLocationChange: function () {
onLocationChange: function onLocationChange() {
onLocationChange.superapply(this, arguments);
statusline.updateUrl();
statusline.updateProgress();
@@ -279,10 +271,12 @@ const Buffer = Module("buffer", {
}, 500);
},
// called at the very end of a page load
asyncUpdateUI: function () {
asyncUpdateUI: function asyncUpdateUI() {
asyncUpdateUI.superapply(this, arguments);
setTimeout(function () { statusline.updateUrl(); }, 100);
},
setOverLink: function (link, b) {
setOverLink: function setOverLink(link, b) {
setOverLink.superapply(this, arguments);
let ssli = options["showstatuslinks"];
if (link && ssli) {
if (ssli == 1)
@@ -298,15 +292,7 @@ const Buffer = Module("buffer", {
modes.show();
}
},
// nsIXULBrowserWindow stubs
setJSDefaultStatus: function (status) {},
setJSStatus: function (status) {},
// Stub for something else, presumably. Not in any documented
// interface.
onLinkIconAvailable: function () {}
},
}),
/**
* @property {Array} The alternative style sheets for the current
@@ -320,19 +306,6 @@ const Buffer = Module("buffer", {
);
},
/**
* @property {Array[Window]} All frames in the current buffer.
*/
get allFrames() {
let frames = [];
(function (frame) {
if (frame.document.body instanceof HTMLBodyElement)
frames.push(frame);
Array.forEach(frame.frames, arguments.callee);
})(window.content);
return frames;
},
/**
* @property {Object} A map of page info sections to their
* content generating functions.
@@ -447,6 +420,19 @@ const Buffer = Module("buffer", {
this.pageInfo[option] = [func, title];
},
/**
* Returns a list of all frames in the given current buffer.
*/
allFrames: function (win) {
let frames = [];
(function rec(frame) {
if (frame.document.body instanceof HTMLBodyElement)
frames.push(frame);
Array.forEach(frame.frames, rec);
})(win || window.content);
return frames;
},
/**
* Returns the currently selected word. If the selection is
* null, it tries to guess the word that the caret is
@@ -581,7 +567,7 @@ const Buffer = Module("buffer", {
let ret = followFrame(window.content);
if (!ret)
// only loop through frames if the main content didn't match
ret = Array.some(buffer.allFrames.frames, followFrame);
ret = Array.some(buffer.allFrames().frames, followFrame);
if (!ret)
dactyl.beep();
@@ -805,7 +791,7 @@ const Buffer = Module("buffer", {
return;
count = Math.max(count, 1);
let frames = buffer.allFrames;
let frames = buffer.allFrames();
if (frames.length == 0) // currently top is always included
return;
@@ -1367,7 +1353,11 @@ const Buffer = Module("buffer", {
};
},
events: function () {
/*
try {
config.browser.removeProgressListener(window.XULBrowserWindow);
}
catch (e) {} // Why? --djk
config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
window.XULBrowserWindow = this.progressListener;
window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
@@ -1376,12 +1366,6 @@ const Buffer = Module("buffer", {
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIXULWindow)
.XULBrowserWindow = this.progressListener;
*/
try {
config.browser.addProgressListener(this.progressListener, Ci.nsIWebProgress.NOTIFY_ALL);
}
catch (e) {} // Why? --djk
let appContent = document.getElementById("appcontent");
if (appContent) {

View File

@@ -15,8 +15,6 @@
* this class when the chrome is ready.
*/
const CommandLine = Module("commandline", {
requires: ["config", "dactyl", "modes", "services", "storage", "template", "util"],
init: function () {
const self = this;
@@ -95,7 +93,7 @@ const CommandLine = Module("commandline", {
////////////////////// TIMERS //////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
this._statusTimer = new Timer(5, 100, function statusTell() {
this._statusTimer = Timer(5, 100, function statusTell() {
if (self._completions == null)
return;
if (self._completions.selected == null)
@@ -104,7 +102,7 @@ const CommandLine = Module("commandline", {
statusline.updateProgress("match " + (self._completions.selected + 1) + " of " + self._completions.items.length);
});
this._autocompleteTimer = new Timer(200, 500, function autocompleteTell(tabPressed) {
this._autocompleteTimer = Timer(200, 500, function autocompleteTell(tabPressed) {
if (!events.feedingKeys && self._completions && options.get("autocomplete").values.length) {
self._completions.complete(true, false);
self._completions.itemList.show();
@@ -114,8 +112,8 @@ const CommandLine = Module("commandline", {
// This timer just prevents <Tab>s from queueing up when the
// system is under load (and, thus, giving us several minutes of
// the completion list scrolling). Multiple <Tab> presses are
// still processed normally, as the time is flushed on "keyup".
this._tabTimer = new Timer(0, 0, function tabTell(event) {
// still processed normally, as the timer is flushed on "keyup".
this._tabTimer = Timer(0, 0, function tabTell(event) {
if (self._completions)
self._completions.tab(event.shiftKey);
});
@@ -463,6 +461,18 @@ const CommandLine = Module("commandline", {
this._keepCommand = false;
},
/**
* Displays the multi-line output of a command, preceded by the last
* executed ex command string.
*
* @param {XML} xml The output as an E4X XML object.
*/
commandOutput: function (xml) {
XML.ignoreWhitespace = false;
XML.prettyPrinting = false;
this.echo(<>:{this.command}<br/>{xml}</>, this.HIGHLIGHT_NORMAL, this.FORCE_MULTILINE);
},
/**
* Hides the command line, and shows any status messages that
* are under it.
@@ -492,7 +502,7 @@ const CommandLine = Module("commandline", {
* commandline.FORCE_MULTILINE - Forces the message to appear in
* the MOW.
*/
echo: function echo(str, highlightGroup, flags) {
echo: requiresMainThread(function echo(str, highlightGroup, flags) {
// dactyl.echo uses different order of flags as it omits the highlight group, change commandline.echo argument order? --mst
if (this._silent)
return;
@@ -506,37 +516,34 @@ const CommandLine = Module("commandline", {
services.get("windowWatcher").activeWindow.dactyl)
return;
// The DOM isn't threadsafe. It must only be accessed from the main thread.
util.callInMainThread(function () {
if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed)
return;
if ((flags & this.DISALLOW_MULTILINE) && !this._outputContainer.collapsed)
return;
let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
let action = this._echoLine;
let single = flags & (this.FORCE_SINGLELINE | this.DISALLOW_MULTILINE);
let action = this._echoLine;
// TODO: this is all a bit convoluted - clean up.
// assume that FORCE_MULTILINE output is fully styled
if (!(flags & this.FORCE_MULTILINE) && !single && (!this._outputContainer.collapsed || this.widgets.message.value == this._lastEcho)) {
highlightGroup += " Message";
action = this._echoMultiline;
}
// TODO: this is all a bit convoluted - clean up.
// assume that FORCE_MULTILINE output is fully styled
if (!(flags & this.FORCE_MULTILINE) && !single && (!this._outputContainer.collapsed || this.widgets.message.value == this._lastEcho)) {
highlightGroup += " Message";
action = this._echoMultiline;
}
if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || typeof str == "xml") && !(flags & this.FORCE_SINGLELINE))
action = this._echoMultiline;
if ((flags & this.FORCE_MULTILINE) || (/\n/.test(str) || typeof str == "xml") && !(flags & this.FORCE_SINGLELINE))
action = this._echoMultiline;
if (single)
this._lastEcho = null;
else {
if (this.widgets.message.value == this._lastEcho)
this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>,
this.widgets.message.getAttributeNS(NS.uri, "highlight"));
this._lastEcho = (action == this._echoLine) && str;
}
if (single)
this._lastEcho = null;
else {
if (this.widgets.message.value == this._lastEcho)
this._echoMultiline(<span highlight="Message">{this._lastEcho}</span>,
this.widgets.message.getAttributeNS(NS.uri, "highlight"));
this._lastEcho = (action == this._echoLine) && str;
}
if (action)
action.call(this, str, highlightGroup, single);
}, this);
},
if (action)
action.call(this, str, highlightGroup, single);
}),
/**
* Prompt the user. Sets modes.main to COMMAND_LINE, which the user may
@@ -675,7 +682,7 @@ const CommandLine = Module("commandline", {
modes.pop();
}
}
else { // any other key
else {
//this.resetCompletions();
}
// allow this event to be handled by the host app
@@ -1097,7 +1104,7 @@ const CommandLine = Module("commandline", {
}
let hist = this.store.get(this.index);
// user pressed DOWN when there is no newer this._history item
// user pressed DOWN when there is no newer history item
if (!hist)
hist = this.original;
else
@@ -1165,13 +1172,6 @@ const CommandLine = Module("commandline", {
get wildtype() this.wildtypes[this.wildIndex] || "",
get type() ({
list: this.wildmode.checkHas(this.wildtype, "list"),
longest: this.wildmode.checkHas(this.wildtype, "longest"),
first: this.wildmode.checkHas(this.wildtype, ""),
full: this.wildmode.checkHas(this.wildtype, "full")
}),
complete: function complete(show, tabPressed) {
this.context.reset();
this.context.tabPressed = tabPressed;
@@ -1181,6 +1181,9 @@ const CommandLine = Module("commandline", {
this.wildIndex = 0;
},
haveType: function (type)
this.wildmode.checkHas(this.wildtype, type == "first" ? "" : type),
preview: function preview() {
this.previewClear();
if (this.wildIndex < 0 || this.suffix || !this.items.length)
@@ -1364,7 +1367,7 @@ const CommandLine = Module("commandline", {
break;
}
if (this.type.list)
if (this.haveType("list"))
this.itemList.show();
this.wildIndex = Math.constrain(this.wildIndex + 1, 0, this.wildtypes.length - 1);
@@ -1549,17 +1552,14 @@ const ItemList = Class("ItemList", {
this._completionElements = [];
var iframe = document.getElementById(id);
if (!iframe) {
dactyl.log("No iframe with id: " + id + " found, strange things may happen!"); // "The truth is out there..." -- djk
return; // XXX
}
this._doc = iframe.contentDocument;
this._win = iframe.contentWindow;
this._container = iframe.parentNode;
this._doc.body.id = id + "-content";
this._doc.body.appendChild(this._doc.createTextNode(""));
this._doc.body.style.borderTop = "1px solid black"; // FIXME: For cases where this._completions/MOW are shown at once, or ls=0. Should use :highlight.
this._doc.body.style.borderTop = "1px solid black"; // FIXME: For cases where completions/MOW are shown at once, or ls=0. Should use :highlight.
this._gradient = template.gradient("GradientLeft", "GradientRight");
@@ -1578,7 +1578,8 @@ const ItemList = Class("ItemList", {
if (this._container.collapsed)
this._div.style.minWidth = document.getElementById("dactyl-commandline").scrollWidth + "px";
this._minHeight = Math.max(this._minHeight, this._divNodes.completions.getBoundingClientRect().bottom);
this._minHeight = Math.max(this._minHeight,
this._win.scrollY + this._divNodes.completions.getBoundingClientRect().bottom);
this._container.height = this._minHeight;
if (this._container.collapsed)

View File

@@ -11,6 +11,75 @@
// Do NOT create instances of this class yourself, use the helper method
// commands.add() instead
/**
* A structure representing the options available for a command.
*
* @property {[string]} names An array of option names. The first name
* is the canonical option name.
* @property {number} type The option's value type. This is one of:
* (@link CommandOption.NOARG),
* (@link CommandOption.STRING),
* (@link CommandOption.BOOL),
* (@link CommandOption.INT),
* (@link CommandOption.FLOAT),
* (@link CommandOption.LIST),
* (@link CommandOption.ANY)
* @property {function} validator A validator function
* @property {function (CompletionContext, object)} completer A list of
* completions, or a completion function which will be passed a
* {@link CompletionContext} and an object like that returned by
* {@link commands.parseArgs} with the following additional keys:
* completeOpt - The name of the option currently being completed.
* @property {boolean} multiple Whether this option can be specified multiple times
* @property {string} description A description of the option
*/
const CommandOption = Struct("names", "type", "validator", "completer", "multiple", "description");
CommandOption.defaultValue("description", function () "");
CommandOption.defaultValue("type", function () CommandOption.NOARG);
CommandOption.defaultValue("multiple", function () false);
update(CommandOption, {
// FIXME: remove later, when our option handler is better
/**
* @property {number} The option argument is unspecified. Any argument
* is accepted and caller is responsible for parsing the return
* value.
* @final
*/
ANY: 0,
/**
* @property {number} The option doesn't accept an argument.
* @final
*/
NOARG: 1,
/**
* @property {number} The option accepts a boolean argument.
* @final
*/
BOOL: 2,
/**
* @property {number} The option accepts a string argument.
* @final
*/
STRING: 3,
/**
* @property {number} The option accepts an integer argument.
* @final
*/
INT: 4,
/**
* @property {number} The option accepts a float argument.
* @final
*/
FLOAT: 5,
/**
* @property {number} The option accepts a string list argument.
* E.g. "foo,bar"
* @final
*/
LIST: 6
});
/**
* A class representing Ex commands. Instances are created by
* the {@link Commands} class.
@@ -35,8 +104,6 @@
* @private
*/
const Command = Class("Command", {
requires: ["config"],
init: function (specs, description, action, extraInfo) {
specs = Array.concat(specs); // XXX
let parsedSpecs = Command.parseSpecs(specs);
@@ -51,6 +118,8 @@ const Command = Class("Command", {
if (extraInfo)
update(this, extraInfo);
if (this.options)
this.options = this.options.map(CommandOption.fromArray, CommandOption);
},
/**
@@ -191,7 +260,7 @@ const Command = Class("Command", {
* invocation which should be restored on subsequent @dactyl
* startups.
*/
serial: null,
serialize: null,
/**
* @property {boolean} When true, invocations of this command
* may contain private data which should be purged from
@@ -240,47 +309,6 @@ const Commands = Module("commands", {
this._exMap = {};
},
// FIXME: remove later, when our option handler is better
/**
* @property {number} The option argument is unspecified. Any argument
* is accepted and caller is responsible for parsing the return
* value.
* @final
*/
OPTION_ANY: 0,
/**
* @property {number} The option doesn't accept an argument.
* @final
*/
OPTION_NOARG: 1,
/**
* @property {number} The option accepts a boolean argument.
* @final
*/
OPTION_BOOL: 2,
/**
* @property {number} The option accepts a string argument.
* @final
*/
OPTION_STRING: 3,
/**
* @property {number} The option accepts an integer argument.
* @final
*/
OPTION_INT: 4,
/**
* @property {number} The option accepts a float argument.
* @final
*/
OPTION_FLOAT: 5,
/**
* @property {number} The option accepts a string list argument.
* E.g. "foo,bar"
* @final
*/
OPTION_LIST: 6,
/**
* @property Indicates that no count was specified for this
* command invocation.
@@ -442,29 +470,8 @@ const Commands = Module("commands", {
*
* @param {string} str The Ex command-line string to parse. E.g.
* "-x=foo -opt=bar arg1 arg2"
* @param {Array} options The options accepted. These are specified as
* an array [names, type, validator, completions, multiple].
* names - an array of option names. The first name is the
* canonical option name.
* type - the option's value type. This is one of:
* (@link Commands#OPTION_NOARG),
* (@link Commands#OPTION_STRING),
* (@link Commands#OPTION_BOOL),
* (@link Commands#OPTION_INT),
* (@link Commands#OPTION_FLOAT),
* (@link Commands#OPTION_LIST),
* (@link Commands#OPTION_ANY)
* validator - a validator function
* completer - a list of completions, or a completion function
* multiple - whether this option can be specified multiple times
* E.g.
* options = [[["-force"], OPTION_NOARG],
* [["-fullscreen", "-f"], OPTION_BOOL],
* [["-language"], OPTION_STRING, validateFunc, ["perl", "ruby"]],
* [["-speed"], OPTION_INT],
* [["-acceleration"], OPTION_FLOAT],
* [["-accessories"], OPTION_LIST, null, ["foo", "bar"], true],
* [["-other"], OPTION_ANY]];
* @param {[CommandOption]} options The options accepted. These are specified
* as an array of {@link CommandOption} structures.
* @param {string} argCount The number of arguments accepted.
* "0": no arguments
* "1": exactly one argument
@@ -518,7 +525,7 @@ const Commands = Module("commands", {
function matchOpts(arg) {
// Push possible option matches into completions
if (complete && !onlyArgumentsRemaining)
completeOpts = [[opt[0], opt[0][0]] for ([i, opt] in Iterator(options)) if (!(opt[0][0] in args))];
completeOpts = options.filter(function (opt) opt.multiple || !(opt.names[0] in args));
}
function resetCompletions() {
completeOpts = null;
@@ -562,14 +569,14 @@ const Commands = Module("commands", {
var optname = "";
if (!onlyArgumentsRemaining) {
for (let [, opt] in Iterator(options)) {
for (let [, optname] in Iterator(opt[0])) {
for (let [, optname] in Iterator(opt.names)) {
if (sub.indexOf(optname) == 0) {
invalid = false;
arg = null;
quote = null;
count = 0;
let sep = sub[optname.length];
if (sep == "=" || /\s/.test(sep) && opt[1] != this.OPTION_NOARG) {
if (sep == "=" || /\s/.test(sep) && opt.type != CommandOption.NOARG) {
[count, arg, quote, error] = getNextArg(sub.substr(optname.length + 1));
dactyl.assert(!error, error);
@@ -595,7 +602,7 @@ const Commands = Module("commands", {
args.completeFilter = arg;
args.quote = Commands.complQuote[quote] || Commands.complQuote[""];
}
let type = Commands.argTypes[opt[1]];
let type = Commands.argTypes[opt.type];
if (type && (!complete || arg != null)) {
let orig = arg;
arg = type.parse(arg);
@@ -610,8 +617,8 @@ const Commands = Module("commands", {
}
// we have a validator function
if (typeof opt[2] == "function") {
if (opt[2].call(this, arg) == false) {
if (typeof opt.validator == "function") {
if (opt.validator.call(this, arg) == false) {
echoerr("Invalid argument for option: " + optname);
if (complete)
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
@@ -620,11 +627,13 @@ const Commands = Module("commands", {
}
}
matchOpts(sub);
// option allowed multiple times
if (!!opt[4])
args[opt[0][0]] = (args[opt[0][0]] || []).concat(arg);
if (opt.multiple)
args[opt.names[0]] = (args[opt.names[0]] || []).concat(arg);
else
args[opt[0][0]] = opt[1] == this.OPTION_NOARG || arg;
args[opt.names[0]] = opt.type == this.OPTION_NOARG || arg;
i += optname.length + count;
if (i == str.length)
@@ -683,17 +692,18 @@ const Commands = Module("commands", {
if (complete) {
if (args.completeOpt) {
let opt = args.completeOpt;
let context = complete.fork(opt[0][0], args.completeStart);
let context = complete.fork(opt.names[0], args.completeStart);
context.filter = args.completeFilter;
if (typeof opt[3] == "function")
var compl = opt[3](context, args);
if (typeof opt.completer == "function")
var compl = opt.completer(context, args);
else
compl = opt[3] || [];
context.title = [opt[0][0]];
compl = opt.completer || [];
context.title = [opt.names[0]];
context.quote = args.quote;
context.completions = compl;
}
complete.advance(args.completeStart);
complete.keys = { text: "names", description: "description" };
complete.title = ["Options"];
if (completeOpts)
complete.completions = completeOpts;
@@ -1012,18 +1022,16 @@ const Commands = Module("commands", {
// : No, array comprehensions are fine, generator statements aren't. --Kris
let cmds = this._exCommands.filter(function (c) c.user && (!cmd || c.name.match("^" + cmd)));
if (cmds.length > 0) {
let str = template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"],
([cmd.bang ? "!" : " ",
cmd.name,
cmd.argCount,
cmd.count ? "0c" : "",
completerToString(cmd.completer),
cmd.replacementText || "function () { ... }"]
for ([, cmd] in Iterator(cmds))));
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
if (cmds.length > 0)
commandline.commandOutput(
template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"]),
([cmd.bang ? "!" : " ",
cmd.name,
cmd.argCount,
cmd.count ? "0c" : "",
completerToString(cmd.completer),
cmd.replacementText || "function () { ... }"]
for ([, cmd] in Iterator(cmds))));
else
dactyl.echomsg("No user-defined commands found");
}
@@ -1036,23 +1044,33 @@ const Commands = Module("commands", {
completion.ex(context);
},
options: [
[["-nargs"], commands.OPTION_STRING,
function (arg) /^[01*?+]$/.test(arg),
[["0", "No arguments are allowed (default)"],
["1", "One argument is allowed"],
["*", "Zero or more arguments are allowed"],
["?", "Zero or one argument is allowed"],
["+", "One or more arguments is allowed"]]],
[["-bang"], commands.OPTION_NOARG],
[["-count"], commands.OPTION_NOARG],
[["-description"], commands.OPTION_STRING],
// TODO: "E180: invalid complete value: " + arg
[["-complete"], commands.OPTION_STRING,
function (arg) arg in completeOptionMap || /custom,\w+/.test(arg),
function (context) [[k, ""] for ([k, v] in Iterator(completeOptionMap))]]
{ names: ["-bang"], description: "Command may be proceeded by a !" },
{ names: ["-count"], description: "Command may be preceeded by a count" },
{
names: ["-description"],
description: "A user-visible description of the command",
type: CommandOption.STRING
}, {
// TODO: "E180: invalid complete value: " + arg
names: ["-complete"],
description: "The argument completion function",
completer: function (context) [[k, ""] for ([k, v] in Iterator(completeOptionMap))],
type: CommandOption.STRING,
validator: function (arg) arg in completeOptionMap || /custom,\w+/.test(arg),
}, {
names: ["-nargs"],
description: "The allowed number of arguments",
completer: [["0", "No arguments are allowed (default)"],
["1", "One argument is allowed"],
["*", "Zero or more arguments are allowed"],
["?", "Zero or one argument is allowed"],
["+", "One or more arguments are allowed"]],
type: CommandOption.STRING,
validator: function (arg) /^[01*?+]$/.test(arg)
},
],
literal: 1,
serial: function () [ {
serialize: function () [ {
command: this.name,
bang: true,
options: util.Array.toObject(

View File

@@ -297,7 +297,7 @@ const CompletionContext = Class("CompletionContext", {
let [k, v] = i;
let _k = "_" + k;
if (typeof v == "string" && /^[.[]/.test(v))
v = eval("(function (i) i" + v + ")");
v = Function("i", "return i" + v);
if (typeof v == "function")
res.__defineGetter__(k, function () _k in this ? this[_k] : (this[_k] = v(this.item)));
else
@@ -325,7 +325,7 @@ const CompletionContext = Class("CompletionContext", {
let lock = {};
this.cache.backgroundLock = lock;
this.incomplete = true;
let thread = this.getCache("backgroundThread", dactyl.newThread);
let thread = this.getCache("backgroundThread", util.newThread);
util.callAsync(thread, this, function () {
if (this.cache.backgroundLock != lock)
return;
@@ -510,7 +510,7 @@ const CompletionContext = Class("CompletionContext", {
if (!context.autoComplete && !context.tabPressed && context.editor)
context.waitingForTab = true;
else if (completer)
return completer.apply(self || this, [context].concat(Array.slice(arguments, arguments.callee.length)));
return completer.apply(self || this, [context].concat(Array.slice(arguments, fork.length)));
if (completer)
return null;
@@ -668,14 +668,13 @@ const Completion = Module("completion", {
context = context.contexts["/list"];
context.wait();
let list = template.commandOutput(commandline.command,
commandline.commandOutput(
<div highlight="Completions">
{ template.map(context.contextList.filter(function (c) c.hasItems),
function (context)
template.completionRow(context.title, "CompTitle") +
template.map(context.items, function (item) context.createRow(item), null, 100)) }
</div>);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
},
////////////////////////////////////////////////////////////////////////////////
@@ -765,13 +764,11 @@ const Completion = Module("completion", {
commands.add(["contexts"],
"List the completion contexts used during completion of an ex command",
function (args) {
commandline.echo(template.commandOutput(commandline.command,
commandline.commandOutput(
<div highlight="Completions">
{ template.completionRow(["Context", "Title"], "CompTitle") }
{ template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
</div>),
null, commandline.FORCE_MULTILINE);
</div>);
},
{
argCount: "1",

View File

@@ -229,13 +229,13 @@ const ConfigBase = Class(ModuleBase, {
HelpOptionalArg color: #6A97D4;
HelpBody display: block; margin: 1em auto; max-width: 100ex;
HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid;
HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid;
HelpCode display: block; white-space: pre; margin-left: 2em; font-family: Terminus, Fixed, monospace;
HelpDefault margin-right: 1ex; white-space: pre;
HelpDescription display: block;
HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal;
HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal;
HelpEx display: inline-block; color: #527BBD; font-weight: bold;
@@ -250,21 +250,21 @@ const ConfigBase = Class(ModuleBase, {
HelpKey color: #102663;
HelpLink,html|a,dactyl://help/* text-decoration: none;
HelpLink,html|a,dactyl://help/* text-decoration: none;
HelpLink[href]:hover text-decoration: underline;
HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc;
HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal;
HelpListItem,html|li,dactyl://help/* display: list-item;
HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc;
HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal;
HelpListItem,html|li,dactyl://help/* display: list-item;
HelpNote color: red; font-weight: bold;
HelpOpt color: #106326;
HelpOptInfo display: inline-block; margin-bottom: 1ex;
HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em;
HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em;
HelpParagraph:first-child margin-top: 0;
HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD;
HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD; margin-right: 2em;
HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top;
HelpString::before content: '"';

View File

@@ -1,4 +1,4 @@
// Copyright (c) 2008-2008 Kris Maglione <maglione.k at Gmail>
// Copyright (c) 2008-2010 Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
@@ -11,55 +11,61 @@
modules.modules = modules;
const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Components.interfaces.mozIJSSubScriptLoader);
function load(script) {
.getService(Components.interfaces.mozIJSSubScriptLoader);
modules.load = function load(script) {
for (let [i, base] in Iterator(prefix)) {
try {
loader.loadSubScript(base + script, modules);
loader.loadSubScript(base + script + ".js", modules);
return;
}
catch (e) {
if (i + 1 < prefix.length)
continue;
if (Components.utils.reportError)
Components.utils.reportError(e);
dump("dactyl: Loading script " + script + ": " + e + "\n");
dump(e.stack + "\n");
if (e !== "Error opening input stream (invalid filename?)")
dump("dactyl: Trying: " + (base + script + ".js") + ": " + e + "\n" + e.stack);
}
}
}
try {
Components.utils.import("resource://dactyl/" + script + ".jsm", modules);
}
catch (e) {
dump("dactyl: Loading script " + script + ": " + e.result + " " + e + "\n");
dump(Error().stack + "\n");
}
};
let prefix = [BASE];
["base.js",
"modules.js",
"autocommands.js",
"buffer.js",
"commandline.js",
"commands.js",
"completion.js",
"configbase.js",
"config.js",
"dactyl.js",
"editor.js",
"events.js",
"finder.js",
"hints.js",
"io.js",
"javascript.js",
"mappings.js",
"marks.js",
"modes.js",
"options.js",
"services.js",
"statusline.js",
"style.js",
"template.js",
"util.js",
].forEach(load);
["base",
"modules",
"storage",
"util",
"autocommands",
"buffer",
"commandline",
"commands",
"completion",
"configbase",
"config",
"dactyl",
"editor",
"events",
"finder",
"highlight",
"hints",
"io",
"javascript",
"mappings",
"marks",
"modes",
"options",
"services",
"statusline",
"styles",
"template",
].forEach(modules.load);
prefix.unshift("chrome://" + modules.Config.prototype.name.toLowerCase() + "/content/");
modules.Config.prototype.scripts.forEach(load);
modules.Config.prototype.scripts.forEach(modules.load);
})();
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -8,7 +8,9 @@
/** @scope modules */
Cu.import("resource://gre/modules/XPCOMUtils.jsm", modules);
default xml namespace = XHTML;
XML.ignoreWhitespace = false;
XML.prettyPrinting = false;
const plugins = { __proto__: modules };
const userContext = { __proto__: modules };
@@ -17,27 +19,6 @@ const EVAL_ERROR = "__dactyl_eval_error";
const EVAL_RESULT = "__dactyl_eval_result";
const EVAL_STRING = "__dactyl_eval_string";
// Move elsewhere?
const Storage = Module("storage", {
requires: ["services"],
init: function () {
Components.utils.import("resource://dactyl/storage.jsm", this);
modules.Timer = this.Timer; // Fix me, please.
try {
let infoPath = services.create("file");
infoPath.initWithPath(File.expandPath(IO.runtimePath.replace(/,.*/, "")));
infoPath.append("info");
infoPath.append(dactyl.profileName);
this.storage.infoPath = infoPath;
}
catch (e) {}
return this.storage;
}
});
const FailedAssertion = Class("FailedAssertion", Error, {
init: function (message) {
this.message = message;
@@ -45,8 +26,6 @@ const FailedAssertion = Class("FailedAssertion", Error, {
});
const Dactyl = Module("dactyl", {
requires: ["config", "services"],
init: function () {
window.dactyl = this;
window.liberator = this;
@@ -61,8 +40,6 @@ const Dactyl = Module("dactyl", {
// without explicitly selecting a profile.
/** @property {string} The name of the current user profile. */
this.profileName = services.get("directory").get("ProfD", Ci.nsIFile).leafName.replace(/^.+?\./, "");
config.features.push(Dactyl.getPlatformFeature());
},
destroy: function () {
@@ -139,29 +116,26 @@ const Dactyl = Module("dactyl", {
* bell may be either audible or visual depending on the value of the
* 'visualbell' option.
*/
beep: function () {
beep: requiresMainThread(function () {
// FIXME: popups clear the command line
if (options["visualbell"]) {
util.callInMainThread(function () {
// flash the visual bell
let popup = document.getElementById("dactyl-visualbell");
let win = config.visualbellWindow;
let rect = win.getBoundingClientRect();
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
// flash the visual bell
let popup = document.getElementById("dactyl-visualbell");
let win = config.visualbellWindow;
let rect = win.getBoundingClientRect();
let width = rect.right - rect.left;
let height = rect.bottom - rect.top;
// NOTE: this doesn't seem to work in FF3 with full box dimensions
popup.openPopup(win, "overlap", 1, 1, false, false);
popup.sizeTo(width - 2, height - 2);
setTimeout(function () { popup.hidePopup(); }, 20);
});
// NOTE: this doesn't seem to work in FF3 with full box dimensions
popup.openPopup(win, "overlap", 1, 1, false, false);
popup.sizeTo(width - 2, height - 2);
setTimeout(function () { popup.hidePopup(); }, 20);
}
else {
let soundService = Cc["@mozilla.org/sound;1"].getService(Ci.nsISound);
soundService.beep();
}
return false; // so you can do: if (...) return dactyl.beep();
},
}),
/**
* Prints a message to the console. If <b>msg</b> is an object it is
@@ -217,9 +191,11 @@ const Dactyl = Module("dactyl", {
echoerr: function (str, flags) {
flags |= commandline.APPEND_TO_MESSAGES;
if (isinstance(str, ["Error", "Exception"]))
dactyl.reportError(str);
if (typeof str == "object" && "echoerr" in str)
str = str.echoerr;
else if (str instanceof Error)
else if (isinstance(str, ["Error"]))
str = str.fileName + ":" + str.lineNumber + ": " + str;
if (options["errorbells"])
@@ -504,7 +480,7 @@ const Dactyl = Module("dactyl", {
// Process plugin help entries.
XML.ignoreWhiteSpace = false;
XML.prettyPrinting = true; // Should be false, but ignoreWhiteSpace=false doesn't work correctly. This is the lesser evil.
XML.prettyPrinting = false;
XML.prettyIndent = 4;
let body = XML();
@@ -594,7 +570,7 @@ const Dactyl = Module("dactyl", {
data.push(">");
if (node instanceof HTMLHeadElement)
data.push(<link rel="stylesheet" type="text/css" href="help.css"/>.toXMLString());
Array.map(node.childNodes, arguments.callee);
Array.map(node.childNodes, fix);
data.push("</"); data.push(node.localName); data.push(">");
}
break;
@@ -1105,6 +1081,9 @@ const Dactyl = Module("dactyl", {
dactyl.help(tag);
}
}, {
config: function () {
config.features.push(Dactyl.getPlatformFeature());
},
// Only general options are added here, which are valid for all Dactyl extensions
options: function () {
@@ -1502,30 +1481,25 @@ const Dactyl = Module("dactyl", {
extensions = extensions.filter(function (extension) extension.name.indexOf(args[0]) >= 0);
extensions.sort(function (a, b) String.localeCompare(a.name, b.name));
if (extensions.length > 0) {
let list = template.tabular(
["Name", "Version", "Status", "Description"], [],
([template.icon({ icon: e.iconURL }, e.name),
e.version,
(e.isActive ? <span highlight="Enabled">enabled</span>
: <span highlight="Disabled">disabled</span>) +
((e.userDisabled || e.appDisabled) == !e.isActive ? XML() :
<>&#xa0;({e.userDisabled || e.appDisabled
? <span highlight="Disabled">disabled</span>
: <span highlight="Enabled">enabled</span>}
on restart)
</>),
e.description] for ([, e] in Iterator(extensions)))
);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
else {
if (filter)
dactyl.echoerr("Exxx: No extension matching " + filter.quote());
else
dactyl.echoerr("No extensions installed");
}
if (extensions.length > 0)
commandline.commandOutput(
template.tabular(["Name", "Version", "Status", "Description"], [],
([template.icon({ icon: e.iconURL }, e.name),
e.version,
(e.isActive ? <span highlight="Enabled">enabled</span>
: <span highlight="Disabled">disabled</span>) +
((e.userDisabled || e.appDisabled) == !e.isActive ? XML() :
<>&#xa0;({e.userDisabled || e.appDisabled
? <span highlight="Disabled">disabled</span>
: <span highlight="Enabled">enabled</span>}
on restart)
</>),
e.description]
for ([, e] in Iterator(extensions)))));
else if (filter)
dactyl.echoerr("Exxx: No extension matching " + filter.quote());
else
dactyl.echoerr("No extensions installed");
});
},
{ argCount: "?" });
@@ -1696,7 +1670,7 @@ const Dactyl = Module("dactyl", {
else
totalUnits = "msec";
let str = template.commandOutput(commandline.command,
commandline.commandOutput(
<table>
<tr highlight="Title" align="left">
<th colspan="3">Code execution summary</th>
@@ -1705,7 +1679,6 @@ const Dactyl = Module("dactyl", {
<tr><td>&#xa0;&#xa0;Average time:</td><td align="right"><span class="time-average">{each.toFixed(2)}</span></td><td>{eachUnits}</td></tr>
<tr><td>&#xa0;&#xa0;Total time:</td><td align="right"><span class="time-total">{total.toFixed(2)}</span></td><td>{totalUnits}</td></tr>
</table>);
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
else {
let beforeTime = Date.now();
@@ -1767,8 +1740,9 @@ const Dactyl = Module("dactyl", {
if (args.bang)
dactyl.open("about:");
else
dactyl.echo(template.commandOutput(commandline.command,
<>{config.name} {dactyl.version} running on:<br/>{navigator.userAgent}</>));
commandline.commandOutput(<>
{config.name} {dactyl.version} running on:<br/>{navigator.userAgent}
</>);
}, {
argCount: "0",
bang: true

View File

@@ -11,8 +11,6 @@
/** @instance editor */
const Editor = Module("editor", {
requires: ["config"],
init: function () {
// store our last search with f, F, t or T
//
@@ -408,10 +406,10 @@ const Editor = Module("editor", {
// blink the textbox after returning
if (textBox) {
let colors = [tmpBg, oldBg, tmpBg, oldBg];
(function () {
(function next() {
textBox.style.backgroundColor = colors.shift();
if (colors.length > 0)
setTimeout(arguments.callee, 100);
setTimeout(next, 100);
})();
}
@@ -577,10 +575,8 @@ const Editor = Module("editor", {
dactyl.echo(mode + " " + lhs + " " + rhs, commandline.FORCE_SINGLELINE); // 2 spaces, 3 spaces
}
else {
list = template.tabular(["", "LHS", "RHS"], [], list);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
}
else
commandline.commandOutput(template.tabular(["", "LHS", "RHS"], [], list));
},
/**
@@ -670,7 +666,7 @@ const Editor = Module("editor", {
}, {
completer: function (context, args) completion.abbreviation(context, args, mode),
literal: 0,
serial: function () [ {
serialize: function () [ {
command: this.name,
arguments: [lhs],
literalArg: abbr[1]

View File

@@ -12,8 +12,6 @@
* @instance events
*/
const Events = Module("events", {
requires: ["autocommands", "config"],
init: function () {
const self = this;
@@ -737,12 +735,10 @@ const Events = Module("events", {
// TODO: Merge with onFocusChange
onFocus: function (event) {
function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
let elem = event.originalTarget;
let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
if (hasHTMLDocument(win) && !buffer.focusAllowed(win)
if (Events.isContentNode(elem) && !buffer.focusAllowed(win)
&& isinstance(elem, [HTMLInputElement, HTMLSelectElement, HTMLTextAreaElement]))
elem.blur();
},
@@ -1107,6 +1103,13 @@ const Events = Module("events", {
editableInputs: set(["date", "datetime", "datetime-local", "email", "file",
"month", "number", "password", "range", "search",
"tel", "text", "time", "url", "week"]),
isContentNode: function (node) {
let win = (node.ownerDocument || node).defaultView;
for (; win; win = win.parent != win && win.parent)
if (win == window.content)
return true;
return false;
},
isInputElemFocused: function () {
let elem = dactyl.focus;
return elem instanceof HTMLInputElement && set.has(Events.editableInputs, elem.type) ||

View File

@@ -8,8 +8,6 @@
/** @instance rangefinder */
const RangeFinder = Module("rangefinder", {
requires: ["config"],
init: function () {
this.lastSearchPattern = "";
},
@@ -128,7 +126,7 @@ const RangeFinder = Module("rangefinder", {
set rangeFind(val) buffer.localStore.rangeFind = val,
/**
* Highlights all occurances of <b>str</b> in the buffer.
* Highlights all occurrences of <b>str</b> in the buffer.
*
* @param {string} str The string to highlight.
*/
@@ -146,6 +144,11 @@ const RangeFinder = Module("rangefinder", {
}
}, {
}, {
modes: function () {
/* Must come before commandline. */
modes.addMode("FIND_FORWARD", true);
modes.addMode("FIND_BACKWARD", true);
},
commandline: function () {
// Event handlers for search - closure is needed
commandline.registerCallback("change", modes.FIND_FORWARD, this.closure.onKeyPress);
@@ -197,10 +200,6 @@ const RangeFinder = Module("rangefinder", {
});
},
modes: function () {
modes.addMode("FIND_FORWARD", true);
modes.addMode("FIND_BACKWARD", true);
},
options: function () {
options.safeSetPref("accessibility.typeaheadfind.autostart", false);
// The above should be sufficient, but: https://bugzilla.mozilla.org/show_bug.cgi?id=348187
@@ -256,8 +255,8 @@ const RangeFinder = Module("rangefinder", {
* implementation will begin searching from the position of the
* caret in the last active frame. This is contrary to the behavior
* of the builtin component, which always starts a search from the
* begining of the first frame in the case of frameset documents,
* and cycles through all frames from begining to end. This makes it
* beginning of the first frame in the case of frameset documents,
* and cycles through all frames from beginning to end. This makes it
* impossible to choose the starting point of a search for such
* documents, and represents a major detriment to productivity where
* large amounts of data are concerned (e.g., for API documents).
@@ -302,8 +301,8 @@ const RangeFind = Class("RangeFind", {
},
compareRanges: function (r1, r2)
this.backward ? r1.compareBoundaryPoints(Range.END_TO_START, r2)
: -r1.compareBoundaryPoints(Range.START_TO_END, r2),
this.backward ? r1.compareBoundaryPoints(r1.END_TO_START, r2)
: -r1.compareBoundaryPoints(r1.START_TO_END, r2),
findRange: function (range) {
let doc = range.startContainer.ownerDocument;
@@ -331,7 +330,7 @@ const RangeFind = Class("RangeFind", {
this.lastRange.commonAncestorContainer).snapshotItem(0);
if (node) {
node.focus();
// Rehighlight collapsed selection
// Re-highlight collapsed selection
this.selectedRange = this.lastRange;
}
},
@@ -358,7 +357,7 @@ const RangeFind = Class("RangeFind", {
let string = this.lastString;
for (let r in this.iter(string)) {
let controller = this.range.selectionController;
for (let node=r.startContainer; node; node=node.parentNode)
for (let node = r.startContainer; node; node = node.parentNode)
if (node instanceof Ci.nsIDOMNSEditableElement) {
controller = node.editor.selectionController;
break;
@@ -370,11 +369,30 @@ const RangeFind = Class("RangeFind", {
this.selections.push(sel);
}
this.highlighted = this.lastString;
this.selectedRange = this.lastRange;
if (this.lastRange)
this.selectedRange = this.lastRange;
this.addListeners();
}
},
indexIter: function (private_) {
let idx = this.range.index;
if (this.backward)
var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)];
else
var groups = [util.range(idx, this.ranges.length), util.range(0, idx + 1)];
for (let i in groups[0])
yield i;
if (!private_) {
this.wrapped = true;
this.lastRange = null;
for (let i in groups[1])
yield i;
}
},
iter: function (word) {
let saved = ["range", "lastRange", "lastString"].map(function (s) [s, this[s]], this);
try {
@@ -382,7 +400,7 @@ const RangeFind = Class("RangeFind", {
this.lastRange = null;
this.lastString = word;
var res;
while ((res = this.search(null, this.reverse, true)))
while (res = this.search(null, this.reverse, true))
yield res;
}
finally {
@@ -504,25 +522,8 @@ const RangeFind = Class("RangeFind", {
if (word == "")
var range = this.startRange;
else {
function indices() {
let idx = this.range.index;
if (this.backward)
var groups = [util.range(idx + 1, 0, -1), util.range(this.ranges.length, idx, -1)];
else
var groups = [util.range(idx, this.ranges.length), util.range(0, idx + 1)];
for (let i in groups[0])
yield i;
if (!private_) {
this.wrapped = true;
this.lastRange = null;
for (let i in groups[1])
yield i;
}
}
for (let i in indices.call(this)) {
else
for (let i in this.indexIter(private_)) {
if (!private_ && this.range.window != this.ranges[i].window && this.range.window != this.ranges[i].window.parent) {
this.range.descroll();
this.range.deselect();
@@ -540,7 +541,6 @@ const RangeFind = Class("RangeFind", {
if (range)
break;
}
}
if (range)
this.lastRange = range.cloneRange();
@@ -559,11 +559,11 @@ const RangeFind = Class("RangeFind", {
},
addListeners: function () {
for (let range in values(this.ranges))
for (let range in array.itervalues(this.ranges))
range.window.addEventListener("unload", this.closure.onUnload, true);
},
purgeListeners: function () {
for (let range in values(this.ranges))
for (let range in array.itervalues(this.ranges))
range.window.removeEventListener("unload", this.closure.onUnload, true);
},
onUnload: function (event) {
@@ -591,8 +591,8 @@ const RangeFind = Class("RangeFind", {
},
intersects: function (range)
this.range.compareBoundaryPoints(Range.START_TO_END, range) >= 0 &&
this.range.compareBoundaryPoints(Range.END_TO_START, range) <= 0,
this.range.compareBoundaryPoints(range.START_TO_END, range) >= 0 &&
this.range.compareBoundaryPoints(range.END_TO_START, range) <= 0,
save: function () {
this.scroll = Point(this.window.pageXOffset, this.window.pageYOffset);
@@ -625,8 +625,8 @@ const RangeFind = Class("RangeFind", {
}),
contains: function (range, r)
range.compareBoundaryPoints(Range.START_TO_END, r) >= 0 &&
range.compareBoundaryPoints(Range.END_TO_START, r) <= 0,
range.compareBoundaryPoints(range.START_TO_END, r) >= 0 &&
range.compareBoundaryPoints(range.END_TO_START, r) <= 0,
endpoint: function (range, before) {
range = range.cloneRange();
range.collapse(before);
@@ -634,7 +634,7 @@ const RangeFind = Class("RangeFind", {
},
equal: function (r1, r2) {
try {
return !r1.compareBoundaryPoints(Range.START_TO_START, r2) && !r1.compareBoundaryPoints(Range.END_TO_END, r2)
return !r1.compareBoundaryPoints(r1.START_TO_START, r2) && !r1.compareBoundaryPoints(r1.END_TO_END, r2)
}
catch (e) {}
return false;

View File

@@ -10,8 +10,6 @@
/** @instance hints */
const Hints = Module("hints", {
requires: ["config"],
init: function () {
this._hintMode = null;

View File

@@ -7,8 +7,6 @@
"use strict";
const History = Module("history", {
requires: ["config"],
get format() bookmarks.format,
get service() services.get("history"),
@@ -196,7 +194,7 @@ const History = Module("history", {
bang: true,
completer: function (context) { context.quote = null; completion.history(context); },
// completer: function (filter) completion.history(filter)
options: [[["-max", "-m"], commands.OPTION_INT]]
options: [{ names: ["-max", "-m"], description: "The maximum number of items to list", type: CommandOption.INT }]
});
},
completion: function () {

View File

@@ -10,311 +10,27 @@
/** @scope modules */
plugins.contexts = {};
const Script = Class("Script", {
init: function (file) {
let self = plugins.contexts[file.path];
if (self) {
if (self.onUnload)
self.onUnload();
return self;
}
plugins.contexts[file.path] = this;
this.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase());
this.PATH = file.path;
this.toString = this.toString;
this.__context__ = this;
this.__proto__ = plugins;
// This belongs elsewhere
for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) {
if (dir.contains(file, false))
plugins[this.NAME] = this;
}
return this;
}
});
/**
* @class File A class to wrap nsIFile objects and simplify operations
* thereon.
*
* @param {nsIFile|string} path Expanded according to {@link IO#expandPath}
* @param {boolean} checkPWD Whether to allow expansion relative to the
* current directory. @default true
*/
const File = Class("File", {
init: function (path, checkPWD) {
if (arguments.length < 2)
checkPWD = true;
let file = services.create("file");
if (path instanceof Ci.nsIFile)
file = path;
else if (/file:\/\//.test(path))
file = services.create("file:").getFileFromURLSpec(path);
else {
let expandedPath = File.expandPath(path);
if (!File.isAbsolutePath(expandedPath) && checkPWD)
file = File.joinPaths(io.getCurrentDirectory().path, expandedPath);
else
file.initWithPath(expandedPath);
}
let self = XPCSafeJSObjectWrapper(file);
self.__proto__ = File.prototype;
function Script(file) {
let self = plugins.contexts[file.path];
if (self) {
if (self.onUnload)
self.onUnload();
return self;
},
}
self = { __proto__: plugins };
plugins.contexts[file.path] = self;
self.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase());
self.PATH = file.path;
self.__context__ = self;
self.__proto__ = plugins;
/**
* Iterates over the objects in this directory.
*/
iterDirectory: function () {
if (!this.isDirectory())
throw Error("Not a directory");
let entries = this.directoryEntries;
while (entries.hasMoreElements())
yield File(entries.getNext().QueryInterface(Ci.nsIFile));
},
/**
* Returns the list of files in this directory.
*
* @param {boolean} sort Whether to sort the returned directory
* entries.
* @returns {nsIFile[]}
*/
readDirectory: function (sort) {
if (!this.isDirectory())
throw Error("Not a directory");
let array = [e for (e in this.iterDirectory())];
if (sort)
array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
return array;
},
/**
* Reads this file's entire contents in "text" mode and returns the
* content as a string.
*
* @param {string} encoding The encoding from which to decode the file.
* @default options["fileencoding"]
* @returns {string}
*/
read: function (encoding) {
let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
if (!encoding)
encoding = options["fileencoding"];
ifstream.init(this, -1, 0, 0);
icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering
let buffer = [];
let str = {};
while (icstream.readString(4096, str) != 0)
buffer.push(str.value);
icstream.close();
ifstream.close();
return buffer.join("");
},
/**
* Writes the string <b>buf</b> to this file.
*
* @param {string} buf The file content.
* @param {string|number} mode The file access mode, a bitwise OR of
* the following flags:
* {@link #MODE_RDONLY}: 0x01
* {@link #MODE_WRONLY}: 0x02
* {@link #MODE_RDWR}: 0x04
* {@link #MODE_CREATE}: 0x08
* {@link #MODE_APPEND}: 0x10
* {@link #MODE_TRUNCATE}: 0x20
* {@link #MODE_SYNC}: 0x40
* Alternatively, the following abbreviations may be used:
* ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE}
* ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND}
* @default ">"
* @param {number} perms The file mode bits of the created file. This
* is only used when creating a new file and does not change
* permissions if the file exists.
* @default 0644
* @param {string} encoding The encoding to used to write the file.
* @default options["fileencoding"]
*/
write: function (buf, mode, perms, encoding) {
let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
function getStream(defaultChar) {
let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
stream.init(ofstream, encoding, 0, defaultChar);
return stream;
}
if (!encoding)
encoding = options["fileencoding"];
if (mode == ">>")
mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_APPEND;
else if (!mode || mode == ">")
mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_TRUNCATE;
if (!perms)
perms = parseInt('0644', 8);
ofstream.init(this, mode, perms, 0);
let ocstream = getStream(0);
try {
ocstream.writeString(buf);
}
catch (e) {
dactyl.dump(e);
if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) {
ocstream = getStream("?".charCodeAt(0));
ocstream.writeString(buf);
return false;
}
else
throw e;
}
finally {
try {
ocstream.close();
}
catch (e) {}
ofstream.close();
}
return true;
// This belongs elsewhere
for (let [, dir] in Iterator(io.getRuntimeDirectories("plugin"))) {
if (dir.contains(file, false))
plugins[self.NAME] = self;
}
}, {
/**
* @property {number} Open for reading only.
* @final
*/
MODE_RDONLY: 0x01,
/**
* @property {number} Open for writing only.
* @final
*/
MODE_WRONLY: 0x02,
/**
* @property {number} Open for reading and writing.
* @final
*/
MODE_RDWR: 0x04,
/**
* @property {number} If the file does not exist, the file is created.
* If the file exists, this flag has no effect.
* @final
*/
MODE_CREATE: 0x08,
/**
* @property {number} The file pointer is set to the end of the file
* prior to each write.
* @final
*/
MODE_APPEND: 0x10,
/**
* @property {number} If the file exists, its length is truncated to 0.
* @final
*/
MODE_TRUNCATE: 0x20,
/**
* @property {number} If set, each write will wait for both the file
* data and file status to be physically updated.
* @final
*/
MODE_SYNC: 0x40,
/**
* @property {number} With MODE_CREATE, if the file does not exist, the
* file is created. If the file already exists, no action and NULL
* is returned.
* @final
*/
MODE_EXCL: 0x80,
expandPathList: function (list) list.map(this.expandPath),
expandPath: function (path, relative) {
// expand any $ENV vars - this is naive but so is Vim and we like to be compatible
// TODO: Vim does not expand variables set to an empty string (and documents it).
// Kris reckons we shouldn't replicate this 'bug'. --djk
// TODO: should we be doing this for all paths?
function expand(path) path.replace(
!dactyl.has("Win32") ? /\$(\w+)\b|\${(\w+)}/g
: /\$(\w+)\b|\${(\w+)}|%(\w+)%/g,
function (m, n1, n2, n3) services.get("environment").get(n1 || n2 || n3) || m
);
path = expand(path);
// expand ~
// Yuck.
if (!relative && RegExp("~(?:$|[/" + util.escapeRegex(IO.PATH_SEP) + "])").test(path)) {
// Try $HOME first, on all systems
let home = services.get("environment").get("HOME");
// Windows has its own idiosyncratic $HOME variables.
if (!home && dactyl.has("Win32"))
home = services.get("environment").get("USERPROFILE") ||
services.get("environment").get("HOMEDRIVE") + services.get("environment").get("HOMEPATH");
path = home + path.substr(1);
}
// TODO: Vim expands paths twice, once before checking for ~, once
// after, but doesn't document it. Is this just a bug? --Kris
path = expand(path);
return path.replace("/", IO.PATH_SEP, "g");
},
getPathsFromPathList: function (list) {
if (!list)
return [];
// empty list item means the current directory
return list.replace(/,$/, "").split(",")
.map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir);
},
replacePathSep: function (path) path.replace("/", IO.PATH_SEP, "g"),
joinPaths: function (head, tail) {
let path = this(head);
try {
path.appendRelativePath(this.expandPath(tail, true)); // FIXME: should only expand env vars and normalise path separators
// TODO: This code breaks the external editor at least in ubuntu
// because /usr/bin/gvim becomes /usr/bin/vim.gnome normalized and for
// some strange reason it will start without a gui then (which is not
// optimal if you don't start firefox from a terminal ;)
// Why do we need this code?
// if (path.exists() && path.normalize)
// path.normalize();
}
catch (e) {
return { exists: function () false, __noSuchMethod__: function () { throw e; } };
}
return path;
},
isAbsolutePath: function (path) {
try {
services.create("file").initWithPath(path);
return true;
}
catch (e) {
return false;
}
}
});
return self;
}
// TODO: why are we passing around strings rather than file objects?
/**
@@ -322,8 +38,6 @@ const File = Class("File", {
* @instance io
*/
const IO = Module("io", {
requires: ["config", "services"],
init: function () {
this._processDir = services.get("directory").get("CurWorkD", Ci.nsIFile);
this._cwd = this._processDir;
@@ -363,7 +77,10 @@ const IO = Module("io", {
* @property {function} File class.
* @final
*/
File: File,
File: Class("File", File, {
init: function init(path, checkCWD)
init.supercall(this, path, (arguments.length < 2 || checkCWD) && io.getCurrentDirectory())
}),
/**
* @property {Object} The current file sourcing context. As a file is
@@ -423,7 +140,7 @@ const IO = Module("io", {
if (newDir == "-")
[this._cwd, this._oldcwd] = [this._oldcwd, this.getCurrentDirectory()];
else {
let dir = File(newDir);
let dir = io.File(newDir);
if (!dir.exists() || !dir.isDirectory()) {
dactyl.echoerr("E344: Can't find directory " + dir.path.quote());
@@ -489,7 +206,7 @@ const IO = Module("io", {
file.append(config.tempFile);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt('0600', 8));
return File(file);
return io.File(file);
},
/**
@@ -507,7 +224,7 @@ const IO = Module("io", {
let file;
if (File.isAbsolutePath(program))
file = File(program, true);
file = io.File(program, true);
else {
let dirs = services.get("environment").get("PATH").split(dactyl.has("Win32") ? ";" : ":");
// Windows tries the CWD first TODO: desirable?
@@ -600,7 +317,7 @@ lookup:
dactyl.dump("sourcing " + filename);
let time = Date.now();
try {
var file = File(filename);
var file = io.File(filename);
this.sourcing = {
file: file.path,
line: 0
@@ -803,12 +520,7 @@ lookup:
/**
* @property {string} The current platform's path seperator.
*/
get PATH_SEP() {
delete this.PATH_SEP;
let f = services.get("directory").get("CurProcD", Ci.nsIFile);
f.append("foo");
return this.PATH_SEP = f.path.substr(f.parent.path.length, 1);
}
PATH_SEP: File.PATH_SEP
}, {
commands: function () {
commands.add(["cd", "chd[ir]"],
@@ -876,13 +588,13 @@ lookup:
dactyl.assert(args.length <= 1, "E172: Only one file name allowed");
let filename = args[0] || io.getRCFile(null, true).path;
let file = File(filename);
let file = io.File(filename);
dactyl.assert(!file.exists() || args.bang,
"E189: " + filename.quote() + " exists (add ! to override)");
// TODO: Use a set/specifiable list here:
let lines = [cmd.serial().map(commands.commandToString) for (cmd in commands) if (cmd.serial)];
let lines = [cmd.serialize().map(commands.commandToString) for (cmd in commands) if (cmd.serialize)];
lines = util.Array.flatten(lines);
// source a user .pentadactylrc file
@@ -922,10 +634,9 @@ lookup:
commands.add(["scrip[tnames]"],
"List all sourced script names",
function () {
let list = template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
([i + 1, file] for ([i, file] in Iterator(io._scriptNames)))); // TODO: add colon and remove column titles for pedantic Vim compatibility?
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
commandline.commandOutput(
template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
([i + 1, file] for ([i, file] in Iterator(io._scriptNames))))); // TODO: add colon and remove column titles for pedantic Vim compatibility?
},
{ argCount: "0" });
@@ -967,8 +678,7 @@ lookup:
let output = io.system(arg);
commandline.command = "!" + arg;
commandline.echo(template.commandOutput(commandline.command,
<span highlight="CmdOutput">{output}</span>));
commandline.commandOutput(<span highlight="CmdOutput">{output}</span>);
autocommands.trigger("ShellCmdPost", {});
}, {
@@ -1034,7 +744,7 @@ lookup:
context.key = dir;
context.generate = function generate_file() {
try {
return File(dir).readDirectory();
return io.File(dir).readDirectory();
}
catch (e) {}
return [];
@@ -1065,7 +775,7 @@ lookup:
});
},
javascript: function () {
JavaScript.setCompleter([this.File, File.expandPath],
JavaScript.setCompleter([File, File.expandPath],
[function (context, obj, args) {
context.quote[2] = "";
completion.file(context, true);
@@ -1088,7 +798,9 @@ lookup:
options.add(["fileencoding", "fenc"],
"Sets the character encoding of read and written files",
"string", "UTF-8", {
completer: function (context) completion.charset(context)
completer: function (context) completion.charset(context),
getter: function () File.defaultEncoding,
setter: function (value) (File.defaultEncoding = value)
});
options.add(["cdpath", "cd"],
"List of directories searched when executing :cd",

View File

@@ -26,7 +26,6 @@ const JavaScript = Module("javascript", {
// Some object members are only accessible as function calls
getKey: function (obj, key) {
try {
// if (!Object.prototype.__lookupGetter__.call(obj, key))
return obj[key];
}
catch (e) {}
@@ -35,20 +34,18 @@ const JavaScript = Module("javascript", {
iter: function iter(obj, toplevel) {
"use strict";
const self = this;
if (obj == null)
return;
let orig = obj;
if(!options["jsdebugger"])
function value(key) self.getKey(orig, key);
else {
let top = services.get("debugger").wrapValue(obj);
function value(key) top.getProperty(key).value.getWrappedValue();
let seen = {};
for (let key in properties(obj, !toplevel)) {
set.add(seen, key);
yield [key, this.getKey(obj, key)];
}
for (let key in properties(obj, !toplevel))
yield [key, value(key)];
for (let key in properties(this.getKey(obj, "wrappedJSObject"), !toplevel))
if (!set.has(seen, key))
yield [key, this.getKey(obj, key)];
},
objectKeys: function objectKeys(obj, toplevel) {

View File

@@ -35,6 +35,7 @@ const Map = Class("Map", {
this.modes = modes;
this.names = keys.map(events.canonicalKeys);
this.name = this.names[0];
this.action = action;
this.description = description;
@@ -122,8 +123,6 @@ const Map = Class("Map", {
* @instance mappings
*/
const Mappings = Module("mappings", {
requires: ["modes"],
init: function () {
this._main = []; // default mappings
this._user = []; // user created mappings
@@ -177,9 +176,9 @@ const Mappings = Module("mappings", {
// Return all mappings present in all @modes
_mappingsIterator: function (modes, stack) {
modes = modes.slice();
return (map for ([i, map] in Iterator(stack[modes.shift()]))
return (map for ([i, map] in Iterator(stack[modes.shift()].sort(function (m1, m2) String.localeCompare(m1.name, m2.name))))
if (modes.every(function (mode) stack[mode].some(
function (m) m.rhs == map.rhs && m.names[0] == map.names[0]))))
function (mapping) m.rhs == map.rhs && m.name == map.name))))
},
// NOTE: just normal mode for now
@@ -363,11 +362,10 @@ const Mappings = Module("mappings", {
</table>;
// TODO: Move this to an ItemList to show this automatically
if (list.*.length() == list.text().length()) {
if (list.*.length() == list.text().length())
dactyl.echomsg("No mapping found");
return;
}
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
else
commandline.commandOutput(list);
}
}, {
}, {
@@ -413,11 +411,9 @@ const Mappings = Module("mappings", {
const opts = {
completer: function (context, args) completion.userMapping(context, args, modes),
options: [
[["<silent>", "<Silent>"], commands.OPTION_NOARG]
],
literal: 1,
serial: function () {
options: [{ names: ["<silent>", "<Silent>"] }],
serialize: function () {
let noremap = this.name.indexOf("noremap") > -1;
return [
{

View File

@@ -11,8 +11,6 @@
* @instance marks
*/
const Marks = Module("marks", {
requires: ["config", "storage"],
init: function init() {
this._localMarks = storage.newMap("local-marks", { store: true, privateData: true });
this._urlMarks = storage.newMap("url-marks", { store: true, privateData: true });
@@ -176,15 +174,15 @@ const Marks = Module("marks", {
dactyl.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);
commandline.commandOutput(
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)))));
},
_onPageLoad: function _onPageLoad(event) {

View File

@@ -9,8 +9,6 @@
/** @scope modules */
const Modes = Module("modes", {
requires: ["config", "util"],
init: function () {
this._main = 1; // NORMAL
this._extended = 0; // NONE
@@ -49,8 +47,6 @@ const Modes = Module("modes", {
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 () {

View File

@@ -53,12 +53,17 @@ const ModuleBase = Class("ModuleBase", {
*
* @returns {function} The constructor for the resulting module.
*/
function Module(name, prototype, classProperties, moduleInit) {
function Module(name) {
let args = Array.slice(arguments);
var base = ModuleBase;
if (callable(prototype))
base = Array.splice(arguments, 1, 1)[0];
if (callable(args[1]))
base = args.splice(1, 1)[0];
let [, prototype, classProperties, moduleInit] = args;
const module = Class(name, base, prototype, classProperties);
module.INIT = moduleInit || {};
module.prototype.INIT = module.INIT;
module.requires = prototype.requires || [];
Module.list.push(module);
Module.constructors[name] = module;
@@ -67,67 +72,82 @@ function Module(name, prototype, classProperties, moduleInit) {
Module.list = [];
Module.constructors = {};
window.addEventListener("load", function () {
window.removeEventListener("load", arguments.callee, false);
window.addEventListener("load", function onLoad() {
window.removeEventListener("load", onLoad, false);
Module.list.forEach(function(module) {
modules.__defineGetter__(module.name, function() {
delete modules[module.name];
return load(module.name, null, Components.stack.caller);
});
});
function dump(str) window.dump(String.replace(str, /\n?$/, "\n").replace(/^/m, Config.prototype.name.toLowerCase() + ": "));
const start = Date.now();
const deferredInit = { load: [] };
const seen = set();
const loaded = [];
const loaded = set(["init"]);
function init(module) {
function init(func)
function () func.call(module, dactyl, modules, window);
set.add(loaded, module.constructor.name);
for (let [mod, func] in Iterator(module.INIT)) {
if (mod in loaded)
init(func)();
else {
deferredInit[mod] = deferredInit[mod] || [];
deferredInit[mod].push(init(func));
}
}
}
defmodule.modules.map(init);
function load(module, prereq, frame) {
if (isstring(module)) {
if (!Module.constructors.hasOwnProperty(module))
modules.load(module);
module = Module.constructors[module];
}
function load(module, prereq) {
try {
if (module.name in modules)
if (module.name in loaded)
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, dep);
load(Module.constructors[dep], module.name);
dump("Load" + (isstring(prereq) ? " " + prereq + " dependency: " : ": ") + module.name);
modules[module.name] = module();
loaded.push(module.name);
if (frame && frame.filename)
dump(" from: " + frame.filename + ":" + frame.lineNumber);
function init(mod, module)
function () module.INIT[mod].call(modules[module.name], modules[mod]);
for (let mod in values(loaded)) {
try {
if (mod in module.INIT)
init(mod, module)();
delete module.INIT[mod];
}
catch (e) {
if (modules.dactyl)
dactyl.reportError(e);
}
}
for (let mod in values(Object.keys(module.INIT))) {
deferredInit[mod] = deferredInit[mod] || [];
deferredInit[mod].push(init(mod, module));
}
delete modules[module.name];
modules[module.name] = module();
init(modules[module.name]);
for (let [, fn] in iter(deferredInit[module.name] || []))
fn();
}
catch (e) {
dump("Loading " + (module && module.name) + ": " + e + "\n");
dump("Loading " + (module && module.name) + ": " + e);
if (e.stack)
dump(e.stack);
}
return modules[module.name];
}
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");
dump("Loaded in " + (Date.now() - start) + "ms");
}, false);
window.addEventListener("unload", function () {
window.removeEventListener("unload", arguments.callee, false);
window.addEventListener("unload", function onUnload() {
window.removeEventListener("unload", onUnload, false);
for (let [, mod] in iter(modules))
if (mod instanceof ModuleBase && "destroy" in mod)
mod.destroy();

View File

@@ -238,8 +238,8 @@ const Option = Class("Option", {
* "string" - String, e.g., "Pentadactyl"
* "charlist" - Character list, e.g., "rb"
* "regexlist" - Regex list, e.g., "^foo,bar$"
* "stringmap" - String map, e.g., "key=v,foo=bar"
* "regexmap" - Regex map, e.g., "^key=v,foo$=bar"
* "stringmap" - String map, e.g., "key:v,foo:bar"
* "regexmap" - Regex map, e.g., "^key:v,foo$:bar"
*/
type: null,
@@ -351,9 +351,9 @@ const Option = Class("Option", {
boolean: function (value) value == "true" || value == true ? true : false,
charlist: function (value) Array.slice(value),
stringlist: function (value) (value === "") ? [] : value.split(","),
stringmap: function (value) array(v.split("=") for (v in values(value.split(",")))).toObject(),
stringmap: function (value) array(v.split(":") for (v in values(value.split(",")))).toObject(),
regexlist: function (value) (value === "") ? [] : value.split(",").map(Option.parseRegex),
regexmap: function (value) value.split(",").map(function (v) v.split("="))
regexmap: function (value) value.split(",").map(function (v) v.split(":"))
.map(function ([k, v]) v != null ? Option.parseRegex(k, v) : Option.parseRegex('.?', k))
},
@@ -478,8 +478,6 @@ Option.ops["regexmap"] = Option.ops["stringlist"];
* @instance options
*/
const Options = Module("options", {
requires: ["config", "highlight", "storage"],
init: function () {
this._optionHash = {};
this._prefContexts = [];
@@ -661,8 +659,7 @@ const Options = Module("options", {
}
};
let list = template.options("Options", opts());
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
commandline.commandOutput(template.options("Options", opts()));
},
/**
@@ -687,7 +684,7 @@ const Options = Module("options", {
if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
continue;
value = options.getPref(pref);
let value = options.getPref(pref);
let option = {
isDefault: !userValue,
@@ -701,8 +698,8 @@ const Options = Module("options", {
}
};
let list = template.options(config.hostApplication + " Options", prefs());
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
commandline.commandOutput(
template.options(config.hostApplication + " Options", prefs()));
},
/**
@@ -783,15 +780,7 @@ const Options = Module("options", {
return this._loadPreference(name, forcedDefault);
},
/**
* Sets the preference <b>name</b> to </b>value</b> but warns the user
* if the value is changed from its default.
*
* @param {string} name The preference name.
* @param {value} value The new preference value.
*/
// FIXME: Well it used to. I'm looking at you mst! --djk
safeSetPref: function (name, value, message) {
_checkPrefSafe: function (name, message) {
let curval = this._loadPreference(name, null, false);
let defval = this._loadPreference(name, null, true);
let saved = this._loadPreference(Options.SAVED + name);
@@ -802,6 +791,30 @@ const Options = Module("options", {
msg += " " + message;
dactyl.echomsg(msg);
}
},
/**
* Resets the preference <b>name</b> to </b>value</b> but warns the user
* if the value is changed from its default.
*
* @param {string} name The preference name.
* @param {value} value The new preference value.
*/
safeResetPref: function (name, message) {
this._checkPrefSafe(name, message);
this.resetPref(name);
this.resetPref(Options.SAVED + name);
},
/**
* Sets the preference <b>name</b> to </b>value</b> but warns the user
* if the value is changed from its default.
*
* @param {string} name The preference name.
* @param {value} value The new preference value.
*/
safeSetPref: function (name, value, message) {
this._checkPrefSafe(name, message);
this._storePreference(name, value);
this._storePreference(Options.SAVED + name, value);
},
@@ -825,9 +838,7 @@ const Options = Module("options", {
try {
services.get("pref").clearUserPref(name);
}
catch (e) {
// ignore - thrown if not a user set value
}
catch (e) {} // ignore - thrown if not a user set value
},
/**
@@ -1238,7 +1249,7 @@ const Options = Module("options", {
completer: function (context, args) {
return setCompleter(context, args);
},
serial: function () [
serialize: function () [
{
command: this.name,
arguments: [opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name

View File

@@ -12,8 +12,6 @@
* @instance quickmarks
*/
const QuickMarks = Module("quickmarks", {
requires: ["config", "storage"],
init: function () {
this._qmarks = storage.newMap("quickmarks", { store: true });
},

View File

@@ -15,8 +15,6 @@
// - finish 1.9.0 support if we're going to support sanitizing in Xulmus
const Sanitizer = Module("sanitizer", {
requires: ["dactyl"],
init: function () {
const self = this;
dactyl.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
@@ -144,10 +142,13 @@ const Sanitizer = Module("sanitizer", {
context.completions = options.get("sanitizeitems").completer();
},
options: [
[["-timespan", "-t"],
commands.OPTION_INT,
function (arg) /^[0-4]$/.test(arg),
function () options.get("sanitizetimespan").completer()]
{
names: ["-timespan", "-t"],
description: "Timespan for which to sanitize items",
completer: function () options.get("sanitizetimespan").completer(),
type: CommandOption.INT,
validator: function (arg) /^[0-4]$/.test(arg)
}
]
});
},

View File

@@ -1,129 +0,0 @@
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */
/**
* Cached XPCOM services and classes.
*
* @constructor
*/
const Services = Module("services", {
init: function () {
this.classes = {};
this.services = {};
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("dactyl:", "@mozilla.org/network/protocol;1?name=dactyl");
this.add("debugger", "@mozilla.org/js/jsd/debugger-service;1", Ci.jsdIDebuggerService);
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.nsIBrowserHistory, Ci.nsIGlobalHistory3, Ci.nsINavHistoryService]);
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.nsIPrefBranch, Ci.nsIPrefBranch2, Ci.nsIPrefService]);
this.add("profile", "@mozilla.org/toolkit/profile-service;1", Ci.nsIToolkitProfileService);
this.add("runtime", "@mozilla.org/xre/runtime;1", [Ci.nsIXULAppInfo, Ci.nsIXULRuntime]);
this.add("rdf", "@mozilla.org/rdf/rdf-service;1", Ci.nsIRDFService);
this.add("sessionStore", "@mozilla.org/browser/sessionstore;1", Ci.nsISessionStore);
this.add("stylesheet", "@mozilla.org/content/style-sheet-service;1", Ci.nsIStyleSheetService);
this.add("subscriptLoader", "@mozilla.org/moz/jssubscript-loader;1", Ci.mozIJSSubScriptLoader);
this.add("tagging", "@mozilla.org/browser/tagging-service;1", Ci.nsITaggingService);
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);
this.addClass("timer", "@mozilla.org/timer;1", Ci.nsITimer);
this.addClass("xmlhttp", "@mozilla.org/xmlextras/xmlhttprequest;1", Ci.nsIXMLHttpRequest);
this.addClass("zipWriter", "@mozilla.org/zipwriter;1", Ci.nsIZipWriter);
if (!this.get("extensionManager"))
Components.utils.import("resource://gre/modules/AddonManager.jsm", modules);
},
_create: function (classes, ifaces, meth) {
try {
let res = Cc[classes][meth || "getService"]();
if (!ifaces)
return res.wrappedJSObject;
ifaces = Array.concat(ifaces);
ifaces.forEach(function (iface) res.QueryInterface(iface));
return res;
}
catch (e) {
// dactyl.log() is not defined at this time, so just dump any error
dump("Service creation failed for '" + classes + "': " + e + "\n");
return null;
}
},
/**
* Adds a new XPCOM service to the cache.
*
* @param {string} name The service's cache key.
* @param {string} class The class's contract ID.
* @param {nsISupports|nsISupports[]} ifaces The interface or array of
* interfaces implemented by this service.
* @param {string} meth The name of the function used to instanciate
* the service.
*/
add: function (name, class_, ifaces, meth) {
const self = this;
this.services.__defineGetter__(name, function () {
delete this[name];
return this[name] = self._create(class_, ifaces, meth);
});
},
/**
* Adds a new XPCOM class to the cache.
*
* @param {string} name The class's cache key.
* @param {string} class The class's contract ID.
* @param {nsISupports|nsISupports[]} ifaces The interface or array of
* interfaces implemented by this class.
*/
addClass: function (name, class_, ifaces) {
const self = this;
return this.classes[name] = function () self._create(class_, ifaces, "createInstance");
},
/**
* Returns the cached service with the specified name.
*
* @param {string} name The service's cache key.
*/
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) this.classes[name]()
}, {
javascript: function (dactyl, modules) {
JavaScript.setCompleter(this.get, [function () services.services]);
JavaScript.setCompleter(this.create, [function () [[c, ""] for (c in services.classes)]]);
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -1,832 +0,0 @@
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */
/**
* @constant
* @property {string} The default highlighting rules. They have the
* form:
* rule ::= selector space space+ css
* selector ::= group
* | group "," css-selector
* | group "," css-selector "," scope
* group ::= groupname
* | groupname css-selector
*/
// <css>
Highlights.prototype.CSS = <![CDATA[
Boolean color: red;
Function color: navy;
Null color: blue;
Number color: blue;
Object color: maroon;
String color: green;
Key font-weight: bold;
Enabled color: blue;
Disabled color: red;
Normal color: black; background: white;
ErrorMsg color: white; background: red; font-weight: bold;
InfoMsg color: black; background: white;
ModeMsg color: black; background: white;
MoreMsg color: green; background: white;
WarningMsg color: red; background: white;
Message white-space: normal; min-width: 100%; padding-left: 2em; text-indent: -2em; display: block;
NonText color: blue; min-height: 16px; padding-left: 2px;
Preview color: gray;
CmdLine,>* font-family: monospace; padding: 1px;
CmdOutput white-space: pre;
CompGroup
CompGroup:not(:first-of-type) margin-top: .5em;
CompTitle color: magenta; background: white; font-weight: bold;
CompTitle>* padding: 0 .5ex;
CompMsg font-style: italic; margin-left: 16px;
CompItem
CompItem[selected] background: yellow;
CompItem>* padding: 0 .5ex;
CompIcon width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
CompIcon>img max-width: 16px; max-height: 16px; vertical-align: middle;
CompResult width: 45%; overflow: hidden;
CompDesc color: gray; width: 50%;
CompLess text-align: center; height: 0; line-height: .5ex; padding-top: 1ex;
CompLess::after content: "\2303" /* Unicode up arrowhead */
CompMore text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
CompMore::after content: "\2304" /* Unicode down arrowhead */
Gradient height: 1px; margin-bottom: -1px; margin-top: -1px;
GradientLeft background-color: magenta;
GradientRight background-color: white;
Indicator color: blue;
Filter font-weight: bold;
Keyword color: red;
Tag color: blue;
LineNr color: orange; background: white;
Question color: green; background: white; font-weight: bold;
StatusLine color: white; background: black;
StatusLineBroken color: black; background: #FFa0a0 /* light-red */
StatusLineSecure color: black; background: #a0a0FF /* light-blue */
StatusLineExtended color: black; background: #a0FFa0 /* light-green */
TabClose,.tab-close-button
TabIcon,.tab-icon
TabText,.tab-text
TabNumber font-weight: bold; margin: 0px; padding-right: .3ex;
TabIconNumber {
font-weight: bold;
color: white;
text-align: center;
text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
}
Title color: magenta; background: white; font-weight: bold;
URL text-decoration: none; color: green; background: inherit;
URL:hover text-decoration: underline; cursor: pointer;
FrameIndicator,,* {
background-color: red;
opacity: 0.5;
z-index: 999;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
Bell border: none; background-color: black;
Hint,,* {
font-family: monospace;
font-size: 10px;
font-weight: bold;
color: white;
background-color: red;
border-color: ButtonShadow;
border-width: 0px;
border-style: solid;
padding: 0px 1px 0px 1px;
}
Hint::after,,* content: attr(number);
HintElem,,* background-color: yellow; color: black;
HintActive,,* background-color: #88FF00; color: black;
HintImage,,* opacity: .5;
Help font-size: 8pt; line-height: 1.4em; font-family: -moz-fixed;
HelpArg color: #6A97D4;
HelpOptionalArg color: #6A97D4;
HelpBody display: block; margin: 1em auto; max-width: 100ex;
HelpBorder,*,dactyl://help/* border-color: silver; border-width: 0px; border-style: solid;
HelpCode display: block; white-space: pre; margin-left: 2em; font-family: Terminus, Fixed, monospace;
HelpDefault margin-right: 1ex; white-space: pre;
HelpDescription display: block;
HelpEm,html|em,dactyl://help/* font-weight: bold; font-style: normal;
HelpEx display: inline-block; color: #527BBD; font-weight: bold;
HelpExample display: block; margin: 1em 0;
HelpExample::before content: "Example: "; font-weight: bold;
HelpInfo display: block; width: 20em; margin-left: auto;
HelpInfoLabel display: inline-block; width: 6em; color: magenta; font-weight: bold; vertical-align: text-top;
HelpInfoValue display: inline-block; width: 14em; text-decoration: none; vertical-align: text-top;
HelpItem display: block; margin: 1em 1em 1em 10em; clear: both;
HelpKey color: #102663;
HelpLink,html|a,dactyl://help/* text-decoration: none;
HelpLink[href]:hover text-decoration: underline;
HelpList,html|ul,dactyl://help/* display: block; list-style: outside disc;
HelpOrderedList,html|ol,dactyl://help/* display: block; list-style: outside decimal;
HelpListItem,html|li,dactyl://help/* display: list-item;
HelpNote color: red; font-weight: bold;
HelpOpt color: #106326;
HelpOptInfo display: inline-block; margin-bottom: 1ex;
HelpParagraph,html|p,dactyl://help/* display: block; margin: 1em 0em;
HelpParagraph:first-child margin-top: 0;
HelpSpec display: block; margin-left: -10em; float: left; clear: left; color: #527BBD;
HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top;
HelpString::before content: '"';
HelpString::after content: '"';
HelpString[delim]::before content: attr(delim);
HelpString[delim]::after content: attr(delim);
HelpHead,html|h1,dactyl://help/* {
display: block;
margin: 1em 0;
padding-bottom: .2ex;
border-bottom-width: 1px;
font-size: 2em;
font-weight: bold;
color: #527BBD;
clear: both;
}
HelpSubhead,html|h2,dactyl://help/* {
display: block;
margin: 1em 0;
padding-bottom: .2ex;
border-bottom-width: 1px;
font-size: 1.2em;
font-weight: bold;
color: #527BBD;
clear: both;
}
HelpSubsubhead,html|h3,dactyl://help/* {
display: block;
margin: 1em 0;
padding-bottom: .2ex;
font-size: 1.1em;
font-weight: bold;
color: #527BBD;
clear: both;
}
HelpTOC
HelpTOC>ol ol margin-left: -1em;
HelpTab,html|dl,dactyl://help/* display: table; width: 100%; margin: 1em 0; border-bottom-width: 1px; border-top-width: 1px; padding: .5ex 0; table-layout: fixed;
HelpTabColumn,html|column,dactyl://help/* display: table-column;
HelpTabColumn:first-child width: 25%;
HelpTabTitle,html|dt,dactyl://help/* display: table-cell; padding: .1ex 1ex; font-weight: bold;
HelpTabDescription,html|dd,dactyl://help/* display: table-cell; padding: .1ex 1ex; border-width: 0px;
HelpTabRow,html|dl>html|tr,dactyl://help/* display: table-row;
HelpTag display: inline-block; color: #527BBD; margin-left: 1ex; font-size: 8pt; font-weight: bold;
HelpTags display: block; float: right; clear: right;
HelpTopic color: #102663;
HelpType margin-right: 2ex;
HelpWarning color: red; font-weight: bold;
Logo
Search,,* {
font-size: inherit;
padding: 0;
color: black;
background-color: yellow;
}
]]>.toString();
/**
* A class to manage highlighting rules. The parameters are the
* standard parameters for any {@link Storage} object.
*
* @author Kris Maglione <maglione.k@gmail.com>
*/
function Highlights(name, store) {
let self = this;
let highlight = {};
let styles = storage.styles;
const Highlight = Struct("class", "selector", "filter", "default", "value", "base");
Highlight.defaultValue("filter", function ()
this.base ? this.base.filter :
["chrome://dactyl/*",
"dactyl:*",
"file://*"].concat(config.styleableChrome).join(","));
Highlight.defaultValue("selector", function () self.selector(this.class));
Highlight.defaultValue("value", function () this.default);
Highlight.defaultValue("base", function () {
let base = this.class.match(/^(\w*)/)[0];
return base != this.class && base in highlight ? highlight[base] : null;
});
Highlight.prototype.toString = function () "Highlight(" + this.class + ")\n\t" + [k + ": " + util.escapeString(v || "undefined") for ([k, v] in this)].join("\n\t");
function keys() [k for ([k, v] in Iterator(highlight))].sort();
this.__iterator__ = function () (highlight[v] for ([k, v] in Iterator(keys())));
this.get = function (k) highlight[k];
this.set = function (key, newStyle, force, append) {
let [, class_, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/);
if (!(class_ in highlight))
return "Unknown highlight keyword: " + class_;
let style = highlight[key] || Highlight(key);
styles.removeSheet(true, style.selector);
if (append)
newStyle = (style.value || "").replace(/;?\s*$/, "; " + newStyle);
if (/^\s*$/.test(newStyle))
newStyle = null;
if (newStyle == null) {
if (style.default == null) {
delete highlight[style.class];
styles.removeSheet(true, style.selector);
return null;
}
newStyle = style.default;
force = true;
}
let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
.replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
if (!/^\s*(?:!\s*important\s*)?;*\s*$/.test(css)) {
css = style.selector + " { " + css + " }";
let error = styles.addSheet(true, "highlight:" + style.class, style.filter, css, true);
if (error)
return error;
}
style.value = newStyle;
highlight[style.class] = style;
return null;
};
/**
* Gets a CSS selector given a highlight group.
*
* @param {string} class
*/
this.selector = function (class_) {
let [, hl, rest] = class_.match(/^(\w*)(.*)/);
let pattern = "[dactyl|highlight~=" + hl + "]"
if (highlight[hl] && highlight[hl].class != class_)
pattern = highlight[hl].selector;
return pattern + rest;
};
/**
* Clears all highlighting rules. Rules with default values are
* reset.
*/
this.clear = function () {
for (let [k, v] in Iterator(highlight))
this.set(k, null, true);
};
/**
* Bulk loads new CSS rules.
*
* @param {string} css The rules to load. See {@link Highlights#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) {
style = Highlight.apply(Highlight, Array.slice(style.match(/^\s*((?:[^,\s]|\s\S)+)(?:,((?:[^,\s]|\s\S)+)?)?(?:,((?:[^,\s]|\s\S)+))?\s*(.*)$/), 1));
if (/^[>+ ]/.test(style.selector))
style.selector = self.selector(style.class) + style.selector;
let old = highlight[style.class];
highlight[style.class] = style;
if (old && old.value != old.default)
style.value = old.value;
});
for (let [class_, hl] in Iterator(highlight)) {
if (hl.value == hl.default)
this.set(class_);
}
};
this.loadCSS(this.CSS);
}
/**
* Manages named and unnamed user style sheets, which apply to both
* chrome and content pages. The parameters are the standard
* parameters for any {@link Storage} object.
*
* @author Kris Maglione <maglione.k@gmail.com>
*/
function Styles(name, store) {
// Can't reference dactyl or Components inside Styles --
// they're members of the window object, which disappear
// with this window.
const self = this;
const util = modules.util;
const sleep = dactyl.sleep;
const storage = modules.storage;
const ios = services.get("io");
const sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
const namespace = "@namespace html " + XHTML.uri.quote() + ";\n" +
"@namespace xul " + XUL.uri.quote() + ";\n" +
"@namespace dactyl " + NS.uri.quote() + ";\n";
const Sheet = Struct("name", "id", "sites", "css", "system", "agent");
Sheet.prototype.__defineGetter__("fullCSS", function wrapCSS() {
let filter = this.sites;
let css = this.css;
if (filter[0] == "*")
return namespace + css;
let selectors = filter.map(function (part) (/[*]$/.test(part) ? "url-prefix" :
/[\/:]/.test(part) ? "url"
: "domain")
+ '("' + part.replace(/"/g, "%22").replace(/[*]$/, "") + '")')
.join(", ");
return namespace + "/* Dactyl style #" + this.id + " */ @-moz-document " + selectors + "{\n" + css + "\n}\n";
});
Sheet.prototype.__defineGetter__("enabled", function () this._enabled);
Sheet.prototype.__defineSetter__("enabled", function (on) {
this._enabled = Boolean(on);
if (on) {
self.registerSheet(cssUri(this.fullCSS));
if (this.agent)
self.registerSheet(cssUri(this.fullCSS), true);
}
else {
self.unregisterSheet(cssUri(this.fullCSS));
self.unregisterSheet(cssUri(this.fullCSS), true);
}
});
let cssUri = function (css) "chrome-data:text/css," + window.encodeURI(css);
let userSheets = [];
let systemSheets = [];
let userNames = {};
let systemNames = {};
let id = 0;
this.__iterator__ = function () Iterator(userSheets.concat(systemSheets));
this.__defineGetter__("systemSheets", function () Iterator(systemSheets));
this.__defineGetter__("userSheets", function () Iterator(userSheets));
this.__defineGetter__("systemNames", function () Iterator(systemNames));
this.__defineGetter__("userNames", function () Iterator(userNames));
/**
* Add a new style sheet.
*
* @param {boolean} system Declares whether this is a system or
* user sheet. System sheets are used internally by
* @dactyl.
* @param {string} name The name given to the style sheet by
* which it may be later referenced.
* @param {string} filter The sites to which this sheet will
* apply. Can be a domain name or a URL. Any URL ending in
* "*" is matched as a prefix.
* @param {string} css The CSS to be applied.
*/
this.addSheet = function (system, name, filter, css, agent) {
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
if (name && name in names)
this.removeSheet(system, name);
let sheet = Sheet(name, id++, filter.split(",").filter(util.identity), String(css), null, system, agent);
try {
sheet.enabled = true;
}
catch (e) {
return e.echoerr || e;
}
sheets.push(sheet);
if (name)
names[name] = sheet;
return null;
};
/**
* Get a sheet with a given name or index.
*
* @param {boolean} system
* @param {string or number} sheet The sheet to retrieve. Strings indicate
* sheet names, while numbers indicate indices.
*/
this.get = function get(system, sheet) {
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
if (typeof sheet === "number")
return sheets[sheet];
return names[sheet];
};
/**
* Find sheets matching the parameters. See {@link #addSheet}
* for parameters.
*
* @param {boolean} system
* @param {string} name
* @param {string} filter
* @param {string} css
* @param {number} index
*/
this.findSheets = function (system, name, filter, css, index) {
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
// Grossly inefficient.
let matches = [k for ([k, v] in Iterator(sheets))];
if (index)
matches = String(index).split(",").filter(function (i) i in sheets);
if (name)
matches = matches.filter(function (i) sheets[i] == names[name]);
if (css)
matches = matches.filter(function (i) sheets[i].css == css);
if (filter)
matches = matches.filter(function (i) sheets[i].sites.indexOf(filter) >= 0);
return matches.map(function (i) sheets[i]);
};
/**
* Remove a style sheet. See {@link #addSheet} for parameters.
* In cases where <b>filter</b> is supplied, the given filters
* are removed from matching sheets. If any remain, the sheet is
* left in place.
*
* @param {boolean} system
* @param {string} name
* @param {string} filter
* @param {string} css
* @param {number} index
*/
this.removeSheet = function (system, name, filter, css, index) {
let self = this;
if (arguments.length == 1) {
var matches = [system];
system = matches[0].system;
}
let sheets = system ? systemSheets : userSheets;
let names = system ? systemNames : userNames;
if (filter && filter.indexOf(",") > -1)
return filter.split(",").reduce(
function (n, f) n + self.removeSheet(system, name, f, index), 0);
if (filter == undefined)
filter = "";
if (!matches)
matches = this.findSheets(system, name, filter, css, index);
if (matches.length == 0)
return null;
for (let [, sheet] in Iterator(matches.reverse())) {
sheet.enabled = false;
if (name)
delete names[name];
if (sheets.indexOf(sheet) > -1)
sheets.splice(sheets.indexOf(sheet), 1);
/* Re-add if we're only changing the site filter. */
if (filter) {
let sites = sheet.sites.filter(function (f) f != filter);
if (sites.length)
this.addSheet(system, name, sites.join(","), css, sheet.agent);
}
}
return matches.length;
};
/**
* Register a user style sheet at the given URI.
*
* @param {string} uri The URI of the sheet to register.
* @param {boolean} agent If true, sheet is registered as an agent sheet.
* @param {boolean} reload Whether to reload any sheets that are
* already registered.
*/
this.registerSheet = function (uri, agent, reload) {
if (reload)
this.unregisterSheet(uri, agent);
uri = ios.newURI(uri, null, null);
if (reload || !sss.sheetRegistered(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET))
sss.loadAndRegisterSheet(uri, agent ? sss.AGENT_SHEET : sss.USER_SHEET);
};
/**
* Unregister a sheet at the given URI.
*
* @param {string} uri The URI of the sheet to unregister.
*/
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);
};
}
Module("styles", {
requires: ["config", "dactyl", "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) {
context.anchored = false;
try {
context.fork("current", 0, this, function (context) {
context.title = ["Current Site"];
context.completions = [
[content.location.host, "Current Host"],
[content.location.href, "Current URL"]
];
});
}
catch (e) {}
context.fork("others", 0, this, function (context) {
context.title = ["Site"];
context.completions = [[s, ""] for ([, s] in Iterator(styles.sites))];
});
}
});
}
return storage.newObject("styles", Styles, { store: false });
}
}, {
}, {
commands: function () {
commands.add(["sty[le]"],
"Add or list user styles",
function (args) {
let [filter, css] = args;
let name = args["-name"];
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"],
["min-width: 1em; text-align: center; color: red; font-weight: bold;",
"padding: 0 1em 0 1ex; vertical-align: top;",
"padding: 0 1em 0 0; vertical-align: top;"],
([sheet.enabled ? "" : "\u00d7",
key,
sheet.sites.join(","),
sheet.css]
for ([i, [key, sheet]] in Iterator(list))
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) {
let sheet = styles.get(false, name);
if (sheet) {
filter = sheet.sites.concat(filter).join(",");
css = sheet.css + " " + css;
}
}
let err = styles.addSheet(false, name, filter, css);
if (err)
dactyl.echoerr(err);
}
},
{
bang: true,
completer: function (context, args) {
let compl = [];
if (args.completeArg == 0)
styles.completeSite(context, content);
else if (args.completeArg == 1) {
let sheet = styles.get(false, args["-name"]);
if (sheet)
context.completions = [[sheet.css, "Current Value"]];
}
},
hereDoc: true,
literal: 1,
options: [[["-name", "-n"], commands.OPTION_STRING, null, function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))]],
[["-append", "-a"], commands.OPTION_NOARG]],
serial: function () [
{
command: this.name,
bang: true,
options: sty.name ? { "-name": sty.name } : {},
arguments: [sty.sites.join(",")],
literalArg: sty.css
} for ([k, sty] in styles.userSheets)
]
});
[
{
name: ["stylee[nable]", "stye[nable]"],
desc: "Enable a user style sheet",
action: function (sheet) sheet.enabled = true,
filter: function (sheet) !sheet.enabled
},
{
name: ["styled[isable]", "styd[isable]"],
desc: "Disable a user style sheet",
action: function (sheet) sheet.enabled = false,
filter: function (sheet) sheet.enabled
},
{
name: ["stylet[oggle]", "styt[oggle]"],
desc: "Toggle a user style sheet",
action: function (sheet) sheet.enabled = !sheet.enabled
},
{
name: ["dels[tyle]"],
desc: "Remove a user style sheet",
action: function (sheet) styles.removeSheet(sheet)
}
].forEach(function (cmd) {
commands.add(cmd.name, cmd.desc,
function (args) {
styles.findSheets(false, args["-name"], args[0], args.literalArg, args["-index"])
.forEach(cmd.action);
},
{
completer: function (context) { context.completions = styles.sites.map(function (site) [site, ""]); },
literal: 1,
options: [[["-index", "-i"], commands.OPTION_INT, null,
function (context) {
context.compare = CompletionContext.Sort.number;
return [[i, <>{sheet.sites.join(",")}: {sheet.css.replace("\n", "\\n")}</>]
for ([i, sheet] in styles.userSheets)
if (!cmd.filter || cmd.filter(sheet))];
}],
[["-name", "-n"], commands.OPTION_STRING, null,
function () [[name, sheet.css]
for ([name, sheet] in Iterator(styles.userNames))
if (!cmd.filter || cmd.filter(sheet))]]]
});
});
},
completion: function () {
JavaScript.setCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]),
[ // Prototype: (system, name, filter, css, index)
null,
function (context, obj, args) args[0] ? this.systemNames : this.userNames,
function (context, obj, args) this.completeSite(context, content),
null,
function (context, obj, args) args[0] ? this.systemSheets : this.userSheets
]);
}
});
Module("highlight", {
requires: ["config", "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
dactyl.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) {
let style = <![CDATA[
;
display: inline-block !important;
position: static !important;
margin: 0px !important; padding: 0px !important;
width: 3em !important; min-width: 3em !important; max-width: 3em !important;
height: 1em !important; min-height: 1em !important; max-height: 1em !important;
overflow: hidden !important;
]]>;
let clear = args[0] == "clear";
if (clear)
args.shift();
let [key, css] = args;
dactyl.assert(!(clear && css), "E488: Trailing characters");
if (!css && !clear) {
// List matching keys
let str = template.tabular(["Key", "Sample", "CSS"],
["padding: 0 1em 0 0; vertical-align: top",
"text-align: center"],
([h.class,
<span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>,
template.highlightRegexp(h.value, /\b[-\w]+(?=:)/g)]
for (h in highlight)
if (!key || h.class.indexOf(key) > -1)));
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
return;
}
if (!key && clear)
highlight.clear();
else {
let error = highlight.set(key, css, clear, "-append" in args);
if (error)
dactyl.echoerr(error);
}
},
{
// TODO: add this as a standard highlight completion function?
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) {
let hl = highlight.get(args[0]);
if (hl)
context.completions = [[hl.value, "Current Value"], [hl.default || "", "Default Value"]];
}
},
hereDoc: true,
literal: 1,
options: [[["-append", "-a"], commands.OPTION_NOARG]],
serial: function () [
{
command: this.name,
arguments: [k],
literalArg: v
}
for ([k, v] in Iterator(highlight))
if (v.value != v.default)
]
});
},
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" };
context.completions = util.Array.flatten(
io.getRuntimeDirectories("colors").map(
function (dir) dir.readDirectory().filter(
function (file) /\.vimp$/.test(file.leafName))))
};
completion.highlightGroup = function highlightGroup(context) {
context.title = ["Highlight Group", "Value"];
context.completions = [[v.class, v.value] for (v in highlight)];
};
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -14,8 +14,6 @@
* @instance tabs
*/
const Tabs = Module("tabs", {
requires: ["config"],
init: function () {
this._alternates = [config.tabbrowser.mCurrentTab, null];
@@ -187,7 +185,7 @@ const Tabs = Module("tabs", {
this._groups = this._groups = iframe ? iframe.contentWindow : null;
if (this._groups)
while (!this._groups.TabItems)
dactyl.threadYield(false, true);
util.threadYield(false, true);
return this._groups;
},
@@ -196,14 +194,15 @@ const Tabs = Module("tabs", {
* selected tab if <b>index</b> is not specified. This is a 0-based
* index.
*
* @param {number} index The index of the tab required.
* @param {number|Node} index The index of the tab required or the tab itself
* @returns {Object}
*/
getTab: function (index) {
if (index instanceof Node)
return index;
if (index != null)
return config.tabbrowser.mTabs[index];
else
return config.tabbrowser.mCurrentTab;
return config.tabbrowser.mCurrentTab;
},
/**

View File

@@ -1,309 +0,0 @@
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */
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) {
if (iter.length) // FIXME: Kludge?
iter = array.itervalues(iter);
let ret = <></>;
let n = 0;
for each (let i in Iterator(iter)) {
let val = func(i);
if (val == undefined)
continue;
if (sep && n++)
ret += sep;
if (interruptable && n % interruptable == 0)
util.threadYield(true, true);
ret += val;
}
return ret;
},
maybeXML: function maybeXML(xml) {
if (typeof xml == "xml")
return xml;
try {
return new XMLList(xml);
}
catch (e) {}
return <>{xml}</>;
},
completionRow: function completionRow(item, highlightGroup) {
if (typeof icon == "function")
icon = icon();
if (highlightGroup) {
var text = item[0] || "";
var desc = item[1] || "";
}
else {
var text = this.process[0].call(this, item, item.text);
var desc = this.process[1].call(this, item, item.description);
}
// <e4x>
return <div highlight={highlightGroup || "CompItem"} style="white-space: nowrap">
<!-- The non-breaking spaces prevent empty elements
- from pushing the baseline down and enlarging
- the row.
-->
<li highlight="CompResult">{text}&#160;</li>
<li highlight="CompDesc">{desc}&#160;</li>
</div>;
// </e4x>
},
bookmarkDescription: function (item, text)
<>
<a href={item.item.url} highlight="URL">{text}</a>&#160;
{
!(item.extra && item.extra.length) ? "" :
<span class="extra-info">
({
template.map(item.extra, function (e)
<>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
<>&#xa0;</>/* Non-breaking space */)
})
</span>
}
</>,
icon: function (item, text) {
return <><span highlight="CompIcon">{item.icon ? <img src={item.icon}/> : <></>}</span><span class="td-strut"/>{text}</>
},
filter: function (str) <span highlight="Filter">{str}</span>,
gradient: function (left, right)
<div highlight="Gradient">
<div style="height: 0px">
<div highlight={right + " Gradient"}
style="border: 0 !important; margin: 0 !important; padding: 0 !important;"/>
</div>
<table width="100%" style="height: 100%">
<tr>
{ template.map(util.range(0, 100), function (i)
<td highlight={left} style={"opacity: " + (1 - i / 100)}
/>) }
</tr>
</table>
</div>,
// 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) {
// some objects like window.JSON or getBrowsers()._browsers need the try/catch
try {
let str = clip ? util.clip(String(arg), clip) : String(arg);
switch (arg == null ? "undefined" : typeof arg) {
case "number":
return <span highlight="Number">{str}</span>;
case "string":
if (processStrings)
str = str.quote();
return <span highlight="String">{str}</span>;
case "boolean":
return <span highlight="Boolean">{str}</span>;
case "function":
// Vim generally doesn't like /foo*/, because */ looks like a comment terminator.
// Using /foo*(:?)/ instead.
if (processStrings)
return <span highlight="Function">{str.replace(/\{(.|\n)*(?:)/g, "{ ... }")}</span>;
<>}</>; /* Vim */
return <>{arg}</>;
case "undefined":
return <span highlight="Null">{arg}</span>;
case "object":
// for java packages value.toString() would crash so badly
// that we cannot even try/catch it
if (/^\[JavaPackage.*\]$/.test(arg))
return <>[JavaPackage]</>;
if (processStrings && false)
str = template.highlightFilter(str, "\n", function () <span highlight="NonText">^J</span>);
return <span highlight="Object">{str}</span>;
case "xml":
return arg;
default:
return <![CDATA[<unknown type>]]>;
}
}
catch (e) {
return <![CDATA[<unknown>]]>;
}
},
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) {
yield [start, filter.length];
start += filter.length;
}
})(), highlight || template.filter);
},
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) {
if (typeof str == "xml")
return str;
if (str == "")
return <>{str}</>;
str = String(str).replace(" ", "\u00a0");
let s = <></>;
let start = 0;
let n = 0;
for (let [i, length] in iter) {
if (n++ > 50) // Prevent infinite loops.
return s + <>{str.substr(start)}</>;
XML.ignoreWhitespace = false;
s += <>{str.substring(start, i)}</>;
s += highlight(str.substr(i, length));
start = i + length;
}
return s + <>{str.substr(start)}</>;
},
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(command, xml) {
return <>:{command}<br/>{xml}</>;
},
genericTable: function genericTable(items, format) {
completion.listCompleter(function (context) {
context.filterFunc = null;
if (format)
context.format = format;
context.completions = items;
});
},
jumps: function jumps(index, elems) {
// <e4x>
return this.commandOutput(
<table>
<tr style="text-align: left;" highlight="Title">
<th colspan="2">jump</th><th>title</th><th>URI</th>
</tr>
{
this.map(Iterator(elems), function ([idx, val])
<tr>
<td class="indicator">{idx == index ? ">" : ""}</td>
<td>{Math.abs(idx - index)}</td>
<td style="width: 250px; max-width: 500px; overflow: hidden;">{val.title}</td>
<td><a href={val.URI.spec} highlight="URL jump-list">{val.URI.spec}</a></td>
</tr>)
}
</table>);
// </e4x>
},
options: function options(title, opts) {
// <e4x>
return this.commandOutput(
<table>
<tr highlight="Title" align="left">
<th>--- {title} ---</th>
</tr>
{
this.map(opts, function (opt)
<tr>
<td>
<span style={opt.isDefault ? "" : "font-weight: bold"}>{opt.pre}{opt.name}</span><span>{opt.value}</span>
{opt.isDefault || opt.default == null ? "" : <span class="extra-info"> (default: {opt.default})</span>}
</td>
</tr>)
}
</table>);
// </e4x>
},
table: function table(title, data, indent) {
let table =
// <e4x>
<table>
<tr highlight="Title" align="left">
<th colspan="2">{title}</th>
</tr>
{
this.map(data, function (datum)
<tr>
<td style={"font-weight: bold; min-width: 150px; padding-left: " + (indent || "2ex")}>{datum[0]}</td>
<td>{template.maybeXML(datum[1])}</td>
</tr>)
}
</table>;
// </e4x>
if (table.tr.length() > 1)
return table;
return XML();
},
tabular: function tabular(headings, style, iter) {
// TODO: This might be mind-bogglingly slow. We'll see.
// <e4x>
return this.commandOutput(
<table>
<tr highlight="Title" align="left">
{
this.map(headings, function (h)
<th>{h}</th>)
}
</tr>
{
this.map(iter, function (row)
<tr>
{
template.map(Iterator(row), function ([i, d])
<td style={style[i] || ""}>{d}</td>)
}
</tr>)
}
</table>);
// </e4x>
},
usage: function usage(iter) {
// <e4x>
return this.commandOutput(
<table>
{
this.map(iter, function (item)
<tr>
<td highlight="Title" style="padding-right: 20px">{item.name || item.names[0]}</td>
<td>{item.description}</td>
</tr>)
}
</table>);
// </e4x>
}
});
// vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -1,715 +0,0 @@
// Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */
const XHTML = Namespace("html", "http://www.w3.org/1999/xhtml");
const XUL = Namespace("xul", "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
const NS = Namespace("dactyl", "http://vimperator.org/namespaces/liberator");
default xml namespace = XHTML;
const Util = Module("util", {
init: function () {
this.Array = array;
},
get activeWindow() services.get("windowWatcher").activeWindow,
callInMainThread: function (callback, self) {
let mainThread = services.get("threadManager").mainThread;
if (!services.get("threadManager").isMainThread)
mainThread.dispatch({ run: callback.call(self) }, mainThread.DISPATCH_NORMAL);
else
callback.call(self);
},
/**
* Returns a shallow copy of <b>obj</b>.
*
* @param {Object} obj
* @returns {Object}
*/
cloneObject: function cloneObject(obj) {
if (isarray(obj))
return obj.slice();
let newObj = {};
for (let [k, v] in Iterator(obj))
newObj[k] = v;
return newObj;
},
/**
* Clips a string to a given length. If the input string is longer
* than <b>length</b>, an ellipsis is appended.
*
* @param {string} str The string to truncate.
* @param {number} length The length of the returned string.
* @returns {string}
*/
clip: function clip(str, length) {
return str.length <= length ? str : str.substr(0, length - 3) + "...";
},
/**
* Compares two strings, case insensitively. Return values are as
* in String#localeCompare.
*
* @param {string} a
* @param {string} b
* @returns {number}
*/
compareIgnoreCase: function compareIgnoreCase(a, b) String.localeCompare(a.toLowerCase(), b.toLowerCase()),
/**
* Returns an object representing a Node's computed CSS style.
*
* @param {Node} node
* @returns {Object}
*/
computedStyle: function computedStyle(node) {
while (node instanceof Ci.nsIDOMText && node.parentNode)
node = node.parentNode;
return node.ownerDocument.defaultView.getComputedStyle(node, null);
},
/**
* Copies a string to the system clipboard. If <b>verbose</b> is specified
* the copied string is also echoed to the command line.
*
* @param {string} str
* @param {boolean} verbose
*/
copyToClipboard: function copyToClipboard(str, verbose) {
const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
clipboardHelper.copyString(str);
if (verbose)
dactyl.echo("Yanked " + str, commandline.FORCE_SINGLELINE);
},
/**
* Converts any arbitrary string into an URI object.
*
* @param {string} str
* @returns {Object}
*/
// FIXME: newURI needed too?
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);
},
/**
* Expands brace globbing patterns in a string.
*
* Example:
* "a{b,c}d" => ["abd", "acd"]
*
* @param {string} pattern The pattern to deglob.
* @returns [string] The resulting strings.
*/
debrace: function deglobBrace(pattern) {
function split(pattern, re, fn, dequote) {
let end = 0, match, res = [];
while (match = re.exec(pattern)) {
end = match.index + match[0].length;
res.push(match[1]);
if (fn)
fn(match);
}
res.push(pattern.substr(end));
return res.map(function (s) util.dequote(s, dequote));
}
let patterns = [], res = [];
let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy,
function (match) {
patterns.push(split(match[2], /((?:[^\\,]|\\.)*),/gy,
null, ",{}"));
}, "{}");
function rec(acc) {
if (acc.length == patterns.length)
res.push(util.Array.zip(substrings, acc).join(""));
else
for (let [, pattern] in Iterator(patterns[acc.length]))
rec(acc.concat(pattern));
}
rec([]);
return res;
},
/**
* Removes certain backslash-quoted characters while leaving other
* backslash-quoting sequences untouched.
*
* @param {string} pattern The string to unquote.
* @param {string} chars The characters to unquote.
* @returns {string}
*/
dequote: function dequote(pattern, chars)
pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0),
/**
* Converts HTML special characters in <b>str</b> to the equivalent HTML
* entities.
*
* @param {string} str
* @returns {string}
*/
escapeHTML: function escapeHTML(str) {
return str.replace(/&/g, "&amp;").replace(/</g, "&lt;");
},
/**
* Escapes Regular Expression special characters in <b>str</b>.
*
* @param {string} str
* @returns {string}
*/
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. {@see String#quote}.
*
* @param {string} str
* @param {string} delimiter
* @returns {string}
*/
escapeString: function escapeString(str, delimiter) {
if (delimiter == undefined)
delimiter = '"';
return delimiter + str.replace(/([\\'"])/g, "\\$1").replace("\n", "\\n", "g").replace("\t", "\\t", "g") + delimiter;
},
/**
* Evaluates an XPath expression in the current or provided
* document. It provides the xhtml, xhtml2 and dactyl XML
* namespaces. The result may be used as an iterator.
*
* @param {string} expression The XPath expression to evaluate.
* @param {Document} doc The document to evaluate the expression in.
* @default The current document.
* @param {Node} elem The context element.
* @default <b>doc</b>
* @param {boolean} asIterator Whether to return the results as an
* XPath iterator.
*/
evaluateXPath: function (expression, doc, elem, asIterator) {
if (!doc)
doc = content.document;
if (!elem)
elem = doc;
if (isarray(expression))
expression = util.makeXPath(expression);
let result = doc.evaluate(expression, elem,
function lookupNamespaceURI(prefix) {
return {
xul: XUL.uri,
xhtml: XHTML.uri,
xhtml2: "http://www.w3.org/2002/06/xhtml2",
dactyl: NS.uri
}[prefix] || null;
},
asIterator ? Ci.nsIDOMXPathResult.ORDERED_NODE_ITERATOR_TYPE : Ci.nsIDOMXPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
null
);
return {
__proto__: result,
__iterator__: asIterator
? function () { let elem; while ((elem = this.iterateNext())) yield elem; }
: function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); }
}
},
extend: function extend(dest) {
Array.slice(arguments, 1).filter(util.identity).forEach(function (src) {
for (let [k, v] in Iterator(src)) {
let get = src.__lookupGetter__(k),
set = src.__lookupSetter__(k);
if (!get && !set)
dest[k] = v;
if (get)
dest.__defineGetter__(k, get);
if (set)
dest.__defineSetter__(k, set);
}
});
return dest;
},
/**
* Returns the selection controller for the given window.
*
* @param {Window} window
* @returns {nsISelectionController}
*/
selectionController: function (win)
win.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIWebNavigation)
.QueryInterface(Ci.nsIDocShell)
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController),
/**
* Converts <b>bytes</b> to a pretty printed data size string.
*
* @param {number} bytes The number of bytes.
* @param {string} decimalPlaces The number of decimal places to use if
* <b>humanReadable</b> is true.
* @param {boolean} humanReadable Use byte multiples.
* @returns {string}
*/
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) {
tmpNum /= 1024;
if (++unitIndex > (unitVal.length - 1))
break;
}
let decPower = Math.pow(10, decimalPlaces);
strNum = ((Math.round(tmpNum * decPower) / decPower) + "").split(".", 2);
if (!strNum[1])
strNum[1] = "";
while (strNum[1].length < decimalPlaces) // pad with "0" to the desired decimalPlaces)
strNum[1] += "0";
}
for (let u = strNum[0].length - 3; u > 0; u -= 3) // make a 10000 a 10,000
strNum[0] = strNum[0].substr(0, u) + "," + strNum[0].substr(u);
if (unitIndex) // decimalPlaces only when > Bytes
strNum[0] += "." + strNum[1];
return strNum[0] + " " + unitVal[unitIndex];
},
/**
* Sends a synchronous or asynchronous HTTP request to <b>url</b> and
* returns the XMLHttpRequest object. If <b>callback</b> is specified the
* request is asynchronous and the <b>callback</b> is invoked with the
* object as its argument.
*
* @param {string} url
* @param {function(XMLHttpRequest)} callback
* @returns {XMLHttpRequest}
*/
httpGet: function httpGet(url, callback) {
try {
let xmlhttp = services.create("xmlhttp");
xmlhttp.mozBackgroundRequest = true;
if (callback) {
xmlhttp.onreadystatechange = function () {
if (xmlhttp.readyState == 4)
callback(xmlhttp);
};
}
xmlhttp.open("GET", url, !!callback);
xmlhttp.send(null);
return xmlhttp;
}
catch (e) {
dactyl.log("Error opening " + String.quote(url) + ": " + e, 1);
return null;
}
},
/**
* The identity function.
*
* @param {Object} k
* @returns {Object}
*/
identity: function identity(k) k,
/**
* Returns the intersection of two rectangles.
*
* @param {Object} r1
* @param {Object} r2
* @returns {Object}
*/
intersection: function (r1, r2) ({
get width() this.right - this.left,
get height() this.bottom - this.top,
left: Math.max(r1.left, r2.left),
right: Math.min(r1.right, r2.right),
top: Math.max(r1.top, r2.top),
bottom: Math.min(r1.bottom, r2.bottom)
}),
/**
* Returns an XPath union expression constructed from the specified node
* tests. An expression is built with node tests for both the null and
* XHTML namespaces. See {@link Buffer#evaluateXPath}.
*
* @param nodes {Array(string)}
* @returns {string}
*/
makeXPath: function makeXPath(nodes) {
return util.Array(nodes).map(util.debrace).flatten()
.map(function (node) [node, "xhtml:" + node]).flatten()
.map(function (node) "//" + node).join(" | ");
},
/**
* Returns the array that results from applying <b>func</b> to each
* property of <b>obj</b>.
*
* @param {Object} obj
* @param {function} func
* @returns {Array}
*/
map: function map(obj, func) {
let ary = [];
for (let i in Iterator(obj))
ary.push(func(i));
return ary;
},
/**
* Memoize the lookup of a property in an object.
*
* @param {object} obj The object to alter.
* @param {string} key The name of the property to memoize.
* @param {function} getter A function of zero to two arguments which
* will return the property's value. <b>obj</b> is
* passed as the first argument, <b>key</b> as the
* second.
*/
memoize: memoize,
/**
* Converts a URI string into a URI object.
*
* @param {string} uri
* @returns {nsIURI}
*/
// FIXME: createURI needed too?
newURI: function (uri) {
return services.get("io").newURI(uri, null, null);
},
/**
* Pretty print a JavaScript object. Use HTML markup to color certain items
* if <b>color</b> is true.
*
* @param {Object} object The object to pretty print.
* @param {boolean} color Whether the output should be colored.
* @returns {string}
*/
objectToString: function objectToString(object, color) {
// Use E4X literals so html is automatically quoted
// 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;
XML.ignoreWhitespace = false;
if (object === null)
return "null\n";
if (typeof object != "object")
return false;
const NAMESPACES = util.Array.toObject([
[NS, 'dactyl'],
[XHTML, 'html'],
[XUL, 'xul']
]);
if (object instanceof Ci.nsIDOMElement) {
let elem = object;
if (elem.nodeType == elem.TEXT_NODE)
return elem.data;
function namespaced(node) {
var ns = NAMESPACES[node.namespaceURI];
if (ns)
return ns + ":" + node.localName;
return node.localName.toLowerCase();
}
try {
let tag = "<" + [namespaced(elem)].concat(
[namespaced(a) + "=" + template.highlight(a.value, true)
for ([i, a] in util.Array.iteritems(elem.attributes))]).join(" ");
if (!elem.firstChild || /^\s*$/.test(elem.firstChild) && !elem.firstChild.nextSibling)
tag += '/>';
else
tag += '>...</' + namespaced(elem) + '>';
return tag;
}
catch (e) {
return {}.toString.call(elem);
}
}
try { // for window.JSON
var obj = String(object);
}
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"
let hasValue = !("__iterator__" in object);
/*
if (modules.isPrototypeOf(object)) {
object = Iterator(object);
hasValue = false;
}
*/
for (let i in object) {
let value = <![CDATA[<no value>]]>;
try {
value = object[i];
}
catch (e) {}
if (!hasValue) {
if (isarray(i) && i.length == 2)
[i, value] = i;
else
var noVal = true;
}
value = template.highlight(value, true, 150);
let key = <span highlight="Key">{i}</span>;
if (!isNaN(i))
i = parseInt(i);
else if (/^[A-Z_]+$/.test(i))
i = "";
keys.push([i, <>{key}{noVal ? "" : <>: {value}</>}<br/>&#xa;</>]);
}
}
catch (e) {}
function compare(a, b) {
if (!isNaN(a[0]) && !isNaN(b[0]))
return a[0] - b[0];
return String.localeCompare(a[0], b[0]);
}
string += template.map(keys.sort(compare), function (f) f[1]);
return color ? string : [s for each (s in string)].join("");
},
/**
* A generator that returns the values between <b>start</b> and <b>end</b>,
* in <b>step</b> increments.
*
* @param {number} start The interval's start value.
* @param {number} end The interval's end value.
* @param {boolean} step The value to step the range by. May be
* negative. @default 1
* @returns {Iterator(Object)}
*/
range: function range(start, end, step) {
if (!step)
step = 1;
if (step > 0) {
for (; start < end; start += step)
yield start;
}
else {
while (start > end)
yield start += step;
}
},
/**
* An interruptible generator that returns all values between <b>start</b>
* and <b>end</b>. The thread yields every <b>time</b> milliseconds.
*
* @param {number} start The interval's start value.
* @param {number} end The interval's end value.
* @param {number} time The time in milliseconds between thread yields.
* @returns {Iterator(Object)}
*/
interruptibleRange: function interruptibleRange(start, end, time) {
let endTime = Date.now() + time;
while (start < end) {
if (Date.now() > endTime) {
util.threadYield(true, true);
endTime = Date.now() + time;
}
yield start++;
}
},
/**
* Reads a string from the system clipboard.
*
* This is same as Firefox's readFromClipboard function, but is needed for
* apps like Thunderbird which do not provide it.
*
* @returns {string}
*/
readFromClipboard: function readFromClipboard() {
let str;
try {
const clipboard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
const transferable = Cc["@mozilla.org/widget/transferable;1"].createInstance(Ci.nsITransferable);
transferable.addDataFlavor("text/unicode");
if (clipboard.supportsSelectionClipboard())
clipboard.getData(transferable, clipboard.kSelectionClipboard);
else
clipboard.getData(transferable, clipboard.kGlobalClipboard);
let data = {};
let dataLen = {};
transferable.getTransferData("text/unicode", data, dataLen);
if (data) {
data = data.value.QueryInterface(Ci.nsISupportsString);
str = data.data.substring(0, dataLen.value / 2);
}
}
catch (e) {}
return str;
},
/**
* Scrolls an element into view if and only if it's not already
* fully visible.
*
* @param {Node} elem The element to make visible.
*/
scrollIntoView: function scrollIntoView(elem) {
let win = elem.ownerDocument.defaultView;
let rect = elem.getBoundingClientRect();
if (!(rect && rect.top < win.innerHeight && rect.bottom >= 0 && rect.left < win.innerWidth && rect.right >= 0))
elem.scrollIntoView();
},
sleep: function (delay) {
let mainThread = services.get("threadManager").mainThread;
let end = Date.now() + delay;
while (Date.now() < end)
mainThread.processNextEvent(true);
return true;
},
/**
* Split a string on literal occurrences of a marker.
*
* Specifically this ignores occurrences preceded by a backslash, or
* contained within 'single' or "double" quotes.
*
* It assumes backslash escaping on strings, and will thus not count quotes
* that are preceded by a backslash or within other quotes as starting or
* ending quoted sections of the string.
*
* @param {string} str
* @param {RegExp} marker
*/
splitLiteral: function splitLiteral(str, marker) {
let results = [];
let resep = RegExp(/^(([^\\'"]|\\.|'([^\\']|\\.)*'|"([^\\"]|\\.)*")*?)/.source + marker.source);
let cont = true;
while (cont) {
cont = false;
str = str.replace(resep, function (match, before) {
results.push(before);
cont = true;
return "";
});
}
results.push(str);
return results;
},
threadYield: function (flush, interruptable) {
let mainThread = services.get("threadManager").mainThread;
/* FIXME */
util.interrupted = false;
do {
mainThread.processNextEvent(!flush);
if (util.interrupted)
throw new Error("Interrupted");
}
while (flush === true && mainThread.hasPendingEvents());
},
/**
* Converts an E4X XML literal to a DOM node.
*
* @param {Node} node
* @param {Document} doc
* @param {Object} nodes If present, nodes with the "key" attribute are
* stored here, keyed to the value thereof.
* @returns {Node}
*/
xmlToDom: function xmlToDom(node, doc, nodes) {
XML.prettyPrinting = false;
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()) {
case "text":
return doc.createTextNode(String(node));
case "element":
let domnode = doc.createElementNS(node.namespace(), node.localName());
for each (let attr in node.@*)
domnode.setAttributeNS(attr.name() == "highlight" ? NS.uri : attr.namespace(), attr.name(), String(attr));
for each (let child in node.*)
domnode.appendChild(arguments.callee(child, doc, nodes));
if (nodes && node.@key)
nodes[node.@key] = domnode;
return domnode;
default:
return null;
}
}
}, {
Array: array
});
/**
* Math utility methods.
* @singleton
*/
var Math = {
__proto__: window.Math,
/**
* Returns the specified <b>value</b> constrained to the range <b>min</b> -
* <b>max</b>.
*
* @param {number} value The value to constrain.
* @param {number} min The minimum constraint.
* @param {number} max The maximum constraint.
* @returns {number}
*/
constrain: function constrain(value, min, max) Math.min(Math.max(min, value), max)
};
// vim: set fdm=marker sw=4 ts=4 et: