diff --git a/.hgignore b/.hgignore
index 40d211a3..93349ece 100644
--- a/.hgignore
+++ b/.hgignore
@@ -11,6 +11,8 @@ syntax: glob
*/locale/*/*.html
*/chrome
*/contrib/vim/*.vba
+*/bak/*
+downloads/*
## Editor backup and swap files
*~
diff --git a/common/Makefile b/common/Makefile
index a918fc96..95441a2e 100644
--- a/common/Makefile
+++ b/common/Makefile
@@ -99,8 +99,8 @@ dist: $(XPI)
$(RDF): $(RDF_IN) Makefile
@echo "Preparing release..."
- $(SED) -e "s,###VERSION###,$(VERSION),g" \
- -e "s,###DATE###,$(BUILD_DATE),g" \
+ $(SED) -e "s,@VERSION@,$(VERSION),g" \
+ -e "s,@DATE@,$(BUILD_DATE),g" \
< $< > $@
@echo "SUCCESS: $@"
diff --git a/common/components/protocols.js b/common/components/protocols.js
index 450c346e..fd6378d1 100644
--- a/common/components/protocols.js
+++ b/common/components/protocols.js
@@ -2,7 +2,7 @@
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-
+"use strict";
/* Adds support for data: URIs with chrome privileges
* and fragment identifiers.
@@ -27,7 +27,7 @@ let channel = Components.classesByID["{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}"]
.QueryInterface(Ci.nsIRequest);
const systemPrincipal = channel.owner;
channel.cancel(NS_BINDING_ABORTED);
-delete channel;
+channel = null;
function dataURL(type, data) "data:" + (type || "application/xml;encoding=UTF-8") + "," + escape(data);
function makeChannel(url, orig) {
@@ -152,8 +152,9 @@ Liberator.prototype = {
}
};
-var components = [ChromeData, Liberator];
-
-function NSGetModule(compMgr, fileSpec) XPCOMUtils.generateModule(components)
+if (XPCOMUtils.generateNSGetFactory)
+ const NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeData, Liberator]);
+else
+ const NSGetModule = XPCOMUtils.generateNSGetModule([ChromeData, Liberator]);
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/autocommands.js b/common/content/autocommands.js
index c0ce9252..e8eb47f7 100755
--- a/common/content/autocommands.js
+++ b/common/content/autocommands.js
@@ -1,8 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -274,9 +276,9 @@ const AutoCommands = Module("autocommands", {
completer: function () config.autocommands.concat([["all", "All events"]])
});
- options.add(["focuscontent", "fc"],
- "Try to stay in normal mode after loading a web page",
- "boolean", false);
+ options.add(["strictfocus", "sf"],
+ "Prevent scripts from focusing input elements without user intervention",
+ "boolean", true);
}
});
diff --git a/common/content/base.js b/common/content/base.js
index 16a6422d..4eab72e7 100644
--- a/common/content/base.js
+++ b/common/content/base.js
@@ -2,6 +2,7 @@
//
// 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;
diff --git a/common/content/bookmarks.js b/common/content/bookmarks.js
index 077c1e8c..eea59c69 100644
--- a/common/content/bookmarks.js
+++ b/common/content/bookmarks.js
@@ -1,8 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
@@ -364,7 +366,6 @@ const Bookmarks = Module("bookmarks", {
// ripped from Firefox
function getShortcutOrURI(url) {
- var shortcutURL = null;
var keyword = url;
var param = "";
var offset = url.indexOf(" ");
@@ -379,7 +380,7 @@ const Bookmarks = Module("bookmarks", {
return [submission.uri.spec, submission.postData];
}
- [shortcutURL, postData] = PlacesUtils.getURLAndPostDataForKeyword(keyword);
+ let [shortcutURL, postData] = PlacesUtils.getURLAndPostDataForKeyword(keyword);
if (!shortcutURL)
return [url, null];
@@ -598,6 +599,18 @@ const Bookmarks = Module("bookmarks", {
context.completions = [["", "Don't perform searches by default"]].concat(context.completions);
}
});
+
+ options.add(["suggestengines"],
+ "Engine Alias which has a feature of suggest",
+ "stringlist", "google",
+ {
+ completer: function completer(value) {
+ let engines = services.get("browserSearch").getEngines({})
+ .filter(function (engine) engine.supportsResponseType("application/x-suggestions+json"));
+
+ return engines.map(function (engine) [engine.alias, engine.description]);
+ }
+ });
},
completion: function () {
completion.bookmark = function bookmark(context, tags, extra) {
@@ -643,8 +656,11 @@ const Bookmarks = Module("bookmarks", {
let rest = item.url.length - end.length;
let query = item.url.substring(begin.length, rest);
if (item.url.substr(rest) == end && query.indexOf("&") == -1) {
- item.url = decodeURIComponent(query.replace(/#.*/, ""));
- return item;
+ try {
+ item.url = decodeURIComponent(query.replace(/#.*/, ""));
+ return item;
+ }
+ catch (e) {}
}
return null;
}).filter(util.identity);
diff --git a/common/content/browser.js b/common/content/browser.js
index aec8bf25..1ceb716c 100644
--- a/common/content/browser.js
+++ b/common/content/browser.js
@@ -1,10 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
// Copyright (c) 2007-2009 by Doug Kearns
// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
diff --git a/common/content/buffer.js b/common/content/buffer.js
index d70de3e5..be2362bf 100644
--- a/common/content/buffer.js
+++ b/common/content/buffer.js
@@ -1,10 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
// Copyright (c) 2007-2009 by Doug Kearns
// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -19,9 +19,10 @@ const Point = Struct("x", "y");
* @instance buffer
*/
const Buffer = Module("buffer", {
- requires: ["config"],
+ requires: ["config", "util"],
init: function () {
+ this.evaluateXPath = util.evaluateXPath;
this.pageInfo = {};
this.addPageInfoSection("f", "Feeds", function (verbose) {
@@ -168,7 +169,7 @@ const Buffer = Module("buffer", {
// called when the active document is scrolled
_updateBufferPosition: function _updateBufferPosition() {
statusline.updateBufferPosition();
- modes.show();
+ modes.show(); // Clear the status line.
},
onDOMContentLoaded: function onDOMContentLoaded(event) {
@@ -199,19 +200,7 @@ const Buffer = Module("buffer", {
// any buffer, even in a background tab
doc.pageIsFullyLoaded = 1;
- // code which is only relevant if the page load is the current tab goes here:
- if (doc == config.browser.contentDocument) {
- // we want to stay in command mode after a page has loaded
- // TODO: move somewhere else, as focusing can already happen earlier than on "load"
- if (options["focuscontent"]) {
- setTimeout(function () {
- let focused = liberator.focus;
- if (focused && (focused.value != null) && focused.value.length == 0)
- focused.blur();
- }, 0);
- }
- }
- else // background tab
+ if (doc != config.browser.contentDocument)
liberator.echomsg("Background tab loaded: " + doc.title || doc.location.href, 3);
this._triggerLoadAutocmd("PageLoad", doc);
@@ -279,7 +268,11 @@ const Buffer = Module("buffer", {
autocommands.trigger("LocationChange", { url: buffer.URL });
// if this is not delayed we get the position of the old buffer
- setTimeout(function () { statusline.updateBufferPosition(); }, 500);
+ setTimeout(function () {
+ statusline.updateBufferPosition();
+ statusline.updateZoomLevel();
+ modes.show(); // Clear the status line.
+ }, 500);
},
// called at the very end of a page load
asyncUpdateUI: function () {
@@ -384,19 +377,18 @@ const Buffer = Module("buffer", {
get pageHeight() window.content.innerHeight,
/**
- * @property {number} The current browser's text zoom level, as a
- * percentage with 100 as 'normal'. Only affects text size.
+ * @property {number} The current browser's zoom level, as a
+ * percentage with 100 as 'normal'.
*/
- get textZoom() config.browser.markupDocumentViewer.textZoom * 100,
- set textZoom(value) { Buffer.setZoom(value, false); },
+ get zoomLevel() config.browser.markupDocumentViewer[this.fullZoom ? "textZoom" : "fullZoom"] * 100,
+ set zoomLevel(value) { Buffer.setZoom(value, this.fullZoom); },
/**
- * @property {number} The current browser's text zoom level, as a
- * percentage with 100 as 'normal'. Affects text size, as well as
- * image size and block size.
+ * @property {boolean} Whether the current browser is using full
+ * zoom, as opposed to text zoom.
*/
- get fullZoom() config.browser.markupDocumentViewer.fullZoom * 100,
- set fullZoom(value) { Buffer.setZoom(value, true); },
+ get fullZoom() ZoomManager.useFullZoom,
+ set fullZoom(value) { Buffer.setZoom(this.zoomLevel, value); },
/**
* @property {string} The current document's title.
@@ -470,6 +462,18 @@ const Buffer = Module("buffer", {
return String(selection);
},
+ /**
+ * Returns true if a scripts are allowed to focus the given input
+ * element or input elements in the given window.
+ *
+ * @param {Node|Window}
+ * @returns {boolean}
+ */
+ focusAllowed: function (elem) {
+ let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
+ return !options["strictfocus"] || elem.liberatorFocusAllowed;
+ },
+
/**
* Focuses the given element. In contrast to a simple
* elem.focus() call, this function works for iframes and
@@ -479,7 +483,10 @@ const Buffer = Module("buffer", {
*/
focusElement: function (elem) {
let doc = window.content.document;
- if (elem instanceof HTMLFrameElement || elem instanceof HTMLIFrameElement)
+ let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
+ win.liberatorFocusAllowed = true;
+
+ if (isinstance(elem, [HTMLFrameElement, HTMLIFrameElement]))
elem.contentWindow.focus();
else if (elem instanceof HTMLInputElement && elem.type == "file") {
Buffer.openUploadPrompt(elem);
@@ -960,14 +967,20 @@ const Buffer = Module("buffer", {
liberator.assert(value >= Buffer.ZOOM_MIN || value <= Buffer.ZOOM_MAX,
"Zoom value out of range (" + Buffer.ZOOM_MIN + " - " + Buffer.ZOOM_MAX + "%)");
- ZoomManager.useFullZoom = fullZoom;
+ if (fullZoom !== undefined)
+ ZoomManager.useFullZoom = fullZoom;
ZoomManager.zoom = value / 100;
+
if ("FullZoom" in window)
FullZoom._applySettingToPref();
- liberator.echomsg((fullZoom ? "Full" : "Text") + " zoom: " + value + "%");
+
+ statusline.updateZoomLevel(value, ZoomManager.useFullZoom);
},
bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) {
+ if (fullZoom === undefined)
+ fullZoom = ZoomManager.useFullZoom;
+
let values = ZoomManager.zoomValues;
let cur = values.indexOf(ZoomManager.snap(ZoomManager.zoom));
let i = util.Math.constrain(cur + steps, 0, values.length - 1);
@@ -1138,8 +1151,8 @@ const Buffer = Module("buffer", {
},
{
argCount: "?",
- literal: 0,
- bang: true
+ bang: true,
+ literal: 0
});
commands.add(["pa[geinfo]"],
@@ -1178,8 +1191,8 @@ const Buffer = Module("buffer", {
"Reload the current web page",
function (args) { tabs.reload(config.browser.mCurrentTab, args.bang); },
{
- bang: true,
- argCount: "0"
+ argCount: "0",
+ bang: true
});
// TODO: we're prompted if download.useDownloadDir isn't set and no arg specified - intentional?
@@ -1470,16 +1483,19 @@ const Buffer = Module("buffer", {
if (count < 1 && buffer.lastInputField)
buffer.focusElement(buffer.lastInputField);
else {
- let xpath = ["input[not(@type) or @type='text' or @type='password' or @type='file']",
- "textarea[not(@disabled) and not(@readonly)]"];
+ let xpath = ["input", "textarea[not(@disabled) and not(@readonly)]"];
- let elements = [m for (m in util.evaluateXPath(xpath))].filter(function (match) {
- let computedStyle = util.computedStyle(match);
+ let elements = [m for (m in util.evaluateXPath(xpath))].filter(function (elem) {
+ if (elem.readOnly || elem instanceof HTMLInputElement && ["file", "search", "text", "password"].indexOf(elem.type) < 0)
+ return false;
+ let computedStyle = util.computedStyle(elem);
return computedStyle.visibility != "hidden" && computedStyle.display != "none";
});
liberator.assert(elements.length > 0);
- buffer.focusElement(elements[util.Math.constrain(count, 1, elements.length) - 1]);
+ let elem = elements[util.Math.constrain(count, 1, elements.length) - 1];
+ buffer.focusElement(elem);
+ util.scrollIntoView(elem);
}
},
{ count: true });
@@ -1504,7 +1520,7 @@ const Buffer = Module("buffer", {
function () {
let url = util.readFromClipboard();
liberator.assert(url);
- liberator.open(url, { from: "activate", where: liberator.NEW_TAB });
+ liberator.open(url, { from: "paste", where: liberator.NEW_TAB });
});
// reloading
@@ -1551,27 +1567,27 @@ const Buffer = Module("buffer", {
function (count) { buffer.textZoom = count > 1 ? count : 100; },
{ count: true });
- mappings.add(myModes, ["zI"],
+ mappings.add(myModes, ["ZI", "zI"],
"Enlarge full zoom of current web page",
function (count) { buffer.zoomIn(Math.max(count, 1), true); },
{ count: true });
- mappings.add(myModes, ["zM"],
+ mappings.add(myModes, ["ZM", "zM"],
"Enlarge full zoom of current web page by a larger amount",
function (count) { buffer.zoomIn(Math.max(count, 1) * 3, true); },
{ count: true });
- mappings.add(myModes, ["zO"],
+ mappings.add(myModes, ["ZO", "zO"],
"Reduce full zoom of current web page",
function (count) { buffer.zoomOut(Math.max(count, 1), true); },
{ count: true });
- mappings.add(myModes, ["zR"],
+ mappings.add(myModes, ["ZR", "zR"],
"Reduce full zoom of current web page by a larger amount",
function (count) { buffer.zoomOut(Math.max(count, 1) * 3, true); },
{ count: true });
- mappings.add(myModes, ["zZ"],
+ mappings.add(myModes, ["ZZ", "zZ"],
"Set full zoom value of current web page",
function (count) { buffer.fullZoom = count > 1 ? count : 100; },
{ count: true });
@@ -1599,7 +1615,7 @@ const Buffer = Module("buffer", {
"Desired info in the :pageinfo output",
"charlist", "gfm",
{
- completer: function (context) [[k, v[1]] for ([k, v] in Iterator(this.pageInfo))]
+ completer: function (context) [[k, v[1]] for ([k, v] in Iterator(buffer.pageInfo))]
});
options.add(["scroll", "scr"],
diff --git a/common/content/commandline.js b/common/content/commandline.js
index 79a4b4f2..e4c29de2 100644
--- a/common/content/commandline.js
+++ b/common/content/commandline.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -102,7 +105,7 @@ const CommandLine = Module("commandline", {
});
this._autocompleteTimer = new Timer(200, 500, function autocompleteTell(tabPressed) {
- if (!events.feedingKeys && self._completions && options.get("wildoptions").has("auto")) {
+ if (!events.feedingKeys && self._completions && options.get("autocomplete").values.length) {
self._completions.complete(true, false);
self._completions.itemList.show();
}
@@ -329,10 +332,11 @@ const CommandLine = Module("commandline", {
FORCE_MULTILINE : 1 << 0,
FORCE_SINGLELINE : 1 << 1,
- DISALLOW_MULTILINE : 1 << 2, // if an echo() should try to use the single line
+ DISALLOW_MULTILINE : 1 << 2, // If an echo() should try to use the single line
// but output nothing when the MOW is open; when also
// FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence
- APPEND_TO_MESSAGES : 1 << 3, // add the string to the message this._history
+ APPEND_TO_MESSAGES : 1 << 3, // Add the string to the message this._history.
+ ACTIVE_WINDOW : 1 << 4, // Only echo in active window.
get completionContext() this._completions.context,
@@ -499,6 +503,10 @@ const CommandLine = Module("commandline", {
if (flags & this.APPEND_TO_MESSAGES)
this._messageHistory.add({ str: str, highlight: highlightGroup });
+ if ((flags & this.ACTIVE_WINDOW) &&
+ window != services.get("windowWatcher").activeWindow &&
+ services.get("windowWatcher").activeWindow.liberator)
+ return;
// The DOM isn't threadsafe. It must only be accessed from the main thread.
liberator.callInMainThread(function () {
@@ -754,12 +762,12 @@ const CommandLine = Module("commandline", {
case "":
case "":
case "":
- openLink(liberator.NEW_BACKGROUND_TAB);
+ openLink({ where: liberator.NEW_TAB, background: true });
break;
case "":
case "":
case "":
- openLink(liberator.NEW_TAB);
+ openLink({ where: liberator.NEW_TAB, background: false });
break;
case "":
openLink(liberator.NEW_WINDOW);
@@ -964,7 +972,7 @@ const CommandLine = Module("commandline", {
let doc = this._multilineOutputWidget.contentDocument;
- availableHeight = config.outputHeight;
+ let availableHeight = config.outputHeight;
if (!this._outputContainer.collapsed)
availableHeight += parseFloat(this._outputContainer.height);
doc.body.style.minWidth = this._commandlineWidget.scrollWidth + "px";
@@ -1032,7 +1040,7 @@ const CommandLine = Module("commandline", {
sanitize: function (timespan) {
let range = [0, Number.MAX_VALUE];
if (liberator.has("sanitizer") && (timespan || options["sanitizetimespan"]))
- range = sanitizer.getClearRange(timespan || options["sanitizetimespan"]);
+ range = Sanitizer.getClearRange(timespan || options["sanitizetimespan"]);
const self = this;
this.store.mutate("filter", function (item) {
@@ -1107,7 +1115,7 @@ const CommandLine = Module("commandline", {
*/
Completions: Class("Completions", {
init: function (input) {
- this.context = CompletionContext(input.editor);
+ this.context = CompletionContext(input.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
this.context.onUpdate = this.closure._reset;
this.editor = input.editor;
this.selected = null;
@@ -1127,7 +1135,7 @@ const CommandLine = Module("commandline", {
let str = commandline.command;
return str.substring(this.prefix.length, str.length - this.suffix.length);
},
- set completion set_completion(completion) {
+ set completion(completion) {
this.previewClear();
// Change the completion text.
@@ -1516,84 +1524,6 @@ const CommandLine = Module("commandline", {
options.add(["showmode", "smd"],
"Show the current mode in the command line",
"boolean", true);
-
- options.add(["suggestengines"],
- "Engine Alias which has a feature of suggest",
- "stringlist", "google",
- {
- completer: function completer(value) {
- let engines = services.get("browserSearch").getEngines({})
- .filter(function (engine) engine.supportsResponseType("application/x-suggestions+json"));
-
- return engines.map(function (engine) [engine.alias, engine.description]);
- }
- });
-
- options.add(["complete", "cpt"],
- "Items which are completed at the :open prompts",
- "charlist", typeof(config.defaults["complete"]) == "string" ? config.defaults["complete"] : "slf",
- {
- completer: function (context) array(values(completion.urlCompleters))
- });
-
- options.add(["wildcase", "wic"],
- "Completion case matching mode",
- "string", "smart",
- {
- completer: function () [
- ["smart", "Case is significant when capital letters are typed"],
- ["match", "Case is always significant"],
- ["ignore", "Case is never significant"]
- ]
- });
-
- options.add(["wildignore", "wig"],
- "List of file patterns to ignore when completing files",
- "stringlist", "",
- {
- validator: function validator(values) {
- // TODO: allow for escaping the ","
- try {
- RegExp("^(" + values.join("|") + ")$");
- return true;
- }
- catch (e) {
- return false;
- }
- }
- });
-
- options.add(["wildmode", "wim"],
- "Define how command line completion works",
- "stringlist", "list:full",
- {
- completer: function (context) [
- // Why do we need ""?
- ["", "Complete only the first match"],
- ["full", "Complete the next full match"],
- ["longest", "Complete to longest common string"],
- ["list", "If more than one match, list all matches"],
- ["list:full", "List all and complete first match"],
- ["list:longest", "List all and complete common string"]
- ],
- checkHas: function (value, val) {
- let [first, second] = value.split(":", 2);
- return first == val || second == val;
- }
- });
-
- options.add(["wildoptions", "wop"],
- "Change how command line completion is done",
- "stringlist", "",
- {
- completer: function completer(value) {
- return [
- ["", "Default completion that won't show or sort the results"],
- ["auto", "Automatically show this._completions while you are typing"],
- ["sort", "Always sort the completion list"]
- ];
- }
- });
},
styles: function () {
let fontSize = util.computedStyle(document.getElementById(config.mainWindowId)).fontSize;
diff --git a/common/content/commands.js b/common/content/commands.js
index 0b15f55e..c774bc28 100644
--- a/common/content/commands.js
+++ b/common/content/commands.js
@@ -1,10 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
// Copyright (c) 2007-2009 by Doug Kearns
// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -73,9 +73,9 @@ const Command = Class("Command", {
modifiers = modifiers || {};
let self = this;
- function exec(args) {
+ function exec(command) {
// FIXME: Move to parseCommand?
- args = self.parseArgs(args);
+ args = self.parseArgs(command);
if (!args)
return;
args.count = count;
@@ -237,6 +237,7 @@ const ArgType = Struct("description", "parse");
const Commands = Module("commands", {
init: function () {
this._exCommands = [];
+ this._exMap = {};
},
// FIXME: remove later, when our option handler is better
@@ -304,7 +305,7 @@ const Commands = Module("commands", {
repeat: null,
_addCommand: function (command, replace) {
- if (this._exCommands.some(function (c) c.hasName(command.name))) {
+ if (command.name in this._exMap) {
if (command.user && replace)
commands.removeUserCommand(command.name);
else {
@@ -314,6 +315,8 @@ const Commands = Module("commands", {
}
this._exCommands.push(command);
+ for(let [,name] in Iterator(command.names))
+ this._exMap[name] = command;
return true;
},
@@ -387,7 +390,7 @@ const Commands = Module("commands", {
* @returns {Command}
*/
get: function (name) {
- return this._exCommands.filter(function (cmd) cmd.hasName(name))[0] || null;
+ return this._exMap[name] || this._exCommands.filter(function (cmd) cmd.hasName(name))[0] || null;
},
/**
@@ -762,6 +765,10 @@ const Commands = Module("commands", {
* any of the command's names.
*/
removeUserCommand: function (name) {
+ for(let [,cmd] in Iterator(this._exCommands))
+ if(cmd.user && cmd.hasName(name))
+ for(let [,name] in Iterator(cmd.names))
+ delete this._exMap[name];
this._exCommands = this._exCommands.filter(function (cmd) !(cmd.user && cmd.hasName(name)));
},
@@ -894,10 +901,10 @@ const Commands = Module("commands", {
}
[prefix] = context.filter.match(/^(?:\w*[\s!]|!)\s*/);
- let cmdContext = context.fork(cmd, prefix.length);
+ let cmdContext = context.fork(command.name, prefix.length);
let argContext = context.fork("args", prefix.length);
args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang });
- if (args) {
+ if (args && !cmdContext.waitingForTab) {
// FIXME: Move to parseCommand
args.count = count;
args.bang = bang;
diff --git a/common/content/completion.js b/common/content/completion.js
index ed7e3cfd..d86d6a18 100755
--- a/common/content/completion.js
+++ b/common/content/completion.js
@@ -1,9 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
-// Copyright (c) 2008-2009 by Kris Maglione
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -34,6 +35,11 @@ const CompletionContext = Class("CompletionContext", {
if (editor instanceof this.constructor) {
let parent = editor;
name = parent.name + "/" + name;
+
+ this.autoComplete = options.get("autocomplete").getKey(name);
+ this.sortResults = options.get("wildsort").getKey(name);
+ this.wildcase = options.get("wildcase").getKey(name);
+
this.contexts = parent.contexts;
if (name in this.contexts)
self = this.contexts[name];
@@ -146,6 +152,8 @@ const CompletionContext = Class("CompletionContext", {
this.top = this;
this.__defineGetter__("incomplete", function () this.contextList.some(function (c) c.parent && c.incomplete));
this.__defineGetter__("waitingForTab", function () this.contextList.some(function (c) c.parent && c.waitingForTab));
+ this.__defineSetter__("incomplete", function (val) {});
+ this.__defineSetter__("waitingForTab", function (val) {});
this.reset();
}
/**
@@ -243,7 +251,7 @@ const CompletionContext = Class("CompletionContext", {
get completions() this._completions || [],
set completions(items) {
// Accept a generator
- if ({}.toString.call(items) != '[object Array]')
+ if (!isarray(items))
items = [x for (x in Iterator(items))];
delete this.cache.filtered;
delete this.cache.filter;
@@ -333,7 +341,7 @@ const CompletionContext = Class("CompletionContext", {
get ignoreCase() {
if ("_ignoreCase" in this)
return this._ignoreCase;
- let mode = options["wildcase"];
+ let mode = this.wildcase;
if (mode == "match")
return this._ignoreCase = false;
if (mode == "ignore")
@@ -367,7 +375,7 @@ const CompletionContext = Class("CompletionContext", {
if (this.maxItems)
filtered = filtered.slice(0, this.maxItems);
- if (options.get("wildoptions").has("sort") && this.compare)
+ if (this.sortResults && this.compare)
filtered.sort(this.compare);
let quote = this.quote;
if (quote)
@@ -404,12 +412,12 @@ const CompletionContext = Class("CompletionContext", {
let filter = fixCase(this.filter);
if (this.anchored) {
var compare = function compare(text, s) text.substr(0, s.length) == s;
- substrings = util.map(util.range(filter.length, text.length + 1),
+ var substrings = util.map(util.range(filter.length, text.length + 1),
function (end) text.substring(0, end));
}
else {
var compare = function compare(text, s) text.indexOf(s) >= 0;
- substrings = [];
+ var substrings = [];
let start = 0;
let idx;
let length = filter.length;
@@ -498,8 +506,14 @@ const CompletionContext = Class("CompletionContext", {
completer = self[completer];
let context = CompletionContext(this, name, offset);
this.contextList.push(context);
- if (completer)
+
+ 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)));
+
+ if (completer)
+ return null;
return context;
},
@@ -682,7 +696,7 @@ const Completion = Module("completion", {
if (skip)
context.advance(skip[0].length);
- if (typeof complete === "undefined")
+ if (complete == null)
complete = options["complete"];
// Will, and should, throw an error if !(c in opts)
@@ -744,6 +758,80 @@ const Completion = Module("completion", {
//}}}
}, {
UrlCompleter: Struct("name", "description", "completer")
+}, {
+ commands: function () {
+ commands.add(["contexts"],
+ "List the completion contexts used during completion of an ex command",
+ function (args) {
+ commandline.echo(template.commandOutput(
+
+ { template.completionRow(["Context", "Title"], "CompTitle") }
+ { template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
+
),
+ null, commandline.FORCE_MULTILINE);
+
+ },
+ {
+ argCount: "1",
+ completer: function (context, args) {
+ let PREFIX = "/ex/contexts";
+ context.fork("ex", 0, completion, "ex");
+ completion.contextList = [[k.substr(PREFIX.length), v.title[0]] for ([k, v] in iter(context.contexts)) if (k.substr(0, PREFIX.length) == PREFIX)];
+ },
+ literal: 0
+ });
+ },
+ options: function () {
+ options.add(["autocomplete", "au"],
+ "Automatically update the completion list on any key press",
+ "regexlist", ".*");
+
+ options.add(["complete", "cpt"],
+ "Items which are completed at the :open prompts",
+ "charlist", typeof(config.defaults["complete"]) == "string" ? config.defaults["complete"] : "slf",
+ {
+ completer: function (context) array(values(completion.urlCompleters))
+ });
+
+ options.add(["wildcase", "wic"],
+ "Completion case matching mode",
+ "regexmap", "smart",
+ {
+ completer: function () [
+ ["smart", "Case is significant when capital letters are typed"],
+ ["match", "Case is always significant"],
+ ["ignore", "Case is never significant"]
+ ]
+ });
+
+ options.add(["wildmode", "wim"],
+ "Define how command line completion works",
+ "stringlist", "list:full",
+ {
+ completer: function (context) [
+ // Why do we need ""?
+ // Because its description is useful during completion. --Kris
+ ["", "Complete only the first match"],
+ ["full", "Complete the next full match"],
+ ["longest", "Complete to longest common string"],
+ ["list", "If more than one match, list all matches"],
+ ["list:full", "List all and complete first match"],
+ ["list:longest", "List all and complete common string"]
+ ],
+ checkHas: function (value, val) {
+ let [first, second] = value.split(":", 2);
+ return first == val || second == val;
+ },
+ has: function () {
+ test = function (val) this.values.some(function (value) this.checkHas(value, val), this);
+ return Array.some(arguments, test, this);
+ }
+ });
+
+ options.add(["wildsort", "wis"],
+ "Regexp list of which contexts to sort",
+ "regexlist", ".*");
+ }
});
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/common/content/configbase.js b/common/content/configbase.js
index 7cb438b2..478a1417 100644
--- a/common/content/configbase.js
+++ b/common/content/configbase.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 ConfigBase = Class(ModuleBase, {
/**
diff --git a/common/content/editor.js b/common/content/editor.js
index 70414226..0307c98a 100644
--- a/common/content/editor.js
+++ b/common/content/editor.js
@@ -2,7 +2,7 @@
//
// 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 */
diff --git a/common/content/events.js b/common/content/events.js
index cfab6641..4ec214d2 100644
--- a/common/content/events.js
+++ b/common/content/events.js
@@ -1,10 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
// Copyright (c) 2007-2009 by Doug Kearns
// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -103,32 +103,16 @@ const Events = Module("events", {
}
}, 100);
- function wrapListener(method) {
- return function (event) {
- try {
- self[method](event);
- }
- catch (e) {
- if (e.message == "Interrupted")
- liberator.echoerr("Interrupted");
- else
- liberator.echoerr("Processing " + event.type + " event: " + (e.echoerr || e));
- liberator.reportError(e);
- }
- };
- }
-
- this._wrappedOnKeyPress = wrapListener("onKeyPress");
- this._wrappedOnKeyUpOrDown = wrapListener("onKeyUpOrDown");
- this.addSessionListener(window, "keypress", this.closure._wrappedOnKeyPress, true);
- this.addSessionListener(window, "keydown", this.closure._wrappedOnKeyUpOrDown, true);
- this.addSessionListener(window, "keyup", this.closure._wrappedOnKeyUpOrDown, true);
-
this._activeMenubar = false;
- this.addSessionListener(window, "popupshown", this.closure.onPopupShown, true);
- this.addSessionListener(window, "popuphidden", this.closure.onPopupHidden, true);
this.addSessionListener(window, "DOMMenuBarActive", this.closure.onDOMMenuBarActive, true);
this.addSessionListener(window, "DOMMenuBarInactive", this.closure.onDOMMenuBarInactive, true);
+ this.addSessionListener(window, "focus", this.wrapListener(this.closure.onFocus), true);
+ this.addSessionListener(window, "keydown", this.wrapListener(this.closure.onKeyUpOrDown), true);
+ this.addSessionListener(window, "keypress", this.wrapListener(this.closure.onKeyPress), true);
+ this.addSessionListener(window, "keyup", this.wrapListener(this.closure.onKeyUpOrDown), true);
+ this.addSessionListener(window, "mousedown", this.wrapListener(this.closure.onMouseDown), true);
+ this.addSessionListener(window, "popuphidden", this.closure.onPopupHidden, true);
+ this.addSessionListener(window, "popupshown", this.closure.onPopupShown, true);
this.addSessionListener(window, "resize", this.closure.onResize, true);
},
@@ -151,10 +135,28 @@ const Events = Module("events", {
*/
addSessionListener: function (target, event, callback, capture) {
let args = Array.slice(arguments, 0);
- target.addEventListener.apply(target, args.slice(1));
+ target.addEventListener.apply(args[0], args.slice(1));
this.sessionListeners.push(args);
},
+ /**
+ * Wraps an event listener to ensure that errors are reported.
+ */
+ wrapListener: function wrapListener(method, self) {
+ return function (event) {
+ try {
+ method.apply(self, arguments);
+ }
+ catch (e) {
+ if (e.message == "Interrupted")
+ liberator.echoerr("Interrupted");
+ else
+ liberator.echoerr("Processing " + event.type + " event: " + (e.echoerr || e));
+ liberator.reportError(e);
+ }
+ };
+ },
+
/**
* @property {boolean} Whether synthetic key events are currently being
* processed.
@@ -651,89 +653,14 @@ const Events = Module("events", {
return ret;
},
- // argument "event" is deliberately not used, as i don't seem to have
- // access to the real focus target
- // Huh? --djk
- onFocusChange: function (event) {
- // command line has it's own focus change handler
- if (liberator.mode == modes.COMMAND_LINE)
- return;
-
- function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
-
- let win = window.document.commandDispatcher.focusedWindow;
- let elem = window.document.commandDispatcher.focusedElement;
-
- if (win && win.top == content && liberator.has("tabs"))
- tabs.localStore.focusedFrame = win;
-
- try {
- if (elem && elem.readOnly)
- return;
-
- if ((elem instanceof HTMLInputElement && /^(text|password)$/.test(elem.type)) ||
- (elem instanceof HTMLSelectElement)) {
- liberator.mode = modes.INSERT;
- if (hasHTMLDocument(win))
- buffer.lastInputField = elem;
- return;
- }
- if (elem instanceof HTMLEmbedElement || elem instanceof HTMLObjectElement) {
- liberator.mode = modes.EMBED;
- return;
- }
-
- if (elem instanceof HTMLTextAreaElement || (elem && elem.contentEditable == "true")) {
- if (options["insertmode"])
- modes.set(modes.INSERT);
- else if (elem.selectionEnd - elem.selectionStart > 0)
- modes.set(modes.VISUAL, modes.TEXTAREA);
- else
- modes.main = modes.TEXTAREA;
- if (hasHTMLDocument(win))
- buffer.lastInputField = elem;
- return;
- }
-
- if (config.focusChange) {
- config.focusChange(win);
- return;
- }
-
- let urlbar = document.getElementById("urlbar");
- if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
- liberator.threadYield(true);
-
- if (liberator.mode & (modes.EMBED | modes.INSERT | modes.TEXTAREA | modes.VISUAL))
- modes.reset();
- }
- finally {
- this._lastFocus = elem;
- }
+ onDOMMenuBarActive: function () {
+ this._activeMenubar = true;
+ modes.add(modes.MENU);
},
- onSelectionChange: function (event) {
- let couldCopy = false;
- let controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
- if (controller && controller.isCommandEnabled("cmd_copy"))
- couldCopy = true;
-
- if (liberator.mode != modes.VISUAL) {
- if (couldCopy) {
- if ((liberator.mode == modes.TEXTAREA ||
- (modes.extended & modes.TEXTAREA))
- && !options["insertmode"])
- modes.set(modes.VISUAL, modes.TEXTAREA);
- else if (liberator.mode == modes.CARET)
- modes.set(modes.VISUAL, modes.CARET);
- }
- }
- // XXX: disabled, as i think automatically starting visual caret mode does more harm than help
- // else
- // {
- // if (!couldCopy && modes.extended & modes.CARET)
- // liberator.mode = modes.CARET;
- // }
+ onDOMMenuBarInactive: function () {
+ this._activeMenubar = false;
+ modes.remove(modes.MENU);
},
/**
@@ -808,6 +735,78 @@ const Events = Module("events", {
}
},
+ 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))
+ elem.blur();
+ },
+
+ // argument "event" is deliberately not used, as i don't seem to have
+ // access to the real focus target
+ // Huh? --djk
+ onFocusChange: function (event) {
+ // command line has it's own focus change handler
+ if (liberator.mode == modes.COMMAND_LINE)
+ return;
+
+ function hasHTMLDocument(win) win && win.document && win.document instanceof HTMLDocument
+
+ let win = window.document.commandDispatcher.focusedWindow;
+ let elem = window.document.commandDispatcher.focusedElement;
+
+ if (win && win.top == content && liberator.has("tabs"))
+ tabs.localStore.focusedFrame = win;
+
+ try {
+ if (elem && elem.readOnly)
+ return;
+
+ if ((elem instanceof HTMLInputElement && /^(search|text|password)$/.test(elem.type)) ||
+ (elem instanceof HTMLSelectElement)) {
+ liberator.mode = modes.INSERT;
+ if (hasHTMLDocument(win))
+ buffer.lastInputField = elem;
+ return;
+ }
+
+ if(isinstance(elem, [HTMLEmbedElement, HTMLEmbedElement])) {
+ liberator.mode = modes.EMBED;
+ return;
+ }
+
+ if (elem instanceof HTMLTextAreaElement || (elem && elem.contentEditable == "true")) {
+ if (options["insertmode"])
+ modes.set(modes.INSERT);
+ else if (elem.selectionEnd - elem.selectionStart > 0)
+ modes.set(modes.VISUAL, modes.TEXTAREA);
+ else
+ modes.main = modes.TEXTAREA;
+ if (hasHTMLDocument(win))
+ buffer.lastInputField = elem;
+ return;
+ }
+
+ if (config.focusChange) {
+ config.focusChange(win);
+ return;
+ }
+
+ let urlbar = document.getElementById("urlbar");
+ if (elem == null && urlbar && urlbar.inputField == this._lastFocus)
+ liberator.threadYield(true);
+
+ if (liberator.mode & (modes.EMBED | modes.INSERT | modes.TEXTAREA | modes.VISUAL))
+ modes.reset();
+ }
+ finally {
+ this._lastFocus = elem;
+ }
+ },
+
// this keypress handler gets always called first, even if e.g.
// the commandline has focus
// TODO: ...help me...please...
@@ -1022,7 +1021,9 @@ const Events = Module("events", {
if (liberator.mode == modes.COMMAND_LINE) {
if (!(modes.extended & modes.INPUT_MULTILINE))
- commandline.onEvent(event); // reroute event in command line mode
+ liberator.trapErrors(function () {
+ commandline.onEvent(event); // reroute event in command line mode
+ });
}
else if (!modes.mainMode.input)
liberator.beep();
@@ -1050,6 +1051,13 @@ const Events = Module("events", {
event.stopPropagation();
},
+ onMouseDown: function (event) {
+ let elem = event.target;
+ let win = elem.ownerDocument && elem.ownerDocument.defaultView || elem;
+ for(; win; win = win != win.parent && win.parent)
+ win.liberatorFocusAllowed = true;
+ },
+
onPopupShown: function (event) {
if (event.originalTarget.localName == "tooltip" || event.originalTarget.id == "liberator-visualbell")
return;
@@ -1062,22 +1070,36 @@ const Events = Module("events", {
modes.remove(modes.MENU);
},
- onDOMMenuBarActive: function () {
- this._activeMenubar = true;
- modes.add(modes.MENU);
- },
-
- onDOMMenuBarInactive: function () {
- this._activeMenubar = false;
- modes.remove(modes.MENU);
- },
-
onResize: function (event) {
if (window.fullScreen != this._fullscreen) {
this._fullscreen = window.fullScreen;
liberator.triggerObserver("fullscreen", this._fullscreen);
autocommands.trigger("Fullscreen", { state: this._fullscreen });
}
+ },
+
+ onSelectionChange: function (event) {
+ let couldCopy = false;
+ let controller = document.commandDispatcher.getControllerForCommand("cmd_copy");
+ if (controller && controller.isCommandEnabled("cmd_copy"))
+ couldCopy = true;
+
+ if (liberator.mode != modes.VISUAL) {
+ if (couldCopy) {
+ if ((liberator.mode == modes.TEXTAREA ||
+ (modes.extended & modes.TEXTAREA))
+ && !options["insertmode"])
+ modes.set(modes.VISUAL, modes.TEXTAREA);
+ else if (liberator.mode == modes.CARET)
+ modes.set(modes.VISUAL, modes.CARET);
+ }
+ }
+ // XXX: disabled, as i think automatically starting visual caret mode does more harm than help
+ // else
+ // {
+ // if (!couldCopy && modes.extended & modes.CARET)
+ // liberator.mode = modes.CARET;
+ // }
}
}, {
isInputElemFocused: function () {
diff --git a/common/content/finder.js b/common/content/finder.js
index 158ce928..b8bad27e 100644
--- a/common/content/finder.js
+++ b/common/content/finder.js
@@ -1,465 +1,12 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2008-2010 by Kris Maglione
//
// 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 */
-// TODO: proper backwards search - implement our own component?
-// : implement our own highlighter?
-// : should cancel search highlighting in 'incsearch' mode and jump
-// back to the presearch page location - can probably use the same
-// solution as marks
-// : 'linksearch' searches should highlight link matches only
-// : changing any search settings should also update the search state including highlighting
-// : incremental searches shouldn't permanently update search modifiers
-//
-// TODO: Clean up this rat's nest. --Kris
-
-/**
- * @instance finder
- */
-const Finder = Module("finder", {
- requires: ["config"],
-
- init: function () {
- const self = this;
-
- this._found = false; // true if the last search was successful
- this._backwards = false; // currently searching backwards
- this._searchString = ""; // current search string (without modifiers)
- this._searchPattern = ""; // current search string (includes modifiers)
- this._lastSearchPattern = ""; // the last searched pattern (includes modifiers)
- this._lastSearchString = ""; // the last searched string (without modifiers)
- this._lastSearchBackwards = false; // like "backwards", but for the last search, so if you cancel a search with this is not set
- this._caseSensitive = false; // search string is case sensitive
- this._linksOnly = false; // search is limited to link text only
-
- /* Stolen from toolkit.jar in Firefox, for the time being. The private
- * methods were unstable, and changed. The new version is not remotely
- * compatible with what we do.
- * The following only applies to this object, and may not be
- * necessary, or accurate, but, just in case:
- * The Original Code is mozilla.org viewsource frontend.
- *
- * The Initial Developer of the Original Code is
- * Netscape Communications Corporation.
- * Portions created by the Initial Developer are Copyright (c) 2003
- * by the Initial Developer. All Rights Reserved.
- *
- * Contributor(s):
- * Blake Ross (Original Author)
- * Masayuki Nakano
- * Ben Basson
- * Jason Barnabe
- * Asaf Romano
- * Ehsan Akhgari
- * Graeme McCutcheon
- */
- this._highlighter = {
-
- doc: null,
-
- spans: [],
-
- search: function (aWord, matchCase) {
- var finder = services.create("find");
- if (matchCase !== undefined)
- self._caseSensitive = matchCase;
-
- var range;
- while ((range = finder.Find(aWord, this.searchRange, this.startPt, this.endPt)))
- yield range;
- },
-
- highlightDoc: function highlightDoc(win, aWord) {
- this.doc = content.document; // XXX
- Array.forEach(win.frames, function (frame) this.highlightDoc(frame, aWord), this);
-
- var doc = win.document;
- if (!doc || !(doc instanceof HTMLDocument))
- return;
-
- if (!aWord) {
- let elems = this._highlighter.spans;
- for (let i = elems.length; --i >= 0;) {
- let elem = elems[i];
- let docfrag = doc.createDocumentFragment();
- let next = elem.nextSibling;
- let parent = elem.parentNode;
-
- let child;
- while ((child = elem.firstChild))
- docfrag.appendChild(child);
-
- parent.removeChild(elem);
- parent.insertBefore(docfrag, next);
- parent.normalize();
- }
- return;
- }
-
- var baseNode = ;
- baseNode = util.xmlToDom(baseNode, window.content.document);
-
- var body = doc.body;
- var count = body.childNodes.length;
- this.searchRange = doc.createRange();
- this.startPt = doc.createRange();
- this.endPt = doc.createRange();
-
- this.searchRange.setStart(body, 0);
- this.searchRange.setEnd(body, count);
-
- this.startPt.setStart(body, 0);
- this.startPt.setEnd(body, 0);
- this.endPt.setStart(body, count);
- this.endPt.setEnd(body, count);
-
- liberator.interrupted = false;
- let n = 0;
- for (let retRange in this.search(aWord, this._caseSensitive)) {
- // Highlight
- var nodeSurround = baseNode.cloneNode(true);
- var node = this.highlight(retRange, nodeSurround);
- this.startPt = node.ownerDocument.createRange();
- this.startPt.setStart(node, node.childNodes.length);
- this.startPt.setEnd(node, node.childNodes.length);
- if (n++ % 20 == 0)
- liberator.threadYield(true);
- if (liberator.interrupted)
- break;
- }
- },
-
- highlight: function highlight(aRange, aNode) {
- var startContainer = aRange.startContainer;
- var startOffset = aRange.startOffset;
- var endOffset = aRange.endOffset;
- var docfrag = aRange.extractContents();
- var before = startContainer.splitText(startOffset);
- var parent = before.parentNode;
- aNode.appendChild(docfrag);
- parent.insertBefore(aNode, before);
- this.spans.push(aNode);
- return aNode;
- },
-
- /**
- * Clears all search highlighting.
- */
- clear: function () {
- this.spans.forEach(function (span) {
- if (span.parentNode) {
- let el = span.firstChild;
- while (el) {
- span.removeChild(el);
- span.parentNode.insertBefore(el, span);
- el = span.firstChild;
- }
- span.parentNode.removeChild(span);
- }
- });
- this.spans = [];
- },
-
- isHighlighted: function (doc) this.doc == doc && this.spans.length > 0
- };
- },
-
- // set searchString, searchPattern, caseSensitive, linksOnly
- _processUserPattern: function (pattern) {
- //// strip off pattern terminator and offset
- //if (backwards)
- // pattern = pattern.replace(/\?.*/, "");
- //else
- // pattern = pattern.replace(/\/.*/, "");
-
- this._searchPattern = pattern;
-
- // links only search - \l wins if both modifiers specified
- if (/\\l/.test(pattern))
- this._linksOnly = true;
- else if (/\L/.test(pattern))
- this._linksOnly = false;
- else if (options["linksearch"])
- this._linksOnly = true;
- else
- this._linksOnly = false;
-
- // strip links-only modifiers
- pattern = pattern.replace(/(\\)?\\[lL]/g, function ($0, $1) { return $1 ? $0 : ""; });
-
- // case sensitivity - \c wins if both modifiers specified
- if (/\c/.test(pattern))
- this._caseSensitive = false;
- else if (/\C/.test(pattern))
- this._caseSensitive = true;
- else if (options["ignorecase"] && options["smartcase"] && /[A-Z]/.test(pattern))
- this._caseSensitive = true;
- else if (options["ignorecase"])
- this._caseSensitive = false;
- else
- this._caseSensitive = true;
-
- // strip case-sensitive modifiers
- pattern = pattern.replace(/(\\)?\\[cC]/g, function ($0, $1) { return $1 ? $0 : ""; });
-
- // remove any modifier escape \
- pattern = pattern.replace(/\\(\\[cClL])/g, "$1");
-
- this._searchString = pattern;
- },
-
- /**
- * Called when the search dialog is requested.
- *
- * @param {number} mode The search mode, either modes.SEARCH_FORWARD or
- * modes.SEARCH_BACKWARD.
- * @default modes.SEARCH_FORWARD
- */
- openPrompt: function (mode) {
- this._backwards = mode == modes.SEARCH_BACKWARD;
- commandline.open(this._backwards ? "?" : "/", "", mode);
- // TODO: focus the top of the currently visible screen
- },
-
- // TODO: backwards seems impossible i fear
- /**
- * Searches the current buffer for str .
- *
- * @param {string} str The string to find.
- */
- find: function (str) {
- let fastFind = config.browser.fastFind;
-
- this._processUserPattern(str);
- fastFind.caseSensitive = this._caseSensitive;
- this._found = fastFind.find(this._searchString, this._linksOnly) != Ci.nsITypeAheadFind.FIND_NOTFOUND;
-
- if (!this._found)
- this.setTimeout(function () liberator.echoerr("E486: Pattern not found: " + this._searchPattern, commandline.FORCE_SINGLELINE), 0);
- },
-
- /**
- * Searches the current buffer again for the most recently used search
- * string.
- *
- * @param {boolean} reverse Whether to search forwards or backwards.
- * @default false
- */
- findAgain: function (reverse) {
- // This hack is needed to make n/N work with the correct string, if
- // we typed /foo after the original search. Since searchString is
- // readonly we have to call find() again to update it.
- if (config.browser.fastFind.searchString != this._lastSearchString)
- this.find(this._lastSearchString);
-
- let up = reverse ? !this._lastSearchBackwards : this._lastSearchBackwards;
- let result = config.browser.fastFind.findAgain(up, this._linksOnly);
-
- if (result == Ci.nsITypeAheadFind.FIND_NOTFOUND)
- liberator.echoerr("E486: Pattern not found: " + this._lastSearchPattern, commandline.FORCE_SINGLELINE);
- else if (result == Ci.nsITypeAheadFind.FIND_WRAPPED) {
- // hack needed, because wrapping causes a "scroll" event which clears
- // our command line
- setTimeout(function () {
- let msg = up ? "search hit TOP, continuing at BOTTOM" : "search hit BOTTOM, continuing at TOP";
- commandline.echo(msg, commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES | commandline.FORCE_SINGLELINE);
- }, 0);
- }
- else {
- commandline.echo((up ? "?" : "/") + this._lastSearchPattern, null, commandline.FORCE_SINGLELINE);
-
- if (options["hlsearch"])
- this.highlight(this._lastSearchString);
- }
- },
-
- /**
- * Called when the user types a key in the search dialog. Triggers a
- * search attempt if 'incsearch' is set.
- *
- * @param {string} str The search string.
- */
- onKeyPress: function (str) {
- if (options["incsearch"])
- this.find(str);
- },
-
- /**
- * Called when the key is pressed to trigger a search.
- *
- * @param {string} str The search string.
- * @param {boolean} forcedBackward Whether to search forwards or
- * backwards. This overrides the direction set in
- * (@link #openPrompt).
- * @default false
- */
- onSubmit: function (str, forcedBackward) {
- if (typeof forcedBackward === "boolean")
- this._backwards = forcedBackward;
-
- if (str)
- var pattern = str;
- else {
- liberator.assert(this._lastSearchPattern, "E35: No previous search pattern");
- pattern = this._lastSearchPattern;
- }
-
- this.clear();
-
- if (!options["incsearch"] || !str || !this._found) {
- // prevent any current match from matching again
- if (!window.content.getSelection().isCollapsed)
- window.content.getSelection().getRangeAt(0).collapse(this._backwards);
-
- this.find(pattern);
- }
-
- this._lastSearchBackwards = this._backwards;
- //lastSearchPattern = pattern.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX
- this._lastSearchPattern = pattern;
- this._lastSearchString = this._searchString;
-
- // TODO: move to find() when reverse incremental searching is kludged in
- // need to find again for reverse searching
- if (this._backwards)
- this.setTimeout(function () { this.findAgain(false); }, 0);
-
- if (options["hlsearch"])
- this.highlight(this._searchString);
-
- modes.reset();
- },
-
- /**
- * Called when the search is canceled. For example, if someone presses
- * while typing a search.
- */
- onCancel: function () {
- // TODO: code to reposition the document to the place before search started
- },
-
- /**
- * Highlights all occurances of str in the buffer.
- *
- * @param {string} str The string to highlight.
- */
- highlight: function (str) {
- // FIXME: Thunderbird incompatible
- if (config.name == "Muttator")
- return;
-
- if (this._highlighter.isHighlighted(content.document))
- return;
-
- if (!str)
- str = this._lastSearchString;
-
- this._highlighter.highlightDoc(window.content, str);
-
- // recreate selection since highlightDoc collapses the selection
- if (window.content.getSelection().isCollapsed)
- config.browser.fastFind.findAgain(this._backwards, this._linksOnly);
-
- // TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"])
- },
-
- /**
- * Clears all search highlighting.
- */
- clear: function () {
- this._highlighter.clear();
- }
-}, {
-}, {
- commandline: function () {
- // Event handlers for search - closure is needed
- commandline.registerCallback("change", modes.SEARCH_FORWARD, this.closure.onKeyPress);
- commandline.registerCallback("submit", modes.SEARCH_FORWARD, this.closure.onSubmit);
- commandline.registerCallback("cancel", modes.SEARCH_FORWARD, this.closure.onCancel);
- // TODO: allow advanced myModes in register/triggerCallback
- commandline.registerCallback("change", modes.SEARCH_BACKWARD, this.closure.onKeyPress);
- commandline.registerCallback("submit", modes.SEARCH_BACKWARD, this.closure.onSubmit);
- commandline.registerCallback("cancel", modes.SEARCH_BACKWARD, this.closure.onCancel);
-
- },
- commands: function () {
- commands.add(["noh[lsearch]"],
- "Remove the search highlighting",
- function () { finder.clear(); },
- { argCount: "0" });
- },
- mappings: function () {
- var myModes = config.browserModes;
- myModes = myModes.concat([modes.CARET]);
-
- mappings.add(myModes,
- ["/"], "Search forward for a pattern",
- function () { finder.openPrompt(modes.SEARCH_FORWARD); });
-
- mappings.add(myModes,
- ["?"], "Search backwards for a pattern",
- function () { finder.openPrompt(modes.SEARCH_BACKWARD); });
-
- mappings.add(myModes,
- ["n"], "Find next",
- function () { finder.findAgain(false); });
-
- mappings.add(myModes,
- ["N"], "Find previous",
- function () { finder.findAgain(true); });
-
- mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["*"],
- "Find word under cursor",
- function () {
- this._found = false;
- finder.onSubmit(buffer.getCurrentWord(), false);
- });
-
- mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["#"],
- "Find word under cursor backwards",
- function () {
- this._found = false;
- finder.onSubmit(buffer.getCurrentWord(), true);
- });
- },
- options: function () {
- options.add(["hlsearch", "hls"],
- "Highlight previous search pattern matches",
- "boolean", "false", {
- setter: function (value) {
- try {
- if (value)
- finder.highlight();
- else
- finder.clear();
- }
- catch (e) {}
-
- return value;
- }
- });
-
- options.add(["ignorecase", "ic"],
- "Ignore case in search patterns",
- "boolean", true);
-
- options.add(["incsearch", "is"],
- "Show where the search pattern matches as it is typed",
- "boolean", true);
-
- options.add(["linksearch", "lks"],
- "Limit the search to hyperlink text",
- "boolean", false);
-
- options.add(["smartcase", "scs"],
- "Override the 'ignorecase' option if the pattern contains uppercase characters",
- "boolean", true);
- }
-});
-
+/** @instance rangefinder */
const RangeFinder = Module("rangefinder", {
requires: ["config"],
@@ -471,6 +18,7 @@ const RangeFinder = Module("rangefinder", {
let backwards = mode == modes.FIND_BACKWARD;
commandline.open(backwards ? "?" : "/", "", mode);
+ this.rangeFind = null;
this.find("", backwards);
},
@@ -537,6 +85,7 @@ const RangeFinder = Module("rangefinder", {
if (options["hlsearch"])
this.highlight();
+ this.rangeFind.focus();
},
// Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set
@@ -557,6 +106,7 @@ const RangeFinder = Module("rangefinder", {
if (options["hlsearch"])
this.highlight();
+ this.rangeFind.focus();
modes.reset();
},
@@ -603,48 +153,106 @@ const RangeFinder = Module("rangefinder", {
},
commands: function () {
+ commands.add(["noh[lsearch]"],
+ "Remove the search highlighting",
+ function () { rangefinder.clear(); },
+ { argCount: "0" });
},
mappings: function () {
var myModes = config.browserModes.concat([modes.CARET]);
mappings.add(myModes,
- ["g/"], "Search forward for a pattern",
+ ["/"], "Search forward for a pattern",
function () { rangefinder.openPrompt(modes.FIND_FORWARD); });
mappings.add(myModes,
- ["g?"], "Search backwards for a pattern",
+ ["?"], "Search backwards for a pattern",
function () { rangefinder.openPrompt(modes.FIND_BACKWARD); });
mappings.add(myModes,
- ["g."], "Find next",
+ ["n"], "Find next",
function () { rangefinder.findAgain(false); });
mappings.add(myModes,
- ["g,"], "Find previous",
+ ["N"], "Find previous",
function () { rangefinder.findAgain(true); });
- mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["g*"],
+ mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["*"],
"Find word under cursor",
function () {
rangefinder._found = false;
rangefinder.onSubmit(buffer.getCurrentWord(), false);
});
- mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["g#"],
+ mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["#"],
"Find word under cursor backwards",
function () {
rangefinder._found = false;
rangefinder.onSubmit(buffer.getCurrentWord(), true);
});
+
},
modes: function () {
modes.addMode("FIND_FORWARD", true);
modes.addMode("FIND_BACKWARD", true);
},
options: function () {
+ options.add(["hlsearch", "hls"],
+ "Highlight previous search pattern matches",
+ "boolean", "false", {
+ setter: function (value) {
+ try {
+ if (value)
+ rangefinder.highlight();
+ else
+ rangefinder.clear();
+ }
+ catch (e) {}
+
+ return value;
+ }
+ });
+
+ options.add(["ignorecase", "ic"],
+ "Ignore case in search patterns",
+ "boolean", true);
+
+ options.add(["incsearch", "is"],
+ "Show where the search pattern matches as it is typed",
+ "boolean", true);
+
+ options.add(["linksearch", "lks"],
+ "Limit the search to hyperlink text",
+ "boolean", false);
+
+ options.add(["smartcase", "scs"],
+ "Override the 'ignorecase' option if the pattern contains uppercase characters",
+ "boolean", true);
}
});
+/**
+ * @class RangeFind
+ *
+ * A fairly sophisticated typeahead-find replacement. It supports
+ * incremental search very much as the builtin component.
+ * Additionally, it supports several features impossible to
+ * implement using the standard component. Incremental searching
+ * works both forwards and backwards. Erasing characters during an
+ * incremental search moves the selection back to the first
+ * available match for the shorter term. The selection and viewport
+ * are restored when the search is canceled.
+ *
+ * Also, in addition to full support for frames and iframes, this
+ * 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
+ * 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).
+ */
const RangeFind = Class("RangeFind", {
init: function (matchCase, backward, elementPath) {
this.window = Cu.getWeakReference(window);
@@ -655,20 +263,28 @@ const RangeFind = Class("RangeFind", {
this.finder.caseSensitive = this.matchCase;
this.ranges = this.makeFrameList(content);
- this.range = RangeFind.Range(tabs.localStore.focusedFrame || content);
- this.startRange = (this.range.selection.rangeCount ? this.range.selection.getRangeAt(0) : this.ranges[0].range).cloneRange();
- this.startRange.collapse(!backward);
- this.range = this.findRange(this.startRange);
- this.ranges.first = this.range;
+ this.reset();
this.highlighted = null;
this.lastString = "";
- this.lastRange = null;
this.forward = null;
this.found = false;
},
+ get selectedRange() {
+ let range = RangeFind.Range(tabs.localStore.focusedFrame || content);
+ return (range.selection.rangeCount ? range.selection.getRangeAt(0) : this.ranges[0].range).cloneRange();
+ },
+
+ reset: function () {
+ this.startRange = this.selectedRange;
+ this.startRange.collapse(!this.reverse);
+ this.lastRange = this.selectedRange;
+ this.range = this.findRange(this.startRange);
+ this.ranges.first = this.range;
+ },
+
sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument,
compareRanges: function (r1, r2)
@@ -699,6 +315,16 @@ const RangeFind = Class("RangeFind", {
}
},
+ focus: function() {
+ if(this.lastRange)
+ var node = util.evaluateXPath(RangeFind.selectNodePath, this.range.document,
+ this.lastRange.commonAncestorContainer).snapshotItem(0);
+ if(node) {
+ node.focus();
+ this.search(null, false); // Rehighlight collapsed range
+ }
+ },
+
makeFrameList: function (win) {
const self = this;
win = win.top;
@@ -706,15 +332,21 @@ const RangeFind = Class("RangeFind", {
let backup = null;
function pushRange(start, end) {
+ function push(r) {
+ r = RangeFind.Range(r, frames.length);
+ if (r)
+ frames.push(r);
+ }
+
let range = start.startContainer.ownerDocument.createRange();
range.setStart(start.startContainer, start.startOffset);
range.setEnd(end.startContainer, end.startOffset);
if (!self.elementPath)
- frames.push(RangeFind.Range(range, frames.length));
+ push(range);
else
for (let r in self.findSubRanges(range))
- frames.push(RangeFind.Range(r, frames.length));
+ push(r);
}
function rec(win) {
let doc = win.document;
@@ -741,8 +373,8 @@ const RangeFind = Class("RangeFind", {
// This doesn't work yet.
resetCaret: function () {
- let equal = function (r1, r2) !r1.compareBoundaryPoints(Range.START_TO_START, r2) && !r1.compareBoundaryPoints(Range.END_TO_END, r2);
- letselection = this.win.getSelection();
+ let equal = RangeFind.equal;
+ let selection = this.win.getSelection();
if (selection.rangeCount == 0)
selection.addRange(this.pageStart);
function getLines() {
@@ -792,6 +424,9 @@ const RangeFind = Class("RangeFind", {
},
search: function (word, reverse, private_) {
+ if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange))
+ this.reset();
+
this.wrapped = false;
this.finder.findBackwards = reverse ? !this.reverse : this.reverse;
let again = word == null;
@@ -886,6 +521,7 @@ const RangeFind = Class("RangeFind", {
parent.insertBefore(node, before);
range.selectNode(node);
}
+
function unhighlight(range) {
let elem = range.startContainer;
while (!(elem instanceof Element) && elem.parentNode)
@@ -913,7 +549,7 @@ const RangeFind = Class("RangeFind", {
else {
this.highlighted = this.lastString;
this.addListeners();
- this.search(null, false);
+ this.search(null, false); // Rehighlight collapsed range
}
},
@@ -952,6 +588,9 @@ const RangeFind = Class("RangeFind", {
this.window = this.document.defaultView;
this.range = range;
+ if (this.selection == null)
+ return false;
+
this.save();
},
@@ -989,12 +628,27 @@ const RangeFind = Class("RangeFind", {
.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController),
- get selection() this.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL)
+ get selection() {
+ try {
+ return this.selectionController.getSelection(Ci.nsISelectionController.SELECTION_NORMAL)
+ } catch (e) {
+ return null;
+ }}
+
}),
+ selectNodePath: ["ancestor-or-self::" + s for ([i, s] in Iterator(
+ ["a", "xhtml:a", "*[@onclick]"]))].join(" | "),
endpoint: function (range, before) {
range = range.cloneRange();
range.collapse(before);
return range;
+ },
+ equal: function (r1, r2) {
+ try {
+ return !r1.compareBoundaryPoints(Range.START_TO_START, r2) && !r1.compareBoundaryPoints(Range.END_TO_END, r2)
+ }
+ catch (e) {}
+ return false;
}
});
diff --git a/common/content/help.js b/common/content/help.js
index 67fbba37..ed1dd22a 100644
--- a/common/content/help.js
+++ b/common/content/help.js
@@ -2,7 +2,7 @@
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
-
+"use strict";
function checkFragment() {
document.title = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "title")[0].textContent;
diff --git a/common/content/help.xsl b/common/content/help.xsl
index 1ad7ee76..e9766465 100644
--- a/common/content/help.xsl
+++ b/common/content/help.xsl
@@ -202,7 +202,7 @@
(default:
-
+
@@ -351,9 +351,13 @@
-
-
-
+
+
+
+ #
+
+
+
diff --git a/common/content/hints.js b/common/content/hints.js
index 48974c7e..ed4b4e9c 100644
--- a/common/content/hints.js
+++ b/common/content/hints.js
@@ -1,8 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
/** @instance hints */
@@ -40,12 +42,12 @@ const Hints = Module("hints", {
"?": Mode("Show information for hint", function (elem) buffer.showElementInfo(elem), extended),
s: Mode("Save hint", function (elem) buffer.saveLink(elem, true)),
a: Mode("Save hint with prompt", function (elem) buffer.saveLink(elem, false)),
- f: Mode("Focus frame", function (elem) elem.ownerDocument.defaultView.focus(), function () util.makeXPath(["body"])),
+ f: Mode("Focus frame", function (elem) elem.ownerDocument.defaultView.focus(), function () ["body"]),
o: Mode("Follow hint", function (elem) buffer.followLink(elem, liberator.CURRENT_TAB)),
t: Mode("Follow hint in a new tab", function (elem) buffer.followLink(elem, liberator.NEW_TAB)),
b: Mode("Follow hint in a background tab", function (elem) buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB)),
w: Mode("Follow hint in a new window", function (elem) buffer.followLink(elem, liberator.NEW_WINDOW), extended),
- F: Mode("Open multiple hints in tabs", followAndReshow),
+ F: Mode("Open multiple hints in tabs", function (elem) { buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB); hints.show("F") }),
O: Mode("Generate an ':open URL' using hint", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)),
T: Mode("Generate a ':tabopen URL' using hint", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)),
W: Mode("Generate a ':winopen URL' using hint", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)),
@@ -57,21 +59,6 @@ const Hints = Module("hints", {
i: Mode("Show image", function (elem) liberator.open(elem.src), images),
I: Mode("Show image in a new tab", function (elem) liberator.open(elem.src, liberator.NEW_TAB), images)
};
-
- /**
- * Follows the specified hint and then reshows all hints. Used to open
- * multiple hints in succession.
- *
- * @param {Node} elem The selected hint.
- */
- function followAndReshow(elem) {
- buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB);
-
- // TODO: Maybe we find a *simple* way to keep the hints displayed rather than
- // showing them again, or is this short flash actually needed as a "usability
- // feature"? --mst
- hints.show("F");
- }
},
/**
@@ -127,7 +114,7 @@ const Hints = Module("hints", {
let type = elem.type;
- if (elem instanceof HTMLInputElement && /(submit|button|this._reset)/.test(type))
+ if (elem instanceof HTMLInputElement && /(submit|button|reset)/.test(type))
return [elem.value, false];
else {
for (let [, option] in Iterator(options["hintinputs"].split(","))) {
@@ -266,14 +253,10 @@ const Hints = Module("hints", {
let hint = { elem: elem, showText: false };
// TODO: for iframes, this calculation is wrong
- rect = elem.getBoundingClientRect();
+ let rect = elem.getBoundingClientRect();
if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0)
continue;
- rect = elem.getClientRects()[0];
- if (!rect)
- continue;
-
let computedStyle = doc.defaultView.getComputedStyle(elem, null);
if (computedStyle.getPropertyValue("visibility") != "visible" || computedStyle.getPropertyValue("display") == "none")
continue;
@@ -370,7 +353,7 @@ const Hints = Module("hints", {
if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof HTMLImageElement) {
if (!hint.imgSpan) {
- rect = hint.elem.firstChild.getBoundingClientRect();
+ var rect = hint.elem.firstChild.getBoundingClientRect();
if (!rect)
continue;
@@ -394,11 +377,11 @@ const Hints = Module("hints", {
}
}
- if (config.browser.markupDocumentViewer.authorStyleDisabled) {
+ if (options["usermode"]) {
let css = [];
// FIXME: Broken for imgspans.
for (let [, { doc: doc }] in Iterator(this._docs)) {
- for (let elem in util.evaluateXPath(" {//*[@liberator:highlight and @number]", doc)) {
+ for (let elem in util.evaluateXPath("//*[@liberator:highlight and @number]", doc)) {
let group = elem.getAttributeNS(NS.uri, "highlight");
css.push(highlight.selector(group) + "[number=" + elem.getAttribute("number").quote() + "] { " + elem.style.cssText + " }");
}
@@ -1058,8 +1041,8 @@ const Hints = Module("hints", {
},
options: function () {
const DEFAULT_HINTTAGS =
- util.makeXPath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select"])
- + " | //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @role='link']";
+ util.makeXPath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select",
+ "*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @role='link']"]);
function checkXPath(val) {
try {
diff --git a/common/content/history.js b/common/content/history.js
index 8f3ae5c8..dbab5522 100644
--- a/common/content/history.js
+++ b/common/content/history.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 History = Module("history", {
requires: ["config"],
diff --git a/common/content/io.js b/common/content/io.js
index 07f15453..7c85d331 100755
--- a/common/content/io.js
+++ b/common/content/io.js
@@ -1,9 +1,11 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
// Some code based on Venkman
//
// 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 */
@@ -160,7 +162,7 @@ const File = Class("File", {
mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_TRUNCATE;
if (!perms)
- perms = 0644;
+ perms = parseInt('0644', 8);
ofstream.init(this, mode, perms, 0);
let ocstream = getStream(0);
@@ -240,7 +242,7 @@ const File = Class("File", {
*/
MODE_EXCL: 0x80,
- expandPathList: function (list) list.split(",").map(this.expandPath).join(","),
+ expandPathList: function (list) list.map(this.expandPath),
expandPath: function (path, relative) {
@@ -338,7 +340,7 @@ const IO = Module("io", {
let file = download.targetFile.path;
let size = download.size;
- liberator.echomsg("Download of " + title + " to " + file + " finished", 1);
+ liberator.echomsg("Download of " + title + " to " + file + " finished", 1, commandline.ACTIVE_WINDOW);
autocommands.trigger("DownloadPost", { url: url, title: title, file: file, size: size });
}
},
@@ -485,7 +487,7 @@ const IO = Module("io", {
let file = services.get("directory").get("TmpD", Ci.nsIFile);
file.append(config.tempFile);
- file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt('0600', 8));
return File(file);
},
@@ -595,6 +597,8 @@ lookup:
*/
source: function (filename, silent) {
let wasSourcing = this.sourcing;
+ liberator.dump("sourcing " + filename);
+ let time = Date.now();
try {
var file = File(filename);
this.sourcing = {
@@ -624,8 +628,7 @@ lookup:
if (/\.js$/.test(filename)) {
try {
liberator.loadScript(uri.spec, Script(file));
- if (liberator.initialized)
- liberator.initHelp();
+ liberator.helpInitialized = false;
}
catch (e) {
let err = new Error();
@@ -713,6 +716,7 @@ lookup:
liberator.echoerr(message);
}
finally {
+ liberator.dump("done sourcing " + filename + ": " + (Date.now() - time) + "ms");
this.sourcing = wasSourcing;
}
},
@@ -1028,8 +1032,8 @@ lookup:
b.isdir - a.isdir || String.localeCompare(a.text, b.text);
if (options["wildignore"]) {
- let wigRegexp = RegExp("(^" + options.get("wildignore").values.join("|") + ")$");
- context.filters.push(function ({item: f}) f.isDirectory() || !wigRegexp.test(f.leafName));
+ let wig = options.get("wildignore");
+ context.filters.push(function ({item: f}) f.isDirectory() || !wig.getKey(this.name));
}
// context.background = true;
@@ -1061,7 +1065,10 @@ lookup:
};
};
- completion.addUrlCompleter("f", "Local files", completion.file);
+ completion.addUrlCompleter("f", "Local files", function (context, full) {
+ if (!/^\.?\//.test(context.filter))
+ completion.file(context, full);
+ });
},
options: function () {
var shell, shellcmdflag;
@@ -1099,6 +1106,10 @@ lookup:
options.add(["shellcmdflag", "shcf"],
"Flag passed to shell when executing :! and :run commands",
"string", shellcmdflag);
+
+ options.add(["wildignore", "wig"],
+ "List of file patterns to ignore when completing files",
+ "regexlist", "");
}
});
diff --git a/common/content/javascript.js b/common/content/javascript.js
index 98804f7c..1c337424 100644
--- a/common/content/javascript.js
+++ b/common/content/javascript.js
@@ -2,6 +2,7 @@
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
+"use strict";
// TODO: Clean this up.
@@ -33,17 +34,18 @@ const JavaScript = Module("javascript", {
},
iter: function iter(obj, toplevel) {
+ "use strict";
toplevel = !!toplevel;
let seen = {};
let ret = {};
- try {
+ if(obj == null)
+ return;
+
+ if(options["jsdebugger"]) {
let orig = obj;
let top = services.get("debugger").wrapValue(obj);
- if (!toplevel)
- obj = obj.__proto__;
-
for (; obj; obj = !toplevel && obj.__proto__) {
services.get("debugger").wrapValue(obj).getProperties(ret, {});
for (let prop in values(ret.value)) {
@@ -51,18 +53,27 @@ const JavaScript = Module("javascript", {
if (name in seen)
continue;
seen[name] = 1;
- yield [prop.name.stringValue, top.getProperty(prop.name.stringValue).value.getWrappedValue()]
+ if (toplevel || obj !== orig)
+ yield [prop.name.stringValue, top.getProperty(prop.name.stringValue).value.getWrappedValue()]
}
}
// The debugger doesn't list some properties. I can't guess why.
- for (let k in orig)
- if (k in orig && !('|' + k in seen) && obj.hasOwnProperty(k) == toplevel)
- yield [k, this.getKey(orig, k)]
+ // This only lists ENUMERABLE properties.
+ try {
+ for (let k in orig)
+ if (k in orig && !('|' + k in seen)
+ && Object.hasOwnProperty(orig, k) == toplevel)
+ yield [k, this.getKey(orig, k)]
+ }
+ catch(e) {}
}
- catch(e) {
- for (k in allkeys(obj))
- if (obj.hasOwnProperty(k) == toplevel)
- yield [k, this.getKey(obj, k)];
+ else {
+ for (let k in allkeys(obj))
+ try {
+ if (Object.hasOwnProperty(obj, k) == toplevel)
+ yield [k, this.getKey(obj, k)];
+ }
+ catch (e) {}
}
},
@@ -101,7 +112,7 @@ const JavaScript = Module("javascript", {
return completions;
},
- eval: function eval(arg, key, tmp) {
+ eval: function evalstr(arg, key, tmp) {
let cache = this.context.cache.eval;
let context = this.context.cache.evalContext;
@@ -230,8 +241,6 @@ const JavaScript = Module("javascript", {
case "'":
case "/":
case "{":
- this._push(this._c);
- break;
case "[":
this._push(this._c);
break;
@@ -341,6 +350,10 @@ const JavaScript = Module("javascript", {
_complete: function (objects, key, compl, string, last) {
const self = this;
+
+ if(!options["jsdebugger"] && !this.context.message)
+ this.context.message = "For better completion data, please enable the JavaScript debugger (:set jsdebugger)";
+
let orig = compl;
if (!compl) {
compl = function (context, obj, recurse) {
@@ -427,7 +440,8 @@ const JavaScript = Module("javascript", {
// Okay, have parse stack. Figure out what we're completing.
// Find any complete statements that we can eval before we eval our object.
- // This allows for things like: let doc = window.content.document; let elem = doc.createElement...; elem.
+ // This allows for things like:
+ // let doc = window.content.document; let elem = doc.createEle ...
let prev = 0;
for (let [, v] in Iterator(this._get(0).fullStatements)) {
let key = this._str.substring(prev, v + 1);
@@ -437,14 +451,13 @@ const JavaScript = Module("javascript", {
prev = v + 1;
}
- // In a string. Check if we're dereferencing an object.
- // Otherwise, do nothing.
+ // In a string. Check if we're dereferencing an object or
+ // completing a function argument. Otherwise, do nothing.
if (this._last == "'" || this._last == '"') {
- //
+
// str = "foo[bar + 'baz"
// obj = "foo"
// key = "bar + ''"
- //
// The top of the stack is the sting we're completing.
// Wrap it in its delimiters and eval it to process escape sequences.
@@ -497,15 +510,15 @@ const JavaScript = Module("javascript", {
// Split up the arguments
let prev = this._get(-2).offset;
let args = [];
- for (let [, idx] in Iterator(this._get(-2).comma)) {
+ for (let [i, idx] in Iterator(this._get(-2).comma)) {
let arg = this._str.substring(prev + 1, idx);
prev = idx;
- util.memoize(args, this._i, function () self.eval(arg));
+ util.memoize(args, i, function () self.eval(arg));
}
let key = this._getKey();
args.push(key + string);
- compl = function (context, obj) {
+ let compl = function (context, obj) {
let res = completer.call(self, context, func, obj, args);
if (res)
context.completions = res;
@@ -520,7 +533,7 @@ const JavaScript = Module("javascript", {
return null;
}
- //
+
// str = "foo.bar.baz"
// obj = "foo.bar"
// key = "baz"
@@ -528,11 +541,11 @@ const JavaScript = Module("javascript", {
// str = "foo"
// obj = [modules, window]
// key = "foo"
- //
+
let [offset, obj, key] = this._getObjKey(-1);
- // Wait for a keypress before completing the default objects.
+ // Wait for a keypress before completing when there's no key
if (!this.context.tabPressed && key == "" && obj.length > 1) {
this.context.waitingForTab = true;
this.context.message = "Waiting for key press";
@@ -589,7 +602,7 @@ const JavaScript = Module("javascript", {
let completer = completers[args.length - 1];
if (!completer)
return [];
- return completer.call(this, context, obj, args);
+ return completer.call(obj, context, obj, args);
};
}
}
diff --git a/common/content/liberator-overlay.js b/common/content/liberator-overlay.js
index 478ecd9e..e9461df4 100644
--- a/common/content/liberator-overlay.js
+++ b/common/content/liberator-overlay.js
@@ -1,7 +1,8 @@
-// Copyright (c) 2008-2009 Kris Maglione
+// Copyright (c) 2008-2008 Kris Maglione
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
+"use strict";
(function () {
const modules = {};
diff --git a/common/content/liberator.js b/common/content/liberator.js
index a4b00ded..8214b461 100644
--- a/common/content/liberator.js
+++ b/common/content/liberator.js
@@ -1,8 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -134,7 +136,7 @@ const Liberator = Module("liberator", {
forceNewWindow: false,
/** @property {string} The Liberator version string. */
- version: "###VERSION### (created: ###DATE###)", // these VERSION and DATE tokens are replaced by the Makefile
+ version: "@VERSION@ (created: @DATE@)", // these VERSION and DATE tokens are replaced by the Makefile
/**
* @property {Object} The map of command-line options. These are
@@ -268,7 +270,7 @@ const Liberator = Module("liberator", {
let stack = Error().stack.replace(/(?:.*\n){2}/, "");
if (frames != null)
[stack] = stack.match(RegExp("(?:.*\n){0," + frames + "}"));
- liberator.dump((msg || "Stack") + "\n" + stack);
+ liberator.dump((msg || "Stack") + "\n" + stack + "\n");
},
/**
@@ -329,7 +331,7 @@ const Liberator = Module("liberator", {
// you don't like them you can set verbose=0, or use :silent when
// someone adds it. I reckon another flag and 'class' of messages
// is just going to unnecessarily complicate things. --djk
- flags |= commandline.APPEND_TO_MESSAGES | commandline.DISALLOW_MULTILINE;
+ flags |= commandline.APPEND_TO_MESSAGES; // | commandline.DISALLOW_MULTILINE;
if (verbosity == null)
verbosity = 0; // verbosity level is exclusionary
@@ -542,85 +544,88 @@ const Liberator = Module("liberator", {
* Initialize the help system.
*/
initHelp: function () {
- let namespaces = [config.name.toLowerCase(), "liberator"];
- services.get("liberator:").init({});
+ if(!this.helpInitialized) {
+ let namespaces = [config.name.toLowerCase(), "liberator"];
+ services.get("liberator:").init({});
- let tagMap = services.get("liberator:").HELP_TAGS;
- let fileMap = services.get("liberator:").FILE_MAP;
- let overlayMap = services.get("liberator:").OVERLAY_MAP;
+ let tagMap = services.get("liberator:").HELP_TAGS;
+ let fileMap = services.get("liberator:").FILE_MAP;
+ let overlayMap = services.get("liberator:").OVERLAY_MAP;
- // Left as an XPCOM instantiation so it can easilly be moved
- // into XPCOM code.
- function XSLTProcessor(sheet) {
- let xslt = Cc["@mozilla.org/document-transformer;1?type=xslt"].createInstance(Ci.nsIXSLTProcessor);
- xslt.importStylesheet(util.httpGet(sheet).responseXML);
- return xslt;
- }
-
- // Find help and overlay files with the given name.
- function findHelpFile(file) {
- let result = [];
- for (let [, namespace] in Iterator(namespaces)) {
- let url = ["chrome://", namespace, "/locale/", file, ".xml"].join("");
- let res = util.httpGet(url);
- if (res) {
- if (res.responseXML.documentElement.localName == "document")
- fileMap[file] = url;
- if (res.responseXML.documentElement.localName == "overlay")
- overlayMap[file] = url;
- result.push(res.responseXML);
- }
+ // Left as an XPCOM instantiation so it can easilly be moved
+ // into XPCOM code.
+ function XSLTProcessor(sheet) {
+ let xslt = Cc["@mozilla.org/document-transformer;1?type=xslt"].createInstance(Ci.nsIXSLTProcessor);
+ xslt.importStylesheet(util.httpGet(sheet).responseXML);
+ return xslt;
}
- return result;
- }
- // Find the tags in the document.
- function addTags(file, doc) {
- doc = XSLT.transformToDocument(doc);
- for (let elem in util.evaluateXPath("//xhtml:a/@id", doc))
- tagMap[elem.value] = file;
- }
- const XSLT = XSLTProcessor("chrome://liberator/content/help-single.xsl");
+ // Find help and overlay files with the given name.
+ function findHelpFile(file) {
+ let result = [];
+ for (let [, namespace] in Iterator(namespaces)) {
+ let url = ["chrome://", namespace, "/locale/", file, ".xml"].join("");
+ let res = util.httpGet(url);
+ if (res) {
+ if (res.responseXML.documentElement.localName == "document")
+ fileMap[file] = url;
+ if (res.responseXML.documentElement.localName == "overlay")
+ overlayMap[file] = url;
+ result.push(res.responseXML);
+ }
+ }
+ return result;
+ }
+ // Find the tags in the document.
+ function addTags(file, doc) {
+ doc = XSLT.transformToDocument(doc);
+ for (let elem in util.evaluateXPath("//xhtml:a/@id", doc))
+ tagMap[elem.value] = file;
+ }
- // Scrape the list of help files from all.xml
- // Always process main and overlay files, since XSLTProcessor and
- // XMLHttpRequest don't allow access to chrome documents.
- tagMap.all = "all";
- let files = findHelpFile("all").map(function (doc)
- [f.value for (f in util.evaluateXPath(
- "//liberator:include/@href", doc))]);
+ const XSLT = XSLTProcessor("chrome://liberator/content/help-single.xsl");
- // Scrape the tags from the rest of the help files.
- util.Array.flatten(files).forEach(function (file) {
- findHelpFile(file).forEach(function (doc) {
- addTags(file, doc);
+ // Scrape the list of help files from all.xml
+ // Always process main and overlay files, since XSLTProcessor and
+ // XMLHttpRequest don't allow access to chrome documents.
+ tagMap.all = "all";
+ let files = findHelpFile("all").map(function (doc)
+ [f.value for (f in util.evaluateXPath(
+ "//liberator:include/@href", doc))]);
+
+ // Scrape the tags from the rest of the help files.
+ util.Array.flatten(files).forEach(function (file) {
+ findHelpFile(file).forEach(function (doc) {
+ addTags(file, doc);
+ });
});
- });
- // Process plugin help entries.
- XML.ignoreWhiteSpace = false;
- XML.prettyPrinting = false;
- XML.prettyPrinting = true; // Should be false, but ignoreWhiteSpace=false doesn't work correctly. This is the lesser evil.
- XML.prettyIndent = 4;
+ // Process plugin help entries.
+ XML.ignoreWhiteSpace = false;
+ XML.prettyPrinting = false;
+ XML.prettyPrinting = true; // Should be false, but ignoreWhiteSpace=false doesn't work correctly. This is the lesser evil.
+ XML.prettyIndent = 4;
- let body = XML();
- for (let [, context] in Iterator(plugins.contexts))
- if (context.INFO instanceof XML)
- body += {context.INFO.@summary} +
- context.INFO;
+ let body = XML();
+ for (let [, context] in Iterator(plugins.contexts))
+ if (context.INFO instanceof XML)
+ body += {context.INFO.@summary} +
+ context.INFO;
- let help = '\n' +
- '\n' +
- '' +
-
- Using Plugins
+ let help = '\n' +
+ '\n' +
+ '' +
+
+ Using Plugins
- {body}
- .toXMLString();
- fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
+ {body}
+ .toXMLString();
+ fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help];
- addTags("plugins", util.httpGet("liberator://help/plugins").responseXML);
+ addTags("plugins", util.httpGet("liberator://help/plugins").responseXML);
+ this.helpInitialized = true;
+ }
},
/**
@@ -632,6 +637,7 @@ const Liberator = Module("liberator", {
* @returns {string}
*/
help: function (topic, unchunked) {
+ liberator.initHelp();
if (!topic) {
let helpFile = unchunked ? "all" : options["helpfile"];
if (helpFile in services.get("liberator:").FILE_MAP)
@@ -737,22 +743,12 @@ const Liberator = Module("liberator", {
*/
open: function (urls, params, force) {
// convert the string to an array of converted URLs
- // -> see util.stringToURLArray for more details
+ // -> see liberator.stringToURLArray for more details
//
// This is strange. And counterintuitive. Is it really
// necessary? --Kris
- if (typeof urls == "string") {
- // rather switch to the tab instead of opening a new url in case of "12: Tab Title" like "urls"
- if (liberator.has("tabs")) {
- let matches = urls.match(/^(\d+):/);
- if (matches) {
- tabs.select(parseInt(matches[1], 10) - 1, false); // make it zero-based
- return;
- }
- }
-
- urls = util.stringToURLArray(urls);
- }
+ if (typeof urls == "string")
+ urls = liberator.stringToURLArray(urls);
if (urls.length > 20 && !force) {
commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ",
@@ -765,7 +761,7 @@ const Liberator = Module("liberator", {
let flags = 0;
params = params || {};
- if (params instanceof Array)
+ if (isarray(params))
params = { where: params };
for (let [opt, flag] in Iterator({ replace: "REPLACE_HISTORY", hide: "BYPASS_HISTORY" }))
@@ -773,15 +769,11 @@ const Liberator = Module("liberator", {
flags |= Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
let where = params.where || liberator.CURRENT_TAB;
+ let background = ("background" in params) ? params.background : params.where == liberator.NEW_BACKGROUND_TAB;
if ("from" in params && liberator.has("tabs")) {
if (!('where' in params) && options.get("newtab").has("all", params.from))
- where = liberator.NEW_BACKGROUND_TAB;
- if (options.get("activate").has("all", params.from)) {
- if (where == liberator.NEW_TAB)
- where = liberator.NEW_BACKGROUND_TAB;
- else if (where == liberator.NEW_BACKGROUND_TAB)
- where = liberator.NEW_TAB;
- }
+ where = liberator.NEW_TAB;
+ background = !options.get("activate").has("all", params.from);
}
if (urls.length == 0)
@@ -799,7 +791,6 @@ const Liberator = Module("liberator", {
browser.loadURIWithFlags(url, flags, null, null, postdata);
break;
- case liberator.NEW_BACKGROUND_TAB:
case liberator.NEW_TAB:
if (!liberator.has("tabs")) {
open(urls, liberator.NEW_WINDOW);
@@ -808,7 +799,7 @@ const Liberator = Module("liberator", {
options.withContext(function () {
options.setPref("browser.tabs.loadInBackground", true);
- browser.loadOneTab(url, null, null, postdata, where == liberator.NEW_BACKGROUND_TAB);
+ browser.loadOneTab(url, null, null, postdata, background);
});
break;
@@ -832,7 +823,7 @@ const Liberator = Module("liberator", {
for (let [, url] in Iterator(urls)) {
open(url, where);
- where = liberator.NEW_BACKGROUND_TAB;
+ background = true;
}
},
@@ -865,6 +856,64 @@ const Liberator = Module("liberator", {
window.goQuitApplication();
},
+ /**
+ * Returns an array of URLs parsed from str .
+ *
+ * Given a string like 'google bla, www.osnews.com' return an array
+ * ['www.google.com/search?q=bla', 'www.osnews.com']
+ *
+ * @param {string} str
+ * @returns {string[]}
+ */
+ stringToURLArray: function stringToURLArray(str) {
+ let urls;
+
+ if (options["urlseparator"])
+ urls = util.splitLiteral(str, RegExp("\\s*" + options["urlseparator"] + "\\s*"));
+ else
+ urls = [str];
+
+ return urls.map(function (url) {
+ if (/^\.?\//.test(url)) {
+ try {
+ // Try to find a matching file.
+ let file = io.File(url);
+ if (file.exists() && file.isReadable())
+ return services.get("io").newFileURI(file).spec;
+ }
+ catch (e) {}
+ }
+
+ // strip each 'URL' - makes things simpler later on
+ url = url.replace(/^\s+|\s+$/, "");
+
+ // Look for a valid protocol
+ let proto = url.match(/^([-\w]+):/);
+ if (proto && Cc["@mozilla.org/network/protocol;1?name=" + proto[1]])
+ // Handle as URL, but remove spaces. Useful for copied/'p'asted URLs.
+ return url.replace(/\s*\n+\s*/g, "");
+
+ // Ok, not a valid proto. If it looks like URL-ish (foo.com/bar),
+ // let Gecko figure it out.
+ if (/^[a-zA-Z0-9-.]+(?:\/|$)/.test(url) && /[.\/]/.test(url) && !/\s/.test(url) || /^[a-zA-Z0-9-.]+:\d+(?:\/|$)/.test(url))
+ return url;
+
+ // TODO: it would be clearer if the appropriate call to
+ // getSearchURL was made based on whether or not the first word was
+ // indeed an SE alias rather than seeing if getSearchURL can
+ // process the call usefully and trying again if it fails
+
+ // check for a search engine match in the string, then try to
+ // search for the whole string in the default engine
+ let searchURL = bookmarks.getSearchURL(url, false) || bookmarks.getSearchURL(url, true);
+ if (searchURL)
+ return searchURL;
+
+ // Hmm. No defsearch? Let the host app deal with it, then.
+ return url;
+ });
+ },
+
/*
* Tests a condition and throws a FailedAssertion error on
* failure.
@@ -1271,17 +1320,8 @@ const Liberator = Module("liberator", {
let arg = args[0];
try {
- // TODO: why are these sorts of properties arrays? --djk
- let dialogs = config.dialogs;
-
- for (let [, dialog] in Iterator(dialogs)) {
- if (util.compareIgnoreCase(arg, dialog[0]) == 0) {
- dialog[2]();
- return;
- }
- }
-
- liberator.echoerr("E475: Invalid argument: " + arg);
+ liberator.assert(args[0] in config.dialogs, "E475: Invalid argument: " + arg);
+ config.dialogs[args[0]][1]();
}
catch (e) {
liberator.echoerr("Error opening " + arg.quote() + ": " + e);
@@ -1331,19 +1371,89 @@ const Liberator = Module("liberator", {
}
});
+ ///////////////////////////////////////////////////////////////////////////
+
+ if (typeof AddonManager == "undefined") {
+ modules.AddonManager = {
+ getInstallForFile: function (file, callback, mimetype) {
+ callback({
+ install: function () {
+ services.get("extensionManager").installItemFromFile(file, "app-profile");
+ }
+ });
+ },
+ getAddonById: function (id, callback) {
+ let addon = id;
+ if (!isobject(addon))
+ addon = services.get("extensionManager").getItemForID(id);
+ if (!addon)
+ return callback(null);
+
+ function getRdfProperty(item, property) {
+ let resource = services.get("rdf").GetResource("urn:mozilla:item:" + item.id);
+ let value = "";
+
+ if (resource) {
+ let target = services.get("extensionManager").datasource.GetTarget(resource,
+ services.get("rdf").GetResource("http://www.mozilla.org/2004/em-rdf#" + property), true);
+ if (target && target instanceof Ci.nsIRDFLiteral)
+ value = target.Value;
+ }
+
+ return value;
+ }
+
+ ["aboutURL", "creator", "description", "developers",
+ "homepageURL", "iconURL", "installDate", "name",
+ "optionsURL", "releaseNotesURI", "updateDate"].forEach(function (item) {
+ addon[item] = getRdfProperty(addon, item);
+ });
+ addon.isActive = getRdfProperty(addon, "isDisabled") != "true";
+
+ addon.uninstall = function () {
+ services.get("extensionManager").uninstallItem(this.id);
+ };
+ addon.appDisabled = false;
+ addon.__defineGetter("userDisabled", function() getRdfProperty("userDisabled") == "true");
+ addon.__defineSetter__("userDisabled", function(val) {
+ services.get("extensionManager")[val ? "enableItem" : "disableItem"](this.id);
+ });
+
+ return callback(addon);
+ },
+ getAddonsByTypes: function (types, callback) {
+ let res = [];
+ for (let [,type] in Iterator(types))
+ for (let [,item] in Iterator(services.get("extensionManager")
+ .getItemList(Ci.nsIUpdateItem["TYPE_" + type.toUpperCase()], {})))
+ res.append(this.getAddonById(item));
+ return res;
+ }
+ };
+
+ }
+
+ ///////////////////////////////////////////////////////////////////////////
+
+ function callResult(method) {
+ let args = Array.slice(arguments, 1);
+ return function (result) { result[method].apply(result, args) };
+ }
+
commands.add(["exta[dd]"],
"Install an extension",
function (args) {
- let file = io.File(args[0]);
-
- if (file.exists() && file.isReadable() && file.isFile())
- services.get("extensionManager").installItemFromFile(file, "app-profile");
- else {
- if (file.exists() && file.isDirectory())
- liberator.echomsg("Cannot install a directory: \"" + file.path + "\"", 0);
+ let url = args[0];
+ let file = io.File(url);
+ if (!file.exists())
+ AddonManager.getInstallForURL(url, callResult("install"), "application/x-xpinstall");
+ else if (file.isReadable() && file.isFile())
+ AddonManager.getInstallForFile(file, callResult("install"), "application/x-xpinstall");
+ else if (file.isDirectory())
+ liberator.echomsg("Cannot install a directory: \"" + file.path + "\"", 0);
+ else
liberator.echoerr("E484: Can't open file " + file.path);
- }
}, {
argCount: "1",
completer: function (context) {
@@ -1357,38 +1467,35 @@ const Liberator = Module("liberator", {
{
name: "extde[lete]",
description: "Uninstall an extension",
- action: "uninstallItem"
+ action: callResult("uninstall")
},
{
name: "exte[nable]",
description: "Enable an extension",
- action: "enableItem",
- filter: function ({ item: e }) !e.enabled
+ action: function (addon) addon.userDisabled = false,
+ filter: function ({ item: e }) e.userDisabled
},
{
name: "extd[isable]",
description: "Disable an extension",
- action: "disableItem",
- filter: function ({ item: e }) e.enabled
+ action: function (addon) addon.userDisabled = true,
+ filter: function ({ item: e }) !e.userDisabled
}
].forEach(function (command) {
commands.add([command.name],
command.description,
function (args) {
let name = args[0];
- function action(e) { services.get("extensionManager")[command.action](e.id); };
-
if (args.bang)
- liberator.extensions.forEach(function (e) { action(e); });
- else {
- liberator.assert(name, "E471: Argument required"); // XXX
+ liberator.assert(!name, "E488: Trailing characters");
+ else
+ liberator.assert(name, "E471: Argument required");
- let extension = liberator.getExtension(name);
- if (extension)
- action(extension);
- else
- liberator.echoerr("E474: Invalid argument");
- }
+ AddonManager.getAddonsByTypes(["extension"], function (list) {
+ if (!args.bang)
+ list = list.filter(function (extension) extension.name == name);
+ list.forEach(command.action);
+ });
}, {
argCount: "?", // FIXME: should be "1"
bang: true,
@@ -1404,51 +1511,64 @@ const Liberator = Module("liberator", {
commands.add(["exto[ptions]", "extp[references]"],
"Open an extension's preference dialog",
function (args) {
- let extension = liberator.getExtension(args[0]);
- liberator.assert(extension && extension.options,
- "E474: Invalid argument");
- if (args.bang)
- window.openDialog(extension.options, "_blank", "chrome");
- else
- liberator.open(extension.options, { from: "extoptions" });
+ AddonManager.getAddonsByTypes(["extension"], function (list) {
+ list = list.filter(function (extension) extension.name == args[0]);
+ if (!list.length || !list[0].optionsURL)
+ liberator.echoerr("E474: Invalid argument");
+ else if (args.bang)
+ window.openDialog(list[0].optionsURL, "_blank", "chrome");
+ else
+ liberator.open(list[0].optionsURL, { from: "extoptions" });
+ });
}, {
argCount: "1",
bang: true,
completer: function (context) {
completion.extension(context);
- context.filters.push(function ({ item: e }) e.options);
+ context.filters.push(function ({ item: e }) e.isActive && e.optionsURL);
},
literal: 0
});
// TODO: maybe indicate pending status too?
- commands.add(["extens[ions]"],
+ commands.add(["extens[ions]", "exts"],
"List available extensions",
function (args) {
- let filter = args[0] || "";
- let extensions = liberator.extensions.filter(function (e) e.name.indexOf(filter) >= 0);
+ AddonManager.getAddonsByTypes(["extension"], function (extensions) {
+ if (args[0])
+ 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(e, e.name),
- e.version,
- e.enabled ? enabled
- : disabled ,
- e.description] for ([, e] in Iterator(extensions)))
- );
+ if (extensions.length > 0) {
+ let list = template.tabular(
+ ["Name", "Version", "Status", "Description"], [],
+ ([template.icon({ icon: e.iconURL }, e.name),
+ e.version,
+ (e.isActive ? enabled
+ : disabled ) +
+ ((e.userDisabled || e.appDisabled) == !e.isActive ? XML() :
+ <> ({e.userDisabled || e.appDisabled
+ ? disabled
+ : enabled }
+ on restart)
+ >),
+ e.description] for ([, e] in Iterator(extensions)))
+ );
- commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
- }
- else {
- if (filter)
- liberator.echoerr("Exxx: No extension matching \"" + filter + "\"");
- else
- liberator.echoerr("No extensions installed");
- }
+ commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
+ }
+ else {
+ if (filter)
+ liberator.echoerr("Exxx: No extension matching \"" + filter + "\"");
+ else
+ liberator.echoerr("No extensions installed");
+ }
+ });
},
{ argCount: "?" });
+ ///////////////////////////////////////////////////////////////////////////
+
commands.add(["exu[sage]"],
"List all Ex commands with a short description",
function (args) { Liberator.showHelpIndex("ex-cmd-index", commands, args.bang); }, {
@@ -1704,17 +1824,22 @@ const Liberator = Module("liberator", {
completion: function () {
completion.dialog = function dialog(context) {
context.title = ["Dialog"];
- context.completions = config.dialogs;
+ context.completions = [[k, v[0]] for ([k, v] in Iterator(config.dialogs))];
};
completion.extension = function extension(context) {
context.title = ["Extension"];
context.anchored = false;
- context.keys = { text: "name", description: "description", icon: "icon" },
- context.completions = liberator.extensions;
+ context.keys = { text: "name", description: "description", icon: "iconURL" },
+ context.incomplete = true;
+ AddonManager.getAddonsByTypes(["extension"], function (addons) {
+ context.incomplete = false;
+ context.completions = addons;
+ });
};
completion.help = function help(context, unchunked) {
+ liberator.initHelp();
context.title = ["Help"];
context.anchored = false;
context.completions = services.get("liberator:").HELP_TAGS;
@@ -1729,6 +1854,7 @@ const Liberator = Module("liberator", {
context.completions = liberator.menuItems;
};
+ var toolbox = document.getElementById("navigator-toolbox");
completion.toolbar = function toolbar(context) {
context.title = ["Toolbar"];
context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
@@ -1817,8 +1943,6 @@ const Liberator = Module("liberator", {
if (options["loadplugins"])
liberator.loadPlugins();
- liberator.initHelp();
-
// after sourcing the initialization files, this function will set
// all gui options to their default values, if they have not been
// set before by any RC file
@@ -1841,6 +1965,7 @@ const Liberator = Module("liberator", {
statusline.update();
liberator.log(config.name + " fully initialized", 0);
+ liberator.initialized = true;
}
});
diff --git a/common/content/liberator.xul b/common/content/liberator.xul
index c1acf2bb..674970d6 100644
--- a/common/content/liberator.xul
+++ b/common/content/liberator.xul
@@ -67,7 +67,7 @@
-
+ id="liberator-statusline-field-status" flex="1" hidden="false" align="center">
+
diff --git a/common/content/mappings.js b/common/content/mappings.js
index af181120..5e02bba5 100644
--- a/common/content/mappings.js
+++ b/common/content/mappings.js
@@ -1,10 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
// Copyright (c) 2007-2009 by Doug Kearns
// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
diff --git a/common/content/marks.js b/common/content/marks.js
index ab4c4789..d6c7b908 100644
--- a/common/content/marks.js
+++ b/common/content/marks.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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
diff --git a/common/content/modes.js b/common/content/modes.js
index e6048bea..16d660b5 100644
--- a/common/content/modes.js
+++ b/common/content/modes.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
diff --git a/common/content/modules.js b/common/content/modules.js
index e3c8b5eb..bc934815 100644
--- a/common/content/modules.js
+++ b/common/content/modules.js
@@ -2,6 +2,7 @@
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
+"use strict";
/**
* @class ModuleBase
diff --git a/common/content/options.js b/common/content/options.js
index e304339f..59070bb6 100644
--- a/common/content/options.js
+++ b/common/content/options.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -22,7 +25,6 @@
* getter - see {@link Option#getter}
* completer - see {@link Option#completer}
* valdator - see {@link Option#validator}
- * checkHas - see {@link Option#checkHas}
* @optional
* @private
*/
@@ -33,6 +35,17 @@ const Option = Class("Option", {
this.type = type;
this.description = description;
+ if (this.type in Option.getKey)
+ this.getKey = Option.getKey[this.type];
+
+ if (this.type in Option.parseValues)
+ this.parseValues = Option.parseValues[this.type];
+
+ if (this.type in Option.joinValues)
+ this.joinValues = Option.joinValues[this.type];
+
+ this._op = Option.ops[this.type];
+
if (arguments.length > 3)
this.defaultValue = defaultValue;
@@ -44,7 +57,7 @@ const Option = Class("Option", {
this.names = array([name, "no" + name] for (name in values(names))).flatten().__proto__;
if (this.globalValue == undefined)
- this.globalValue = this.defaultValue;
+ this.globalValue = this.parseValues(this.defaultValue);
},
/** @property {value} The option's global value. @see #scope */
@@ -58,13 +71,7 @@ const Option = Class("Option", {
* @param {value} value The option value.
* @returns {value|string[]}
*/
- parseValues: function (value) {
- if (this.type == "stringlist")
- return (value === "") ? [] : value.split(",");
- if (this.type == "charlist")
- return Array.slice(value);
- return value;
- },
+ parseValues: function (value) value,
/**
* Returns values packed in the appropriate format for the option
@@ -73,16 +80,10 @@ const Option = Class("Option", {
* @param {value|string[]} values The option value.
* @returns {value}
*/
- joinValues: function (values) {
- if (this.type == "stringlist")
- return values.join(",");
- if (this.type == "charlist")
- return values.join("");
- return values;
- },
+ joinValues: function (vals) vals,
/** @property {value|string[]} The option value or array of values. */
- get values() this.parseValues(this.value),
+ get values() this.getValues(this.scope),
set values(values) this.setValues(values, this.scope),
/**
@@ -93,7 +94,26 @@ const Option = Class("Option", {
* {@link Option#scope}).
* @returns {value|string[]}
*/
- getValues: function (scope) this.parseValues(this.get(scope)),
+ getValues: function (scope) {
+ if (scope) {
+ if ((scope & this.scope) == 0) // option doesn't exist in this scope
+ return null;
+ }
+ else
+ scope = this.scope;
+
+ let values;
+
+ if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL))
+ values = tabs.options[this.name];
+ if ((scope & Option.SCOPE_GLOBAL) && (values == undefined))
+ values = this.globalValue;
+
+ if (this.getter)
+ return liberator.trapErrors(this.getter, this, values);
+
+ return values;
+ },
/**
* Sets the option's value from an array of values if the option type is
@@ -102,8 +122,22 @@ const Option = Class("Option", {
* @param {number} scope The scope to apply these values to (see
* {@link Option#scope}).
*/
- setValues: function (values, scope) {
- this.set(this.joinValues(values), scope || this.scope);
+ setValues: function (newValues, scope, skipGlobal) {
+ scope = scope || this.scope;
+ if ((scope & this.scope) == 0) // option doesn't exist in this scope
+ return;
+
+ if (this.setter)
+ newValues = liberator.trapErrors(this.setter, this, newValues);
+ if (newValues === undefined)
+ return;
+
+ if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL))
+ tabs.options[this.name] = newValues;
+ if ((scope & Option.SCOPE_GLOBAL) && !skipGlobal)
+ this.globalValue = newValues;
+
+ this.hasChanged = true;
},
/**
@@ -115,26 +149,7 @@ const Option = Class("Option", {
* {@link Option#scope}).
* @returns {value}
*/
- get: function (scope) {
- if (scope) {
- if ((scope & this.scope) == 0) // option doesn't exist in this scope
- return null;
- }
- else
- scope = this.scope;
-
- let value;
-
- if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL))
- value = tabs.options[this.name];
- if ((scope & Option.SCOPE_GLOBAL) && (value == undefined))
- value = this.globalValue;
-
- if (this.getter)
- return liberator.trapErrors(this.getter, this, value);
-
- return value;
- },
+ get: function (scope) this.joinValues(this.getValues(scope)),
/**
* Sets the option value to newValue for the specified scope .
@@ -145,21 +160,7 @@ const Option = Class("Option", {
* @param {number} scope The scope to apply this value to (see
* {@link Option#scope}).
*/
- set: function (newValue, scope) {
- scope = scope || this.scope;
- if ((scope & this.scope) == 0) // option doesn't exist in this scope
- return;
-
- if (this.setter)
- newValue = liberator.trapErrors(this.setter, this, newValue);
-
- if (liberator.has("tabs") && (scope & Option.SCOPE_LOCAL))
- tabs.options[this.name] = newValue;
- if ((scope & Option.SCOPE_GLOBAL) && newValue != this.globalValue)
- this.globalValue = newValue;
-
- this.hasChanged = true;
- },
+ set: function (newValue, scope) this.setValues(this.parseValues(newValue), scope),
/**
* @property {value} The option's current value. The option's local value,
@@ -169,21 +170,15 @@ const Option = Class("Option", {
get value() this.get(),
set value(val) this.set(val),
+ getKey: function (key) undefined,
+
/**
* Returns whether the option value contains one or more of the specified
* arguments.
*
* @returns {boolean}
*/
- has: function () {
- let self = this;
- let test = function (val) values.indexOf(val) >= 0;
- if (this.checkHas)
- test = function (val) values.some(function (value) self.checkHas(value, val));
- let values = this.values;
- // return whether some argument matches
- return Array.some(arguments, function (val) test(val));
- },
+ has: function () Array.some(arguments, function (val) this.values.indexOf(val) >= 0, this),
/**
* Returns whether this option is identified by name .
@@ -216,97 +211,16 @@ const Option = Class("Option", {
* @param {boolean} invert Whether this is an invert boolean operation.
*/
op: function (operator, values, scope, invert) {
- let newValue = null;
- let self = this;
- switch (this.type) {
- case "boolean":
- if (operator != "=")
- break;
+ let newValues = this._op(operator, values, scope, invert);
- if (invert)
- newValue = !this.value;
- else
- newValue = values;
- break;
-
- case "number":
- // TODO: support floats? Validators need updating.
- if (!/^[+-]?(?:0x[0-9a-f]+|0[0-7]+|0|[1-9]\d*)$/i.test(values))
- return "E521: Number required after := " + this.name + "=" + values;
-
- let value = parseInt(values/* deduce radix */);
-
- switch (operator) {
- case "+":
- newValue = this.value + value;
- break;
- case "-":
- newValue = this.value - value;
- break;
- case "^":
- newValue = this.value * value;
- break;
- case "=":
- newValue = value;
- break;
- }
-
- break;
-
- case "charlist":
- case "stringlist":
- values = Array.concat(values);
- switch (operator) {
- case "+":
- newValue = util.Array.uniq(Array.concat(this.values, values), true);
- break;
- case "^":
- // NOTE: Vim doesn't prepend if there's a match in the current value
- newValue = util.Array.uniq(Array.concat(values, this.values), true);
- break;
- case "-":
- newValue = this.values.filter(function (item) values.indexOf(item) == -1);
- break;
- case "=":
- newValue = values;
- if (invert) {
- let keepValues = this.values.filter(function (item) values.indexOf(item) == -1);
- let addValues = values.filter(function (item) self.values.indexOf(item) == -1);
- newValue = addValues.concat(keepValues);
- }
- break;
- }
-
- break;
-
- case "string":
- switch (operator) {
- case "+":
- newValue = this.value + values;
- break;
- case "-":
- newValue = this.value.replace(values, "");
- break;
- case "^":
- newValue = values + this.value;
- break;
- case "=":
- newValue = values;
- break;
- }
-
- break;
-
- default:
- return "E685: Internal error: option type `" + this.type + "' not supported";
- }
-
- if (newValue == null)
+ if (newValues == null)
return "Operator " + operator + " not supported for option type " + this.type;
- if (!this.isValidValue(newValue))
+
+ if (!this.isValidValue(newValues))
return "E474: Invalid argument: " + values;
- this.setValues(newValue, scope);
+
+ this.setValues(newValues, scope);
return null;
},
@@ -319,11 +233,13 @@ const Option = Class("Option", {
/**
* @property {string} The option's data type. One of:
- * "boolean" - Boolean E.g. true
- * "number" - Integer E.g. 1
- * "string" - String E.g. "Vimperator"
- * "charlist" - Character list E.g. "rb"
- * "stringlist" - String list E.g. "homepage,quickmark,tabopen,paste"
+ * "boolean" - Boolean, e.g., true
+ * "number" - Integer, e.g., 1
+ * "string" - String, e.g., "Vimperator"
+ * "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"
*/
type: null,
@@ -370,12 +286,6 @@ const Option = Class("Option", {
return Option.validateCompleter.apply(this, arguments);
return true;
},
- /**
- * @property The function called to determine whether the option already
- * contains a specified value.
- * @see #has
- */
- checkHas: null,
/**
* @property {boolean} Set to true whenever the option is first set. This
@@ -410,6 +320,132 @@ const Option = Class("Option", {
*/
SCOPE_BOTH: 3,
+ parseRegex: function (val, result) {
+ let [, bang, val] = /^(!?)(.*)/.exec(val);
+ let re = RegExp(val);
+ re.bang = bang;
+ re.result = arguments.length == 2 ? result : !bang;
+ return re;
+ },
+ unparseRegex: function (re) re.bang + re.source + (typeof re.result == "string" ? "=" + re.result : ""),
+
+ getKey: {
+ stringlist: function (k) this.values.indexOf(k) >= 0,
+ regexlist: function (k) {
+ for (let re in values(this.values))
+ if (re.test(k))
+ return re.result;
+ return null;
+ }
+ },
+
+ joinValues: {
+ charlist: function (vals) vals.join(""),
+ stringlist: function (vals) vals.join(","),
+ stringmap: function (vals) [k + "=" + v for ([k, v] in Iterator(vals))].join(","),
+ regexlist: function (vals) vals.map(Option.unparseRegex).join(","),
+ },
+
+ parseValues: {
+ number: function (value) Number(value),
+ 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(),
+ regexlist: function (value) (value === "") ? [] : value.split(",").map(Option.parseRegex),
+ regexmap: function (value) value.split(",").map(function (v) v.split("="))
+ .map(function ([k, v]) v != null ? Option.parseRegex(k, v) : Option.parseRegex('.?', k))
+ },
+
+ ops: {
+ boolean: function (operator, values, scope, invert) {
+ if (operator != "=")
+ return null;
+ if (invert)
+ return !this.value;
+ return values;
+ },
+
+ number: function (operator, values, scope, invert) {
+ // TODO: support floats? Validators need updating.
+ if (!/^[+-]?(?:0x[0-9a-f]+|0[0-7]*|[1-9]+)$/i.test(values))
+ return "E521: Number required after := " + this.name + "=" + values;
+
+ let value = parseInt(values);
+
+ switch (operator) {
+ case "+":
+ return this.value + value;
+ case "-":
+ return this.value - value;
+ case "^":
+ return this.value * value;
+ case "=":
+ return value;
+ }
+ return null;
+ },
+
+ stringmap: function (operator, values, scope, invert) {
+ values = Array.concat(values);
+ orig = [k + "=" + v for ([k, v] in Iterator(this.values))];
+
+ switch (operator) {
+ case "+":
+ return util.Array.uniq(Array.concat(orig, values), true);
+ case "^":
+ // NOTE: Vim doesn't prepend if there's a match in the current value
+ return util.Array.uniq(Array.concat(values, orig), true);
+ case "-":
+ return orig.filter(function (item) values.indexOf(item) == -1);
+ case "=":
+ if (invert) {
+ let keepValues = orig.filter(function (item) values.indexOf(item) == -1);
+ let addValues = values.filter(function (item) self.values.indexOf(item) == -1);
+ return addValues.concat(keepValues);
+ }
+ return values;
+ }
+ return null;
+ },
+
+ stringlist: function (operator, values, scope, invert) {
+ const self = this;
+ values = Array.concat(values);
+ switch (operator) {
+ case "+":
+ return util.Array.uniq(Array.concat(this.values, values), true);
+ case "^":
+ // NOTE: Vim doesn't prepend if there's a match in the current value
+ return util.Array.uniq(Array.concat(values, this.values), true);
+ case "-":
+ return this.values.filter(function (item) values.indexOf(item) == -1);
+ case "=":
+ if (invert) {
+ let keepValues = this.values.filter(function (item) values.indexOf(item) == -1);
+ let addValues = values.filter(function (item) self.values.indexOf(item) == -1);
+ return addValues.concat(keepValues);
+ }
+ return values;
+ }
+ return null;
+ },
+
+ string: function (operator, values, scope, invert) {
+ switch (operator) {
+ case "+":
+ return this.value + values;
+ case "-":
+ return this.value.replace(values, "");
+ case "^":
+ return values + this.value;
+ case "=":
+ return values;
+ }
+ return null;
+ }
+ },
+
// TODO: Run this by default?
/**
* Validates the specified values against values generated by the
@@ -423,10 +459,21 @@ const Option = Class("Option", {
let res = context.fork("", 0, this, this.completer);
if (!res)
res = context.allItems.items.map(function (item) [item.text]);
+ if (this.type == "regexmap")
+ return Array.concat(values).every(function (re) res.some(function (item) item[0] == re.result));
return Array.concat(values).every(function (value) res.some(function (item) item[0] == value));
}
});
+Option.joinValues["regexmap"] = Option.joinValues["regexlist"];
+
+Option.getKey["charlist"] = Option.getKey["stringlist"];
+Option.getKey["regexmap"] = Option.getKey["regexlist"];
+
+Option.ops["charlist"] = Option.ops["stringlist"];
+Option.ops["regexlist"] = Option.ops["stringlist"];
+Option.ops["regexmap"] = Option.ops["stringlist"];
+
/**
* @instance options
*/
@@ -462,7 +509,7 @@ const Options = Module("options", {
// Trigger any setters.
let opt = options.get(option);
if (event == "change" && opt)
- opt.set(opt.value, Option.SCOPE_GLOBAL);
+ opt.setValues(opt.globalValue, Option.SCOPE_GLOBAL, true);
}
storage.newMap("options", { store: false });
@@ -1008,8 +1055,6 @@ const Options = Module("options", {
}
}
// write access
- // NOTE: the behavior is generally Vim compatible but could be
- // improved. i.e. Vim's behavior is pretty sloppy to no real benefit
else {
option.setFrom = modifiers.setFrom || null;
@@ -1017,7 +1062,12 @@ const Options = Module("options", {
liberator.assert(!opt.valueGiven, "E474: Invalid argument: " + arg);
opt.values = !opt.unsetBoolean;
}
- let res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert);
+ try {
+ var res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert);
+ }
+ catch (e) {
+ res = e;
+ }
if (res)
liberator.echoerr(res);
}
@@ -1244,8 +1294,15 @@ const Options = Module("options", {
if (!completer)
return;
- let curValues = curValue != null ? opt.parseValues(curValue) : opt.values;
- let newValues = opt.parseValues(context.filter);
+ try {
+ var curValues = curValue != null ? opt.parseValues(curValue) : opt.values;
+ var newValues = opt.parseValues(context.filter);
+ }
+ catch (e) {
+ context.message = "Error: " + e;
+ context.completions = [];
+ return;
+ }
let len = context.filter.length;
switch (opt.type) {
@@ -1253,9 +1310,18 @@ const Options = Module("options", {
if (!completer)
completer = function () [["true", ""], ["false", ""]];
break;
+ case "regexlist":
+ newValues = context.filter.split(",");
+ // Fallthrough
case "stringlist":
- let target = newValues.pop();
- len = target ? target.length : 0;
+ let target = newValues.pop() || "";
+ len = target.length;
+ break;
+ case "stringmap":
+ case "regexmap":
+ let vals = context.filter.split(",");
+ target = vals.pop() || "";
+ len = target.length - (target.indexOf("=") + 1);
break;
case "charlist":
len = 0;
@@ -1268,9 +1334,10 @@ const Options = Module("options", {
let completions = completer(context);
if (!completions)
return;
+
// Not Vim compatible, but is a significant enough improvement
// that it's worth breaking compatibility.
- if (newValues instanceof Array) {
+ if (isarray(newValues)) {
completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1);
switch (op) {
case "+":
diff --git a/common/content/quickmarks.js b/common/content/quickmarks.js
index b6d6914b..3c02dfd5 100644
--- a/common/content/quickmarks.js
+++ b/common/content/quickmarks.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
diff --git a/common/content/sanitizer.js b/common/content/sanitizer.js
index b0a9eb16..85b8c4ea 100644
--- a/common/content/sanitizer.js
+++ b/common/content/sanitizer.js
@@ -2,6 +2,7 @@
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file.
+"use strict";
// TODO:
// - fix Sanitize autocommand
@@ -19,6 +20,7 @@ const Sanitizer = Module("sanitizer", {
init: function () {
const self = this;
liberator.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
+ Sanitizer.getClearRange = Sanitizer.Sanitizer.getClearRange;
this.__proto__.__proto__ = new Sanitizer.Sanitizer; // Good enough.
// TODO: remove this version test
@@ -213,10 +215,9 @@ const Sanitizer = Module("sanitizer", {
{
setter: function (values) {
for (let [, pref] in Iterator(sanitizer.prefNames)) {
- continue;
options.setPref(pref, false);
- for (let [, value] in Iterator(this.parseValues(values))) {
+ for (let [, value] in Iterator(values)) {
if (Sanitizer.prefToArg(pref) == value) {
options.setPref(pref, true);
break;
@@ -226,7 +227,7 @@ const Sanitizer = Module("sanitizer", {
return values;
},
- getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(Sanitizer.prefToArg).join(","),
+ getter: function () sanitizer.prefNames.filter(function (pref) options.getPref(pref)).map(Sanitizer.prefToArg),
completer: function (value) [
["cache", "Cache"],
["commandline", "Command-line history"],
diff --git a/common/content/services.js b/common/content/services.js
index 20e73b48..3311bf4c 100644
--- a/common/content/services.js
+++ b/common/content/services.js
@@ -2,6 +2,7 @@
//
// 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 */
@@ -47,6 +48,10 @@ const Services = Module("services", {
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("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) {
diff --git a/common/content/statusline.js b/common/content/statusline.js
index c5f8a254..e82c9550 100755
--- a/common/content/statusline.js
+++ b/common/content/statusline.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -11,12 +14,8 @@ const StatusLine = Module("statusline", {
this._statusBar.collapsed = true; // it is later restored unless the user sets laststatus=0
// our status bar fields
- this._statuslineWidget = document.getElementById("liberator-statusline");
- this._urlWidget = document.getElementById("liberator-statusline-field-url");
- this._inputBufferWidget = document.getElementById("liberator-statusline-field-inputbuffer");
- this._progressWidget = document.getElementById("liberator-statusline-field-progress");
- this._tabCountWidget = document.getElementById("liberator-statusline-field-tabcount");
- this._bufferPositionWidget = document.getElementById("liberator-statusline-field-bufferposition");
+ this.widgets = dict(["status", "url", "inputbuffer", "progress", "tabcount", "bufferposition", "zoomlevel"].map(
+ function (field) [field, document.getElementById("liberator-statusline-field-" + field)]));
},
/**
@@ -47,6 +46,7 @@ const StatusLine = Module("statusline", {
this.updateProgress();
this.updateTabCount();
this.updateBufferPosition();
+ this.updateZoomLevel();
},
/**
@@ -61,20 +61,17 @@ const StatusLine = Module("statusline", {
updateUrl: function updateUrl(url) {
// ripped from Firefox; modified
function losslessDecodeURI(url) {
- // Countermeasure for "Error: malformed URI sequence".
- // This error occurs when URL is encoded by not UTF-8 encoding.
- function _decodeURI(url) {
- try {
- return decodeURI(url);
- }
- catch (e) {
- return url;
- }
- };
-
// 1. decodeURI decodes %25 to %, which creates unintended
// encoding sequences.
- url = url.split("%25").map(_decodeURI).join("%25");
+ url = url.split("%25").map(function (url) {
+ // Non-UTF-8 complient URLs cause "malformed URI sequence" errors.
+ try {
+ return decodeURI(url);
+ }
+ catch (e) {
+ return url;
+ }
+ }).join("%25");
// 2. Re-encode whitespace so that it doesn't get eaten away
// by the location bar (bug 410726).
url = url.replace(/[\r\n\t]/g, encodeURIComponent);
@@ -125,7 +122,7 @@ const StatusLine = Module("statusline", {
if (modified)
url += " [" + modified + "]";
- this._urlWidget.value = url;
+ this.widgets.url.value = url;
},
/**
@@ -140,7 +137,7 @@ const StatusLine = Module("statusline", {
if (!buffer || typeof buffer != "string")
buffer = "";
- this._inputBufferWidget.value = buffer;
+ this.widgets.inputbuffer.value = buffer;
},
/**
@@ -157,7 +154,7 @@ const StatusLine = Module("statusline", {
progress = "";
if (typeof progress == "string")
- this._progressWidget.value = progress;
+ this.widgets.progress.value = progress;
else if (typeof progress == "number") {
let progressStr = "";
if (progress <= 0)
@@ -170,7 +167,7 @@ const StatusLine = Module("statusline", {
+ " ".substr(0, 19 - progress)
+ "]";
}
- this._progressWidget.value = progressStr;
+ this.widgets.progress.value = progressStr;
}
},
@@ -194,7 +191,7 @@ const StatusLine = Module("statusline", {
for (let [i, tab] in util.Array.iteritems(config.browser.mTabs))
tab.setAttribute("ordinal", i + 1);
- this._tabCountWidget.value = "[" + (tabs.index() + 1) + "/" + tabs.count + "]";
+ this.widgets.tabcount.value = "[" + (tabs.index() + 1) + "/" + tabs.count + "]";
}
},
@@ -205,7 +202,7 @@ const StatusLine = Module("statusline", {
* @param {number} percent The position, as a percentage. @optional
*/
updateBufferPosition: function updateBufferPosition(percent) {
- if (!percent || typeof percent != "number") {
+ if (typeof percent != "number") {
let win = document.commandDispatcher.focusedWindow;
if (!win)
return;
@@ -218,14 +215,35 @@ const StatusLine = Module("statusline", {
bufferPositionStr = "All";
else if (percent == 0)
bufferPositionStr = "Top";
- else if (percent < 10)
- bufferPositionStr = " " + percent + "%";
else if (percent >= 100)
bufferPositionStr = "Bot";
+ else if (percent < 10)
+ bufferPositionStr = " " + percent + "%";
else
bufferPositionStr = percent + "%";
- this._bufferPositionWidget.value = bufferPositionStr;
+ this.widgets.bufferposition.value = bufferPositionStr;
+ },
+
+ /**
+ * Display the main content's zoom level.
+ *
+ * @param {number} percent The zoom level, as a percentage. @optional
+ * @param {boolean} full True if full zoom is in operation. @optional
+ */
+ updateZoomLevel: function updateZoomLevel(percent, full) {
+ if (arguments.length == 0)
+ [percent, full] = [buffer.zoomLevel, buffer.fullZoom];
+
+ if (percent == 100)
+ this.widgets.zoomlevel.value = "";
+ else {
+ percent = (" " + percent).substr(-3);
+ if (full)
+ this.widgets.zoomlevel.value = " [" + percent + "%]";
+ else
+ this.widgets.zoomlevel.value = " (" + percent + "%)";
+ }
}
}, {
diff --git a/common/content/style.js b/common/content/style.js
index 9183df53..3fc6824a 100644
--- a/common/content/style.js
+++ b/common/content/style.js
@@ -2,6 +2,7 @@
//
// 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 */
@@ -167,6 +168,8 @@ Highlights.prototype.CSS =
+// Copyright (c) 2006-2008 by Martin Stubenschrott
// Copyright (c) 2007-2009 by Doug Kearns
// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -797,14 +797,7 @@ const Tabs = Module("tabs", {
commands.add(["tabopen", "t[open]", "tabnew"],
"Open one or more URLs in a new tab",
function (args) {
- let special = args.bang;
- args = args.string;
-
- let where = special ? liberator.NEW_TAB : liberator.NEW_BACKGROUND_TAB;
- if (args)
- liberator.open(args, { from: "tabopen", where: where });
- else
- liberator.open("about:blank", { from: "tabopen", where: where });
+ liberator.open(args.string || "about:blank", { from: "tabopen", where: liberator.NEW_TAB, background: args.bang });
}, {
bang: true,
completer: function (context) completion.url(context),
@@ -1094,9 +1087,9 @@ const Tabs = Module("tabs", {
"Where to show requested popup windows",
"stringlist", "tab",
{
- setter: function (value) {
+ setter: function (values) {
let [open, restriction] = [1, 0];
- for (let [, opt] in Iterator(this.parseValues(value))) {
+ for (let [, opt] in Iterator(values)) {
if (opt == "tab")
open = 3;
else if (opt == "window")
@@ -1107,7 +1100,7 @@ const Tabs = Module("tabs", {
options.safeSetPref("browser.link.open_newwindow", open, "See 'popups' option.");
options.safeSetPref("browser.link.open_newwindow.restriction", restriction, "See 'popups' option.");
- return value;
+ return values;
},
completer: function (context) [
["tab", "Open popups in a new tab"],
diff --git a/common/content/template.js b/common/content/template.js
index bd5edf76..c6e822f5 100644
--- a/common/content/template.js
+++ b/common/content/template.js
@@ -1,8 +1,8 @@
-// Copyright (c) 2006-2009 by Kris Maglione
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -194,8 +194,6 @@ const Template = Module("template", {
return <>:{commandline.command} {xml}>;
},
- // every item must have a .xml property which defines how to draw itself
- // @param headers is an array of strings, the text for the header columns
genericTable: function genericTable(items, format) {
completion.listCompleter(function (context) {
context.filterFunc = null;
diff --git a/common/content/util.js b/common/content/util.js
index 4ae31928..7dfe3c3b 100644
--- a/common/content/util.js
+++ b/common/content/util.js
@@ -1,7 +1,10 @@
-// Copyright (c) 2006-2009 by Martin Stubenschrott
+// Copyright (c) 2006-2008 by Martin Stubenschrott
+// Copyright (c) 2007-2009 by Doug Kearns
+// Copyright (c) 2008-2009 by Kris Maglione
//
// 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 */
@@ -91,6 +94,55 @@ const Util = Module("util", {
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 str to the equivalent HTML
* entities.
@@ -158,7 +210,8 @@ const Util = Module("util", {
* @returns {string}
*/
makeXPath: function makeXPath(nodes) {
- return util.Array(nodes).map(function (node) [node, "xhtml:" + node]).flatten()
+ return util.Array(nodes).map(util.debrace).flatten()
+ .map(function (node) [node, "xhtml:" + node]).flatten()
.map(function (node) "//" + node).join(" | ");
},
@@ -257,8 +310,9 @@ const Util = Module("util", {
const PATH = FILE.leafName.replace(/\..*/, "") + "/";
const TIME = Date.now();
+ liberator.initHelp();
let zip = services.create("zipWriter");
- zip.open(FILE, io.MODE_CREATE | io.MODE_WRONLY | io.MODE_TRUNCATE);
+ zip.open(FILE, File.MODE_CREATE | File.MODE_WRONLY | File.MODE_TRUNCATE);
function addURIEntry(file, uri)
zip.addEntryChannel(PATH + file, TIME, 9,
services.get("io").newChannel(uri, null, null), false);
@@ -407,11 +461,12 @@ const Util = Module("util", {
null
);
- result.__iterator__ = asIterator
+ 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); };
-
- return result;
+ : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); }
+ }
},
/**
@@ -664,61 +719,16 @@ const Util = Module("util", {
},
/**
- * Returns an array of URLs parsed from str .
+ * Scrolls an element into view if and only if it's not already
+ * fully visible.
*
- * Given a string like 'google bla, www.osnews.com' return an array
- * ['www.google.com/search?q=bla', 'www.osnews.com']
- *
- * @param {string} str
- * @returns {string[]}
+ * @param {Node} elem The element to make visible.
*/
- stringToURLArray: function stringToURLArray(str) {
- let urls;
-
- if (options["urlseparator"])
- urls = util.splitLiteral(str, RegExp("\\s*" + options["urlseparator"] + "\\s*"));
- else
- urls = [str];
-
- return urls.map(function (url) {
- if (url.substr(0, 5) != "file:") {
- try {
- // Try to find a matching file.
- let file = io.File(url);
- if (file.exists() && file.isReadable())
- return services.get("io").newFileURI(file).spec;
- }
- catch (e) {}
- }
-
- // strip each 'URL' - makes things simpler later on
- url = url.replace(/^\s+|\s+$/, "");
-
- // Look for a valid protocol
- let proto = url.match(/^([-\w]+):/);
- if (proto && Cc["@mozilla.org/network/protocol;1?name=" + proto[1]])
- // Handle as URL, but remove spaces. Useful for copied/'p'asted URLs.
- return url.replace(/\s*\n+\s*/g, "");
-
- // Ok, not a valid proto. If it looks like URL-ish (foo.com/bar),
- // let Gecko figure it out.
- if (/[.\/]/.test(url) && !/\s/.test(url) || /^[\w-.]+:\d+(?:\/|$)/.test(url))
- return url;
-
- // TODO: it would be clearer if the appropriate call to
- // getSearchURL was made based on whether or not the first word was
- // indeed an SE alias rather than seeing if getSearchURL can
- // process the call usefully and trying again if it fails
-
- // check for a search engine match in the string, then try to
- // search for the whole string in the default engine
- let searchURL = bookmarks.getSearchURL(url, false) || bookmarks.getSearchURL(url, true);
- if (searchURL)
- return searchURL;
-
- // Hmm. No defsearch? Let the host app deal with it, then.
- return url;
- });
+ 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();
},
/**
@@ -740,7 +750,7 @@ const Util = Module("util", {
}
switch (node.nodeKind()) {
case "text":
- return doc.createTextNode(node);
+ return doc.createTextNode(String(node));
case "element":
let domnode = doc.createElementNS(node.namespace(), node.localName());
for each (let attr in node.@*)
@@ -814,7 +824,7 @@ const Util = Module("util", {
* @param {Array} ary
* @returns {Array}
*/
- flatten: function flatten(ary) Array.concat.apply([], ary),
+ flatten: function flatten(ary) ary.length ? Array.concat.apply([], ary) : [],
/**
* Returns an Iterator for an array's values.
@@ -864,6 +874,22 @@ const Util = Module("util", {
}
}
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;
}
})
});
diff --git a/common/locale/en-US/buffer.xml b/common/locale/en-US/buffer.xml
index a0e96318..da56c7bb 100644
--- a/common/locale/en-US/buffer.xml
+++ b/common/locale/en-US/buffer.xml
@@ -417,8 +417,8 @@ preference.
-
-
zI
- count zI
+ ZI zI
+ count ZI
Enlarge full zoom of current web page. Mnemonic: zoom in.
@@ -426,8 +426,8 @@ preference.
-
-
zM
- count zM
+ ZM zM
+ count ZM
Enlarge full zoom of current web page by a larger amount. Mnemonic: zoom more.
@@ -435,8 +435,8 @@ preference.
-
-
zO
- count zO
+ ZO zO
+ count ZO
Reduce full zoom of current web page. Mnemonic: zoom out.
@@ -444,8 +444,8 @@ preference.
-
-
zR
- count zR
+ ZR zR
+ count ZR
Reduce full zoom of current web page by a larger amount. Mnemonic: zoom reduce.
@@ -453,8 +453,8 @@ preference.
-
-
zZ
- count zZ
+ ZZ zZ
+ count ZZ
Set full zoom value of current web page. Zoom value can be between 30 and
diff --git a/common/locale/en-US/developer.xml b/common/locale/en-US/developer.xml
index 3e7a8052..5bf793a7 100644
--- a/common/locale/en-US/developer.xml
+++ b/common/locale/en-US/developer.xml
@@ -179,7 +179,7 @@
- :extens :extensions
+ :exts :extens :extensions
:extensions
+ :exts
List all installed extensions.
diff --git a/common/locale/en-US/index.xml b/common/locale/en-US/index.xml
index 2482ad84..0546ff9a 100644
--- a/common/locale/en-US/index.xml
+++ b/common/locale/en-US/index.xml
@@ -393,7 +393,6 @@ This file contains a list of all available commands, mappings and options.
exrc Allow reading of an RC file in the current directory
extendedhinttags XPath string of hintable elements activated by ;
fileencoding Changes the character encoding that &liberator.appname; uses to read and write files
- focuscontent Try to stay in Normal mode after loading a web page
followhints Change the behaviour of in Hints mode
fullscreen Show the current window fullscreen
guioptions Show or hide certain GUI elements like the menu or toolbar
@@ -430,6 +429,7 @@ This file contains a list of all available commands, mappings and options.
showstatuslinks Show the destination of the link under the cursor in the status bar
showtabline Control when to show the tab bar of opened web pages
smartcase Override the ignorecase option if the pattern contains uppercase characters
+ strictfocus Prevent scripts from focusing input elements without user intervention
suggestengines Engine Alias which has a feature of suggest
titlestring Change the title of the window
urlseparator Set the separator regex used to separate multiple URL args
diff --git a/common/locale/en-US/options.xml b/common/locale/en-US/options.xml
index d4c7773f..5b7ca1f3 100644
--- a/common/locale/en-US/options.xml
+++ b/common/locale/en-US/options.xml
@@ -17,11 +17,27 @@
- boolean can only be on or off
- number has a numeric value
- string has a string value
- charlist like a string but with unique characters
- stringlist a comma-separated list of strings
+ boolean Can only be on or off
+ number A numeric value
+ string A string value
+ charlist A string containing a discrete set of distinct characters
+ stringlist A comma-separated list of strings
+ stringmap A comma-separated list of key-value pairs, e.g., key=val,foo=bar
+ regexlist
+
+ A comma-separated list of regular expressions. Expressions may be
+ prefixed with a ! , in which case the match will be negated. A
+ literal ! at the begining of the expression may be matched with
+ [!] . Generally, the first matching regular expression is used.
+
+ regexmap
+
+ A combination of a stringmap and a regexlist . Each key
+ in the key =value pair is a regexp. If the regexp begins with a
+ ! , the sense match is negated, such that a non-matching
+ expression will be considered a match and vice versa .
+ The first key to match yields value.
+
Setting options
@@ -293,6 +309,40 @@
+-
+
'au' 'autocomplete'
+ 'autocomplete' 'au'
+ regexlist
+ .*
+
+
+ A list of regexps defining which completion contexts should be
+ autocompleted. When the value is non-empty, the completion list is
+ automatically opened along with the commandline. Thereafter, any key
+ press triggers a completion update for the matching contexts.
+ Non-matching contexts will only be updated when the tab key is
+ pressed. This option is useful for disabling autocompletion for
+ computationally intense contexts that don't perform well on your
+ system under load.
+
+
+
+ To enable autocompletion for everything but :history or
+ :bmarks , you would choose a value such as,
+ !/ex/bmarks,.?
+
+
+
+ Completion contexts have names very much like Unix path names. This
+ denotes the tree in which they're called. A completer will never be
+ called unless every completer preceding it in the tree was also
+ called. For example, if your completer excludes /ex/ , it
+ will also exclude /ex/bmarks , and so on.
+
+
+ See also :contexts
+
+
-
$CDPATH
@@ -469,19 +519,14 @@
-
-
'nofc' 'nofocuscontent'
- 'fc' 'focuscontent'
- 'focuscontent' 'fc'
+ 'nosf' 'nostrictfocus'
+ 'sf' 'strictfocus'
+ 'strictfocus' 'sf'
boolean
- off
+ on
- Focus the content after a page has loaded. This is useful if you
- always want to stay in Normal mode when browsing between web sites.
- When on , it blurs any textbox which often is
- automatically focused on page load. If you usually like
- focuscontent but sometimes you'd like to focus the first
- input field, you can use gi to jump to it.
+ Prevent scripts from focusing input elements without user intervention.
@@ -1249,41 +1294,50 @@
-
-
-
'wildcase' 'wic'
+ 'wic' 'wildcase'
'wildcase' 'wic'
- string
+ regexmap
smart
- Defines how completions are matched with regard to character case. Possible values:
+
+ Defines how completions are matched for a given completion context
+ with regard to character case.
+
+
+ Possible values:
smart Case is significant when capital letters are typed
match Case is always significant
ignore Case is never significant
+
+ See also :contexts
-
-
'wildignore' 'wig'
'wildignore' 'wig'
- stringlist
+ regexlist
List of file patterns to ignore when completing files. E.g., to ignore object
files and Vim swap files
- :set wildignore=.\\.o,\\.. \\.s[a-z]\\2
+ :set wildignore=\.o$ ,^\..*\.s[a-z]2 $
+
Unlike Vim each pattern is a regex rather than a glob.
+
+ The only way to include a literal comma in a pattern is with the
+ escape \u0044 .
+
-
-
'wim' 'wildmode'
'wildmode' 'wim'
@@ -1318,25 +1372,23 @@
-
-
-
'wop' 'wildoptions'
- 'wildoptions' 'wop'
- stringlist
-
+ 'wis' 'wildsort'
+ 'wildsort' 'wis'
+ regexlist
+ .*
- A list of words that change how command-line completion is done.
+
+ A list of regexps defining which completion contexts
+ should be sorted. The main purpose of this option is to
+ prevent sorting of certain completion lists that don't
+ perform well under load.
+
- Possible words:
-
-
- auto Automatically show completions while you are typing.
- sort Always sort the completion list, overriding the complete option.
-
+ See also :contexts
-
-
'wsp' 'wordseparators'
'wordseparators' 'wsp'
diff --git a/common/locale/en-US/various.xml b/common/locale/en-US/various.xml
index 9fc05efb..7f7f7327 100644
--- a/common/locale/en-US/various.xml
+++ b/common/locale/en-US/various.xml
@@ -17,16 +17,27 @@
:beep
:beep
- Play a system beep.
+
+ Play a system beep. This should not be used for any purpose other
+ than testing the visual bell.
+
-
-
CTRL-L :redr :redraw]]>
- :redraw
+ :contexts
+ :contexts ex-command
- Redraws the screen. Useful to update the screen halfway executing a script or function.
+
+ Lists the completion contexts used during the completion of its
+ arguments. These context names are used in options such as
+ autocomplete and wildcase . Note that completion must
+ be triggered in order for this command to be effective, so if
+ autocompletion is not active, you'll need to press the
+ key at least once. You should also be aware that
+ this command is only useful from the commandline.
+
@@ -46,6 +57,15 @@
+-
+
CTRL-L :redr :redraw]]>
+ :redraw
+
+ Redraws the screen. Useful to update the screen halfway executing a script or function.
+
+
+
+
-
:run :! :!cmd
:!cmd
diff --git a/common/make_jar.sh b/common/make_jar.sh
index a0b04e79..2f4d9312 100644
--- a/common/make_jar.sh
+++ b/common/make_jar.sh
@@ -18,8 +18,8 @@ getfiles () {
find "$@" -not -path '*\.hg*' 2>/dev/null | grep -E "$filter" || true
}
copytext () {
- sed -e "s,###VERSION###,$VERSION,g" \
- -e "s,###DATE###,$BUILD_DATE,g" \
+ sed -e "s,@VERSION@,$VERSION,g" \
+ -e "s,@DATE@,$BUILD_DATE,g" \
<"$1" >"$2"
cmp -s "$1" "$2" ||
( echo "modified: $1"; diff -u "$1" "$2" | grep '^[-+][^-+]' )
diff --git a/common/modules/storage.jsm b/common/modules/storage.jsm
index 287c6189..d749ecbf 100644
--- a/common/modules/storage.jsm
+++ b/common/modules/storage.jsm
@@ -19,6 +19,7 @@
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
}}} ***** END LICENSE BLOCK *****/
+"use strict";
var EXPORTED_SYMBOLS = ["storage", "Timer"];
@@ -102,12 +103,12 @@ function readFile(file) {
function writeFile(file, data) {
if (!file.exists())
- file.create(file.NORMAL_FILE_TYPE, 0600);
+ file.create(file.NORMAL_FILE_TYPE, parseInt('0600', 8));
let fileStream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
- fileStream.init(file, 0x20 | 0x08 | 0x02, 0600, 0); // PR_TRUNCATE | PR_CREATE | PR_WRITE
+ fileStream.init(file, 0x20 | 0x08 | 0x02, parseInt('0600', 8), 0); // PR_TRUNCATE | PR_CREATE | PR_WRITE
stream.init(fileStream, "UTF-8", 0, 0);
stream.writeString(data);
diff --git a/common/skin/liberator.css b/common/skin/liberator.css
index 146619a1..f4d60dba 100644
--- a/common/skin/liberator.css
+++ b/common/skin/liberator.css
@@ -1,4 +1,5 @@
@namespace liberator url("http://vimperator.org/namespaces/liberator");
+@namespace html url("http://www.w3.org/1999/xhtml");
/* Applied to all content */
[liberator|activeframe] {
@@ -122,6 +123,12 @@ statusbarpanel {
color: inherit;
margin: 0px;
}
+#liberator-commandline-command html|*:focus {
+ outline-width: 0px !important
+}
+#liberator-commandline-command .textbox-search-icons {
+ visibility: collapse !important;
+}
#liberator-message {
margin: 0px;
}
diff --git a/vimperator/NEWS b/vimperator/NEWS
index 15e3732b..34c932f6 100755
--- a/vimperator/NEWS
+++ b/vimperator/NEWS
@@ -1,12 +1,16 @@
2009-XX-XX:
- * version 2.3a1pre
- * add basic plugin authorship documentation
- * plugins may now provide full-fleged ':help' documentation
- * asciidoc is no longer required to build Vimperator
- * the help system is newly modularized
- * remove [c]:edit[c], [c]:tabedit[c], and [c]:winedit[c]
- * add 'jsdebugger' option - switch on/off javascript debugger service
- * add "addons", "downloads", "extoptions" and "help" to the 'activate' option.
+ * Replaced 'focuscontent' with 'strictfocus'
+ * Replaced previous incremental search implementation
+ * :open now only opens files begining with / or ./
+ * Page zoom information is now shown in the status bar
+ * Added ZO, ZI, ZM, and ZR as aliases for zO, zI, zM, and zR
+ * Add basic plugin authorship documentation
+ * Plugins may now provide full-fleged ':help' documentation
+ * The help system is newly modularized
+ * Asciidoc is no longer for building
+ * Remove [c]:edit[c], [c]:tabedit[c], and [c]:winedit[c]
+ * Add 'jsdebugger' option - switch on/off javascript debugger service
+ * Add "addons", "downloads", "extoptions" and "help" to the 'activate' option.
2009-10-28:
* version 2.2
@@ -27,8 +31,6 @@
...................................
* IMPORTANT: shifted key notation now matches Vim's behaviour. E.g.
and are equivalent, to map the uppercase character use .
- (this might change again, as this is REALLY inconsistent, and i don't
- know if I like copying bugs)
* IMPORTANT: 'popups' now takes a stringlist rather than a number.
* add [c]:winonly[c]
diff --git a/vimperator/TODO b/vimperator/TODO
index 382ffc27..b3d08ca0 100644
--- a/vimperator/TODO
+++ b/vimperator/TODO
@@ -42,7 +42,6 @@ BUGS:
FEATURES:
8 Document Textarea, Caret and Visual modes.
-8 Incremental searches should retreat to their starting position on
8 Replace config.name tests in liberator with more specific feature
tests or overridable APIs where at all feasible.
8 change the extension ID to vimperator@vimperator.org rather than
@@ -65,25 +64,11 @@ FEATURES:
8 :redir and 'verbosefile'
8 middleclick in content == p, and if command line is open, paste there the clipboard buffer
8 all search commands should start searching from the top of the visible viewport
-8 :addsearch wikpedia http://en.wikipedia.org/wiki/Special:Search?search=%s to allow saving of
- quick searches in the RC file.
- Why not just add a bookmark? --Kris
- This would require performance tests, how fast it is to add 20 keywords that way, as we need
- to search all existing bookmarks to see if the keyword is already defined, and then just update
- that bookmark. --mst
-
- Wah? I don't see how that's especially relevant, since they only
- need to be added once, but, if you insist:
- :100time bookmarks.getKeywords().some(function(k) k.keyword == "wikipedia")
- Code execution summary
- Executed: 100 times
- Average time: 2.48 msec
- Total time: 0.25 sec
- --Kris
-
8 allow for multiple ex commands separated with | (see #24)
8 / should work as in vim (i.e., save page positions as well as
locations in the history list).
+8 jump to the next heading with ]h, next image ]i, previous textbox [t and so on
+8 pipe selected text/link/website to an external command
7 use ctrl-n/p in insert mode for word completion
7 implement QuickFix window based on ItemList
7 wherever possible: get rid of dialogs and ask console-like dialog questions
@@ -93,7 +78,7 @@ FEATURES:
opera's fast forward does something like this
7 make an option to disable session saving by default when you close Firefox
7 The output of the pageinfo-command should contain the security-information of ssl-encrypted sites
-7 Add :every command
+7 :grep support (needs location list)
6 :mksession
6 add [count] support to :b* and :tab* commands where missing
6 registers
@@ -101,12 +86,10 @@ FEATURES:
always be the default register. --Ted
6 check/correct spellings in insert mode with some mappings
6 add more autocommands (TabClose, TabOpen, TabChanged etc)
-6 jump to the next heading with ]h, next image ]i, previous textbox [t and so on
-6 :grep support (needs location list)
-6 pipe selected text/link/website to an external command
6 Use ctrl-w+j/k/w to switch between sidebar, content, preview window
6 Command :tags for getting a list of used tags
6 ;? should show more information
+6 Add information to liberator/HACKING file about testing and optimization
5 when looking at a zoomed out image (because it's large), zi should zoom in
maybe with this? : http://mxr.mozilla.org/seamonkey/source/content/html/document/public/nsIImageDocument.idl
5 make a command to search within google search results
@@ -117,8 +100,5 @@ FEATURES:
3 add a command-line window (:help cmdline-window in Vim).
3 Splitting Windows with [:sp :vsp ctrl-w,s ctrl-w,v] and closing with [ctrl-w,q], moving with [ctrl-w,w or tab]
have a look into the split browser extension
-1 Add information to liberator/HACKING file about testing and optimization
-1 Document remote branches in liberator/HACKING
1 Reformat liberator/HACKING so that git diff can find sections and report changes @ somewhere
-- many other ideas are listed in the wiki
diff --git a/vimperator/chrome.manifest b/vimperator/chrome.manifest
index 57ebfae3..276496d7 100644
--- a/vimperator/chrome.manifest
+++ b/vimperator/chrome.manifest
@@ -1,16 +1,28 @@
# Firefox
content vimperator content/
skin vimperator classic/1.0 skin/
-locale vimperator en-US locale/en-US/
-locale liberator en-US ../common/locale/en-US/
+locale vimperator en-US locale/en-US/
+locale liberator en-US ../common/locale/en-US/
content liberator ../common/content/
resource liberator ../common/modules/
skin liberator classic/1.0 ../common/skin/
-override chrome://liberator/content/liberator.dtd chrome://vimperator/content/liberator.dtd
-override chrome://liberator/content/config.js chrome://vimperator/content/config.js
+override chrome://liberator/content/liberator.dtd chrome://vimperator/content/liberator.dtd
+override chrome://liberator/content/config.js chrome://vimperator/content/config.js
overlay chrome://browser/content/browser.xul chrome://liberator/content/liberator.xul
overlay chrome://browser/content/browser.xul chrome://vimperator/content/vimperator.xul
+component {81495d80-89ee-4c36-a88d-ea7c4e5ac63f} components/about-handler.js
+contract @mozilla.org/network/protocol/about;1?what=vimperator {81495d80-89ee-4c36-a88d-ea7c4e5ac63f}
+
+component {16dc34f7-6d22-4aa4-a67f-2921fb5dcb69} components/commandline-handler.js
+contract @mozilla.org/commandlinehandler/general-startup;1?type=vimperator {16dc34f7-6d22-4aa4-a67f-2921fb5dcb69}
+category command-line-handler m-vimperator @mozilla.org/commandlinehandler/general-startup;1?type=vimperator
+
+component {c1b67a07-18f7-4e13-b361-2edcc35a5a0d} components/protocols.js
+contract @mozilla.org/network/protocol;1?name=chrome-data {c1b67a07-18f7-4e13-b361-2edcc35a5a0d}
+component {9c8f2530-51c8-4d41-b356-319e0b155c44} components/protocols.js
+contract @mozilla.org/network/protocol;1?name=liberator {9c8f2530-51c8-4d41-b356-319e0b155c44}
+
diff --git a/vimperator/components/about-handler.js b/vimperator/components/about-handler.js
index f6a386c0..fc82098a 100644
--- a/vimperator/components/about-handler.js
+++ b/vimperator/components/about-handler.js
@@ -1,4 +1,5 @@
// Header:
+"use strict";
const Name = "Vimperator";
/*
* We can't load our modules here, so the following code is sadly
@@ -38,6 +39,9 @@ AboutHandler.prototype = {
getURIFlags: function (uri) Ci.nsIAboutModule.ALLOW_SCRIPT,
};
-function NSGetModule(compMgr, fileSpec) XPCOMUtils.generateModule([AboutHandler])
+if (XPCOMUtils.generateNSGetFactory)
+ const NSGetFactory = XPCOMUtils.generateNSGetFactory([AboutHandler]);
+else
+ const NSGetModule = XPCOMUtils.generateNSGetModule([AboutHandler]);
// vim: set fdm=marker sw=4 ts=4 et:
diff --git a/vimperator/components/commandline-handler.js b/vimperator/components/commandline-handler.js
index 5ddb6713..7d8501ad 100644
--- a/vimperator/components/commandline-handler.js
+++ b/vimperator/components/commandline-handler.js
@@ -1,4 +1,5 @@
// Header:
+"use strict";
const Name = "Vimperator";
/*
* We can't load our modules here, so the following code is sadly
@@ -43,6 +44,9 @@ CommandLineHandler.prototype = {
}
};
-function NSGetModule(compMgr, fileSpec) XPCOMUtils.generateModule([CommandLineHandler])
+if (XPCOMUtils.generateNSGetFactory)
+ const NSGetFactory = XPCOMUtils.generateNSGetFactory([CommandLineHandler]);
+else
+ const NSGetModule = XPCOMUtils.generateNSGetModule([CommandLineHandler]);
// vim: set ft=javascript fdm=marker sw=4 ts=4 et:
diff --git a/vimperator/content/config.js b/vimperator/content/config.js
index 6988d5a6..29446ee4 100644
--- a/vimperator/content/config.js
+++ b/vimperator/content/config.js
@@ -46,58 +46,58 @@ const Config = Module("config", ConfigBase, {
["VimperatorLeavePre", "Triggered before exiting Firefox, just before destroying each module"],
["VimperatorLeave", "Triggered before exiting Firefox"]],
- dialogs: [
- ["about", "About Firefox",
+ dialogs: {
+ about: ["About Firefox",
function () { window.openDialog("chrome://browser/content/aboutDialog.xul", "_blank", "chrome,dialog,modal,centerscreen"); }],
- ["addbookmark", "Add bookmark for the current page",
+ addbookmark: ["Add bookmark for the current page",
function () { PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksRootId); }],
- ["addons", "Manage Add-ons",
+ addons: ["Manage Add-ons",
function () { window.BrowserOpenAddonsMgr(); }],
- ["bookmarks", "List your bookmarks",
+ bookmarks: ["List your bookmarks",
function () { window.openDialog("chrome://browser/content/bookmarks/bookmarksPanel.xul", "Bookmarks", "dialog,centerscreen,width=600,height=600"); }],
- ["checkupdates", "Check for updates",
+ checkupdates: ["Check for updates",
function () { window.checkForUpdates(); }],
- ["cleardata", "Clear private data",
+ cleardata: ["Clear private data",
function () { Cc[GLUE_CID].getService(Ci.nsIBrowserGlue).sanitize(window || null); }],
- ["cookies", "List your cookies",
+ cookies: ["List your cookies",
function () { window.toOpenWindowByType("Browser:Cookies", "chrome://browser/content/preferences/cookies.xul", "chrome,dialog=no,resizable"); }],
- ["console", "JavaScript console",
+ console: ["JavaScript console",
function () { window.toJavaScriptConsole(); }],
- ["customizetoolbar", "Customize the Toolbar",
+ customizetoolbar: ["Customize the Toolbar",
function () { window.BrowserCustomizeToolbar(); }],
- ["dominspector", "DOM Inspector",
+ dominspector: ["DOM Inspector",
function () { try { window.inspectDOMDocument(content.document); } catch (e) { liberator.echoerr("DOM Inspector extension not installed"); } }],
- ["downloads", "Manage Downloads",
+ downloads: ["Manage Downloads",
function () { window.toOpenWindowByType("Download:Manager", "chrome://mozapps/content/downloads/downloads.xul", "chrome,dialog=no,resizable"); }],
- ["history", "List your history",
+ history: ["List your history",
function () { window.openDialog("chrome://browser/content/history/history-panel.xul", "History", "dialog,centerscreen,width=600,height=600"); }],
- ["import", "Import Preferences, Bookmarks, History, etc. from other browsers",
+ import: ["Import Preferences, Bookmarks, History, etc. from other browsers",
function () { window.BrowserImport(); }],
- ["openfile", "Open the file selector dialog",
+ openfile: ["Open the file selector dialog",
function () { window.BrowserOpenFileWindow(); }],
- ["pageinfo", "Show information about the current page",
+ pageinfo: ["Show information about the current page",
function () { window.BrowserPageInfo(); }],
- ["pagesource", "View page source",
+ pagesource: ["View page source",
function () { window.BrowserViewSourceOfDocument(content.document); }],
- ["places", "Places Organizer: Manage your bookmarks and history",
+ places: ["Places Organizer: Manage your bookmarks and history",
function () { PlacesCommandHook.showPlacesOrganizer(ORGANIZER_ROOT_BOOKMARKS); }],
- ["preferences", "Show Firefox preferences dialog",
+ preferences: ["Show Firefox preferences dialog",
function () { window.openPreferences(); }],
- ["printpreview", "Preview the page before printing",
+ printpreview: ["Preview the page before printing",
function () { PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); }],
- ["printsetup", "Setup the page size and orientation before printing",
+ printsetup: ["Setup the page size and orientation before printing",
function () { PrintUtils.showPageSetup(); }],
- ["print", "Show print dialog",
+ print: ["Show print dialog",
function () { PrintUtils.print(); }],
- ["saveframe", "Save frame to disk",
+ saveframe: ["Save frame to disk",
function () { window.saveFrameDocument(); }],
- ["savepage", "Save page to disk",
+ savepage: ["Save page to disk",
function () { window.saveDocument(window.content.document); }],
- ["searchengines", "Manage installed search engines",
+ searchengines: ["Manage installed search engines",
function () { window.openDialog("chrome://browser/content/search/engineManager.xul", "_blank", "chrome,dialog,modal,centerscreen"); }],
- ["selectionsource", "View selection source",
+ selectionsource: ["View selection source",
function () { buffer.viewSelectionSource(); }]
- ],
+ },
hasTabbrowser: true,
@@ -232,13 +232,20 @@ const Config = Module("config", ConfigBase, {
return;
context.anchored = false;
- context.title = ["Smart Completions"];
- context.keys.icon = 2;
- context.incomplete = true;
- context.hasItems = context.completions.length > 0; // XXX
- context.filterFunc = null;
- context.cancel = function () { if (searchRunning) { services.get("autoCompleteSearch").stopSearch(); searchRunning = false; } };
context.compare = CompletionContext.Sort.unsorted;
+ context.filterFunc = null;
+ context.hasItems = context.completions.length > 0; // XXX
+ context.incomplete = true;
+ context.keys.icon = 2;
+ context.title = ["Smart Completions"];
+ context.cancel = function () {
+ if (searchRunning) {
+ services.get("autoCompleteSearch").stopSearch();
+ searchRunning = false;
+ }
+ };
+ if (searchRunning)
+ services.get("autoCompleteSearch").stopSearch();
let timer = new Timer(50, 100, function (result) {
context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING;
context.completions = [
@@ -246,9 +253,6 @@ const Config = Module("config", ConfigBase, {
for (i in util.range(0, result.matchCount))
];
});
- if (searchRunning)
- services.get("autoCompleteSearch").stopSearch();
- searchRunning = true;
services.get("autoCompleteSearch").startSearch(context.filter, "", context.result, {
onSearchResult: function onSearchResult(search, result) {
timer.tell(result);
@@ -258,6 +262,7 @@ const Config = Module("config", ConfigBase, {
}
}
});
+ searchRunning = true;
};
completion.sidebar = function sidebar(context) {
diff --git a/vimperator/install.rdf b/vimperator/install.rdf
index 1f67bd6f..cf814ed9 100644
--- a/vimperator/install.rdf
+++ b/vimperator/install.rdf
@@ -4,7 +4,7 @@
vimperator@mozdev.org
Vimperator
- ###VERSION###
+ @VERSION@
Make Firefox behave like Vim
Martin Stubenschrott
http://vimperator.org
@@ -19,7 +19,7 @@
{ec8030f7-c20a-464f-9b0e-13a3a9e97384}
3.5
- 3.6b3
+ 4.0