1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2026-03-12 02:35:47 +01:00

Merge with testing.

This commit is contained in:
Kris Maglione
2010-08-26 15:22:17 -04:00
53 changed files with 1524 additions and 1432 deletions

View File

@@ -11,6 +11,8 @@ syntax: glob
*/locale/*/*.html */locale/*/*.html
*/chrome */chrome
*/contrib/vim/*.vba */contrib/vim/*.vba
*/bak/*
downloads/*
## Editor backup and swap files ## Editor backup and swap files
*~ *~

View File

@@ -99,8 +99,8 @@ dist: $(XPI)
$(RDF): $(RDF_IN) Makefile $(RDF): $(RDF_IN) Makefile
@echo "Preparing release..." @echo "Preparing release..."
$(SED) -e "s,###VERSION###,$(VERSION),g" \ $(SED) -e "s,@VERSION@,$(VERSION),g" \
-e "s,###DATE###,$(BUILD_DATE),g" \ -e "s,@DATE@,$(BUILD_DATE),g" \
< $< > $@ < $< > $@
@echo "SUCCESS: $@" @echo "SUCCESS: $@"

View File

@@ -2,7 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/* Adds support for data: URIs with chrome privileges /* Adds support for data: URIs with chrome privileges
* and fragment identifiers. * and fragment identifiers.
@@ -27,7 +27,7 @@ let channel = Components.classesByID["{61ba33c0-3031-11d3-8cd0-0060b0fc14a3}"]
.QueryInterface(Ci.nsIRequest); .QueryInterface(Ci.nsIRequest);
const systemPrincipal = channel.owner; const systemPrincipal = channel.owner;
channel.cancel(NS_BINDING_ABORTED); channel.cancel(NS_BINDING_ABORTED);
delete channel; channel = null;
function dataURL(type, data) "data:" + (type || "application/xml;encoding=UTF-8") + "," + escape(data); function dataURL(type, data) "data:" + (type || "application/xml;encoding=UTF-8") + "," + escape(data);
function makeChannel(url, orig) { function makeChannel(url, orig) {
@@ -152,8 +152,9 @@ Liberator.prototype = {
} }
}; };
var components = [ChromeData, Liberator]; if (XPCOMUtils.generateNSGetFactory)
const NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeData, Liberator]);
function NSGetModule(compMgr, fileSpec) XPCOMUtils.generateModule(components) else
const NSGetModule = XPCOMUtils.generateNSGetModule([ChromeData, Liberator]);
// vim: set fdm=marker sw=4 ts=4 et: // vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -1,8 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -274,9 +276,9 @@ const AutoCommands = Module("autocommands", {
completer: function () config.autocommands.concat([["all", "All events"]]) completer: function () config.autocommands.concat([["all", "All events"]])
}); });
options.add(["focuscontent", "fc"], options.add(["strictfocus", "sf"],
"Try to stay in normal mode after loading a web page", "Prevent scripts from focusing input elements without user intervention",
"boolean", false); "boolean", true);
} }
}); });

View File

@@ -2,6 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
const Cc = Components.classes; const Cc = Components.classes;
const Ci = Components.interfaces; const Ci = Components.interfaces;

View File

@@ -1,8 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png"; const DEFAULT_FAVICON = "chrome://mozapps/skin/places/defaultFavicon.png";
@@ -364,7 +366,6 @@ const Bookmarks = Module("bookmarks", {
// ripped from Firefox // ripped from Firefox
function getShortcutOrURI(url) { function getShortcutOrURI(url) {
var shortcutURL = null;
var keyword = url; var keyword = url;
var param = ""; var param = "";
var offset = url.indexOf(" "); var offset = url.indexOf(" ");
@@ -379,7 +380,7 @@ const Bookmarks = Module("bookmarks", {
return [submission.uri.spec, submission.postData]; return [submission.uri.spec, submission.postData];
} }
[shortcutURL, postData] = PlacesUtils.getURLAndPostDataForKeyword(keyword); let [shortcutURL, postData] = PlacesUtils.getURLAndPostDataForKeyword(keyword);
if (!shortcutURL) if (!shortcutURL)
return [url, null]; return [url, null];
@@ -598,6 +599,18 @@ const Bookmarks = Module("bookmarks", {
context.completions = [["", "Don't perform searches by default"]].concat(context.completions); 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: function () {
completion.bookmark = function bookmark(context, tags, extra) { completion.bookmark = function bookmark(context, tags, extra) {
@@ -643,8 +656,11 @@ const Bookmarks = Module("bookmarks", {
let rest = item.url.length - end.length; let rest = item.url.length - end.length;
let query = item.url.substring(begin.length, rest); let query = item.url.substring(begin.length, rest);
if (item.url.substr(rest) == end && query.indexOf("&") == -1) { if (item.url.substr(rest) == end && query.indexOf("&") == -1) {
item.url = decodeURIComponent(query.replace(/#.*/, "")); try {
return item; item.url = decodeURIComponent(query.replace(/#.*/, ""));
return item;
}
catch (e) {}
} }
return null; return null;
}).filter(util.identity); }).filter(util.identity);

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -19,9 +19,10 @@ const Point = Struct("x", "y");
* @instance buffer * @instance buffer
*/ */
const Buffer = Module("buffer", { const Buffer = Module("buffer", {
requires: ["config"], requires: ["config", "util"],
init: function () { init: function () {
this.evaluateXPath = util.evaluateXPath;
this.pageInfo = {}; this.pageInfo = {};
this.addPageInfoSection("f", "Feeds", function (verbose) { this.addPageInfoSection("f", "Feeds", function (verbose) {
@@ -168,7 +169,7 @@ const Buffer = Module("buffer", {
// called when the active document is scrolled // called when the active document is scrolled
_updateBufferPosition: function _updateBufferPosition() { _updateBufferPosition: function _updateBufferPosition() {
statusline.updateBufferPosition(); statusline.updateBufferPosition();
modes.show(); modes.show(); // Clear the status line.
}, },
onDOMContentLoaded: function onDOMContentLoaded(event) { onDOMContentLoaded: function onDOMContentLoaded(event) {
@@ -199,19 +200,7 @@ const Buffer = Module("buffer", {
// any buffer, even in a background tab // any buffer, even in a background tab
doc.pageIsFullyLoaded = 1; doc.pageIsFullyLoaded = 1;
// code which is only relevant if the page load is the current tab goes here: if (doc != config.browser.contentDocument)
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
liberator.echomsg("Background tab loaded: " + doc.title || doc.location.href, 3); liberator.echomsg("Background tab loaded: " + doc.title || doc.location.href, 3);
this._triggerLoadAutocmd("PageLoad", doc); this._triggerLoadAutocmd("PageLoad", doc);
@@ -279,7 +268,11 @@ const Buffer = Module("buffer", {
autocommands.trigger("LocationChange", { url: buffer.URL }); autocommands.trigger("LocationChange", { url: buffer.URL });
// if this is not delayed we get the position of the old buffer // 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 // called at the very end of a page load
asyncUpdateUI: function () { asyncUpdateUI: function () {
@@ -384,19 +377,18 @@ const Buffer = Module("buffer", {
get pageHeight() window.content.innerHeight, get pageHeight() window.content.innerHeight,
/** /**
* @property {number} The current browser's text zoom level, as a * @property {number} The current browser's zoom level, as a
* percentage with 100 as 'normal'. Only affects text size. * percentage with 100 as 'normal'.
*/ */
get textZoom() config.browser.markupDocumentViewer.textZoom * 100, get zoomLevel() config.browser.markupDocumentViewer[this.fullZoom ? "textZoom" : "fullZoom"] * 100,
set textZoom(value) { Buffer.setZoom(value, false); }, set zoomLevel(value) { Buffer.setZoom(value, this.fullZoom); },
/** /**
* @property {number} The current browser's text zoom level, as a * @property {boolean} Whether the current browser is using full
* percentage with 100 as 'normal'. Affects text size, as well as * zoom, as opposed to text zoom.
* image size and block size.
*/ */
get fullZoom() config.browser.markupDocumentViewer.fullZoom * 100, get fullZoom() ZoomManager.useFullZoom,
set fullZoom(value) { Buffer.setZoom(value, true); }, set fullZoom(value) { Buffer.setZoom(this.zoomLevel, value); },
/** /**
* @property {string} The current document's title. * @property {string} The current document's title.
@@ -470,6 +462,18 @@ const Buffer = Module("buffer", {
return String(selection); 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 * Focuses the given element. In contrast to a simple
* elem.focus() call, this function works for iframes and * elem.focus() call, this function works for iframes and
@@ -479,7 +483,10 @@ const Buffer = Module("buffer", {
*/ */
focusElement: function (elem) { focusElement: function (elem) {
let doc = window.content.document; 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(); elem.contentWindow.focus();
else if (elem instanceof HTMLInputElement && elem.type == "file") { else if (elem instanceof HTMLInputElement && elem.type == "file") {
Buffer.openUploadPrompt(elem); Buffer.openUploadPrompt(elem);
@@ -960,14 +967,20 @@ const Buffer = Module("buffer", {
liberator.assert(value >= Buffer.ZOOM_MIN || value <= Buffer.ZOOM_MAX, liberator.assert(value >= Buffer.ZOOM_MIN || value <= Buffer.ZOOM_MAX,
"Zoom value out of range (" + Buffer.ZOOM_MIN + " - " + 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; ZoomManager.zoom = value / 100;
if ("FullZoom" in window) if ("FullZoom" in window)
FullZoom._applySettingToPref(); FullZoom._applySettingToPref();
liberator.echomsg((fullZoom ? "Full" : "Text") + " zoom: " + value + "%");
statusline.updateZoomLevel(value, ZoomManager.useFullZoom);
}, },
bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) { bumpZoomLevel: function bumpZoomLevel(steps, fullZoom) {
if (fullZoom === undefined)
fullZoom = ZoomManager.useFullZoom;
let values = ZoomManager.zoomValues; let values = ZoomManager.zoomValues;
let cur = values.indexOf(ZoomManager.snap(ZoomManager.zoom)); let cur = values.indexOf(ZoomManager.snap(ZoomManager.zoom));
let i = util.Math.constrain(cur + steps, 0, values.length - 1); let i = util.Math.constrain(cur + steps, 0, values.length - 1);
@@ -1138,8 +1151,8 @@ const Buffer = Module("buffer", {
}, },
{ {
argCount: "?", argCount: "?",
literal: 0, bang: true,
bang: true literal: 0
}); });
commands.add(["pa[geinfo]"], commands.add(["pa[geinfo]"],
@@ -1178,8 +1191,8 @@ const Buffer = Module("buffer", {
"Reload the current web page", "Reload the current web page",
function (args) { tabs.reload(config.browser.mCurrentTab, args.bang); }, 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? // 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) if (count < 1 && buffer.lastInputField)
buffer.focusElement(buffer.lastInputField); buffer.focusElement(buffer.lastInputField);
else { else {
let xpath = ["input[not(@type) or @type='text' or @type='password' or @type='file']", let xpath = ["input", "textarea[not(@disabled) and not(@readonly)]"];
"textarea[not(@disabled) and not(@readonly)]"];
let elements = [m for (m in util.evaluateXPath(xpath))].filter(function (match) { let elements = [m for (m in util.evaluateXPath(xpath))].filter(function (elem) {
let computedStyle = util.computedStyle(match); 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"; return computedStyle.visibility != "hidden" && computedStyle.display != "none";
}); });
liberator.assert(elements.length > 0); 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 }); { count: true });
@@ -1504,7 +1520,7 @@ const Buffer = Module("buffer", {
function () { function () {
let url = util.readFromClipboard(); let url = util.readFromClipboard();
liberator.assert(url); liberator.assert(url);
liberator.open(url, { from: "activate", where: liberator.NEW_TAB }); liberator.open(url, { from: "paste", where: liberator.NEW_TAB });
}); });
// reloading // reloading
@@ -1551,27 +1567,27 @@ const Buffer = Module("buffer", {
function (count) { buffer.textZoom = count > 1 ? count : 100; }, function (count) { buffer.textZoom = count > 1 ? count : 100; },
{ count: true }); { count: true });
mappings.add(myModes, ["zI"], mappings.add(myModes, ["ZI", "zI"],
"Enlarge full zoom of current web page", "Enlarge full zoom of current web page",
function (count) { buffer.zoomIn(Math.max(count, 1), true); }, function (count) { buffer.zoomIn(Math.max(count, 1), true); },
{ count: true }); { count: true });
mappings.add(myModes, ["zM"], mappings.add(myModes, ["ZM", "zM"],
"Enlarge full zoom of current web page by a larger amount", "Enlarge full zoom of current web page by a larger amount",
function (count) { buffer.zoomIn(Math.max(count, 1) * 3, true); }, function (count) { buffer.zoomIn(Math.max(count, 1) * 3, true); },
{ count: true }); { count: true });
mappings.add(myModes, ["zO"], mappings.add(myModes, ["ZO", "zO"],
"Reduce full zoom of current web page", "Reduce full zoom of current web page",
function (count) { buffer.zoomOut(Math.max(count, 1), true); }, function (count) { buffer.zoomOut(Math.max(count, 1), true); },
{ count: true }); { count: true });
mappings.add(myModes, ["zR"], mappings.add(myModes, ["ZR", "zR"],
"Reduce full zoom of current web page by a larger amount", "Reduce full zoom of current web page by a larger amount",
function (count) { buffer.zoomOut(Math.max(count, 1) * 3, true); }, function (count) { buffer.zoomOut(Math.max(count, 1) * 3, true); },
{ count: true }); { count: true });
mappings.add(myModes, ["zZ"], mappings.add(myModes, ["ZZ", "zZ"],
"Set full zoom value of current web page", "Set full zoom value of current web page",
function (count) { buffer.fullZoom = count > 1 ? count : 100; }, function (count) { buffer.fullZoom = count > 1 ? count : 100; },
{ count: true }); { count: true });
@@ -1599,7 +1615,7 @@ const Buffer = Module("buffer", {
"Desired info in the :pageinfo output", "Desired info in the :pageinfo output",
"charlist", "gfm", "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"], options.add(["scroll", "scr"],

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -102,7 +105,7 @@ const CommandLine = Module("commandline", {
}); });
this._autocompleteTimer = new Timer(200, 500, function autocompleteTell(tabPressed) { 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.complete(true, false);
self._completions.itemList.show(); self._completions.itemList.show();
} }
@@ -329,10 +332,11 @@ const CommandLine = Module("commandline", {
FORCE_MULTILINE : 1 << 0, FORCE_MULTILINE : 1 << 0,
FORCE_SINGLELINE : 1 << 1, 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 // but output nothing when the MOW is open; when also
// FORCE_MULTILINE is given, FORCE_MULTILINE takes precedence // 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, get completionContext() this._completions.context,
@@ -499,6 +503,10 @@ const CommandLine = Module("commandline", {
if (flags & this.APPEND_TO_MESSAGES) if (flags & this.APPEND_TO_MESSAGES)
this._messageHistory.add({ str: str, highlight: highlightGroup }); 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. // The DOM isn't threadsafe. It must only be accessed from the main thread.
liberator.callInMainThread(function () { liberator.callInMainThread(function () {
@@ -754,12 +762,12 @@ const CommandLine = Module("commandline", {
case "<MiddleMouse>": case "<MiddleMouse>":
case "<C-LeftMouse>": case "<C-LeftMouse>":
case "<C-M-LeftMouse>": case "<C-M-LeftMouse>":
openLink(liberator.NEW_BACKGROUND_TAB); openLink({ where: liberator.NEW_TAB, background: true });
break; break;
case "<S-MiddleMouse>": case "<S-MiddleMouse>":
case "<C-S-LeftMouse>": case "<C-S-LeftMouse>":
case "<C-M-S-LeftMouse>": case "<C-M-S-LeftMouse>":
openLink(liberator.NEW_TAB); openLink({ where: liberator.NEW_TAB, background: false });
break; break;
case "<S-LeftMouse>": case "<S-LeftMouse>":
openLink(liberator.NEW_WINDOW); openLink(liberator.NEW_WINDOW);
@@ -964,7 +972,7 @@ const CommandLine = Module("commandline", {
let doc = this._multilineOutputWidget.contentDocument; let doc = this._multilineOutputWidget.contentDocument;
availableHeight = config.outputHeight; let availableHeight = config.outputHeight;
if (!this._outputContainer.collapsed) if (!this._outputContainer.collapsed)
availableHeight += parseFloat(this._outputContainer.height); availableHeight += parseFloat(this._outputContainer.height);
doc.body.style.minWidth = this._commandlineWidget.scrollWidth + "px"; doc.body.style.minWidth = this._commandlineWidget.scrollWidth + "px";
@@ -1032,7 +1040,7 @@ const CommandLine = Module("commandline", {
sanitize: function (timespan) { sanitize: function (timespan) {
let range = [0, Number.MAX_VALUE]; let range = [0, Number.MAX_VALUE];
if (liberator.has("sanitizer") && (timespan || options["sanitizetimespan"])) if (liberator.has("sanitizer") && (timespan || options["sanitizetimespan"]))
range = sanitizer.getClearRange(timespan || options["sanitizetimespan"]); range = Sanitizer.getClearRange(timespan || options["sanitizetimespan"]);
const self = this; const self = this;
this.store.mutate("filter", function (item) { this.store.mutate("filter", function (item) {
@@ -1107,7 +1115,7 @@ const CommandLine = Module("commandline", {
*/ */
Completions: Class("Completions", { Completions: Class("Completions", {
init: function (input) { init: function (input) {
this.context = CompletionContext(input.editor); this.context = CompletionContext(input.QueryInterface(Ci.nsIDOMNSEditableElement).editor);
this.context.onUpdate = this.closure._reset; this.context.onUpdate = this.closure._reset;
this.editor = input.editor; this.editor = input.editor;
this.selected = null; this.selected = null;
@@ -1127,7 +1135,7 @@ const CommandLine = Module("commandline", {
let str = commandline.command; let str = commandline.command;
return str.substring(this.prefix.length, str.length - this.suffix.length); return str.substring(this.prefix.length, str.length - this.suffix.length);
}, },
set completion set_completion(completion) { set completion(completion) {
this.previewClear(); this.previewClear();
// Change the completion text. // Change the completion text.
@@ -1516,84 +1524,6 @@ const CommandLine = Module("commandline", {
options.add(["showmode", "smd"], options.add(["showmode", "smd"],
"Show the current mode in the command line", "Show the current mode in the command line",
"boolean", true); "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 () { styles: function () {
let fontSize = util.computedStyle(document.getElementById(config.mainWindowId)).fontSize; let fontSize = util.computedStyle(document.getElementById(config.mainWindowId)).fontSize;

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -73,9 +73,9 @@ const Command = Class("Command", {
modifiers = modifiers || {}; modifiers = modifiers || {};
let self = this; let self = this;
function exec(args) { function exec(command) {
// FIXME: Move to parseCommand? // FIXME: Move to parseCommand?
args = self.parseArgs(args); args = self.parseArgs(command);
if (!args) if (!args)
return; return;
args.count = count; args.count = count;
@@ -237,6 +237,7 @@ const ArgType = Struct("description", "parse");
const Commands = Module("commands", { const Commands = Module("commands", {
init: function () { init: function () {
this._exCommands = []; this._exCommands = [];
this._exMap = {};
}, },
// FIXME: remove later, when our option handler is better // FIXME: remove later, when our option handler is better
@@ -304,7 +305,7 @@ const Commands = Module("commands", {
repeat: null, repeat: null,
_addCommand: function (command, replace) { _addCommand: function (command, replace) {
if (this._exCommands.some(function (c) c.hasName(command.name))) { if (command.name in this._exMap) {
if (command.user && replace) if (command.user && replace)
commands.removeUserCommand(command.name); commands.removeUserCommand(command.name);
else { else {
@@ -314,6 +315,8 @@ const Commands = Module("commands", {
} }
this._exCommands.push(command); this._exCommands.push(command);
for(let [,name] in Iterator(command.names))
this._exMap[name] = command;
return true; return true;
}, },
@@ -387,7 +390,7 @@ const Commands = Module("commands", {
* @returns {Command} * @returns {Command}
*/ */
get: function (name) { 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. * any of the command's names.
*/ */
removeUserCommand: function (name) { 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))); 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*/); [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); let argContext = context.fork("args", prefix.length);
args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang }); args = command.parseArgs(cmdContext.filter, argContext, { count: count, bang: bang });
if (args) { if (args && !cmdContext.waitingForTab) {
// FIXME: Move to parseCommand // FIXME: Move to parseCommand
args.count = count; args.count = count;
args.bang = bang; args.bang = bang;

View File

@@ -1,9 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -34,6 +35,11 @@ const CompletionContext = Class("CompletionContext", {
if (editor instanceof this.constructor) { if (editor instanceof this.constructor) {
let parent = editor; let parent = editor;
name = parent.name + "/" + name; 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; this.contexts = parent.contexts;
if (name in this.contexts) if (name in this.contexts)
self = this.contexts[name]; self = this.contexts[name];
@@ -146,6 +152,8 @@ const CompletionContext = Class("CompletionContext", {
this.top = this; this.top = this;
this.__defineGetter__("incomplete", function () this.contextList.some(function (c) c.parent && c.incomplete)); 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.__defineGetter__("waitingForTab", function () this.contextList.some(function (c) c.parent && c.waitingForTab));
this.__defineSetter__("incomplete", function (val) {});
this.__defineSetter__("waitingForTab", function (val) {});
this.reset(); this.reset();
} }
/** /**
@@ -243,7 +251,7 @@ const CompletionContext = Class("CompletionContext", {
get completions() this._completions || [], get completions() this._completions || [],
set completions(items) { set completions(items) {
// Accept a generator // Accept a generator
if ({}.toString.call(items) != '[object Array]') if (!isarray(items))
items = [x for (x in Iterator(items))]; items = [x for (x in Iterator(items))];
delete this.cache.filtered; delete this.cache.filtered;
delete this.cache.filter; delete this.cache.filter;
@@ -333,7 +341,7 @@ const CompletionContext = Class("CompletionContext", {
get ignoreCase() { get ignoreCase() {
if ("_ignoreCase" in this) if ("_ignoreCase" in this)
return this._ignoreCase; return this._ignoreCase;
let mode = options["wildcase"]; let mode = this.wildcase;
if (mode == "match") if (mode == "match")
return this._ignoreCase = false; return this._ignoreCase = false;
if (mode == "ignore") if (mode == "ignore")
@@ -367,7 +375,7 @@ const CompletionContext = Class("CompletionContext", {
if (this.maxItems) if (this.maxItems)
filtered = filtered.slice(0, 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); filtered.sort(this.compare);
let quote = this.quote; let quote = this.quote;
if (quote) if (quote)
@@ -404,12 +412,12 @@ const CompletionContext = Class("CompletionContext", {
let filter = fixCase(this.filter); let filter = fixCase(this.filter);
if (this.anchored) { if (this.anchored) {
var compare = function compare(text, s) text.substr(0, s.length) == s; 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)); function (end) text.substring(0, end));
} }
else { else {
var compare = function compare(text, s) text.indexOf(s) >= 0; var compare = function compare(text, s) text.indexOf(s) >= 0;
substrings = []; var substrings = [];
let start = 0; let start = 0;
let idx; let idx;
let length = filter.length; let length = filter.length;
@@ -498,8 +506,14 @@ const CompletionContext = Class("CompletionContext", {
completer = self[completer]; completer = self[completer];
let context = CompletionContext(this, name, offset); let context = CompletionContext(this, name, offset);
this.contextList.push(context); 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))); return completer.apply(self || this, [context].concat(Array.slice(arguments, arguments.callee.length)));
if (completer)
return null;
return context; return context;
}, },
@@ -682,7 +696,7 @@ const Completion = Module("completion", {
if (skip) if (skip)
context.advance(skip[0].length); context.advance(skip[0].length);
if (typeof complete === "undefined") if (complete == null)
complete = options["complete"]; complete = options["complete"];
// Will, and should, throw an error if !(c in opts) // Will, and should, throw an error if !(c in opts)
@@ -744,6 +758,80 @@ const Completion = Module("completion", {
//}}} //}}}
}, { }, {
UrlCompleter: Struct("name", "description", "completer") 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(
<div highlight="Completions">
{ template.completionRow(["Context", "Title"], "CompTitle") }
{ template.map(completion.contextList || [], function (item) template.completionRow(item, "CompItem")) }
</div>),
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: // vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
const ConfigBase = Class(ModuleBase, { const ConfigBase = Class(ModuleBase, {
/** /**

View File

@@ -2,7 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -103,32 +103,16 @@ const Events = Module("events", {
} }
}, 100); }, 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._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, "DOMMenuBarActive", this.closure.onDOMMenuBarActive, true);
this.addSessionListener(window, "DOMMenuBarInactive", this.closure.onDOMMenuBarInactive, 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); this.addSessionListener(window, "resize", this.closure.onResize, true);
}, },
@@ -151,10 +135,28 @@ const Events = Module("events", {
*/ */
addSessionListener: function (target, event, callback, capture) { addSessionListener: function (target, event, callback, capture) {
let args = Array.slice(arguments, 0); 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); 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 * @property {boolean} Whether synthetic key events are currently being
* processed. * processed.
@@ -651,89 +653,14 @@ const Events = Module("events", {
return ret; return ret;
}, },
// argument "event" is deliberately not used, as i don't seem to have onDOMMenuBarActive: function () {
// access to the real focus target this._activeMenubar = true;
// Huh? --djk modes.add(modes.MENU);
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;
}
}, },
onSelectionChange: function (event) { onDOMMenuBarInactive: function () {
let couldCopy = false; this._activeMenubar = false;
let controller = document.commandDispatcher.getControllerForCommand("cmd_copy"); modes.remove(modes.MENU);
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;
// }
}, },
/** /**
@@ -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. // this keypress handler gets always called first, even if e.g.
// the commandline has focus // the commandline has focus
// TODO: ...help me...please... // TODO: ...help me...please...
@@ -1022,7 +1021,9 @@ const Events = Module("events", {
if (liberator.mode == modes.COMMAND_LINE) { if (liberator.mode == modes.COMMAND_LINE) {
if (!(modes.extended & modes.INPUT_MULTILINE)) 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) else if (!modes.mainMode.input)
liberator.beep(); liberator.beep();
@@ -1050,6 +1051,13 @@ const Events = Module("events", {
event.stopPropagation(); 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) { onPopupShown: function (event) {
if (event.originalTarget.localName == "tooltip" || event.originalTarget.id == "liberator-visualbell") if (event.originalTarget.localName == "tooltip" || event.originalTarget.id == "liberator-visualbell")
return; return;
@@ -1062,22 +1070,36 @@ const Events = Module("events", {
modes.remove(modes.MENU); 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) { onResize: function (event) {
if (window.fullScreen != this._fullscreen) { if (window.fullScreen != this._fullscreen) {
this._fullscreen = window.fullScreen; this._fullscreen = window.fullScreen;
liberator.triggerObserver("fullscreen", this._fullscreen); liberator.triggerObserver("fullscreen", this._fullscreen);
autocommands.trigger("Fullscreen", { state: 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 () { isInputElemFocused: function () {

View File

@@ -1,465 +1,12 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2008-2010 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
// TODO: proper backwards search - implement our own component? /** @instance rangefinder */
// : implement our own highlighter?
// : <ESC> 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 <esc> this is not set
this._caseSensitive = false; // search string is case sensitive
this._linksOnly = false; // search is limited to link text only
/* Stolen from toolkit.jar in Firefox, for the time being. The private
* methods were unstable, and changed. The new version is not remotely
* 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 <blake@cs.stanford.edu> (Original Author)
* Masayuki Nakano <masayuki@d-toybox.com>
* Ben Basson <contact@cusser.net>
* Jason Barnabe <jason_barnabe@fastmail.fm>
* Asaf Romano <mano@mozilla.com>
* Ehsan Akhgari <ehsan.akhgari@gmail.com>
* Graeme McCutcheon <graememcc_firefox@graeme-online.co.uk>
*/
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 = <span highlight="Search"/>;
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 <b>str</b>.
*
* @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<esc> 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 <Enter> key is pressed to trigger a search.
*
* @param {string} str The search string.
* @param {boolean} forcedBackward Whether to search forwards or
* backwards. This overrides the direction set in
* (@link #openPrompt).
* @default false
*/
onSubmit: function (str, forcedBackward) {
if (typeof forcedBackward === "boolean")
this._backwards = forcedBackward;
if (str)
var pattern = str;
else {
liberator.assert(this._lastSearchPattern, "E35: No previous search pattern");
pattern = this._lastSearchPattern;
}
this.clear();
if (!options["incsearch"] || !str || !this._found) {
// prevent any current match from matching again
if (!window.content.getSelection().isCollapsed)
window.content.getSelection().getRangeAt(0).collapse(this._backwards);
this.find(pattern);
}
this._lastSearchBackwards = this._backwards;
//lastSearchPattern = pattern.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX
this._lastSearchPattern = pattern;
this._lastSearchString = this._searchString;
// TODO: move to find() when reverse incremental searching is kludged in
// need to find again for reverse searching
if (this._backwards)
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
* <Esc> while typing a search.
*/
onCancel: function () {
// TODO: code to reposition the document to the place before search started
},
/**
* Highlights all occurances of <b>str</b> in the buffer.
*
* @param {string} str The string to highlight.
*/
highlight: function (str) {
// FIXME: Thunderbird incompatible
if (config.name == "Muttator")
return;
if (this._highlighter.isHighlighted(content.document))
return;
if (!str)
str = this._lastSearchString;
this._highlighter.highlightDoc(window.content, str);
// recreate selection since highlightDoc collapses the selection
if (window.content.getSelection().isCollapsed)
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);
}
});
const RangeFinder = Module("rangefinder", { const RangeFinder = Module("rangefinder", {
requires: ["config"], requires: ["config"],
@@ -471,6 +18,7 @@ const RangeFinder = Module("rangefinder", {
let backwards = mode == modes.FIND_BACKWARD; let backwards = mode == modes.FIND_BACKWARD;
commandline.open(backwards ? "?" : "/", "", mode); commandline.open(backwards ? "?" : "/", "", mode);
this.rangeFind = null;
this.find("", backwards); this.find("", backwards);
}, },
@@ -537,6 +85,7 @@ const RangeFinder = Module("rangefinder", {
if (options["hlsearch"]) if (options["hlsearch"])
this.highlight(); this.highlight();
this.rangeFind.focus();
}, },
// Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set // 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"]) if (options["hlsearch"])
this.highlight(); this.highlight();
this.rangeFind.focus();
modes.reset(); modes.reset();
}, },
@@ -603,48 +153,106 @@ const RangeFinder = Module("rangefinder", {
}, },
commands: function () { commands: function () {
commands.add(["noh[lsearch]"],
"Remove the search highlighting",
function () { rangefinder.clear(); },
{ argCount: "0" });
}, },
mappings: function () { mappings: function () {
var myModes = config.browserModes.concat([modes.CARET]); var myModes = config.browserModes.concat([modes.CARET]);
mappings.add(myModes, mappings.add(myModes,
["g/"], "Search forward for a pattern", ["/"], "Search forward for a pattern",
function () { rangefinder.openPrompt(modes.FIND_FORWARD); }); function () { rangefinder.openPrompt(modes.FIND_FORWARD); });
mappings.add(myModes, mappings.add(myModes,
["g?"], "Search backwards for a pattern", ["?"], "Search backwards for a pattern",
function () { rangefinder.openPrompt(modes.FIND_BACKWARD); }); function () { rangefinder.openPrompt(modes.FIND_BACKWARD); });
mappings.add(myModes, mappings.add(myModes,
["g."], "Find next", ["n"], "Find next",
function () { rangefinder.findAgain(false); }); function () { rangefinder.findAgain(false); });
mappings.add(myModes, mappings.add(myModes,
["g,"], "Find previous", ["N"], "Find previous",
function () { rangefinder.findAgain(true); }); 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", "Find word under cursor",
function () { function () {
rangefinder._found = false; rangefinder._found = false;
rangefinder.onSubmit(buffer.getCurrentWord(), 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", "Find word under cursor backwards",
function () { function () {
rangefinder._found = false; rangefinder._found = false;
rangefinder.onSubmit(buffer.getCurrentWord(), true); rangefinder.onSubmit(buffer.getCurrentWord(), true);
}); });
}, },
modes: function () { modes: function () {
modes.addMode("FIND_FORWARD", true); modes.addMode("FIND_FORWARD", true);
modes.addMode("FIND_BACKWARD", true); modes.addMode("FIND_BACKWARD", true);
}, },
options: function () { 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", { const RangeFind = Class("RangeFind", {
init: function (matchCase, backward, elementPath) { init: function (matchCase, backward, elementPath) {
this.window = Cu.getWeakReference(window); this.window = Cu.getWeakReference(window);
@@ -655,20 +263,28 @@ const RangeFind = Class("RangeFind", {
this.finder.caseSensitive = this.matchCase; this.finder.caseSensitive = this.matchCase;
this.ranges = this.makeFrameList(content); 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.reset();
this.startRange.collapse(!backward);
this.range = this.findRange(this.startRange);
this.ranges.first = this.range;
this.highlighted = null; this.highlighted = null;
this.lastString = ""; this.lastString = "";
this.lastRange = null;
this.forward = null; this.forward = null;
this.found = false; 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, sameDocument: function (r1, r2) r1 && r2 && r1.endContainer.ownerDocument == r2.endContainer.ownerDocument,
compareRanges: function (r1, r2) 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) { makeFrameList: function (win) {
const self = this; const self = this;
win = win.top; win = win.top;
@@ -706,15 +332,21 @@ const RangeFind = Class("RangeFind", {
let backup = null; let backup = null;
function pushRange(start, end) { function pushRange(start, end) {
function push(r) {
r = RangeFind.Range(r, frames.length);
if (r)
frames.push(r);
}
let range = start.startContainer.ownerDocument.createRange(); let range = start.startContainer.ownerDocument.createRange();
range.setStart(start.startContainer, start.startOffset); range.setStart(start.startContainer, start.startOffset);
range.setEnd(end.startContainer, end.startOffset); range.setEnd(end.startContainer, end.startOffset);
if (!self.elementPath) if (!self.elementPath)
frames.push(RangeFind.Range(range, frames.length)); push(range);
else else
for (let r in self.findSubRanges(range)) for (let r in self.findSubRanges(range))
frames.push(RangeFind.Range(r, frames.length)); push(r);
} }
function rec(win) { function rec(win) {
let doc = win.document; let doc = win.document;
@@ -741,8 +373,8 @@ const RangeFind = Class("RangeFind", {
// This doesn't work yet. // This doesn't work yet.
resetCaret: function () { resetCaret: function () {
let equal = function (r1, r2) !r1.compareBoundaryPoints(Range.START_TO_START, r2) && !r1.compareBoundaryPoints(Range.END_TO_END, r2); let equal = RangeFind.equal;
letselection = this.win.getSelection(); let selection = this.win.getSelection();
if (selection.rangeCount == 0) if (selection.rangeCount == 0)
selection.addRange(this.pageStart); selection.addRange(this.pageStart);
function getLines() { function getLines() {
@@ -792,6 +424,9 @@ const RangeFind = Class("RangeFind", {
}, },
search: function (word, reverse, private_) { search: function (word, reverse, private_) {
if (!private_ && this.lastRange && !RangeFind.equal(this.selectedRange, this.lastRange))
this.reset();
this.wrapped = false; this.wrapped = false;
this.finder.findBackwards = reverse ? !this.reverse : this.reverse; this.finder.findBackwards = reverse ? !this.reverse : this.reverse;
let again = word == null; let again = word == null;
@@ -886,6 +521,7 @@ const RangeFind = Class("RangeFind", {
parent.insertBefore(node, before); parent.insertBefore(node, before);
range.selectNode(node); range.selectNode(node);
} }
function unhighlight(range) { function unhighlight(range) {
let elem = range.startContainer; let elem = range.startContainer;
while (!(elem instanceof Element) && elem.parentNode) while (!(elem instanceof Element) && elem.parentNode)
@@ -913,7 +549,7 @@ const RangeFind = Class("RangeFind", {
else { else {
this.highlighted = this.lastString; this.highlighted = this.lastString;
this.addListeners(); 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.window = this.document.defaultView;
this.range = range; this.range = range;
if (this.selection == null)
return false;
this.save(); this.save();
}, },
@@ -989,12 +628,27 @@ const RangeFind = Class("RangeFind", {
.QueryInterface(Ci.nsIInterfaceRequestor) .QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsISelectionDisplay) .getInterface(Ci.nsISelectionDisplay)
.QueryInterface(Ci.nsISelectionController), .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) { endpoint: function (range, before) {
range = range.cloneRange(); range = range.cloneRange();
range.collapse(before); range.collapse(before);
return range; 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;
} }
}); });

View File

@@ -2,7 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
function checkFragment() { function checkFragment() {
document.title = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "title")[0].textContent; document.title = document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "title")[0].textContent;

View File

@@ -202,7 +202,7 @@
<xsl:variable name="type" select="preceding-sibling::liberator:type[1] | following-sibling::liberator:type[1]"/> <xsl:variable name="type" select="preceding-sibling::liberator:type[1] | following-sibling::liberator:type[1]"/>
<span liberator:highlight="HelpDefault">(default:<xsl:text> </xsl:text> <span liberator:highlight="HelpDefault">(default:<xsl:text> </xsl:text>
<xsl:choose> <xsl:choose>
<xsl:when test="starts-with($type, 'string')"> <xsl:when test="starts-with($type, 'string') or starts-with($type, 'regex')">
<span liberator:highlight="HelpString"><xsl:apply-templates/></span> <span liberator:highlight="HelpString"><xsl:apply-templates/></span>
</xsl:when> </xsl:when>
<xsl:otherwise> <xsl:otherwise>
@@ -351,9 +351,13 @@
<xsl:template match="liberator:ex" mode="pass-2"> <xsl:template match="liberator:ex" mode="pass-2">
<span liberator:highlight="HelpEx"> <span liberator:highlight="HelpEx">
<xsl:call-template name="linkify-tag"> <xsl:variable name="tag" select="str:tokenize(text(), ' [!')[1]"/>
<xsl:with-param name="contents" select="."/> <a href="liberator://help-tag/{$tag}" style="color: inherit;">
</xsl:call-template> <xsl:if test="contains($tags, concat(' ', $tag, ' '))">
<xsl:attribute name="href">#<xsl:value-of select="$tag"/></xsl:attribute>
</xsl:if>
<xsl:apply-templates/>
</a>
</span> </span>
</xsl:template> </xsl:template>

View File

@@ -1,8 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
/** @instance hints */ /** @instance hints */
@@ -40,12 +42,12 @@ const Hints = Module("hints", {
"?": Mode("Show information for hint", function (elem) buffer.showElementInfo(elem), extended), "?": Mode("Show information for hint", function (elem) buffer.showElementInfo(elem), extended),
s: Mode("Save hint", function (elem) buffer.saveLink(elem, true)), s: Mode("Save hint", function (elem) buffer.saveLink(elem, true)),
a: Mode("Save hint with prompt", function (elem) buffer.saveLink(elem, false)), 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)), 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)), 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)), 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), 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)), 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)), 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)), 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", 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) 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; 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]; return [elem.value, false];
else { else {
for (let [, option] in Iterator(options["hintinputs"].split(","))) { for (let [, option] in Iterator(options["hintinputs"].split(","))) {
@@ -266,14 +253,10 @@ const Hints = Module("hints", {
let hint = { elem: elem, showText: false }; let hint = { elem: elem, showText: false };
// TODO: for iframes, this calculation is wrong // 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) if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0)
continue; continue;
rect = elem.getClientRects()[0];
if (!rect)
continue;
let computedStyle = doc.defaultView.getComputedStyle(elem, null); let computedStyle = doc.defaultView.getComputedStyle(elem, null);
if (computedStyle.getPropertyValue("visibility") != "visible" || computedStyle.getPropertyValue("display") == "none") if (computedStyle.getPropertyValue("visibility") != "visible" || computedStyle.getPropertyValue("display") == "none")
continue; continue;
@@ -370,7 +353,7 @@ const Hints = Module("hints", {
if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof HTMLImageElement) { if (hint.text == "" && hint.elem.firstChild && hint.elem.firstChild instanceof HTMLImageElement) {
if (!hint.imgSpan) { if (!hint.imgSpan) {
rect = hint.elem.firstChild.getBoundingClientRect(); var rect = hint.elem.firstChild.getBoundingClientRect();
if (!rect) if (!rect)
continue; continue;
@@ -394,11 +377,11 @@ const Hints = Module("hints", {
} }
} }
if (config.browser.markupDocumentViewer.authorStyleDisabled) { if (options["usermode"]) {
let css = []; let css = [];
// FIXME: Broken for imgspans. // FIXME: Broken for imgspans.
for (let [, { doc: doc }] in Iterator(this._docs)) { 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"); let group = elem.getAttributeNS(NS.uri, "highlight");
css.push(highlight.selector(group) + "[number=" + elem.getAttribute("number").quote() + "] { " + elem.style.cssText + " }"); css.push(highlight.selector(group) + "[number=" + elem.getAttribute("number").quote() + "] { " + elem.style.cssText + " }");
} }
@@ -1058,8 +1041,8 @@ const Hints = Module("hints", {
}, },
options: function () { options: function () {
const DEFAULT_HINTTAGS = const DEFAULT_HINTTAGS =
util.makeXPath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select"]) util.makeXPath(["input[not(@type='hidden')]", "a", "area", "iframe", "textarea", "button", "select",
+ " | //*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @role='link']"; "*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @role='link']"]);
function checkXPath(val) { function checkXPath(val) {
try { try {

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
const History = Module("history", { const History = Module("history", {
requires: ["config"], requires: ["config"],

View File

@@ -1,9 +1,11 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// Some code based on Venkman // Some code based on Venkman
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -160,7 +162,7 @@ const File = Class("File", {
mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_TRUNCATE; mode = File.MODE_WRONLY | File.MODE_CREATE | File.MODE_TRUNCATE;
if (!perms) if (!perms)
perms = 0644; perms = parseInt('0644', 8);
ofstream.init(this, mode, perms, 0); ofstream.init(this, mode, perms, 0);
let ocstream = getStream(0); let ocstream = getStream(0);
@@ -240,7 +242,7 @@ const File = Class("File", {
*/ */
MODE_EXCL: 0x80, MODE_EXCL: 0x80,
expandPathList: function (list) list.split(",").map(this.expandPath).join(","), expandPathList: function (list) list.map(this.expandPath),
expandPath: function (path, relative) { expandPath: function (path, relative) {
@@ -338,7 +340,7 @@ const IO = Module("io", {
let file = download.targetFile.path; let file = download.targetFile.path;
let size = download.size; 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 }); 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); let file = services.get("directory").get("TmpD", Ci.nsIFile);
file.append(config.tempFile); 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); return File(file);
}, },
@@ -595,6 +597,8 @@ lookup:
*/ */
source: function (filename, silent) { source: function (filename, silent) {
let wasSourcing = this.sourcing; let wasSourcing = this.sourcing;
liberator.dump("sourcing " + filename);
let time = Date.now();
try { try {
var file = File(filename); var file = File(filename);
this.sourcing = { this.sourcing = {
@@ -624,8 +628,7 @@ lookup:
if (/\.js$/.test(filename)) { if (/\.js$/.test(filename)) {
try { try {
liberator.loadScript(uri.spec, Script(file)); liberator.loadScript(uri.spec, Script(file));
if (liberator.initialized) liberator.helpInitialized = false;
liberator.initHelp();
} }
catch (e) { catch (e) {
let err = new Error(); let err = new Error();
@@ -713,6 +716,7 @@ lookup:
liberator.echoerr(message); liberator.echoerr(message);
} }
finally { finally {
liberator.dump("done sourcing " + filename + ": " + (Date.now() - time) + "ms");
this.sourcing = wasSourcing; this.sourcing = wasSourcing;
} }
}, },
@@ -1028,8 +1032,8 @@ lookup:
b.isdir - a.isdir || String.localeCompare(a.text, b.text); b.isdir - a.isdir || String.localeCompare(a.text, b.text);
if (options["wildignore"]) { if (options["wildignore"]) {
let wigRegexp = RegExp("(^" + options.get("wildignore").values.join("|") + ")$"); let wig = options.get("wildignore");
context.filters.push(function ({item: f}) f.isDirectory() || !wigRegexp.test(f.leafName)); context.filters.push(function ({item: f}) f.isDirectory() || !wig.getKey(this.name));
} }
// context.background = true; // 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 () { options: function () {
var shell, shellcmdflag; var shell, shellcmdflag;
@@ -1099,6 +1106,10 @@ lookup:
options.add(["shellcmdflag", "shcf"], options.add(["shellcmdflag", "shcf"],
"Flag passed to shell when executing :! and :run commands", "Flag passed to shell when executing :! and :run commands",
"string", shellcmdflag); "string", shellcmdflag);
options.add(["wildignore", "wig"],
"List of file patterns to ignore when completing files",
"regexlist", "");
} }
}); });

View File

@@ -2,6 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
// TODO: Clean this up. // TODO: Clean this up.
@@ -33,17 +34,18 @@ const JavaScript = Module("javascript", {
}, },
iter: function iter(obj, toplevel) { iter: function iter(obj, toplevel) {
"use strict";
toplevel = !!toplevel; toplevel = !!toplevel;
let seen = {}; let seen = {};
let ret = {}; let ret = {};
try { if(obj == null)
return;
if(options["jsdebugger"]) {
let orig = obj; let orig = obj;
let top = services.get("debugger").wrapValue(obj); let top = services.get("debugger").wrapValue(obj);
if (!toplevel)
obj = obj.__proto__;
for (; obj; obj = !toplevel && obj.__proto__) { for (; obj; obj = !toplevel && obj.__proto__) {
services.get("debugger").wrapValue(obj).getProperties(ret, {}); services.get("debugger").wrapValue(obj).getProperties(ret, {});
for (let prop in values(ret.value)) { for (let prop in values(ret.value)) {
@@ -51,18 +53,27 @@ const JavaScript = Module("javascript", {
if (name in seen) if (name in seen)
continue; continue;
seen[name] = 1; 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. // The debugger doesn't list some properties. I can't guess why.
for (let k in orig) // This only lists ENUMERABLE properties.
if (k in orig && !('|' + k in seen) && obj.hasOwnProperty(k) == toplevel) try {
yield [k, this.getKey(orig, k)] 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) { else {
for (k in allkeys(obj)) for (let k in allkeys(obj))
if (obj.hasOwnProperty(k) == toplevel) try {
yield [k, this.getKey(obj, k)]; if (Object.hasOwnProperty(obj, k) == toplevel)
yield [k, this.getKey(obj, k)];
}
catch (e) {}
} }
}, },
@@ -101,7 +112,7 @@ const JavaScript = Module("javascript", {
return completions; return completions;
}, },
eval: function eval(arg, key, tmp) { eval: function evalstr(arg, key, tmp) {
let cache = this.context.cache.eval; let cache = this.context.cache.eval;
let context = this.context.cache.evalContext; let context = this.context.cache.evalContext;
@@ -230,8 +241,6 @@ const JavaScript = Module("javascript", {
case "'": case "'":
case "/": case "/":
case "{": case "{":
this._push(this._c);
break;
case "[": case "[":
this._push(this._c); this._push(this._c);
break; break;
@@ -341,6 +350,10 @@ const JavaScript = Module("javascript", {
_complete: function (objects, key, compl, string, last) { _complete: function (objects, key, compl, string, last) {
const self = this; 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; let orig = compl;
if (!compl) { if (!compl) {
compl = function (context, obj, recurse) { compl = function (context, obj, recurse) {
@@ -427,7 +440,8 @@ const JavaScript = Module("javascript", {
// Okay, have parse stack. Figure out what we're completing. // Okay, have parse stack. Figure out what we're completing.
// Find any complete statements that we can eval before we eval our object. // 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.<Tab> // This allows for things like:
// let doc = window.content.document; let elem = doc.createEle<Tab> ...
let prev = 0; let prev = 0;
for (let [, v] in Iterator(this._get(0).fullStatements)) { for (let [, v] in Iterator(this._get(0).fullStatements)) {
let key = this._str.substring(prev, v + 1); let key = this._str.substring(prev, v + 1);
@@ -437,14 +451,13 @@ const JavaScript = Module("javascript", {
prev = v + 1; prev = v + 1;
} }
// In a string. Check if we're dereferencing an object. // In a string. Check if we're dereferencing an object or
// Otherwise, do nothing. // completing a function argument. Otherwise, do nothing.
if (this._last == "'" || this._last == '"') { if (this._last == "'" || this._last == '"') {
//
// str = "foo[bar + 'baz" // str = "foo[bar + 'baz"
// obj = "foo" // obj = "foo"
// key = "bar + ''" // key = "bar + ''"
//
// The top of the stack is the sting we're completing. // The top of the stack is the sting we're completing.
// Wrap it in its delimiters and eval it to process escape sequences. // Wrap it in its delimiters and eval it to process escape sequences.
@@ -497,15 +510,15 @@ const JavaScript = Module("javascript", {
// Split up the arguments // Split up the arguments
let prev = this._get(-2).offset; let prev = this._get(-2).offset;
let args = []; 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); let arg = this._str.substring(prev + 1, idx);
prev = idx; prev = idx;
util.memoize(args, this._i, function () self.eval(arg)); util.memoize(args, i, function () self.eval(arg));
} }
let key = this._getKey(); let key = this._getKey();
args.push(key + string); args.push(key + string);
compl = function (context, obj) { let compl = function (context, obj) {
let res = completer.call(self, context, func, obj, args); let res = completer.call(self, context, func, obj, args);
if (res) if (res)
context.completions = res; context.completions = res;
@@ -520,7 +533,7 @@ const JavaScript = Module("javascript", {
return null; return null;
} }
//
// str = "foo.bar.baz" // str = "foo.bar.baz"
// obj = "foo.bar" // obj = "foo.bar"
// key = "baz" // key = "baz"
@@ -528,11 +541,11 @@ const JavaScript = Module("javascript", {
// str = "foo" // str = "foo"
// obj = [modules, window] // obj = [modules, window]
// key = "foo" // key = "foo"
//
let [offset, obj, key] = this._getObjKey(-1); 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) { if (!this.context.tabPressed && key == "" && obj.length > 1) {
this.context.waitingForTab = true; this.context.waitingForTab = true;
this.context.message = "Waiting for key press"; this.context.message = "Waiting for key press";
@@ -589,7 +602,7 @@ const JavaScript = Module("javascript", {
let completer = completers[args.length - 1]; let completer = completers[args.length - 1];
if (!completer) if (!completer)
return []; return [];
return completer.call(this, context, obj, args); return completer.call(obj, context, obj, args);
}; };
} }
} }

View File

@@ -1,7 +1,8 @@
// Copyright (c) 2008-2009 Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2008 Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
(function () { (function () {
const modules = {}; const modules = {};

View File

@@ -1,8 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -134,7 +136,7 @@ const Liberator = Module("liberator", {
forceNewWindow: false, forceNewWindow: false,
/** @property {string} The Liberator version string. */ /** @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 * @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}/, ""); let stack = Error().stack.replace(/(?:.*\n){2}/, "");
if (frames != null) if (frames != null)
[stack] = stack.match(RegExp("(?:.*\n){0," + frames + "}")); [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 // 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 // someone adds it. I reckon another flag and 'class' of messages
// is just going to unnecessarily complicate things. --djk // 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) if (verbosity == null)
verbosity = 0; // verbosity level is exclusionary verbosity = 0; // verbosity level is exclusionary
@@ -542,85 +544,88 @@ const Liberator = Module("liberator", {
* Initialize the help system. * Initialize the help system.
*/ */
initHelp: function () { initHelp: function () {
let namespaces = [config.name.toLowerCase(), "liberator"]; if(!this.helpInitialized) {
services.get("liberator:").init({}); let namespaces = [config.name.toLowerCase(), "liberator"];
services.get("liberator:").init({});
let tagMap = services.get("liberator:").HELP_TAGS; let tagMap = services.get("liberator:").HELP_TAGS;
let fileMap = services.get("liberator:").FILE_MAP; let fileMap = services.get("liberator:").FILE_MAP;
let overlayMap = services.get("liberator:").OVERLAY_MAP; let overlayMap = services.get("liberator:").OVERLAY_MAP;
// Left as an XPCOM instantiation so it can easilly be moved // Left as an XPCOM instantiation so it can easilly be moved
// into XPCOM code. // into XPCOM code.
function XSLTProcessor(sheet) { function XSLTProcessor(sheet) {
let xslt = Cc["@mozilla.org/document-transformer;1?type=xslt"].createInstance(Ci.nsIXSLTProcessor); let xslt = Cc["@mozilla.org/document-transformer;1?type=xslt"].createInstance(Ci.nsIXSLTProcessor);
xslt.importStylesheet(util.httpGet(sheet).responseXML); xslt.importStylesheet(util.httpGet(sheet).responseXML);
return xslt; 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);
}
} }
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 const XSLT = XSLTProcessor("chrome://liberator/content/help-single.xsl");
// 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. // Scrape the list of help files from all.xml
util.Array.flatten(files).forEach(function (file) { // Always process main and overlay files, since XSLTProcessor and
findHelpFile(file).forEach(function (doc) { // XMLHttpRequest don't allow access to chrome documents.
addTags(file, doc); 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. // Process plugin help entries.
XML.ignoreWhiteSpace = false; XML.ignoreWhiteSpace = false;
XML.prettyPrinting = false; XML.prettyPrinting = false;
XML.prettyPrinting = true; // Should be false, but ignoreWhiteSpace=false doesn't work correctly. This is the lesser evil. XML.prettyPrinting = true; // Should be false, but ignoreWhiteSpace=false doesn't work correctly. This is the lesser evil.
XML.prettyIndent = 4; XML.prettyIndent = 4;
let body = XML(); let body = XML();
for (let [, context] in Iterator(plugins.contexts)) for (let [, context] in Iterator(plugins.contexts))
if (context.INFO instanceof XML) if (context.INFO instanceof XML)
body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> + body += <h2 xmlns={NS.uri} tag={context.INFO.@name + '-plugin'}>{context.INFO.@summary}</h2> +
context.INFO; context.INFO;
let help = '<?xml version="1.0"?>\n' + let help = '<?xml version="1.0"?>\n' +
'<?xml-stylesheet type="text/xsl" href="chrome://liberator/content/help.xsl"?>\n' + '<?xml-stylesheet type="text/xsl" href="chrome://liberator/content/help.xsl"?>\n' +
'<!DOCTYPE document SYSTEM "chrome://liberator/content/liberator.dtd">' + '<!DOCTYPE document SYSTEM "chrome://liberator/content/liberator.dtd">' +
<document xmlns={NS} <document xmlns={NS}
name="plugins" title={config.name + " Plugins"}> name="plugins" title={config.name + " Plugins"}>
<h1 tag="using-plugins">Using Plugins</h1> <h1 tag="using-plugins">Using Plugins</h1>
{body} {body}
</document>.toXMLString(); </document>.toXMLString();
fileMap["plugins"] = function () ['text/xml;charset=UTF-8', help]; 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} * @returns {string}
*/ */
help: function (topic, unchunked) { help: function (topic, unchunked) {
liberator.initHelp();
if (!topic) { if (!topic) {
let helpFile = unchunked ? "all" : options["helpfile"]; let helpFile = unchunked ? "all" : options["helpfile"];
if (helpFile in services.get("liberator:").FILE_MAP) if (helpFile in services.get("liberator:").FILE_MAP)
@@ -737,22 +743,12 @@ const Liberator = Module("liberator", {
*/ */
open: function (urls, params, force) { open: function (urls, params, force) {
// convert the string to an array of converted URLs // 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 // This is strange. And counterintuitive. Is it really
// necessary? --Kris // necessary? --Kris
if (typeof urls == "string") { if (typeof urls == "string")
// rather switch to the tab instead of opening a new url in case of "12: Tab Title" like "urls" urls = liberator.stringToURLArray(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 (urls.length > 20 && !force) { if (urls.length > 20 && !force) {
commandline.input("This will open " + urls.length + " new tabs. Would you like to continue? (yes/[no]) ", 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; let flags = 0;
params = params || {}; params = params || {};
if (params instanceof Array) if (isarray(params))
params = { where: params }; params = { where: params };
for (let [opt, flag] in Iterator({ replace: "REPLACE_HISTORY", hide: "BYPASS_HISTORY" })) 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]; flags |= Ci.nsIWebNavigation["LOAD_FLAGS_" + flag];
let where = params.where || liberator.CURRENT_TAB; 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 ("from" in params && liberator.has("tabs")) {
if (!('where' in params) && options.get("newtab").has("all", params.from)) if (!('where' in params) && options.get("newtab").has("all", params.from))
where = liberator.NEW_BACKGROUND_TAB; where = liberator.NEW_TAB;
if (options.get("activate").has("all", params.from)) { background = !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;
}
} }
if (urls.length == 0) if (urls.length == 0)
@@ -799,7 +791,6 @@ const Liberator = Module("liberator", {
browser.loadURIWithFlags(url, flags, null, null, postdata); browser.loadURIWithFlags(url, flags, null, null, postdata);
break; break;
case liberator.NEW_BACKGROUND_TAB:
case liberator.NEW_TAB: case liberator.NEW_TAB:
if (!liberator.has("tabs")) { if (!liberator.has("tabs")) {
open(urls, liberator.NEW_WINDOW); open(urls, liberator.NEW_WINDOW);
@@ -808,7 +799,7 @@ const Liberator = Module("liberator", {
options.withContext(function () { options.withContext(function () {
options.setPref("browser.tabs.loadInBackground", true); 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; break;
@@ -832,7 +823,7 @@ const Liberator = Module("liberator", {
for (let [, url] in Iterator(urls)) { for (let [, url] in Iterator(urls)) {
open(url, where); open(url, where);
where = liberator.NEW_BACKGROUND_TAB; background = true;
} }
}, },
@@ -865,6 +856,64 @@ const Liberator = Module("liberator", {
window.goQuitApplication(); window.goQuitApplication();
}, },
/**
* Returns an array of URLs parsed from <b>str</b>.
*
* 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 * Tests a condition and throws a FailedAssertion error on
* failure. * failure.
@@ -1271,17 +1320,8 @@ const Liberator = Module("liberator", {
let arg = args[0]; let arg = args[0];
try { try {
// TODO: why are these sorts of properties arrays? --djk liberator.assert(args[0] in config.dialogs, "E475: Invalid argument: " + arg);
let dialogs = config.dialogs; config.dialogs[args[0]][1]();
for (let [, dialog] in Iterator(dialogs)) {
if (util.compareIgnoreCase(arg, dialog[0]) == 0) {
dialog[2]();
return;
}
}
liberator.echoerr("E475: Invalid argument: " + arg);
} }
catch (e) { catch (e) {
liberator.echoerr("Error opening " + arg.quote() + ": " + 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]"], commands.add(["exta[dd]"],
"Install an extension", "Install an extension",
function (args) { function (args) {
let file = io.File(args[0]); let url = args[0];
let file = io.File(url);
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);
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); liberator.echoerr("E484: Can't open file " + file.path);
}
}, { }, {
argCount: "1", argCount: "1",
completer: function (context) { completer: function (context) {
@@ -1357,38 +1467,35 @@ const Liberator = Module("liberator", {
{ {
name: "extde[lete]", name: "extde[lete]",
description: "Uninstall an extension", description: "Uninstall an extension",
action: "uninstallItem" action: callResult("uninstall")
}, },
{ {
name: "exte[nable]", name: "exte[nable]",
description: "Enable an extension", description: "Enable an extension",
action: "enableItem", action: function (addon) addon.userDisabled = false,
filter: function ({ item: e }) !e.enabled filter: function ({ item: e }) e.userDisabled
}, },
{ {
name: "extd[isable]", name: "extd[isable]",
description: "Disable an extension", description: "Disable an extension",
action: "disableItem", action: function (addon) addon.userDisabled = true,
filter: function ({ item: e }) e.enabled filter: function ({ item: e }) !e.userDisabled
} }
].forEach(function (command) { ].forEach(function (command) {
commands.add([command.name], commands.add([command.name],
command.description, command.description,
function (args) { function (args) {
let name = args[0]; let name = args[0];
function action(e) { services.get("extensionManager")[command.action](e.id); };
if (args.bang) if (args.bang)
liberator.extensions.forEach(function (e) { action(e); }); liberator.assert(!name, "E488: Trailing characters");
else { else
liberator.assert(name, "E471: Argument required"); // XXX liberator.assert(name, "E471: Argument required");
let extension = liberator.getExtension(name); AddonManager.getAddonsByTypes(["extension"], function (list) {
if (extension) if (!args.bang)
action(extension); list = list.filter(function (extension) extension.name == name);
else list.forEach(command.action);
liberator.echoerr("E474: Invalid argument"); });
}
}, { }, {
argCount: "?", // FIXME: should be "1" argCount: "?", // FIXME: should be "1"
bang: true, bang: true,
@@ -1404,51 +1511,64 @@ const Liberator = Module("liberator", {
commands.add(["exto[ptions]", "extp[references]"], commands.add(["exto[ptions]", "extp[references]"],
"Open an extension's preference dialog", "Open an extension's preference dialog",
function (args) { function (args) {
let extension = liberator.getExtension(args[0]); AddonManager.getAddonsByTypes(["extension"], function (list) {
liberator.assert(extension && extension.options, list = list.filter(function (extension) extension.name == args[0]);
"E474: Invalid argument"); if (!list.length || !list[0].optionsURL)
if (args.bang) liberator.echoerr("E474: Invalid argument");
window.openDialog(extension.options, "_blank", "chrome"); else if (args.bang)
else window.openDialog(list[0].optionsURL, "_blank", "chrome");
liberator.open(extension.options, { from: "extoptions" }); else
liberator.open(list[0].optionsURL, { from: "extoptions" });
});
}, { }, {
argCount: "1", argCount: "1",
bang: true, bang: true,
completer: function (context) { completer: function (context) {
completion.extension(context); completion.extension(context);
context.filters.push(function ({ item: e }) e.options); context.filters.push(function ({ item: e }) e.isActive && e.optionsURL);
}, },
literal: 0 literal: 0
}); });
// TODO: maybe indicate pending status too? // TODO: maybe indicate pending status too?
commands.add(["extens[ions]"], commands.add(["extens[ions]", "exts"],
"List available extensions", "List available extensions",
function (args) { function (args) {
let filter = args[0] || ""; AddonManager.getAddonsByTypes(["extension"], function (extensions) {
let extensions = liberator.extensions.filter(function (e) e.name.indexOf(filter) >= 0); 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) { if (extensions.length > 0) {
let list = template.tabular( let list = template.tabular(
["Name", "Version", "Status", "Description"], [], ["Name", "Version", "Status", "Description"], [],
([template.icon(e, e.name), ([template.icon({ icon: e.iconURL }, e.name),
e.version, e.version,
e.enabled ? <span highlight="Enabled">enabled</span> (e.isActive ? <span highlight="Enabled">enabled</span>
: <span highlight="Disabled">disabled</span>, : <span highlight="Disabled">disabled</span>) +
e.description] for ([, e] in Iterator(extensions))) ((e.userDisabled || e.appDisabled) == !e.isActive ? XML() :
); <>&#xa0;({e.userDisabled || e.appDisabled
? <span highlight="Disabled">disabled</span>
: <span highlight="Enabled">enabled</span>}
on restart)
</>),
e.description] for ([, e] in Iterator(extensions)))
);
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE); commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
} }
else { else {
if (filter) if (filter)
liberator.echoerr("Exxx: No extension matching \"" + filter + "\""); liberator.echoerr("Exxx: No extension matching \"" + filter + "\"");
else else
liberator.echoerr("No extensions installed"); liberator.echoerr("No extensions installed");
} }
});
}, },
{ argCount: "?" }); { argCount: "?" });
///////////////////////////////////////////////////////////////////////////
commands.add(["exu[sage]"], commands.add(["exu[sage]"],
"List all Ex commands with a short description", "List all Ex commands with a short description",
function (args) { Liberator.showHelpIndex("ex-cmd-index", commands, args.bang); }, { function (args) { Liberator.showHelpIndex("ex-cmd-index", commands, args.bang); }, {
@@ -1704,17 +1824,22 @@ const Liberator = Module("liberator", {
completion: function () { completion: function () {
completion.dialog = function dialog(context) { completion.dialog = function dialog(context) {
context.title = ["Dialog"]; context.title = ["Dialog"];
context.completions = config.dialogs; context.completions = [[k, v[0]] for ([k, v] in Iterator(config.dialogs))];
}; };
completion.extension = function extension(context) { completion.extension = function extension(context) {
context.title = ["Extension"]; context.title = ["Extension"];
context.anchored = false; context.anchored = false;
context.keys = { text: "name", description: "description", icon: "icon" }, context.keys = { text: "name", description: "description", icon: "iconURL" },
context.completions = liberator.extensions; context.incomplete = true;
AddonManager.getAddonsByTypes(["extension"], function (addons) {
context.incomplete = false;
context.completions = addons;
});
}; };
completion.help = function help(context, unchunked) { completion.help = function help(context, unchunked) {
liberator.initHelp();
context.title = ["Help"]; context.title = ["Help"];
context.anchored = false; context.anchored = false;
context.completions = services.get("liberator:").HELP_TAGS; context.completions = services.get("liberator:").HELP_TAGS;
@@ -1729,6 +1854,7 @@ const Liberator = Module("liberator", {
context.completions = liberator.menuItems; context.completions = liberator.menuItems;
}; };
var toolbox = document.getElementById("navigator-toolbox");
completion.toolbar = function toolbar(context) { completion.toolbar = function toolbar(context) {
context.title = ["Toolbar"]; context.title = ["Toolbar"];
context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" }; context.keys = { text: function (item) item.getAttribute("toolbarname"), description: function () "" };
@@ -1817,8 +1943,6 @@ const Liberator = Module("liberator", {
if (options["loadplugins"]) if (options["loadplugins"])
liberator.loadPlugins(); liberator.loadPlugins();
liberator.initHelp();
// after sourcing the initialization files, this function will set // after sourcing the initialization files, this function will set
// all gui options to their default values, if they have not been // all gui options to their default values, if they have not been
// set before by any RC file // set before by any RC file
@@ -1841,6 +1965,7 @@ const Liberator = Module("liberator", {
statusline.update(); statusline.update();
liberator.log(config.name + " fully initialized", 0); liberator.log(config.name + " fully initialized", 0);
liberator.initialized = true;
} }
}); });

View File

@@ -67,7 +67,7 @@
<textbox class="plain" id="liberator-message" flex="1" readonly="true" liberator:highlight="Normal"/> <textbox class="plain" id="liberator-message" flex="1" readonly="true" liberator:highlight="Normal"/>
<hbox id="liberator-commandline" hidden="false" collapsed="true" class="liberator-container" liberator:highlight="Normal"> <hbox id="liberator-commandline" hidden="false" collapsed="true" class="liberator-container" liberator:highlight="Normal">
<label class="plain" id="liberator-commandline-prompt" flex="0" crop="end" value="" collapsed="true"/> <label class="plain" id="liberator-commandline-prompt" flex="0" crop="end" value="" collapsed="true"/>
<textbox class="plain" id="liberator-commandline-command" flex="1" type="timed" timeout="100" <textbox class="plain" id="liberator-commandline-command" flex="1" type="search" timeout="100"
oninput="window.liberator &and; liberator.modules.commandline.onEvent(event);" oninput="window.liberator &and; liberator.modules.commandline.onEvent(event);"
onkeyup="window.liberator &and; liberator.modules.commandline.onEvent(event);" onkeyup="window.liberator &and; liberator.modules.commandline.onEvent(event);"
onfocus="window.liberator &and; liberator.modules.commandline.onEvent(event);" onfocus="window.liberator &and; liberator.modules.commandline.onEvent(event);"
@@ -86,12 +86,13 @@
<statusbar id="status-bar" liberator:highlight="StatusLine"> <statusbar id="status-bar" liberator:highlight="StatusLine">
<hbox insertbefore="&liberator.statusBefore;" insertafter="&liberator.statusAfter;" <hbox insertbefore="&liberator.statusBefore;" insertafter="&liberator.statusAfter;"
id="liberator-statusline" flex="1" hidden="false" align="center"> id="liberator-statusline-field-status" flex="1" hidden="false" align="center">
<textbox class="plain" id="liberator-statusline-field-url" readonly="false" flex="1" crop="end"/> <textbox class="plain" id="liberator-statusline-field-url" readonly="false" flex="1" crop="end"/>
<label class="plain" id="liberator-statusline-field-inputbuffer" flex="0"/> <label class="plain" id="liberator-statusline-field-inputbuffer" flex="0"/>
<label class="plain" id="liberator-statusline-field-progress" flex="0"/> <label class="plain" id="liberator-statusline-field-progress" flex="0"/>
<label class="plain" id="liberator-statusline-field-tabcount" flex="0"/> <label class="plain" id="liberator-statusline-field-tabcount" flex="0"/>
<label class="plain" id="liberator-statusline-field-bufferposition" flex="0"/> <label class="plain" id="liberator-statusline-field-bufferposition" flex="0"/>
<label class="plain" id="liberator-statusline-field-zoomlevel" flex="0"/>
</hbox> </hbox>
<!-- just hide them since other elements expect them --> <!-- just hide them since other elements expect them -->
<statusbarpanel id="statusbar-display" hidden="true"/> <statusbarpanel id="statusbar-display" hidden="true"/>

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** /**
* @scope modules * @scope modules

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */

View File

@@ -2,6 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** /**
* @class ModuleBase * @class ModuleBase

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -22,7 +25,6 @@
* getter - see {@link Option#getter} * getter - see {@link Option#getter}
* completer - see {@link Option#completer} * completer - see {@link Option#completer}
* valdator - see {@link Option#validator} * valdator - see {@link Option#validator}
* checkHas - see {@link Option#checkHas}
* @optional * @optional
* @private * @private
*/ */
@@ -33,6 +35,17 @@ const Option = Class("Option", {
this.type = type; this.type = type;
this.description = description; 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) if (arguments.length > 3)
this.defaultValue = defaultValue; this.defaultValue = defaultValue;
@@ -44,7 +57,7 @@ const Option = Class("Option", {
this.names = array([name, "no" + name] for (name in values(names))).flatten().__proto__; this.names = array([name, "no" + name] for (name in values(names))).flatten().__proto__;
if (this.globalValue == undefined) if (this.globalValue == undefined)
this.globalValue = this.defaultValue; this.globalValue = this.parseValues(this.defaultValue);
}, },
/** @property {value} The option's global value. @see #scope */ /** @property {value} The option's global value. @see #scope */
@@ -58,13 +71,7 @@ const Option = Class("Option", {
* @param {value} value The option value. * @param {value} value The option value.
* @returns {value|string[]} * @returns {value|string[]}
*/ */
parseValues: function (value) { parseValues: function (value) value,
if (this.type == "stringlist")
return (value === "") ? [] : value.split(",");
if (this.type == "charlist")
return Array.slice(value);
return value;
},
/** /**
* Returns <b>values</b> packed in the appropriate format for the option * Returns <b>values</b> packed in the appropriate format for the option
@@ -73,16 +80,10 @@ const Option = Class("Option", {
* @param {value|string[]} values The option value. * @param {value|string[]} values The option value.
* @returns {value} * @returns {value}
*/ */
joinValues: function (values) { joinValues: function (vals) vals,
if (this.type == "stringlist")
return values.join(",");
if (this.type == "charlist")
return values.join("");
return values;
},
/** @property {value|string[]} The option value or array of values. */ /** @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), set values(values) this.setValues(values, this.scope),
/** /**
@@ -93,7 +94,26 @@ const Option = Class("Option", {
* {@link Option#scope}). * {@link Option#scope}).
* @returns {value|string[]} * @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 * 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 * @param {number} scope The scope to apply these values to (see
* {@link Option#scope}). * {@link Option#scope}).
*/ */
setValues: function (values, scope) { setValues: function (newValues, scope, skipGlobal) {
this.set(this.joinValues(values), scope || this.scope); 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}). * {@link Option#scope}).
* @returns {value} * @returns {value}
*/ */
get: function (scope) { get: function (scope) this.joinValues(this.getValues(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;
},
/** /**
* Sets the option value to <b>newValue</b> for the specified <b>scope</b>. * Sets the option value to <b>newValue</b> for the specified <b>scope</b>.
@@ -145,21 +160,7 @@ const Option = Class("Option", {
* @param {number} scope The scope to apply this value to (see * @param {number} scope The scope to apply this value to (see
* {@link Option#scope}). * {@link Option#scope}).
*/ */
set: function (newValue, scope) { set: function (newValue, scope) this.setValues(this.parseValues(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;
},
/** /**
* @property {value} The option's current value. The option's local value, * @property {value} The option's current value. The option's local value,
@@ -169,21 +170,15 @@ const Option = Class("Option", {
get value() this.get(), get value() this.get(),
set value(val) this.set(val), set value(val) this.set(val),
getKey: function (key) undefined,
/** /**
* Returns whether the option value contains one or more of the specified * Returns whether the option value contains one or more of the specified
* arguments. * arguments.
* *
* @returns {boolean} * @returns {boolean}
*/ */
has: function () { has: function () Array.some(arguments, function (val) this.values.indexOf(val) >= 0, this),
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));
},
/** /**
* Returns whether this option is identified by <b>name</b>. * Returns whether this option is identified by <b>name</b>.
@@ -216,97 +211,16 @@ const Option = Class("Option", {
* @param {boolean} invert Whether this is an invert boolean operation. * @param {boolean} invert Whether this is an invert boolean operation.
*/ */
op: function (operator, values, scope, invert) { op: function (operator, values, scope, invert) {
let newValue = null;
let self = this;
switch (this.type) { let newValues = this._op(operator, values, scope, invert);
case "boolean":
if (operator != "=")
break;
if (invert) if (newValues == null)
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)
return "Operator " + operator + " not supported for option type " + this.type; return "Operator " + operator + " not supported for option type " + this.type;
if (!this.isValidValue(newValue))
if (!this.isValidValue(newValues))
return "E474: Invalid argument: " + values; return "E474: Invalid argument: " + values;
this.setValues(newValue, scope);
this.setValues(newValues, scope);
return null; return null;
}, },
@@ -319,11 +233,13 @@ const Option = Class("Option", {
/** /**
* @property {string} The option's data type. One of: * @property {string} The option's data type. One of:
* "boolean" - Boolean E.g. true * "boolean" - Boolean, e.g., true
* "number" - Integer E.g. 1 * "number" - Integer, e.g., 1
* "string" - String E.g. "Vimperator" * "string" - String, e.g., "Vimperator"
* "charlist" - Character list E.g. "rb" * "charlist" - Character list, e.g., "rb"
* "stringlist" - String list E.g. "homepage,quickmark,tabopen,paste" * "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, type: null,
@@ -370,12 +286,6 @@ const Option = Class("Option", {
return Option.validateCompleter.apply(this, arguments); return Option.validateCompleter.apply(this, arguments);
return true; 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 * @property {boolean} Set to true whenever the option is first set. This
@@ -410,6 +320,132 @@ const Option = Class("Option", {
*/ */
SCOPE_BOTH: 3, 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? // TODO: Run this by default?
/** /**
* Validates the specified <b>values</b> against values generated by the * Validates the specified <b>values</b> against values generated by the
@@ -423,10 +459,21 @@ const Option = Class("Option", {
let res = context.fork("", 0, this, this.completer); let res = context.fork("", 0, this, this.completer);
if (!res) if (!res)
res = context.allItems.items.map(function (item) [item.text]); 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)); 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 * @instance options
*/ */
@@ -462,7 +509,7 @@ const Options = Module("options", {
// Trigger any setters. // Trigger any setters.
let opt = options.get(option); let opt = options.get(option);
if (event == "change" && opt) if (event == "change" && opt)
opt.set(opt.value, Option.SCOPE_GLOBAL); opt.setValues(opt.globalValue, Option.SCOPE_GLOBAL, true);
} }
storage.newMap("options", { store: false }); storage.newMap("options", { store: false });
@@ -1008,8 +1055,6 @@ const Options = Module("options", {
} }
} }
// write access // 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 { else {
option.setFrom = modifiers.setFrom || null; option.setFrom = modifiers.setFrom || null;
@@ -1017,7 +1062,12 @@ const Options = Module("options", {
liberator.assert(!opt.valueGiven, "E474: Invalid argument: " + arg); liberator.assert(!opt.valueGiven, "E474: Invalid argument: " + arg);
opt.values = !opt.unsetBoolean; 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) if (res)
liberator.echoerr(res); liberator.echoerr(res);
} }
@@ -1244,8 +1294,15 @@ const Options = Module("options", {
if (!completer) if (!completer)
return; return;
let curValues = curValue != null ? opt.parseValues(curValue) : opt.values; try {
let newValues = opt.parseValues(context.filter); 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; let len = context.filter.length;
switch (opt.type) { switch (opt.type) {
@@ -1253,9 +1310,18 @@ const Options = Module("options", {
if (!completer) if (!completer)
completer = function () [["true", ""], ["false", ""]]; completer = function () [["true", ""], ["false", ""]];
break; break;
case "regexlist":
newValues = context.filter.split(",");
// Fallthrough
case "stringlist": case "stringlist":
let target = newValues.pop(); let target = newValues.pop() || "";
len = target ? target.length : 0; len = target.length;
break;
case "stringmap":
case "regexmap":
let vals = context.filter.split(",");
target = vals.pop() || "";
len = target.length - (target.indexOf("=") + 1);
break; break;
case "charlist": case "charlist":
len = 0; len = 0;
@@ -1268,9 +1334,10 @@ const Options = Module("options", {
let completions = completer(context); let completions = completer(context);
if (!completions) if (!completions)
return; return;
// Not Vim compatible, but is a significant enough improvement // Not Vim compatible, but is a significant enough improvement
// that it's worth breaking compatibility. // that it's worth breaking compatibility.
if (newValues instanceof Array) { if (isarray(newValues)) {
completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1); completions = completions.filter(function (val) newValues.indexOf(val[0]) == -1);
switch (op) { switch (op) {
case "+": case "+":

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */

View File

@@ -2,6 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
// TODO: // TODO:
// - fix Sanitize autocommand // - fix Sanitize autocommand
@@ -19,6 +20,7 @@ const Sanitizer = Module("sanitizer", {
init: function () { init: function () {
const self = this; const self = this;
liberator.loadScript("chrome://browser/content/sanitize.js", Sanitizer); liberator.loadScript("chrome://browser/content/sanitize.js", Sanitizer);
Sanitizer.getClearRange = Sanitizer.Sanitizer.getClearRange;
this.__proto__.__proto__ = new Sanitizer.Sanitizer; // Good enough. this.__proto__.__proto__ = new Sanitizer.Sanitizer; // Good enough.
// TODO: remove this version test // TODO: remove this version test
@@ -213,10 +215,9 @@ const Sanitizer = Module("sanitizer", {
{ {
setter: function (values) { setter: function (values) {
for (let [, pref] in Iterator(sanitizer.prefNames)) { for (let [, pref] in Iterator(sanitizer.prefNames)) {
continue;
options.setPref(pref, false); options.setPref(pref, false);
for (let [, value] in Iterator(this.parseValues(values))) { for (let [, value] in Iterator(values)) {
if (Sanitizer.prefToArg(pref) == value) { if (Sanitizer.prefToArg(pref) == value) {
options.setPref(pref, true); options.setPref(pref, true);
break; break;
@@ -226,7 +227,7 @@ const Sanitizer = Module("sanitizer", {
return values; 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) [ completer: function (value) [
["cache", "Cache"], ["cache", "Cache"],
["commandline", "Command-line history"], ["commandline", "Command-line history"],

View File

@@ -2,6 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -47,6 +48,10 @@ const Services = Module("services", {
this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler); this.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler);
this.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind); this.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind);
this.addClass("process", "@mozilla.org/process/util;1", Ci.nsIProcess); 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) { _create: function (classes, ifaces, meth) {

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -11,12 +14,8 @@ const StatusLine = Module("statusline", {
this._statusBar.collapsed = true; // it is later restored unless the user sets laststatus=0 this._statusBar.collapsed = true; // it is later restored unless the user sets laststatus=0
// our status bar fields // our status bar fields
this._statuslineWidget = document.getElementById("liberator-statusline"); this.widgets = dict(["status", "url", "inputbuffer", "progress", "tabcount", "bufferposition", "zoomlevel"].map(
this._urlWidget = document.getElementById("liberator-statusline-field-url"); function (field) [field, document.getElementById("liberator-statusline-field-" + field)]));
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");
}, },
/** /**
@@ -47,6 +46,7 @@ const StatusLine = Module("statusline", {
this.updateProgress(); this.updateProgress();
this.updateTabCount(); this.updateTabCount();
this.updateBufferPosition(); this.updateBufferPosition();
this.updateZoomLevel();
}, },
/** /**
@@ -61,20 +61,17 @@ const StatusLine = Module("statusline", {
updateUrl: function updateUrl(url) { updateUrl: function updateUrl(url) {
// ripped from Firefox; modified // ripped from Firefox; modified
function losslessDecodeURI(url) { 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 // 1. decodeURI decodes %25 to %, which creates unintended
// encoding sequences. // 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 // 2. Re-encode whitespace so that it doesn't get eaten away
// by the location bar (bug 410726). // by the location bar (bug 410726).
url = url.replace(/[\r\n\t]/g, encodeURIComponent); url = url.replace(/[\r\n\t]/g, encodeURIComponent);
@@ -125,7 +122,7 @@ const StatusLine = Module("statusline", {
if (modified) if (modified)
url += " [" + 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") if (!buffer || typeof buffer != "string")
buffer = ""; buffer = "";
this._inputBufferWidget.value = buffer; this.widgets.inputbuffer.value = buffer;
}, },
/** /**
@@ -157,7 +154,7 @@ const StatusLine = Module("statusline", {
progress = ""; progress = "";
if (typeof progress == "string") if (typeof progress == "string")
this._progressWidget.value = progress; this.widgets.progress.value = progress;
else if (typeof progress == "number") { else if (typeof progress == "number") {
let progressStr = ""; let progressStr = "";
if (progress <= 0) if (progress <= 0)
@@ -170,7 +167,7 @@ const StatusLine = Module("statusline", {
+ " ".substr(0, 19 - progress) + " ".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)) for (let [i, tab] in util.Array.iteritems(config.browser.mTabs))
tab.setAttribute("ordinal", i + 1); 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 * @param {number} percent The position, as a percentage. @optional
*/ */
updateBufferPosition: function updateBufferPosition(percent) { updateBufferPosition: function updateBufferPosition(percent) {
if (!percent || typeof percent != "number") { if (typeof percent != "number") {
let win = document.commandDispatcher.focusedWindow; let win = document.commandDispatcher.focusedWindow;
if (!win) if (!win)
return; return;
@@ -218,14 +215,35 @@ const StatusLine = Module("statusline", {
bufferPositionStr = "All"; bufferPositionStr = "All";
else if (percent == 0) else if (percent == 0)
bufferPositionStr = "Top"; bufferPositionStr = "Top";
else if (percent < 10)
bufferPositionStr = " " + percent + "%";
else if (percent >= 100) else if (percent >= 100)
bufferPositionStr = "Bot"; bufferPositionStr = "Bot";
else if (percent < 10)
bufferPositionStr = " " + percent + "%";
else else
bufferPositionStr = percent + "%"; 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 + "%)";
}
} }
}, { }, {

View File

@@ -2,6 +2,7 @@
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -167,6 +168,8 @@ Highlights.prototype.CSS = <![CDATA[
HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top; HelpString display: inline-block; color: green; font-weight: normal; vertical-align: text-top;
HelpString::before content: '"'; HelpString::before content: '"';
HelpString::after content: '"'; HelpString::after content: '"';
HelpString[delim]::before content: attr(delim);
HelpString[delim]::after content: attr(delim);
HelpHead,html|h1,liberator://help/* { HelpHead,html|h1,liberator://help/* {
display: block; display: block;
@@ -699,10 +702,10 @@ Module("styles", {
JavaScript.setCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]), JavaScript.setCompleter(["get", "addSheet", "removeSheet", "findSheets"].map(function (m) styles[m]),
[ // Prototype: (system, name, filter, css, index) [ // Prototype: (system, name, filter, css, index)
null, null,
function (context, obj, args) args[0] ? styles.systemNames : styles.userNames, function (context, obj, args) args[0] ? this.systemNames : this.userNames,
function (context, obj, args) styles.completeSite(context, content), function (context, obj, args) this.completeSite(context, content),
null, null,
function (context, obj, args) args[0] ? styles.systemSheets : styles.userSheets function (context, obj, args) args[0] ? this.systemSheets : this.userSheets
]); ]);
} }
}); });

View File

@@ -1,10 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com> // Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -797,14 +797,7 @@ const Tabs = Module("tabs", {
commands.add(["tabopen", "t[open]", "tabnew"], commands.add(["tabopen", "t[open]", "tabnew"],
"Open one or more URLs in a new tab", "Open one or more URLs in a new tab",
function (args) { function (args) {
let special = args.bang; liberator.open(args.string || "about:blank", { from: "tabopen", where: liberator.NEW_TAB, background: 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 });
}, { }, {
bang: true, bang: true,
completer: function (context) completion.url(context), completer: function (context) completion.url(context),
@@ -1094,9 +1087,9 @@ const Tabs = Module("tabs", {
"Where to show requested popup windows", "Where to show requested popup windows",
"stringlist", "tab", "stringlist", "tab",
{ {
setter: function (value) { setter: function (values) {
let [open, restriction] = [1, 0]; let [open, restriction] = [1, 0];
for (let [, opt] in Iterator(this.parseValues(value))) { for (let [, opt] in Iterator(values)) {
if (opt == "tab") if (opt == "tab")
open = 3; open = 3;
else if (opt == "window") 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", open, "See 'popups' option.");
options.safeSetPref("browser.link.open_newwindow.restriction", restriction, "See 'popups' option."); options.safeSetPref("browser.link.open_newwindow.restriction", restriction, "See 'popups' option.");
return value; return values;
}, },
completer: function (context) [ completer: function (context) [
["tab", "Open popups in a new tab"], ["tab", "Open popups in a new tab"],

View File

@@ -1,8 +1,8 @@
// Copyright (c) 2006-2009 by Kris Maglione <maglione.k at Gmail> // Copyright (c) 2008-2009 by Kris Maglione <maglione.k at Gmail>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -194,8 +194,6 @@ const Template = Module("template", {
return <>:{commandline.command}<br/>{xml}</>; return <>:{commandline.command}<br/>{xml}</>;
}, },
// every item must have a .xml property which defines how to draw itself
// @param headers is an array of strings, the text for the header columns
genericTable: function genericTable(items, format) { genericTable: function genericTable(items, format) {
completion.listCompleter(function (context) { completion.listCompleter(function (context) {
context.filterFunc = null; context.filterFunc = null;

View File

@@ -1,7 +1,10 @@
// Copyright (c) 2006-2009 by Martin Stubenschrott <stubenschrott@vimperator.org> // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
// Copyright (c) 2007-2009 by Doug Kearns <dougkearns@gmail.com>
// Copyright (c) 2008-2009 by Kris Maglione <maglione.k@gmail.com>
// //
// This work is licensed for reuse under an MIT license. Details are // This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE.txt file included with this file. // given in the LICENSE.txt file included with this file.
"use strict";
/** @scope modules */ /** @scope modules */
@@ -91,6 +94,55 @@ const Util = Module("util", {
return fixup.createFixupURI(str, fixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP); return fixup.createFixupURI(str, fixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
}, },
/**
* Expands brace globbing patterns in a string.
*
* Example:
* "a{b,c}d" => ["abd", "acd"]
*
* @param {string} pattern The pattern to deglob.
* @returns [string] The resulting strings.
*/
debrace: function deglobBrace(pattern) {
function split(pattern, re, fn, dequote) {
let end = 0, match, res = [];
while (match = re.exec(pattern)) {
end = match.index + match[0].length;
res.push(match[1]);
if (fn)
fn(match);
}
res.push(pattern.substr(end));
return res.map(function (s) util.dequote(s, dequote));
}
let patterns = [], res = [];
let substrings = split(pattern, /((?:[^\\{]|\\.)*)\{((?:[^\\}]|\\.)*)\}/gy,
function (match) {
patterns.push(split(match[2], /((?:[^\\,]|\\.)*),/gy,
null, ",{}"));
}, "{}");
function rec(acc) {
if (acc.length == patterns.length)
res.push(util.Array.zip(substrings, acc).join(""));
else
for (let [, pattern] in Iterator(patterns[acc.length]))
rec(acc.concat(pattern));
}
rec([]);
return res;
},
/**
* Removes certain backslash-quoted characters while leaving other
* backslash-quoting sequences untouched.
*
* @param {string} pattern The string to unquote.
* @param {string} chars The characters to unquote.
* @returns {string}
*/
dequote: function dequote(pattern, chars)
pattern.replace(/\\(.)/, function (m0, m1) chars.indexOf(m1) >= 0 ? m1 : m0),
/** /**
* Converts HTML special characters in <b>str</b> to the equivalent HTML * Converts HTML special characters in <b>str</b> to the equivalent HTML
* entities. * entities.
@@ -158,7 +210,8 @@ const Util = Module("util", {
* @returns {string} * @returns {string}
*/ */
makeXPath: function makeXPath(nodes) { 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(" | "); .map(function (node) "//" + node).join(" | ");
}, },
@@ -257,8 +310,9 @@ const Util = Module("util", {
const PATH = FILE.leafName.replace(/\..*/, "") + "/"; const PATH = FILE.leafName.replace(/\..*/, "") + "/";
const TIME = Date.now(); const TIME = Date.now();
liberator.initHelp();
let zip = services.create("zipWriter"); 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) function addURIEntry(file, uri)
zip.addEntryChannel(PATH + file, TIME, 9, zip.addEntryChannel(PATH + file, TIME, 9,
services.get("io").newChannel(uri, null, null), false); services.get("io").newChannel(uri, null, null), false);
@@ -407,11 +461,12 @@ const Util = Module("util", {
null null
); );
result.__iterator__ = asIterator return {
__proto__: result,
__iterator__: asIterator
? function () { let elem; while ((elem = this.iterateNext())) yield elem; } ? function () { let elem; while ((elem = this.iterateNext())) yield elem; }
: function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); }; : function () { for (let i = 0; i < this.snapshotLength; i++) yield this.snapshotItem(i); }
}
return result;
}, },
/** /**
@@ -664,61 +719,16 @@ const Util = Module("util", {
}, },
/** /**
* Returns an array of URLs parsed from <b>str</b>. * 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 * @param {Node} elem The element to make visible.
* ['www.google.com/search?q=bla', 'www.osnews.com']
*
* @param {string} str
* @returns {string[]}
*/ */
stringToURLArray: function stringToURLArray(str) { scrollIntoView: function scrollIntoView(elem) {
let urls; let win = elem.ownerDocument.defaultView;
let rect = elem.getBoundingClientRect();
if (options["urlseparator"]) if (!(rect && rect.top < win.innerHeight && rect.bottom >= 0 && rect.left < win.innerWidth && rect.right >= 0))
urls = util.splitLiteral(str, RegExp("\\s*" + options["urlseparator"] + "\\s*")); elem.scrollIntoView();
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;
});
}, },
/** /**
@@ -740,7 +750,7 @@ const Util = Module("util", {
} }
switch (node.nodeKind()) { switch (node.nodeKind()) {
case "text": case "text":
return doc.createTextNode(node); return doc.createTextNode(String(node));
case "element": case "element":
let domnode = doc.createElementNS(node.namespace(), node.localName()); let domnode = doc.createElementNS(node.namespace(), node.localName());
for each (let attr in node.@*) for each (let attr in node.@*)
@@ -814,7 +824,7 @@ const Util = Module("util", {
* @param {Array} ary * @param {Array} ary
* @returns {Array} * @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. * Returns an Iterator for an array's values.
@@ -864,6 +874,22 @@ const Util = Module("util", {
} }
} }
return ret; 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;
} }
}) })
}); });

View File

@@ -417,8 +417,8 @@ preference.
<item> <item>
<tags>zI</tags> <tags>ZI zI</tags>
<spec><oa>count</oa>zI</spec> <spec><oa>count</oa>ZI</spec>
<description> <description>
<p>Enlarge full zoom of current web page. Mnemonic: zoom in.</p> <p>Enlarge full zoom of current web page. Mnemonic: zoom in.</p>
</description> </description>
@@ -426,8 +426,8 @@ preference.
<item> <item>
<tags>zM</tags> <tags>ZM zM</tags>
<spec><oa>count</oa>zM</spec> <spec><oa>count</oa>ZM</spec>
<description> <description>
<p>Enlarge full zoom of current web page by a larger amount. Mnemonic: zoom more.</p> <p>Enlarge full zoom of current web page by a larger amount. Mnemonic: zoom more.</p>
</description> </description>
@@ -435,8 +435,8 @@ preference.
<item> <item>
<tags>zO</tags> <tags>ZO zO</tags>
<spec><oa>count</oa>zO</spec> <spec><oa>count</oa>ZO</spec>
<description> <description>
<p>Reduce full zoom of current web page. Mnemonic: zoom out.</p> <p>Reduce full zoom of current web page. Mnemonic: zoom out.</p>
</description> </description>
@@ -444,8 +444,8 @@ preference.
<item> <item>
<tags>zR</tags> <tags>ZR zR</tags>
<spec><oa>count</oa>zR</spec> <spec><oa>count</oa>ZR</spec>
<description> <description>
<p>Reduce full zoom of current web page by a larger amount. Mnemonic: zoom reduce.</p> <p>Reduce full zoom of current web page by a larger amount. Mnemonic: zoom reduce.</p>
</description> </description>
@@ -453,8 +453,8 @@ preference.
<item> <item>
<tags>zZ</tags> <tags>ZZ zZ</tags>
<spec><oa>count</oa>zZ</spec> <spec><oa>count</oa>ZZ</spec>
<description> <description>
<p> <p>
Set full zoom value of current web page. Zoom value can be between 30 and Set full zoom value of current web page. Zoom value can be between 30 and

View File

@@ -151,8 +151,9 @@
<item> <item>
<tags>:extens :extensions</tags> <tags>:exts :extens :extensions</tags>
<spec>:extens<oa>ions</oa></spec> <spec>:extens<oa>ions</oa></spec>
<spec>:exts</spec>
<description> <description>
<p>List all installed extensions.</p> <p>List all installed extensions.</p>
</description> </description>

View File

@@ -393,7 +393,6 @@ This file contains a list of all available commands, mappings and options.
<dt><o>exrc</o></dt> <dd>Allow reading of an RC file in the current directory</dd> <dt><o>exrc</o></dt> <dd>Allow reading of an RC file in the current directory</dd>
<dt><o>extendedhinttags</o></dt> <dd>XPath string of hintable elements activated by <k>;</k></dd> <dt><o>extendedhinttags</o></dt> <dd>XPath string of hintable elements activated by <k>;</k></dd>
<dt><o>fileencoding</o></dt> <dd>Changes the character encoding that &liberator.appname; uses to read and write files</dd> <dt><o>fileencoding</o></dt> <dd>Changes the character encoding that &liberator.appname; uses to read and write files</dd>
<dt><o>focuscontent</o></dt> <dd>Try to stay in Normal mode after loading a web page</dd>
<dt><o>followhints</o></dt> <dd>Change the behaviour of <k name="Return"/> in Hints mode</dd> <dt><o>followhints</o></dt> <dd>Change the behaviour of <k name="Return"/> in Hints mode</dd>
<dt><o>fullscreen</o></dt> <dd>Show the current window fullscreen</dd> <dt><o>fullscreen</o></dt> <dd>Show the current window fullscreen</dd>
<dt><o>guioptions</o></dt> <dd>Show or hide certain GUI elements like the menu or toolbar</dd> <dt><o>guioptions</o></dt> <dd>Show or hide certain GUI elements like the menu or toolbar</dd>
@@ -430,6 +429,7 @@ This file contains a list of all available commands, mappings and options.
<dt><o>showstatuslinks</o></dt> <dd>Show the destination of the link under the cursor in the status bar</dd> <dt><o>showstatuslinks</o></dt> <dd>Show the destination of the link under the cursor in the status bar</dd>
<dt><o>showtabline</o></dt> <dd>Control when to show the tab bar of opened web pages</dd> <dt><o>showtabline</o></dt> <dd>Control when to show the tab bar of opened web pages</dd>
<dt><o>smartcase</o></dt> <dd>Override the <o>ignorecase</o> option if the pattern contains uppercase characters</dd> <dt><o>smartcase</o></dt> <dd>Override the <o>ignorecase</o> option if the pattern contains uppercase characters</dd>
<dt><o>strictfocus</o></dt> <dd>Prevent scripts from focusing input elements without user intervention</dd>
<dt><o>suggestengines</o></dt> <dd>Engine Alias which has a feature of suggest</dd> <dt><o>suggestengines</o></dt> <dd>Engine Alias which has a feature of suggest</dd>
<dt><o>titlestring</o></dt> <dd>Change the title of the window</dd> <dt><o>titlestring</o></dt> <dd>Change the title of the window</dd>
<dt><o>urlseparator</o></dt> <dd>Set the separator regex used to separate multiple URL args</dd> <dt><o>urlseparator</o></dt> <dd>Set the separator regex used to separate multiple URL args</dd>

View File

@@ -17,11 +17,27 @@
</p> </p>
<dl> <dl>
<dt>boolean</dt> <dd>can only be on or off</dd> <dt>boolean</dt> <dd>Can only be on or off</dd>
<dt>number</dt> <dd>has a numeric value</dd> <dt>number</dt> <dd>A numeric value</dd>
<dt>string</dt> <dd>has a string value</dd> <dt>string</dt> <dd>A string value</dd>
<dt>charlist</dt> <dd>like a string but with unique characters</dd> <dt>charlist</dt> <dd>A string containing a discrete set of distinct characters</dd>
<dt>stringlist</dt> <dd>a comma-separated list of strings</dd> <dt>stringlist</dt> <dd>A comma-separated list of strings</dd>
<dt>stringmap</dt> <dd>A comma-separated list of key-value pairs, e.g., <str>key=val,foo=bar</str></dd>
<dt>regexlist</dt>
<dd>
A comma-separated list of regular expressions. Expressions may be
prefixed with a <em>!</em>, in which case the match will be negated. A
literal <em>!</em> at the begining of the expression may be matched with
<em>[!]</em>. Generally, the first matching regular expression is used.
</dd>
<dt>regexmap</dt>
<dd>
A combination of a <em>stringmap</em> and a <em>regexlist</em>. Each key
in the <a>key</a>=<a>value</a> pair is a regexp. If the regexp begins with a
<em>!</em>, the sense match is negated, such that a non-matching
expression will be considered a match and <html:i>vice versa</html:i>.
The first <a>key</a> to match yields value.
</dd>
</dl> </dl>
<h2 tag="set-option E764">Setting options</h2> <h2 tag="set-option E764">Setting options</h2>
@@ -293,6 +309,40 @@
</description> </description>
</item> </item>
<item>
<tags>'au' 'autocomplete'</tags>
<spec>'autocomplete' 'au'</spec>
<type>regexlist</type>
<default>.*</default>
<description>
<p>
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.
</p>
<example>
To enable autocompletion for everything but <ex>:history</ex> or
<ex>:bmarks</ex>, you would choose a value such as,
<str delim="'">!/ex/bmarks,.?</str>
</example>
<note>
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 <str>/ex/</str>, it
will also exclude <str>/ex/bmarks</str>, and so on.
</note>
<p>See also <ex>:contexts</ex></p>
</description>
</item>
<item> <item>
<tags>$CDPATH</tags> <tags>$CDPATH</tags>
@@ -469,19 +519,14 @@
<item> <item>
<tags>'nofc' 'nofocuscontent'</tags> <tags>'nosf' 'nostrictfocus'</tags>
<tags>'fc' 'focuscontent'</tags> <tags>'sf' 'strictfocus'</tags>
<spec>'focuscontent' 'fc'</spec> <spec>'strictfocus' 'sf'</spec>
<type>boolean</type> <type>boolean</type>
<default>off</default> <default>on</default>
<description> <description>
<p> <p>
Focus the content after a page has loaded. This is useful if you Prevent scripts from focusing input elements without user intervention.
always want to stay in Normal mode when browsing between web sites.
When <str>on</str>, it blurs any textbox which often is
automatically focused on page load. If you usually like
<o>focuscontent</o> but sometimes you'd like to focus the first
input field, you can use <k>gi</k> to jump to it.
</p> </p>
</description> </description>
</item> </item>
@@ -1249,41 +1294,50 @@
</description> </description>
</item> </item>
<item> <item>
<tags>'wildcase' 'wic'</tags> <tags>'wic' 'wildcase'</tags>
<spec>'wildcase' 'wic'</spec> <spec>'wildcase' 'wic'</spec>
<type>string</type> <type>regexmap</type>
<default>smart</default> <default>smart</default>
<description> <description>
<p>Defines how completions are matched with regard to character case. Possible values:</p> <p>
Defines how completions are matched for a given completion context
with regard to character case.
</p>
<p>Possible values:</p>
<dl> <dl>
<dt><str>smart</str></dt> <dd>Case is significant when capital letters are typed</dd> <dt><str>smart</str></dt> <dd>Case is significant when capital letters are typed</dd>
<dt><str>match</str></dt> <dd>Case is always significant</dd> <dt><str>match</str></dt> <dd>Case is always significant</dd>
<dt><str>ignore</str></dt> <dd>Case is never significant</dd> <dt><str>ignore</str></dt> <dd>Case is never significant</dd>
</dl> </dl>
<p>See also <ex>:contexts</ex></p>
</description> </description>
</item> </item>
<item> <item>
<tags>'wildignore' 'wig'</tags> <tags>'wildignore' 'wig'</tags>
<spec>'wildignore' 'wig'</spec> <spec>'wildignore' 'wig'</spec>
<type>stringlist</type> <type>regexlist</type>
<default></default> <default></default>
<description> <description>
<p> <p>
List of file patterns to ignore when completing files. E.g., to ignore object List of file patterns to ignore when completing files. E.g., to ignore object
files and Vim swap files files and Vim swap files
<ex>:set wildignore=<str>.<em>\\.o,\\..</em>\\.s[a-z]\\<a>2</a></str></ex>
</p> </p>
<code><ex>:set wildignore=<str delim="'">\.o$</str>,<str delim="'">^\..*\.s[a-z]<a>2</a>$</str></ex></code>
<note>Unlike Vim each pattern is a regex rather than a glob.</note> <note>Unlike Vim each pattern is a regex rather than a glob.</note>
<note>
The only way to include a literal comma in a pattern is with the
escape <str>\u0044</str>.
</note>
</description> </description>
</item> </item>
<item> <item>
<tags>'wim' 'wildmode'</tags> <tags>'wim' 'wildmode'</tags>
<spec>'wildmode' 'wim'</spec> <spec>'wildmode' 'wim'</spec>
@@ -1318,25 +1372,23 @@
</description> </description>
</item> </item>
<item> <item>
<tags>'wop' 'wildoptions'</tags> <tags>'wis' 'wildsort'</tags>
<spec>'wildoptions' 'wop'</spec> <spec>'wildsort' 'wis'</spec>
<type>stringlist</type> <type>regexlist</type>
<default></default> <default>.*</default>
<description> <description>
<p>A list of words that change how command-line completion is done.</p> <p>
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.
</p>
<p>Possible words:</p> <p>See also <ex>:contexts</ex></p>
<dl>
<dt>auto</dt> <dd>Automatically show completions while you are typing.</dd>
<dt>sort</dt> <dd>Always sort the completion list, overriding the <o>complete</o> option.</dd>
</dl>
</description> </description>
</item> </item>
<item> <item>
<tags>'wsp' 'wordseparators'</tags> <tags>'wsp' 'wordseparators'</tags>
<spec>'wordseparators' 'wsp'</spec> <spec>'wordseparators' 'wsp'</spec>

View File

@@ -17,16 +17,27 @@
<tags>:beep</tags> <tags>:beep</tags>
<spec>:beep</spec> <spec>:beep</spec>
<description> <description>
<p>Play a system beep.</p> <p>
Play a system beep. This should not be used for any purpose other
than testing the visual bell.
</p>
</description> </description>
</item> </item>
<item> <item>
<tags><![CDATA[<C-l> CTRL-L :redr :redraw]]></tags> <tags>:contexts</tags>
<spec>:redr<oa>aw</oa></spec> <spec>:contexts <a>ex-command</a></spec>
<description> <description>
<p>Redraws the screen. Useful to update the screen halfway executing a script or function.</p> <p>
Lists the completion contexts used during the completion of its
arguments. These context names are used in options such as
<o>autocomplete</o> and <o>wildcase</o>. 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
<k name="Tab"/> key at least once. You should also be aware that
this command is only useful from the commandline.
</p>
</description> </description>
</item> </item>
@@ -46,6 +57,15 @@
</item> </item>
<item>
<tags><![CDATA[<C-l> CTRL-L :redr :redraw]]></tags>
<spec>:redr<oa>aw</oa></spec>
<description>
<p>Redraws the screen. Useful to update the screen halfway executing a script or function.</p>
</description>
</item>
<item> <item>
<tags>:run :! :!cmd</tags> <tags>:run :! :!cmd</tags>
<spec>:!<a>cmd</a></spec> <spec>:!<a>cmd</a></spec>

View File

@@ -18,8 +18,8 @@ getfiles () {
find "$@" -not -path '*\.hg*' 2>/dev/null | grep -E "$filter" || true find "$@" -not -path '*\.hg*' 2>/dev/null | grep -E "$filter" || true
} }
copytext () { copytext () {
sed -e "s,###VERSION###,$VERSION,g" \ sed -e "s,@VERSION@,$VERSION,g" \
-e "s,###DATE###,$BUILD_DATE,g" \ -e "s,@DATE@,$BUILD_DATE,g" \
<"$1" >"$2" <"$1" >"$2"
cmp -s "$1" "$2" || cmp -s "$1" "$2" ||
( echo "modified: $1"; diff -u "$1" "$2" | grep '^[-+][^-+]' ) ( echo "modified: $1"; diff -u "$1" "$2" | grep '^[-+][^-+]' )

View File

@@ -19,6 +19,7 @@
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
}}} ***** END LICENSE BLOCK *****/ }}} ***** END LICENSE BLOCK *****/
"use strict";
var EXPORTED_SYMBOLS = ["storage", "Timer"]; var EXPORTED_SYMBOLS = ["storage", "Timer"];
@@ -102,12 +103,12 @@ function readFile(file) {
function writeFile(file, data) { function writeFile(file, data) {
if (!file.exists()) 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 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); 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.init(fileStream, "UTF-8", 0, 0);
stream.writeString(data); stream.writeString(data);

View File

@@ -1,4 +1,5 @@
@namespace liberator url("http://vimperator.org/namespaces/liberator"); @namespace liberator url("http://vimperator.org/namespaces/liberator");
@namespace html url("http://www.w3.org/1999/xhtml");
/* Applied to all content */ /* Applied to all content */
[liberator|activeframe] { [liberator|activeframe] {
@@ -122,6 +123,12 @@ statusbarpanel {
color: inherit; color: inherit;
margin: 0px; margin: 0px;
} }
#liberator-commandline-command html|*:focus {
outline-width: 0px !important
}
#liberator-commandline-command .textbox-search-icons {
visibility: collapse !important;
}
#liberator-message { #liberator-message {
margin: 0px; margin: 0px;
} }

View File

@@ -1,12 +1,16 @@
2009-XX-XX: 2009-XX-XX:
* version 2.3a1pre * Replaced 'focuscontent' with 'strictfocus'
* add basic plugin authorship documentation * Replaced previous incremental search implementation
* plugins may now provide full-fleged ':help' documentation * :open now only opens files begining with / or ./
* asciidoc is no longer required to build Vimperator * Page zoom information is now shown in the status bar
* the help system is newly modularized * Added ZO, ZI, ZM, and ZR as aliases for zO, zI, zM, and zR
* remove [c]:edit[c], [c]:tabedit[c], and [c]:winedit[c] * Add basic plugin authorship documentation
* add 'jsdebugger' option - switch on/off javascript debugger service * Plugins may now provide full-fleged ':help' documentation
* add "addons", "downloads", "extoptions" and "help" to the 'activate' option. * 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: 2009-10-28:
* version 2.2 * version 2.2
@@ -27,8 +31,6 @@
................................... ...................................
* IMPORTANT: shifted key notation now matches Vim's behaviour. E.g. <C-a> * IMPORTANT: shifted key notation now matches Vim's behaviour. E.g. <C-a>
and <C-A> are equivalent, to map the uppercase character use <C-S-A>. and <C-A> are equivalent, to map the uppercase character use <C-S-A>.
(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. * IMPORTANT: 'popups' now takes a stringlist rather than a number.
* add [c]:winonly[c] * add [c]:winonly[c]

View File

@@ -42,7 +42,6 @@ BUGS:
FEATURES: FEATURES:
8 Document Textarea, Caret and Visual modes. 8 Document Textarea, Caret and Visual modes.
8 Incremental searches should retreat to their starting position on <Backspace>
8 Replace config.name tests in liberator with more specific feature 8 Replace config.name tests in liberator with more specific feature
tests or overridable APIs where at all feasible. tests or overridable APIs where at all feasible.
8 change the extension ID to vimperator@vimperator.org rather than 8 change the extension ID to vimperator@vimperator.org rather than
@@ -65,25 +64,11 @@ FEATURES:
8 :redir and 'verbosefile' 8 :redir and 'verbosefile'
8 middleclick in content == p, and if command line is open, paste there the clipboard buffer 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 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 allow for multiple ex commands separated with | (see #24)
8 <C-o>/<C-i> should work as in vim (i.e., save page positions as well as 8 <C-o>/<C-i> should work as in vim (i.e., save page positions as well as
locations in the history list). 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 use ctrl-n/p in insert mode for word completion
7 implement QuickFix window based on ItemList 7 implement QuickFix window based on ItemList
7 wherever possible: get rid of dialogs and ask console-like dialog questions 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 opera's fast forward does something like this
7 make an option to disable session saving by default when you close Firefox 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 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 :mksession
6 add [count] support to :b* and :tab* commands where missing 6 add [count] support to :b* and :tab* commands where missing
6 registers 6 registers
@@ -101,12 +86,10 @@ FEATURES:
always be the default register. --Ted always be the default register. --Ted
6 check/correct spellings in insert mode with some mappings 6 check/correct spellings in insert mode with some mappings
6 add more autocommands (TabClose, TabOpen, TabChanged etc) 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 Use ctrl-w+j/k/w to switch between sidebar, content, preview window
6 Command :tags for getting a list of used tags 6 Command :tags for getting a list of used tags
6 ;?<hint> should show more information 6 ;?<hint> 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 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 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 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 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] 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 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 1 Reformat liberator/HACKING so that git diff can find sections and report changes @ somewhere
- many other ideas are listed in the wiki

View File

@@ -1,16 +1,28 @@
# Firefox # Firefox
content vimperator content/ content vimperator content/
skin vimperator classic/1.0 skin/ skin vimperator classic/1.0 skin/
locale vimperator en-US locale/en-US/ locale vimperator en-US locale/en-US/
locale liberator en-US ../common/locale/en-US/ locale liberator en-US ../common/locale/en-US/
content liberator ../common/content/ content liberator ../common/content/
resource liberator ../common/modules/ resource liberator ../common/modules/
skin liberator classic/1.0 ../common/skin/ skin liberator classic/1.0 ../common/skin/
override chrome://liberator/content/liberator.dtd chrome://vimperator/content/liberator.dtd 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/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://liberator/content/liberator.xul
overlay chrome://browser/content/browser.xul chrome://vimperator/content/vimperator.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}

View File

@@ -1,4 +1,5 @@
// Header: // Header:
"use strict";
const Name = "Vimperator"; const Name = "Vimperator";
/* /*
* We can't load our modules here, so the following code is sadly * 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, 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: // vim: set fdm=marker sw=4 ts=4 et:

View File

@@ -1,4 +1,5 @@
// Header: // Header:
"use strict";
const Name = "Vimperator"; const Name = "Vimperator";
/* /*
* We can't load our modules here, so the following code is sadly * 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: // vim: set ft=javascript fdm=marker sw=4 ts=4 et:

View File

@@ -46,58 +46,58 @@ const Config = Module("config", ConfigBase, {
["VimperatorLeavePre", "Triggered before exiting Firefox, just before destroying each module"], ["VimperatorLeavePre", "Triggered before exiting Firefox, just before destroying each module"],
["VimperatorLeave", "Triggered before exiting Firefox"]], ["VimperatorLeave", "Triggered before exiting Firefox"]],
dialogs: [ dialogs: {
["about", "About Firefox", about: ["About Firefox",
function () { window.openDialog("chrome://browser/content/aboutDialog.xul", "_blank", "chrome,dialog,modal,centerscreen"); }], 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); }], function () { PlacesCommandHook.bookmarkCurrentPage(true, PlacesUtils.bookmarksRootId); }],
["addons", "Manage Add-ons", addons: ["Manage Add-ons",
function () { window.BrowserOpenAddonsMgr(); }], 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"); }], 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(); }], function () { window.checkForUpdates(); }],
["cleardata", "Clear private data", cleardata: ["Clear private data",
function () { Cc[GLUE_CID].getService(Ci.nsIBrowserGlue).sanitize(window || null); }], 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"); }], function () { window.toOpenWindowByType("Browser:Cookies", "chrome://browser/content/preferences/cookies.xul", "chrome,dialog=no,resizable"); }],
["console", "JavaScript console", console: ["JavaScript console",
function () { window.toJavaScriptConsole(); }], function () { window.toJavaScriptConsole(); }],
["customizetoolbar", "Customize the Toolbar", customizetoolbar: ["Customize the Toolbar",
function () { window.BrowserCustomizeToolbar(); }], function () { window.BrowserCustomizeToolbar(); }],
["dominspector", "DOM Inspector", dominspector: ["DOM Inspector",
function () { try { window.inspectDOMDocument(content.document); } catch (e) { liberator.echoerr("DOM Inspector extension not installed"); } }], 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"); }], 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"); }], 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(); }], function () { window.BrowserImport(); }],
["openfile", "Open the file selector dialog", openfile: ["Open the file selector dialog",
function () { window.BrowserOpenFileWindow(); }], function () { window.BrowserOpenFileWindow(); }],
["pageinfo", "Show information about the current page", pageinfo: ["Show information about the current page",
function () { window.BrowserPageInfo(); }], function () { window.BrowserPageInfo(); }],
["pagesource", "View page source", pagesource: ["View page source",
function () { window.BrowserViewSourceOfDocument(content.document); }], 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); }], function () { PlacesCommandHook.showPlacesOrganizer(ORGANIZER_ROOT_BOOKMARKS); }],
["preferences", "Show Firefox preferences dialog", preferences: ["Show Firefox preferences dialog",
function () { window.openPreferences(); }], function () { window.openPreferences(); }],
["printpreview", "Preview the page before printing", printpreview: ["Preview the page before printing",
function () { PrintUtils.printPreview(onEnterPrintPreview, onExitPrintPreview); }], 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(); }], function () { PrintUtils.showPageSetup(); }],
["print", "Show print dialog", print: ["Show print dialog",
function () { PrintUtils.print(); }], function () { PrintUtils.print(); }],
["saveframe", "Save frame to disk", saveframe: ["Save frame to disk",
function () { window.saveFrameDocument(); }], function () { window.saveFrameDocument(); }],
["savepage", "Save page to disk", savepage: ["Save page to disk",
function () { window.saveDocument(window.content.document); }], 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"); }], 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(); }] function () { buffer.viewSelectionSource(); }]
], },
hasTabbrowser: true, hasTabbrowser: true,
@@ -232,13 +232,20 @@ const Config = Module("config", ConfigBase, {
return; return;
context.anchored = false; 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.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) { let timer = new Timer(50, 100, function (result) {
context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING; context.incomplete = result.searchResult >= result.RESULT_NOMATCH_ONGOING;
context.completions = [ context.completions = [
@@ -246,9 +253,6 @@ const Config = Module("config", ConfigBase, {
for (i in util.range(0, result.matchCount)) for (i in util.range(0, result.matchCount))
]; ];
}); });
if (searchRunning)
services.get("autoCompleteSearch").stopSearch();
searchRunning = true;
services.get("autoCompleteSearch").startSearch(context.filter, "", context.result, { services.get("autoCompleteSearch").startSearch(context.filter, "", context.result, {
onSearchResult: function onSearchResult(search, result) { onSearchResult: function onSearchResult(search, result) {
timer.tell(result); timer.tell(result);
@@ -258,6 +262,7 @@ const Config = Module("config", ConfigBase, {
} }
} }
}); });
searchRunning = true;
}; };
completion.sidebar = function sidebar(context) { completion.sidebar = function sidebar(context) {

View File

@@ -4,7 +4,7 @@
<Description about="urn:mozilla:install-manifest"> <Description about="urn:mozilla:install-manifest">
<em:id>vimperator@mozdev.org</em:id> <em:id>vimperator@mozdev.org</em:id>
<em:name>Vimperator</em:name> <em:name>Vimperator</em:name>
<em:version>###VERSION###</em:version> <em:version>@VERSION@</em:version>
<em:description>Make Firefox behave like Vim</em:description> <em:description>Make Firefox behave like Vim</em:description>
<em:creator>Martin Stubenschrott</em:creator> <em:creator>Martin Stubenschrott</em:creator>
<em:homepageURL>http://vimperator.org</em:homepageURL> <em:homepageURL>http://vimperator.org</em:homepageURL>
@@ -19,7 +19,7 @@
<Description> <Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>3.5</em:minVersion> <em:minVersion>3.5</em:minVersion>
<em:maxVersion>3.6b3</em:maxVersion> <em:maxVersion>4.0</em:maxVersion>
</Description> </Description>
</em:targetApplication> </em:targetApplication>
</Description> </Description>