mirror of
https://github.com/gryf/pentadactyl-pm.git
synced 2026-02-13 07:05:46 +01:00
Rename liberator/ to common/
This commit is contained in:
110
common/Makefile.common
Normal file
110
common/Makefile.common
Normal file
@@ -0,0 +1,110 @@
|
||||
#### configuration
|
||||
|
||||
TOP = $(shell pwd)
|
||||
OS = $(shell uname -s)
|
||||
BUILD_DATE = $(shell date "+%Y/%m/%d %H:%M:%S")
|
||||
BASE = $(TOP)/../liberator
|
||||
|
||||
DOC_SRC_FILES = $(wildcard locale/*/*.txt)
|
||||
DOC_FILES = ${DOC_SRC_FILES:%.txt=%.html}
|
||||
|
||||
MAKE_JAR = VERSION="$(VERSION)" DATE="$(BUILD_DATE)" sh $(BASE)/make_jar.sh
|
||||
|
||||
# TODO: specify source files manually?
|
||||
JAR_BASES = $(TOP) $(BASE)
|
||||
JAR_DIRS = content skin locale
|
||||
JAR_TEXTS = js css dtd xml xul html xhtml
|
||||
JAR_BINS = png
|
||||
|
||||
JAR = chrome/${NAME}.jar
|
||||
|
||||
XPI_BASES = $(JAR_BASES) $(TOP)/..
|
||||
XPI_FILES = install.rdf TODO AUTHORS Donators NEWS License.txt
|
||||
XPI_DIRS = modules components chrome
|
||||
XPI_TEXTS = js jsm
|
||||
XPI_BINS = jar
|
||||
|
||||
XPI_NAME = ${NAME}_${VERSION}
|
||||
XPI_PATH = ../downloads/${XPI_NAME}
|
||||
XPI = $(XPI_PATH).xpi
|
||||
|
||||
RDF = ../downloads/update.rdf
|
||||
RDF_IN = ${RDF}.in
|
||||
|
||||
BUILD_DIR = build.${VERSION}.${OS}
|
||||
|
||||
ASCIIDOC = asciidoc
|
||||
|
||||
.SILENT:
|
||||
|
||||
#### rules
|
||||
|
||||
.PHONY: all help info doc jar xpi install clean distclean $(JAR)
|
||||
all: help
|
||||
|
||||
help:
|
||||
@echo "${NAME} ${VERSION} build"
|
||||
@echo
|
||||
@echo " make help - display this help"
|
||||
@echo " make info - show some info about the system"
|
||||
@echo " make doc - build doc files"
|
||||
@echo " make jar - build a JAR (${JAR})"
|
||||
@echo " make xpi - build an XPI (${XPI_NAME})"
|
||||
@echo " make release - updates update.rdf (this is not for you)"
|
||||
@echo " make clean - clean up"
|
||||
@echo " make distclean - clean up more"
|
||||
@echo
|
||||
@echo "running some commands with V=1 will show more build details"
|
||||
|
||||
info:
|
||||
@echo "version ${VERSION}"
|
||||
@echo "release file ${XPI}"
|
||||
@echo "doc files ${DOC_SRC_FILES}"
|
||||
@echo -e "jar files $(shell echo ${JAR_FILES} | sed 's/ /\\n /g' )"
|
||||
@echo "xpi files ${XPI_FILES}"
|
||||
|
||||
doc: ${DOC_FILES}
|
||||
xpi: ${XPI}
|
||||
jar: ${JAR}
|
||||
|
||||
release: ${XPI} ${RDF}
|
||||
|
||||
${RDF}: ${RDF_IN} Makefile
|
||||
@echo "Preparing release..."
|
||||
${Q}${SED} -e "s,###VERSION###,${VERSION},g" \
|
||||
-e "s,###DATE###,${BUILD_DATE},g" \
|
||||
< $< > $@
|
||||
@echo "SUCCESS: $@"
|
||||
|
||||
clean:
|
||||
@echo "Cleanup..."
|
||||
rm -f ${JAR} ${XPI}
|
||||
find . -name '*~' -exec rm -f {} \;
|
||||
|
||||
distclean: clean
|
||||
@echo "More cleanup..."
|
||||
rm -f ${DOC_FILES}
|
||||
rm -rf ${BUILD_DIR}
|
||||
|
||||
#### xpi
|
||||
|
||||
$(XPI): $(JAR)
|
||||
@echo "Building XPI..."
|
||||
mkdir -p $(XPI_PATH)
|
||||
awk -v 'name=$(NAME)' -f $(BASE)/process_manifest.awk $(TOP)/chrome.manifest >$(XPI_PATH)/chrome.manifest
|
||||
$(MAKE_JAR) "$(XPI)" "$(XPI_BASES)" "$(XPI_DIRS)" "$(XPI_TEXTS)" "$(XPI_BINS)" "$(XPI_FILES)"
|
||||
@echo "SUCCESS: $@"
|
||||
|
||||
#### jar
|
||||
|
||||
$(JAR): doc
|
||||
@echo "Building JAR..."
|
||||
$(MAKE_JAR) "$(JAR)" "$(JAR_BASES)" "$(JAR_DIRS)" "$(JAR_TEXTS)" "$(JAR_BINS)" "$(JAR_FILES)"
|
||||
@echo "SUCCESS: $@"
|
||||
|
||||
#### doc
|
||||
|
||||
${DOC_FILES}: %.html: %.txt $(BASE)/Makefile.common locale/en-US/asciidoc.conf
|
||||
@echo "DOC $@"
|
||||
${ASCIIDOC} --unsafe -a linkcss -o $@ $<
|
||||
|
||||
149
common/content/README.E4X
Normal file
149
common/content/README.E4X
Normal file
@@ -0,0 +1,149 @@
|
||||
A terse introduction to E4X
|
||||
Public Domain
|
||||
|
||||
The inline XML literals in this code are part of E4X, a standard
|
||||
XML processing interface for ECMAScript. In addition to syntax
|
||||
for XML literals, E4X provides a new kind of native object,
|
||||
"xml", and a syntax, similar to XPath, for accessing and
|
||||
modifying the tree. Here is a brief synopsis of the kind of
|
||||
usage you'll see herein:
|
||||
|
||||
> let xml =
|
||||
<foo bar="baz" baz="qux">
|
||||
<bar>
|
||||
<baz id="1"/>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
</foo>;
|
||||
|
||||
// Select all bar elements of the root foo element
|
||||
> xml.bar
|
||||
<bar><baz id="1"/></bar>
|
||||
|
||||
// Select all baz elements anywhere beneath the root
|
||||
> xml..baz
|
||||
<baz id="1"/>
|
||||
<baz id="2"/>
|
||||
|
||||
// Select all of the immediate children of the root
|
||||
> xml.*
|
||||
<bar><baz id="1"/></bar>
|
||||
<baz id="2"/>
|
||||
|
||||
// Select the bar attribute of the root node
|
||||
> xml.@bar
|
||||
baz
|
||||
|
||||
// Select all id attributes in the tree
|
||||
> xml..@id
|
||||
1
|
||||
2
|
||||
|
||||
// Select all attributes of the root node
|
||||
> xml.@*
|
||||
baz
|
||||
quz
|
||||
|
||||
// Add a quux elemend beneath the first baz
|
||||
> xml..baz[0] += <quux/>
|
||||
<baz id="1"/>
|
||||
<quux/>
|
||||
> xml
|
||||
<foo bar="baz" baz="qux">
|
||||
<bar>
|
||||
<baz id="1"/>
|
||||
<quux/>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
</foo>
|
||||
|
||||
// and beneath the second
|
||||
> xml.baz[1] = <quux id="1"/>
|
||||
> xml
|
||||
<foo bar="baz" baz="qux">
|
||||
<bar>
|
||||
<baz id="1"/>
|
||||
<quux/>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
<quux id="1"/>
|
||||
</foo>
|
||||
|
||||
// Replace bar's subtree with a foo element
|
||||
> xml.bar.* = <foo id="1"/>
|
||||
> xml
|
||||
<foo bar="baz" baz="qux">
|
||||
<bar>
|
||||
<foo id="1"/>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
<quux id="1"/>
|
||||
</foo>
|
||||
|
||||
// Add a bar below bar
|
||||
> xml.bar.* += <bar id="1"/>
|
||||
<foo id="1"/>
|
||||
<bar id="1"/>
|
||||
> xml
|
||||
<foo bar="baz" baz="qux">
|
||||
<bar>
|
||||
<foo id="1"/>
|
||||
<bar id="1"/>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
<quux id="1"/>
|
||||
</foo>
|
||||
|
||||
// Adding a quux attribute to the root
|
||||
> xml.@quux = "foo"
|
||||
foo
|
||||
> xml
|
||||
<foo bar="baz" baz="qux" quux="foo">
|
||||
<bar>
|
||||
<foo id="1"/>
|
||||
<bar id="1"/>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
<quux id="1"/>
|
||||
</foo>
|
||||
|
||||
> xml.bar.@id = "0"
|
||||
> xml..foo[0] = "Foo"
|
||||
Foo
|
||||
> xml..bar[1] = "Bar"
|
||||
Bar
|
||||
> xml
|
||||
js> xml
|
||||
<foo bar="baz" baz="qux" quux="foo" id="0">
|
||||
<bar id="0">
|
||||
<foo id="1">Foo</foo>
|
||||
<bar id="1">Bar</bar>
|
||||
</bar>
|
||||
<baz id="2"/>
|
||||
<quux id="1"/>
|
||||
</foo>
|
||||
|
||||
// Selecting all bar elements where id="1"
|
||||
> xml..bar.(@id == 1)
|
||||
Bar
|
||||
|
||||
// Literals:
|
||||
// XMLList literal. No root node.
|
||||
> <>Foo<br/>Baz</>
|
||||
Foo
|
||||
<br/>
|
||||
Baz
|
||||
|
||||
// Interpolation.
|
||||
> let x = "<foo/>"
|
||||
> <foo bar={x}>{x + "<?>"}</foo>
|
||||
<foo/><?>
|
||||
> <foo bar={x}>{x + "<?>"}</foo>.toXMLString()
|
||||
<foo bar="<foo/>"><foo/><?></foo>
|
||||
|
||||
> let x = <foo/>
|
||||
> <foo bar={x}>{x}</foo>.toXMLString()
|
||||
<foo bar="">
|
||||
<foo/>
|
||||
</foo>
|
||||
|
||||
37
common/content/bindings.xml
Normal file
37
common/content/bindings.xml
Normal file
@@ -0,0 +1,37 @@
|
||||
<?xml version="1.0"?>
|
||||
|
||||
<bindings xmlns="http://www.mozilla.org/xbl"
|
||||
xmlns:liberator="http://vimperator.org/namespaces/liberator"
|
||||
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
|
||||
xmlns:xbl="http://www.mozilla.org/xbl"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml">
|
||||
|
||||
<binding id="frame">
|
||||
<content>
|
||||
<html:div liberator:highlight="FrameIndicator"/>
|
||||
<children/>
|
||||
</content>
|
||||
</binding>
|
||||
<binding id="compitem-td">
|
||||
<!-- No white space. The table is white-space: pre; :( -->
|
||||
<content><html:span class="td-strut"/><html:span class="td-span"><children/></html:span></content>
|
||||
</binding>
|
||||
<binding id="tab" display="xul:hbox"
|
||||
extends="chrome://global/content/bindings/tabbox.xml#tab">
|
||||
<content chromedir="ltr" closetabtext="Close Tab">
|
||||
<xul:stack class="liberator-tab-stack">
|
||||
<xul:image xbl:inherits="validate,src=image" class="tab-icon-image" liberator:highlight="TabIcon"/>
|
||||
<xul:vbox>
|
||||
<xul:spring flex="1"/>
|
||||
<xul:label xbl:inherits="value=ordinal" liberator:highlight="TabIconNumber"/>
|
||||
<xul:spring flex="1"/>
|
||||
</xul:vbox>
|
||||
</xul:stack>
|
||||
<xul:label xbl:inherits="value=ordinal" liberator:highlight="TabNumber"/>
|
||||
<xul:label flex="1" xbl:inherits="value=label,crop,accesskey" class="tab-text" liberator:highlight="TabText"/>
|
||||
<xul:toolbarbutton anonid="close-button" tabindex="-1" class="tab-close-button" liberator:highlight="TabClose"/>
|
||||
</content>
|
||||
</binding>
|
||||
</bindings>
|
||||
|
||||
<!-- vim:se ft=xbl sw=4 sts=4 tw=0 et: -->
|
||||
1708
common/content/buffer.js
Normal file
1708
common/content/buffer.js
Normal file
File diff suppressed because it is too large
Load Diff
9
common/content/buffer.xhtml
Normal file
9
common/content/buffer.xhtml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
|
||||
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<head>
|
||||
<title/>
|
||||
</head>
|
||||
<body/>
|
||||
</html>
|
||||
892
common/content/commands.js
Normal file
892
common/content/commands.js
Normal file
@@ -0,0 +1,892 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// Do NOT create instances of this class yourself, use the helper method
|
||||
// commands.add() instead
|
||||
function Command(specs, description, action, extraInfo) //{{{
|
||||
{
|
||||
if (!specs || !action)
|
||||
return null;
|
||||
|
||||
if (!extraInfo)
|
||||
extraInfo = {};
|
||||
|
||||
// convert command name abbreviation specs of the form
|
||||
// 'shortname[optional-tail]' to short and long versions Eg. 'abc[def]' ->
|
||||
// 'abc', 'abcdef'
|
||||
function parseSpecs(specs)
|
||||
{
|
||||
// Whoever wrote the following should be ashamed. :(
|
||||
// Good grief! I have no words... -- djk ;-)
|
||||
// let shortNames = longNames = names = [];
|
||||
let names = [];
|
||||
let longNames = [];
|
||||
let shortNames = [];
|
||||
|
||||
for (let [,spec] in Iterator(specs))
|
||||
{
|
||||
let matches = spec.match(/(\w+)\[(\w+)\]/);
|
||||
|
||||
if (matches)
|
||||
{
|
||||
shortNames.push(matches[1]);
|
||||
longNames.push(matches[1] + matches[2]);
|
||||
// order as long1, short1, long2, short2
|
||||
names.push(matches[1] + matches[2]);
|
||||
names.push(matches[1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
longNames.push(spec);
|
||||
names.push(spec);
|
||||
}
|
||||
}
|
||||
|
||||
return { names: names, longNames: longNames, shortNames: shortNames };
|
||||
};
|
||||
|
||||
let expandedSpecs = parseSpecs(specs);
|
||||
this.specs = specs;
|
||||
this.shortNames = expandedSpecs.shortNames;
|
||||
this.longNames = expandedSpecs.longNames;
|
||||
|
||||
// return the primary command name (the long name of the first spec listed)
|
||||
this.name = this.longNames[0];
|
||||
this.names = expandedSpecs.names; // return all command name aliases
|
||||
this.description = description || "";
|
||||
this.action = action;
|
||||
this.argCount = extraInfo.argCount || 0;
|
||||
this.completer = extraInfo.completer || null;
|
||||
this.hereDoc = extraInfo.hereDoc || false;
|
||||
this.options = extraInfo.options || [];
|
||||
this.bang = extraInfo.bang || false;
|
||||
this.count = extraInfo.count || false;
|
||||
this.literal = extraInfo.literal == null ? null : extraInfo.literal;
|
||||
this.serial = extraInfo.serial;
|
||||
|
||||
this.isUserCommand = extraInfo.isUserCommand || false;
|
||||
this.replacementText = extraInfo.replacementText || null;
|
||||
};
|
||||
|
||||
Command.prototype = {
|
||||
|
||||
execute: function (args, bang, count, modifiers)
|
||||
{
|
||||
// XXX
|
||||
bang = !!bang;
|
||||
count = (count === undefined) ? -1 : count;
|
||||
modifiers = modifiers || {};
|
||||
|
||||
let self = this;
|
||||
function exec(args)
|
||||
{
|
||||
// FIXME: Move to parseCommand?
|
||||
args = self.parseArgs(args);
|
||||
if (!args)
|
||||
return;
|
||||
args.count = count;
|
||||
args.bang = bang;
|
||||
self.action.call(self, args, bang, count, modifiers);
|
||||
}
|
||||
|
||||
if (this.hereDoc)
|
||||
{
|
||||
let matches = args.match(/(.*)<<\s*(\S+)$/);
|
||||
if (matches && matches[2])
|
||||
{
|
||||
commandline.inputMultiline(new RegExp("^" + matches[2] + "$", "m"),
|
||||
function (args) { exec(matches[1] + "\n" + args) });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
exec(args);
|
||||
},
|
||||
|
||||
hasName: function (name)
|
||||
{
|
||||
for (let [,spec] in Iterator(this.specs))
|
||||
{
|
||||
let fullName = spec.replace(/\[(\w+)]$/, "$1");
|
||||
let index = spec.indexOf("[");
|
||||
let min = index == -1 ? fullName.length : index;
|
||||
|
||||
if (fullName.indexOf(name) == 0 && name.length >= min)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
parseArgs: function (args, complete) commands.parseArgs(args, this.options, this.argCount, false, this.literal, complete)
|
||||
|
||||
}; //}}}
|
||||
|
||||
function Commands() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var exCommands = [];
|
||||
|
||||
function parseBool(arg)
|
||||
{
|
||||
if (arg == "true" || arg == "1" || arg == "on")
|
||||
return true;
|
||||
if (arg == "false" || arg == "0" || arg == "off")
|
||||
return false;
|
||||
return NaN;
|
||||
}
|
||||
|
||||
const quoteMap = {
|
||||
"\n": "n",
|
||||
"\t": "t"
|
||||
}
|
||||
function quote(q, list)
|
||||
{
|
||||
let re = RegExp("[" + list + "]", "g");
|
||||
return function (str) q + String.replace(str, re, function ($0) $0 in quoteMap ? quoteMap[$0] : ("\\" + $0)) + q;
|
||||
}
|
||||
const complQuote = { // FIXME
|
||||
'"': ['"', quote("", '\n\t"\\\\'), '"'],
|
||||
"'": ["'", quote("", "\\\\'"), "'"],
|
||||
"": ["", quote("", "\\\\ "), ""]
|
||||
};
|
||||
const quoteArg = {
|
||||
'"': quote('"', '\n\t"\\\\'),
|
||||
"'": quote("'", "\\\\'"),
|
||||
"": quote("", "\\\\ ")
|
||||
}
|
||||
|
||||
const ArgType = new Struct("description", "parse");
|
||||
const argTypes = [
|
||||
null,
|
||||
["no arg", function (arg) !arg],
|
||||
["boolean", parseBool],
|
||||
["string", function (val) val],
|
||||
["int", parseInt],
|
||||
["float", parseFloat],
|
||||
["list", function (arg) arg && arg.split(/\s*,\s*/)]
|
||||
].map(function (x) x && ArgType.apply(null, x));
|
||||
|
||||
function addCommand(command, isUserCommand, replace)
|
||||
{
|
||||
if (!command) // XXX
|
||||
return false;
|
||||
|
||||
if (exCommands.some(function (c) c.hasName(command.name)))
|
||||
{
|
||||
if (isUserCommand && replace)
|
||||
{
|
||||
commands.removeUserCommand(command.name);
|
||||
}
|
||||
else
|
||||
{
|
||||
liberator.log("Warning: :" + command.name + " already exists, NOT replacing existing command.", 1);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
exCommands.push(command);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
liberator.registerObserver("load_completion", function ()
|
||||
{
|
||||
completion.setFunctionCompleter(commands.get, [function () ([c.name, c.description] for (c in commands))]);
|
||||
});
|
||||
|
||||
var commandManager = {
|
||||
|
||||
// FIXME: remove later, when our option handler is better
|
||||
OPTION_ANY: 0, // can be given no argument or an argument of any type,
|
||||
// caller is responsible for parsing the return value
|
||||
OPTION_NOARG: 1,
|
||||
OPTION_BOOL: 2,
|
||||
OPTION_STRING: 3,
|
||||
OPTION_INT: 4,
|
||||
OPTION_FLOAT: 5,
|
||||
OPTION_LIST: 6,
|
||||
|
||||
COUNT_NONE: -1,
|
||||
COUNT_ALL: -2, // :%...
|
||||
|
||||
__iterator__: function ()
|
||||
{
|
||||
let sorted = exCommands.sort(function (a, b) a.name > b.name);
|
||||
return util.Array.iterator(sorted);
|
||||
},
|
||||
|
||||
add: function (names, description, action, extra)
|
||||
{
|
||||
return addCommand(new Command(names, description, action, extra), false, false);
|
||||
},
|
||||
|
||||
addUserCommand: function (names, description, action, extra, replace)
|
||||
{
|
||||
extra = extra || {};
|
||||
extra.isUserCommand = true;
|
||||
description = description || "User defined command";
|
||||
|
||||
return addCommand(new Command(names, description, action, extra), true, replace);
|
||||
},
|
||||
|
||||
commandToString: function (args)
|
||||
{
|
||||
let res = [args.command + (args.bang ? "!" : "")];
|
||||
function quote(str) quoteArg[/\s/.test(str) ? '"' : ""](str);
|
||||
|
||||
for (let [opt, val] in Iterator(args.options || {}))
|
||||
{
|
||||
res.push(opt);
|
||||
if (val != null)
|
||||
res.push(quote(val));
|
||||
}
|
||||
for (let [,arg] in Iterator(args.arguments || []))
|
||||
res.push(quote(arg));
|
||||
|
||||
let str = args.literalArg;
|
||||
if (str)
|
||||
res.push(/\n/.test(str) ? "<<EOF\n" + str + "EOF" : str);
|
||||
return res.join(" ");
|
||||
},
|
||||
|
||||
get: function (name)
|
||||
{
|
||||
return exCommands.filter(function (cmd) cmd.hasName(name))[0] || null;
|
||||
},
|
||||
|
||||
getUserCommand: function (name)
|
||||
{
|
||||
return exCommands.filter(function (cmd) cmd.isUserCommand && cmd.hasName(name))[0] || null;
|
||||
},
|
||||
|
||||
getUserCommands: function ()
|
||||
{
|
||||
return exCommands.filter(function (cmd) cmd.isUserCommand);
|
||||
},
|
||||
|
||||
// in '-quoted strings, only ' and \ itself are escaped
|
||||
// in "-quoted strings, also ", \n and \t are translated
|
||||
// in non-quoted strings everything is taken literally apart from "\ " and "\\"
|
||||
//
|
||||
// @param str: something like "-x=foo -opt=bar arg1 arg2"
|
||||
// "options" is an array [name, type, validator, completions] and could look like:
|
||||
// options = [[["-force"], OPTION_NOARG],
|
||||
// [["-fullscreen", "-f"], OPTION_BOOL],
|
||||
// [["-language"], OPTION_STRING, validateFunc, ["perl", "ruby"]],
|
||||
// [["-speed"], OPTION_INT],
|
||||
// [["-acceleration"], OPTION_FLOAT],
|
||||
// [["-accessories"], OPTION_LIST, null, ["foo", "bar"]],
|
||||
// [["-other"], OPTION_ANY]];
|
||||
// @param argCount can be:
|
||||
// "0": no arguments
|
||||
// "1": exactly one argument
|
||||
// "+": one or more arguments
|
||||
// "*": zero or more arguments (default if unspecified)
|
||||
// "?": zero or one arguments
|
||||
// @param allowUnknownOptions: -foo won't result in an error, if -foo isn't
|
||||
// specified in "options"
|
||||
// TODO: should it handle comments?
|
||||
parseArgs: function (str, options, argCount, allowUnknownOptions, literal, complete)
|
||||
{
|
||||
// returns [count, parsed_argument]
|
||||
function getNextArg(str)
|
||||
{
|
||||
var stringDelimiter = null;
|
||||
var escapeNext = false;
|
||||
|
||||
var arg = "";
|
||||
|
||||
outer:
|
||||
for (let i = 0; i < str.length; i++)
|
||||
{
|
||||
inner:
|
||||
switch (str[i])
|
||||
{
|
||||
case '"':
|
||||
case "'":
|
||||
if (escapeNext)
|
||||
{
|
||||
escapeNext = false;
|
||||
break;
|
||||
}
|
||||
switch (stringDelimiter)
|
||||
{
|
||||
case str[i]:
|
||||
stringDelimiter = null;
|
||||
continue outer;
|
||||
case null:
|
||||
stringDelimiter = str[i];
|
||||
continue outer;
|
||||
}
|
||||
break;
|
||||
|
||||
// \ is an escape key for non quoted or "-quoted strings
|
||||
// for '-quoted strings it is taken literally, apart from \' and \\
|
||||
case "\\":
|
||||
if (escapeNext)
|
||||
{
|
||||
escapeNext = false;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// in non-quoted strings, only escape "\\" and "\ ", otherwise drop "\\"
|
||||
if (!stringDelimiter && str[i + 1] != "\\" && str[i + 1] != " ")
|
||||
continue outer;
|
||||
// in single quoted strings, only escape "\\" and "\'", otherwise keep "\\"
|
||||
if (stringDelimiter == "'" && str[i + 1] != "\\" && str[i + 1] != "'")
|
||||
break;
|
||||
escapeNext = true;
|
||||
continue outer;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if (stringDelimiter == "'")
|
||||
{
|
||||
escapeNext = false;
|
||||
break;
|
||||
}
|
||||
if (escapeNext)
|
||||
{
|
||||
escapeNext = false;
|
||||
switch (str[i])
|
||||
{
|
||||
case "n": arg += "\n"; break;
|
||||
case "t": arg += "\t"; break;
|
||||
default:
|
||||
break inner; // this makes "a\fb" -> afb; wanted or should we return ab? --mst
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
else if (stringDelimiter != '"' && /\s/.test(str[i]))
|
||||
{
|
||||
return [i, arg];
|
||||
}
|
||||
break;
|
||||
}
|
||||
arg += str[i];
|
||||
}
|
||||
|
||||
// TODO: add parsing of a " comment here:
|
||||
if (stringDelimiter)
|
||||
return [str.length, arg, stringDelimiter];
|
||||
if (escapeNext)
|
||||
return [str.length, arg, "\\"];
|
||||
else
|
||||
return [str.length, arg];
|
||||
}
|
||||
|
||||
if (!options)
|
||||
options = [];
|
||||
|
||||
if (!argCount)
|
||||
argCount = "*";
|
||||
|
||||
var args = []; // parsed options
|
||||
args.__iterator__ = util.Array.iterator2;
|
||||
args.string = str; // for access to the unparsed string
|
||||
args.literalArg = "";
|
||||
|
||||
var invalid = false;
|
||||
// FIXME: best way to specify these requirements?
|
||||
var onlyArgumentsRemaining = allowUnknownOptions || options.length == 0 || false; // after a -- has been found
|
||||
var arg = null;
|
||||
var count = 0; // the length of the argument
|
||||
var i = 0;
|
||||
var completeOpts;
|
||||
|
||||
// XXX
|
||||
function matchOpts(arg)
|
||||
{
|
||||
// Push possible option matches into completions
|
||||
if (complete && !onlyArgumentsRemaining)
|
||||
completeOpts = [[opt[0], opt[0][0]] for ([i, opt] in Iterator(options)) if (!(opt[0][0] in args))];
|
||||
}
|
||||
function resetCompletions()
|
||||
{
|
||||
completeOpts = null;
|
||||
args.completeArg = null;
|
||||
args.completeOpt = null;
|
||||
args.completeFilter = null;
|
||||
args.completeStart = i;
|
||||
args.quote = complQuote[""];
|
||||
}
|
||||
if (complete)
|
||||
{
|
||||
resetCompletions();
|
||||
matchOpts("");
|
||||
args.completeArg = 0;
|
||||
}
|
||||
|
||||
function echoerr(error)
|
||||
{
|
||||
if (complete)
|
||||
complete.message = error;
|
||||
else
|
||||
liberator.echoerr(error);
|
||||
}
|
||||
|
||||
outer:
|
||||
while (i < str.length || complete)
|
||||
{
|
||||
// skip whitespace
|
||||
while (/\s/.test(str[i]) && i < str.length)
|
||||
i++;
|
||||
if (i == str.length && !complete)
|
||||
break;
|
||||
|
||||
if (complete)
|
||||
resetCompletions();
|
||||
|
||||
var sub = str.substr(i);
|
||||
if ((!onlyArgumentsRemaining) && /^--(\s|$)/.test(sub))
|
||||
{
|
||||
onlyArgumentsRemaining = true;
|
||||
i += 2;
|
||||
continue;
|
||||
}
|
||||
|
||||
var optname = "";
|
||||
if (!onlyArgumentsRemaining)
|
||||
{
|
||||
for (let [,opt] in Iterator(options))
|
||||
{
|
||||
for (let [,optname] in Iterator(opt[0]))
|
||||
{
|
||||
if (sub.indexOf(optname) == 0)
|
||||
{
|
||||
invalid = false;
|
||||
arg = null;
|
||||
quote = null;
|
||||
count = 0;
|
||||
let sep = sub[optname.length];
|
||||
if (sep == "=" || /\s/.test(sep) && opt[1] != this.OPTION_NOARG)
|
||||
{
|
||||
[count, arg, quote] = getNextArg(sub.substr(optname.length + 1));
|
||||
|
||||
// if we add the argument to an option after a space, it MUST not be empty
|
||||
if (sep != "=" && !quote && arg.length == 0)
|
||||
arg = null;
|
||||
|
||||
count++; // to compensate the "=" character
|
||||
}
|
||||
else if (!/\s/.test(sep)) // this isn't really an option as it has trailing characters, parse it as an argument
|
||||
{
|
||||
invalid = true;
|
||||
}
|
||||
|
||||
let context = null;
|
||||
if (!complete && quote)
|
||||
{
|
||||
liberator.echoerr("Invalid argument for option " + optname);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!invalid)
|
||||
{
|
||||
if (complete && count > 0)
|
||||
{
|
||||
args.completeStart += optname.length + 1;
|
||||
args.completeOpt = opt;
|
||||
args.completeFilter = arg;
|
||||
args.quote = complQuote[quote] || complQuote[""];
|
||||
}
|
||||
let type = argTypes[opt[1]];
|
||||
if (type && (!complete || arg != null))
|
||||
{
|
||||
let orig = arg;
|
||||
arg = type.parse(arg);
|
||||
if (arg == null || (typeof arg == "number" && isNaN(arg)))
|
||||
{
|
||||
if (!complete || orig != "" || args.completeStart != str.length)
|
||||
echoerr("Invalid argument for " + type.description + " option: " + optname);
|
||||
if (complete)
|
||||
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// we have a validator function
|
||||
if (typeof opt[2] == "function")
|
||||
{
|
||||
if (opt[2].call(this, arg) == false)
|
||||
{
|
||||
echoerr("Invalid argument for option: " + optname);
|
||||
if (complete)
|
||||
complete.highlight(args.completeStart, count - 1, "SPELLCHECK");
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
args[opt[0][0]] = arg; // always use the first name of the option
|
||||
i += optname.length + count;
|
||||
if (i == str.length)
|
||||
break outer;
|
||||
continue outer;
|
||||
}
|
||||
// if it is invalid, just fall through and try the next argument
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
matchOpts(sub);
|
||||
|
||||
if (complete)
|
||||
{
|
||||
if (argCount == "0" || args.length > 0 && (/[1?]/.test(argCount)))
|
||||
complete.highlight(i, sub.length, "SPELLCHECK")
|
||||
}
|
||||
|
||||
if (args.length == literal)
|
||||
{
|
||||
if (complete)
|
||||
args.completeArg = args.length;
|
||||
args.literalArg = sub;
|
||||
args.push(sub);
|
||||
args.quote = null;
|
||||
break;
|
||||
}
|
||||
|
||||
// if not an option, treat this token as an argument
|
||||
var [count, arg, quote] = getNextArg(sub);
|
||||
if (complete)
|
||||
{
|
||||
args.quote = complQuote[quote] || complQuote[""];
|
||||
args.completeFilter = arg || "";
|
||||
}
|
||||
else if (count == -1)
|
||||
{
|
||||
liberator.echoerr("Error parsing arguments: " + arg);
|
||||
return null;
|
||||
}
|
||||
else if (!onlyArgumentsRemaining && /^-/.test(arg))
|
||||
{
|
||||
liberator.echoerr("Invalid option: " + arg);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (arg != null)
|
||||
args.push(arg);
|
||||
if (complete)
|
||||
args.completeArg = args.length - 1;
|
||||
|
||||
i += count;
|
||||
if (count <= 0 || i == str.length)
|
||||
break;
|
||||
}
|
||||
|
||||
if (complete)
|
||||
{
|
||||
if (args.completeOpt)
|
||||
{
|
||||
let opt = args.completeOpt;
|
||||
let context = complete.fork(opt[0][0], args.completeStart);
|
||||
context.filter = args.completeFilter;
|
||||
if (typeof opt[3] == "function")
|
||||
var compl = opt[3](context, args);
|
||||
else
|
||||
compl = opt[3] || [];
|
||||
context.title = [opt[0][0]];
|
||||
context.quote = args.quote;
|
||||
context.completions = compl;
|
||||
}
|
||||
complete.advance(args.completeStart);
|
||||
complete.title = ["Options"];
|
||||
if (completeOpts)
|
||||
complete.completions = completeOpts;
|
||||
}
|
||||
|
||||
// check for correct number of arguments
|
||||
if (args.length == 0 && /^[1+]$/.test(argCount) ||
|
||||
literal != null && /[1+]/.test(argCount) && !/\S/.test(args.literalArg || ""))
|
||||
{
|
||||
if (!complete)
|
||||
{
|
||||
liberator.echoerr("E471: Argument required");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (args.length == 1 && (argCount == "0") ||
|
||||
args.length > 1 && /^[01?]$/.test(argCount))
|
||||
{
|
||||
echoerr("E488: Trailing characters");
|
||||
return null;
|
||||
}
|
||||
|
||||
return args;
|
||||
},
|
||||
|
||||
// return [null, null, null, null, heredoc_tag || false];
|
||||
// [count, cmd, special, args] = match;
|
||||
parseCommand: function (str, tag)
|
||||
{
|
||||
// remove comments
|
||||
str.replace(/\s*".*$/, "");
|
||||
|
||||
if (tag) // we already have a multiline heredoc construct
|
||||
{
|
||||
if (str == tag)
|
||||
return [null, null, null, null, false];
|
||||
else
|
||||
return [null, null, null, str, tag];
|
||||
}
|
||||
|
||||
// 0 - count, 1 - cmd, 2 - special, 3 - args, 4 - heredoc tag
|
||||
let matches = str.match(/^:*(\d+|%)?([a-zA-Z]+|!)(!)?(?:\s*(.*?))?$/);
|
||||
//var matches = str.match(/^:*(\d+|%)?([a-zA-Z]+|!)(!)?(?:\s*(.*?)\s*)?$/);
|
||||
if (!matches)
|
||||
return [null, null, null, null, null];
|
||||
let [, count, cmd, special, args, heredoc] = matches;
|
||||
|
||||
// parse count
|
||||
if (count)
|
||||
count = count == "%" ? this.COUNT_ALL: parseInt(count, 10);
|
||||
else
|
||||
count = this.COUNT_NONE;
|
||||
|
||||
if (args)
|
||||
{
|
||||
tag = args.match(/<<\s*(\w+)\s*$/);
|
||||
if (tag && tag[1])
|
||||
heredoc = tag[1];
|
||||
}
|
||||
|
||||
return [count, cmd, !!special, args || "", heredoc];
|
||||
},
|
||||
|
||||
get quoteArg() quoteArg,
|
||||
|
||||
removeUserCommand: function (name)
|
||||
{
|
||||
exCommands = exCommands.filter(function (cmd) !(cmd.isUserCommand && cmd.hasName(name)));
|
||||
},
|
||||
|
||||
// FIXME: still belong here? Also used for autocommand parameters
|
||||
replaceTokens: function replaceTokens(str, tokens)
|
||||
{
|
||||
return str.replace(/<((?:q-)?)([a-zA-Z]+)?>/g, function (match, quote, token)
|
||||
{
|
||||
if (token == "lt") // Don't quote, as in vim (but, why so in vim? You'd think people wouldn't say <q-lt> if they didn't want it)
|
||||
return "<";
|
||||
let res = tokens[token];
|
||||
if (res == undefined) // Ignore anything undefined
|
||||
res = "<" + token + ">";
|
||||
if (quote && typeof res != "number")
|
||||
return quoteArg['"'](res);
|
||||
return res;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// COMMANDS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
function userCommand(args, modifiers)
|
||||
{
|
||||
let tokens = {
|
||||
args: this.argCount && args.string,
|
||||
bang: this.bang && args.bang ? "!" : "",
|
||||
count: this.count && args.count
|
||||
};
|
||||
|
||||
liberator.execute(commands.replaceTokens(this.replacementText, tokens));
|
||||
}
|
||||
|
||||
// TODO: offer completion.ex?
|
||||
var completeOptionMap = {
|
||||
altstyle: "alternateStylesheet", bookmark: "bookmark",
|
||||
buffer: "buffer", color: "colorScheme", command: "command",
|
||||
dialog: "dialog", dir: "directory", environment: "environment",
|
||||
event: "autocmdEvent", file: "file", help: "help",
|
||||
highlight: "highlightGroup", javascript: "javascript", macro: "macro",
|
||||
mapping: "userMapping", menu: "menuItem", option: "option",
|
||||
preference: "preference", search: "search",
|
||||
shellcmd: "shellCommand", sidebar: "sidebar", url: "url",
|
||||
usercommand: "userCommand"
|
||||
};
|
||||
|
||||
// TODO: Vim allows commands to be defined without {rep} if there are {attr}s
|
||||
// specified - useful?
|
||||
commandManager.add(["com[mand]"],
|
||||
"List and define commands",
|
||||
function (args)
|
||||
{
|
||||
let cmd = args[0];
|
||||
|
||||
if (cmd != null && /\W/.test(cmd))
|
||||
{
|
||||
liberator.echoerr("E182: Invalid command name");
|
||||
return;
|
||||
}
|
||||
|
||||
if (args.literalArg)
|
||||
{
|
||||
let nargsOpt = args["-nargs"] || "0";
|
||||
let bangOpt = "-bang" in args;
|
||||
let countOpt = "-count" in args;
|
||||
let completeOpt = args["-complete"];
|
||||
|
||||
let completeFunc = null; // default to no completion for user commands
|
||||
|
||||
if (completeOpt)
|
||||
{
|
||||
let func;
|
||||
|
||||
if (/^custom,/.test(completeOpt))
|
||||
func = completeOpt.replace("custom,", "");
|
||||
else
|
||||
func = "completion." + completeOptionMap[completeOpt];
|
||||
|
||||
completeFunc = eval(func);
|
||||
}
|
||||
|
||||
if (!commands.addUserCommand(
|
||||
[cmd],
|
||||
"User defined command",
|
||||
userCommand,
|
||||
{
|
||||
argCount: nargsOpt,
|
||||
bang: bangOpt,
|
||||
count: countOpt,
|
||||
completer: completeFunc,
|
||||
replacementText: args.literalArg
|
||||
},
|
||||
args.bang)
|
||||
)
|
||||
{
|
||||
liberator.echoerr("E174: Command already exists: add ! to replace it");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
function completerToString(completer)
|
||||
{
|
||||
if (completer)
|
||||
return [k for ([k, v] in Iterator(completeOptionMap))
|
||||
if (v == completer.name)][0] || "custom";
|
||||
else
|
||||
return "";
|
||||
}
|
||||
|
||||
// TODO: using an array comprehension here generates flakey results across repeated calls
|
||||
// : perhaps we shouldn't allow options in a list call but just ignore them for now
|
||||
let cmds = exCommands.filter(function (c) c.isUserCommand && (!cmd || c.name.match("^" + cmd)));
|
||||
|
||||
if (cmds.length > 0)
|
||||
{
|
||||
let str = template.tabular(["", "Name", "Args", "Range", "Complete", "Definition"], ["padding-right: 2em;"],
|
||||
([cmd.bang ? "!" : " ",
|
||||
cmd.name,
|
||||
cmd.argCount,
|
||||
cmd.count ? "0c" : "",
|
||||
completerToString(cmd.completer),
|
||||
cmd.replacementText || "function () { ... }"]
|
||||
for each (cmd in cmds)));
|
||||
|
||||
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
}
|
||||
else
|
||||
{
|
||||
liberator.echo("No user-defined commands found");
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
bang: true,
|
||||
completer: function (context) completion.userCommand(context),
|
||||
options: [
|
||||
[["-nargs"], commandManager.OPTION_STRING,
|
||||
function (arg) /^[01*?+]$/.test(arg), ["0", "1", "*", "?", "+"]],
|
||||
[["-bang"], commandManager.OPTION_NOARG],
|
||||
[["-count"], commandManager.OPTION_NOARG],
|
||||
[["-complete"], commandManager.OPTION_STRING,
|
||||
function (arg) arg in completeOptionMap || /custom,\w+/.test(arg)]
|
||||
],
|
||||
literal: 1,
|
||||
serial: function () [
|
||||
{
|
||||
command: this.name,
|
||||
bang: true,
|
||||
// Yeah, this is a bit scary. Perhaps I'll fix it when I'm
|
||||
// awake.
|
||||
options: util.Array.assocToObj(
|
||||
util.map({ argCount: "-nargs", bang: "-bang", count: "-count" },
|
||||
function ([k, v]) k in cmd && cmd[k] != "0" && [v, typeof cmd[k] == "boolean" ? null : cmd[k]])
|
||||
.filter(util.identity)),
|
||||
arguments: [cmd.name],
|
||||
literalArg: cmd.replacementText
|
||||
}
|
||||
for ([k, cmd] in Iterator(exCommands))
|
||||
if (cmd.isUserCommand && cmd.replacementText)
|
||||
]
|
||||
});
|
||||
|
||||
commandManager.add(["comc[lear]"],
|
||||
"Delete all user-defined commands",
|
||||
function ()
|
||||
{
|
||||
commands.getUserCommands().forEach(function (cmd) { commands.removeUserCommand(cmd.name); });
|
||||
},
|
||||
{ argCount: "0" });
|
||||
|
||||
commandManager.add(["delc[ommand]"],
|
||||
"Delete the specified user-defined command",
|
||||
function (args)
|
||||
{
|
||||
let name = args[0];
|
||||
|
||||
if (commands.get(name))
|
||||
commands.removeUserCommand(name);
|
||||
else
|
||||
liberator.echoerr("E184: No such user-defined command: " + name);
|
||||
},
|
||||
{
|
||||
argCount: "1",
|
||||
completer: function (context) completion.userCommand(context)
|
||||
});
|
||||
|
||||
//}}}
|
||||
|
||||
return commandManager;
|
||||
|
||||
}; //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1704
common/content/completion.js
Normal file
1704
common/content/completion.js
Normal file
File diff suppressed because it is too large
Load Diff
1128
common/content/editor.js
Normal file
1128
common/content/editor.js
Normal file
File diff suppressed because it is too large
Load Diff
10
common/content/eval.js
Normal file
10
common/content/eval.js
Normal file
@@ -0,0 +1,10 @@
|
||||
try { __liberator_eval_result = eval(__liberator_eval_string)
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
__liberator_eval_error = e;
|
||||
}
|
||||
// Important: The eval statement *must* remain on the first line
|
||||
// in order for line numbering in any errors to remain correct.
|
||||
//
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1694
common/content/events.js
Normal file
1694
common/content/events.js
Normal file
File diff suppressed because it is too large
Load Diff
473
common/content/find.js
Normal file
473
common/content/find.js
Normal file
@@ -0,0 +1,473 @@
|
||||
/***** B/GIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// TODO: proper backwards search - implement our own component?
|
||||
// : implement our own highlighter?
|
||||
// : frameset pages
|
||||
// : <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
|
||||
|
||||
// make sure you only create this object when the "liberator" object is ready
|
||||
function Search() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// FIXME:
|
||||
//var self = this; // needed for callbacks since "this" is the "liberator" object in a callback
|
||||
var found = false; // true if the last search was successful
|
||||
var backwards = false; // currently searching backwards
|
||||
var searchString = ""; // current search string (without modifiers)
|
||||
var searchPattern = ""; // current search string (includes modifiers)
|
||||
var lastSearchPattern = ""; // the last searched pattern (includes modifiers)
|
||||
var lastSearchString = ""; // the last searched string (without modifiers)
|
||||
var lastSearchBackwards = false; // like "backwards", but for the last search, so if you cancel a search with <esc> this is not set
|
||||
var caseSensitive = false; // search string is case sensitive
|
||||
var linksOnly = false; // search is limited to link text only
|
||||
|
||||
// Event handlers for search - closure is needed
|
||||
liberator.registerCallback("change", modes.SEARCH_FORWARD, function (command) { search.searchKeyPressed(command); });
|
||||
liberator.registerCallback("submit", modes.SEARCH_FORWARD, function (command) { search.searchSubmitted(command); });
|
||||
liberator.registerCallback("cancel", modes.SEARCH_FORWARD, function () { search.searchCanceled(); });
|
||||
// TODO: allow advanced myModes in register/triggerCallback
|
||||
liberator.registerCallback("change", modes.SEARCH_BACKWARD, function (command) { search.searchKeyPressed(command); });
|
||||
liberator.registerCallback("submit", modes.SEARCH_BACKWARD, function (command) { search.searchSubmitted(command); });
|
||||
liberator.registerCallback("cancel", modes.SEARCH_BACKWARD, function () { search.searchCanceled(); });
|
||||
|
||||
// set searchString, searchPattern, caseSensitive, linksOnly
|
||||
function processUserPattern(pattern)
|
||||
{
|
||||
//// strip off pattern terminator and offset
|
||||
//if (backwards)
|
||||
// pattern = pattern.replace(/\?.*/, "");
|
||||
//else
|
||||
// pattern = pattern.replace(/\/.*/, "");
|
||||
|
||||
searchPattern = pattern;
|
||||
|
||||
// links only search - \l wins if both modifiers specified
|
||||
if (/\\l/.test(pattern))
|
||||
linksOnly = true;
|
||||
else if (/\L/.test(pattern))
|
||||
linksOnly = false;
|
||||
else if (options["linksearch"])
|
||||
linksOnly = true;
|
||||
else
|
||||
linksOnly = false;
|
||||
|
||||
// strip links-only modifiers
|
||||
pattern = pattern.replace(/(\\)?\\[lL]/g, function ($0, $1) { return $1 ? $0 : ""; });
|
||||
|
||||
// case sensitivity - \c wins if both modifiers specified
|
||||
if (/\c/.test(pattern))
|
||||
caseSensitive = false;
|
||||
else if (/\C/.test(pattern))
|
||||
caseSensitive = true;
|
||||
else if (options["ignorecase"] && options["smartcase"] && /[A-Z]/.test(pattern))
|
||||
caseSensitive = true;
|
||||
else if (options["ignorecase"])
|
||||
caseSensitive = false;
|
||||
else
|
||||
caseSensitive = true;
|
||||
|
||||
// strip case-sensitive modifiers
|
||||
pattern = pattern.replace(/(\\)?\\[cC]/g, function ($0, $1) { return $1 ? $0 : ""; });
|
||||
|
||||
// remove any modifer escape \
|
||||
pattern = pattern.replace(/\\(\\[cClL])/g, "$1");
|
||||
|
||||
searchString = pattern;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* 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>
|
||||
*/
|
||||
var highlightObj = {
|
||||
search: function (aWord, matchCase)
|
||||
{
|
||||
var finder = Components.classes["@mozilla.org/embedcomp/rangefind;1"]
|
||||
.createInstance()
|
||||
.QueryInterface(Components.interfaces.nsIFind);
|
||||
if (matchCase !== undefined)
|
||||
finder.caseSensitive = matchCase;
|
||||
|
||||
var range;
|
||||
while ((range = finder.Find(aWord,
|
||||
this.searchRange,
|
||||
this.startPt,
|
||||
this.endPt)))
|
||||
yield range;
|
||||
},
|
||||
|
||||
highlightDoc: function highlightDoc(win, aWord)
|
||||
{
|
||||
Array.forEach(win.frames, function (frame) highlightObj.highlightDoc(frame, aWord));
|
||||
|
||||
var doc = win.document;
|
||||
if (!doc || !(doc instanceof HTMLDocument))
|
||||
return;
|
||||
|
||||
if (!aWord)
|
||||
{
|
||||
let elems = highlightObj.getSpans(doc);
|
||||
for (let i = elems.snapshotLength; --i >= 0;)
|
||||
{
|
||||
let elem = elems.snapshotItem(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, 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);
|
||||
return aNode;
|
||||
},
|
||||
|
||||
getSpans: function (doc) buffer.evaluateXPath("//*[@liberator:highlight='Search']", doc)
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// OPTIONS /////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
options.add(["hlsearch", "hls"],
|
||||
"Highlight previous search pattern matches",
|
||||
"boolean", "false",
|
||||
{
|
||||
setter: function (value)
|
||||
{
|
||||
if (value)
|
||||
search.highlight();
|
||||
else
|
||||
search.clear();
|
||||
|
||||
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);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// MAPPINGS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var myModes = config.browserModes;
|
||||
myModes = myModes.concat([modes.CARET]);
|
||||
|
||||
mappings.add(myModes,
|
||||
["/"], "Search forward for a pattern",
|
||||
function () { search.openSearchDialog(modes.SEARCH_FORWARD); });
|
||||
|
||||
mappings.add(myModes,
|
||||
["?"], "Search backwards for a pattern",
|
||||
function () { search.openSearchDialog(modes.SEARCH_BACKWARD); });
|
||||
|
||||
mappings.add(myModes,
|
||||
["n"], "Find next",
|
||||
function () { search.findAgain(false); });
|
||||
|
||||
mappings.add(myModes,
|
||||
["N"], "Find previous",
|
||||
function () { search.findAgain(true); });
|
||||
|
||||
mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["*"],
|
||||
"Find word under cursor",
|
||||
function ()
|
||||
{
|
||||
search.searchSubmitted(buffer.getCurrentWord(), false);
|
||||
search.findAgain();
|
||||
});
|
||||
|
||||
mappings.add(myModes.concat([modes.CARET, modes.TEXTAREA]), ["#"],
|
||||
"Find word under cursor backwards",
|
||||
function ()
|
||||
{
|
||||
search.searchSubmitted(buffer.getCurrentWord(), true);
|
||||
search.findAgain();
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// COMMANDS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
commands.add(["noh[lsearch]"],
|
||||
"Remove the search highlighting",
|
||||
function () { search.clear(); },
|
||||
{ argCount: "0" });
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
return {
|
||||
|
||||
// Called when the search dialog is asked for
|
||||
// If you omit "mode", it will default to forward searching
|
||||
openSearchDialog: function (mode)
|
||||
{
|
||||
if (mode == modes.SEARCH_BACKWARD)
|
||||
{
|
||||
commandline.open("?", "", modes.SEARCH_BACKWARD);
|
||||
backwards = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
commandline.open("/", "", modes.SEARCH_FORWARD);
|
||||
backwards = false;
|
||||
}
|
||||
|
||||
// TODO: focus the top of the currently visible screen
|
||||
},
|
||||
|
||||
// Finds text in a page
|
||||
// TODO: backwards seems impossible i fear :(
|
||||
find: function (str, backwards)
|
||||
{
|
||||
var fastFind = getBrowser().fastFind;
|
||||
|
||||
processUserPattern(str);
|
||||
|
||||
fastFind.caseSensitive = caseSensitive;
|
||||
found = fastFind.find(searchString, linksOnly) != Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND;
|
||||
|
||||
if (!found)
|
||||
setTimeout(function () { liberator.echoerr("E486: Pattern not found: " + searchPattern); }, 0);
|
||||
|
||||
return found;
|
||||
},
|
||||
|
||||
// Called when the current search needs to be repeated
|
||||
findAgain: function (reverse)
|
||||
{
|
||||
// this hack is needed to make n/N work with the correct string, if
|
||||
// we typed /foo<esc> after the original search. Since searchString is
|
||||
// readonly we have to call find() again to update it.
|
||||
if (getBrowser().fastFind.searchString != lastSearchString)
|
||||
this.find(lastSearchString, false);
|
||||
|
||||
var up = reverse ? !lastSearchBackwards : lastSearchBackwards;
|
||||
var result = getBrowser().fastFind.findAgain(up, linksOnly);
|
||||
|
||||
if (result == Components.interfaces.nsITypeAheadFind.FIND_NOTFOUND)
|
||||
{
|
||||
liberator.echoerr("E486: Pattern not found: " + lastSearchPattern);
|
||||
}
|
||||
else if (result == Components.interfaces.nsITypeAheadFind.FIND_WRAPPED)
|
||||
{
|
||||
// hack needed, because wrapping causes a "scroll" event which clears
|
||||
// our command line
|
||||
setTimeout(function () {
|
||||
if (up)
|
||||
commandline.echo("search hit TOP, continuing at BOTTOM",
|
||||
commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
|
||||
else
|
||||
commandline.echo("search hit BOTTOM, continuing at TOP",
|
||||
commandline.HL_WARNINGMSG, commandline.APPEND_TO_MESSAGES);
|
||||
}, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
liberator.echo((up ? "?" : "/") + lastSearchPattern, null, commandline.FORCE_SINGLELINE);
|
||||
|
||||
if (options["hlsearch"])
|
||||
this.highlight(lastSearchString);
|
||||
}
|
||||
},
|
||||
|
||||
// Called when the user types a key in the search dialog. Triggers a find attempt if 'incsearch' is set
|
||||
searchKeyPressed: function (command)
|
||||
{
|
||||
if (options["incsearch"])
|
||||
this.find(command, backwards);
|
||||
},
|
||||
|
||||
// Called when the enter key is pressed to trigger a search
|
||||
// use forcedBackward if you call this function directly
|
||||
searchSubmitted: function (command, forcedBackward)
|
||||
{
|
||||
if (typeof forcedBackward === "boolean")
|
||||
backwards = forcedBackward;
|
||||
|
||||
// use the last pattern if none specified
|
||||
if (!command)
|
||||
command = lastSearchPattern;
|
||||
|
||||
if (!options["incsearch"] || !found)
|
||||
{
|
||||
this.clear();
|
||||
this.find(command, backwards);
|
||||
}
|
||||
|
||||
lastSearchBackwards = backwards;
|
||||
//lastSearchPattern = command.replace(backwards ? /\?.*/ : /\/.*/, ""); // XXX
|
||||
lastSearchPattern = command;
|
||||
lastSearchString = searchString;
|
||||
|
||||
// TODO: move to find() when reverse incremental searching is kludged in
|
||||
// need to find again for reverse searching
|
||||
if (backwards)
|
||||
setTimeout(function () { search.findAgain(false); }, 0);
|
||||
|
||||
if (options["hlsearch"])
|
||||
this.highlight(searchString);
|
||||
|
||||
modes.reset();
|
||||
},
|
||||
|
||||
// Called when the search is canceled - for example if someone presses
|
||||
// escape while typing a search
|
||||
searchCanceled: function ()
|
||||
{
|
||||
this.clear();
|
||||
// TODO: code to reposition the document to the place before search started
|
||||
},
|
||||
|
||||
// FIXME: thunderbird incompatible
|
||||
// this is not dependent on the value of 'hlsearch'
|
||||
highlight: function (text)
|
||||
{
|
||||
if (config.name == "Muttator")
|
||||
return;
|
||||
|
||||
// already highlighted?
|
||||
if (highlightObj.getSpans(content.document).snapshotLength > 0)
|
||||
return;
|
||||
|
||||
if (!text)
|
||||
text = lastSearchString;
|
||||
|
||||
highlightObj.highlightDoc(window.content, text);
|
||||
|
||||
// recreate selection since _highlightDoc collapses the selection backwards
|
||||
getBrowser().fastFind.findAgain(false, linksOnly);
|
||||
|
||||
// TODO: remove highlighting from non-link matches (HTML - A/AREA with href attribute; XML - Xlink [type="simple"])
|
||||
},
|
||||
|
||||
clear: function ()
|
||||
{
|
||||
highlightObj.highlightDoc(window.content);
|
||||
// need to manually collapse the selection if the document is not
|
||||
// highlighted
|
||||
getBrowser().fastFind.collapseSelection();
|
||||
}
|
||||
|
||||
};
|
||||
//}}}
|
||||
}; //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
149
common/content/help.css
Normal file
149
common/content/help.css
Normal file
@@ -0,0 +1,149 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
div.main {
|
||||
font-family: -moz-fixed;
|
||||
white-space: -moz-pre-wrap;
|
||||
width: 800px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
p.tagline {
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
table.vimperator {
|
||||
border-width: 1px;
|
||||
border-style: dotted;
|
||||
border-color: gray;
|
||||
/*margin-bottom: 2em; /* FIXME: just a quick hack until we have proper pages */
|
||||
}
|
||||
table.vimperator td {
|
||||
border: none;
|
||||
padding: 3px;
|
||||
}
|
||||
tr.separator {
|
||||
height: 10px;
|
||||
}
|
||||
hr {
|
||||
height: 1px;
|
||||
background-color: white;
|
||||
border-style: none;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
td.taglist {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
border-spacing: 13px 10px;
|
||||
}
|
||||
td.taglist td {
|
||||
width: 100px;
|
||||
padding: 3px 0px;
|
||||
}
|
||||
tr.taglist code, td.usage code {
|
||||
margin: 0px 2px;
|
||||
}
|
||||
td.usage code {
|
||||
white-space: nowrap;
|
||||
}
|
||||
td.taglist code {
|
||||
margin-left: 2em;
|
||||
}
|
||||
code.tag {
|
||||
font-weight: bold;
|
||||
color: rgb(255, 0, 255); /* magenta */
|
||||
padding-left: 5px;
|
||||
}
|
||||
tr.description {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
table.commands {
|
||||
background-color: rgb(250, 240, 230);
|
||||
color: black;
|
||||
}
|
||||
table.mappings {
|
||||
background-color: rgb(230, 240, 250);
|
||||
color: black;
|
||||
}
|
||||
table.options {
|
||||
background-color: rgb(240, 250, 230);
|
||||
color: black;
|
||||
}
|
||||
|
||||
fieldset.paypal {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.argument {
|
||||
color: #6A97D4;
|
||||
}
|
||||
|
||||
.command {
|
||||
font-weight: bold;
|
||||
color: #632610;
|
||||
}
|
||||
|
||||
.mapping {
|
||||
font-weight: bold;
|
||||
color: #102663;
|
||||
}
|
||||
|
||||
.option {
|
||||
font-weight: bold;
|
||||
color: #106326;
|
||||
}
|
||||
|
||||
.code {
|
||||
color: #108826;
|
||||
}
|
||||
|
||||
.shorthelp {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.version {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 2%;
|
||||
color: #C0C0C0;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.warning {
|
||||
font-weight: bold;
|
||||
color: red;
|
||||
}
|
||||
|
||||
/* vim: set fdm=marker sw=4 ts=4 et: */
|
||||
811
common/content/hints.js
Normal file
811
common/content/hints.js
Normal file
@@ -0,0 +1,811 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
function Hints() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
const ELEM = 0, TEXT = 1, SPAN = 2, IMGSPAN = 3;
|
||||
|
||||
var myModes = config.browserModes;
|
||||
|
||||
var hintMode;
|
||||
var submode = ""; // used for extended mode, can be "o", "t", "y", etc.
|
||||
var hintString = ""; // the typed string part of the hint is in this string
|
||||
var hintNumber = 0; // only the numerical part of the hint
|
||||
var usedTabKey = false; // when we used <Tab> to select an element
|
||||
var prevInput = ""; // record previous user input type, "text" || "number"
|
||||
|
||||
// hints[] = [elem, text, span, imgspan, elem.style.backgroundColor, elem.style.color]
|
||||
var pageHints = [];
|
||||
var validHints = []; // store the indices of the "hints" array with valid elements
|
||||
|
||||
var escapeNumbers = false; // escape mode for numbers. true -> treated as hint-text
|
||||
var activeTimeout = null; // needed for hinttimeout > 0
|
||||
var canUpdate = false;
|
||||
|
||||
// keep track of the documents which we generated the hints for
|
||||
// docs = { doc: document, start: start_index in hints[], end: end_index in hints[] }
|
||||
var docs = [];
|
||||
|
||||
const Mode = new Struct("prompt", "action", "tags");
|
||||
Mode.defaultValue("tags", function () function () options.hinttags);
|
||||
function extended() options.extendedhinttags;
|
||||
const hintModes = {
|
||||
";": Mode("Focus hint", function (elem) buffer.focusElement(elem), extended),
|
||||
a: Mode("Save hint with prompt", function (elem) buffer.saveLink(elem, false)),
|
||||
s: Mode("Save hint", function (elem) buffer.saveLink(elem, true)),
|
||||
o: Mode("Follow hint", function (elem) buffer.followLink(elem, liberator.CURRENT_TAB)),
|
||||
t: Mode("Follow hint in a new tab", function (elem) buffer.followLink(elem, liberator.NEW_TAB)),
|
||||
b: Mode("Follow hint in a background tab", function (elem) buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB)),
|
||||
v: Mode("View hint source", function (elem, loc) buffer.viewSource(loc, false), extended),
|
||||
V: Mode("View hint source", function (elem, loc) buffer.viewSource(loc, true), extended),
|
||||
w: Mode("Follow hint in a new window", function (elem) buffer.followLink(elem, liberator.NEW_WINDOW), extended),
|
||||
|
||||
"?": Mode("Show information for hint", function (elem) buffer.showElementInfo(elem), extended),
|
||||
O: Mode("Open location based on hint", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)),
|
||||
T: Mode("Open new tab based on hint", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)),
|
||||
W: Mode("Open new window based on hint", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)),
|
||||
y: Mode("Yank hint location", function (elem, loc) util.copyToClipboard(loc, true)),
|
||||
Y: Mode("Yank hint description", function (elem) util.copyToClipboard(elem.textContent || "", true), extended),
|
||||
};
|
||||
|
||||
// reset all important variables
|
||||
function reset()
|
||||
{
|
||||
statusline.updateInputBuffer("");
|
||||
hintString = "";
|
||||
hintNumber = 0;
|
||||
usedTabKey = false;
|
||||
prevInput = "";
|
||||
pageHints = [];
|
||||
validHints = [];
|
||||
canUpdate = false;
|
||||
docs = [];
|
||||
escapeNumbers = false;
|
||||
|
||||
if (activeTimeout)
|
||||
clearTimeout(activeTimeout);
|
||||
activeTimeout = null;
|
||||
}
|
||||
|
||||
function updateStatusline()
|
||||
{
|
||||
statusline.updateInputBuffer((escapeNumbers ? mappings.getMapLeader() : "") + (hintNumber || ""));
|
||||
}
|
||||
|
||||
function generate(win)
|
||||
{
|
||||
if (!win)
|
||||
win = window.content;
|
||||
|
||||
var doc = win.document;
|
||||
var height = win.innerHeight;
|
||||
var width = win.innerWidth;
|
||||
var scrollX = doc.defaultView.scrollX;
|
||||
var scrollY = doc.defaultView.scrollY;
|
||||
|
||||
var baseNodeAbsolute = util.xmlToDom(<span highlight="Hint"/>, doc);
|
||||
|
||||
var elem, tagname, text, span, rect;
|
||||
var res = buffer.evaluateXPath(hintMode.tags(), doc, null, true);
|
||||
|
||||
var fragment = util.xmlToDom(<div highlight="hints"/>, doc);
|
||||
var start = pageHints.length;
|
||||
for (let elem in res)
|
||||
{
|
||||
// TODO: for iframes, this calculation is wrong
|
||||
rect = elem.getBoundingClientRect();
|
||||
if (!rect || rect.top > height || rect.bottom < 0 || rect.left > width || rect.right < 0)
|
||||
continue;
|
||||
|
||||
rect = elem.getClientRects()[0];
|
||||
if (!rect)
|
||||
continue;
|
||||
|
||||
var computedStyle = doc.defaultView.getComputedStyle(elem, null);
|
||||
if (computedStyle.getPropertyValue("visibility") == "hidden" || computedStyle.getPropertyValue("display") == "none")
|
||||
continue;
|
||||
|
||||
// TODO: mozilla docs recommend localName instead of tagName
|
||||
tagname = elem.tagName.toLowerCase();
|
||||
if (tagname == "input" || tagname == "textarea")
|
||||
text = elem.value;
|
||||
else if (tagname == "select")
|
||||
{
|
||||
if (elem.selectedIndex >= 0)
|
||||
text = elem.item(elem.selectedIndex).text;
|
||||
else
|
||||
text = "";
|
||||
}
|
||||
else
|
||||
text = elem.textContent.toLowerCase();
|
||||
|
||||
span = baseNodeAbsolute.cloneNode(true);
|
||||
span.style.left = (rect.left + scrollX) + "px";
|
||||
span.style.top = (rect.top + scrollY) + "px";
|
||||
fragment.appendChild(span);
|
||||
|
||||
pageHints.push([elem, text, span, null, elem.style.backgroundColor, elem.style.color]);
|
||||
}
|
||||
|
||||
if (doc.body)
|
||||
{
|
||||
doc.body.appendChild(fragment);
|
||||
docs.push({ doc: doc, start: start, end: pageHints.length - 1 });
|
||||
}
|
||||
|
||||
// also generate hints for frames
|
||||
Array.forEach(win.frames, function (frame) { generate(frame); });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: make it aware of imgspans
|
||||
function showActiveHint(newID, oldID)
|
||||
{
|
||||
var oldElem = validHints[oldID - 1];
|
||||
if (oldElem)
|
||||
setClass(oldElem, false);
|
||||
|
||||
var newElem = validHints[newID - 1];
|
||||
if (newElem)
|
||||
setClass(newElem, true);
|
||||
}
|
||||
|
||||
function setClass(elem, active)
|
||||
{
|
||||
let prefix = (elem.getAttributeNS(NS.uri, "class") || "") + " ";
|
||||
if (active)
|
||||
elem.setAttributeNS(NS.uri, "highlight", prefix + "HintActive");
|
||||
else
|
||||
elem.setAttributeNS(NS.uri, "highlight", prefix + "HintElem");
|
||||
}
|
||||
|
||||
function showHints()
|
||||
{
|
||||
|
||||
let elem, tagname, text, rect, span, imgspan;
|
||||
let hintnum = 1;
|
||||
let validHint = hintMatcher(hintString.toLowerCase());
|
||||
let activeHint = hintNumber || 1;
|
||||
validHints = [];
|
||||
|
||||
for (let [,{ doc: doc, start: start, end: end }] in Iterator(docs))
|
||||
{
|
||||
let scrollX = doc.defaultView.scrollX;
|
||||
let scrollY = doc.defaultView.scrollY;
|
||||
|
||||
inner:
|
||||
for (let i in (util.interruptableRange(start, end + 1, 500)))
|
||||
{
|
||||
let hint = pageHints[i];
|
||||
[elem, text, span, imgspan] = hint;
|
||||
|
||||
if (!validHint(text))
|
||||
{
|
||||
span.style.display = "none";
|
||||
if (imgspan)
|
||||
imgspan.style.display = "none";
|
||||
|
||||
elem.removeAttributeNS(NS.uri, "highlight");
|
||||
continue inner;
|
||||
}
|
||||
|
||||
if (text == "" && elem.firstChild && elem.firstChild.tagName == "IMG")
|
||||
{
|
||||
if (!imgspan)
|
||||
{
|
||||
rect = elem.firstChild.getBoundingClientRect();
|
||||
if (!rect)
|
||||
continue;
|
||||
|
||||
imgspan = util.xmlToDom(<span highlight="Hint"/>, doc);
|
||||
imgspan.setAttributeNS(NS.uri, "class", "HintImage");
|
||||
imgspan.style.left = (rect.left + scrollX) + "px";
|
||||
imgspan.style.top = (rect.top + scrollY) + "px";
|
||||
imgspan.style.width = (rect.right - rect.left) + "px";
|
||||
imgspan.style.height = (rect.bottom - rect.top) + "px";
|
||||
hint[IMGSPAN] = imgspan;
|
||||
span.parentNode.appendChild(imgspan);
|
||||
}
|
||||
setClass(imgspan, activeHint == hintnum)
|
||||
}
|
||||
|
||||
span.setAttribute("number", hintnum++);
|
||||
if (imgspan)
|
||||
imgspan.setAttribute("number", hintnum);
|
||||
else
|
||||
setClass(elem, activeHint == hintnum);
|
||||
validHints.push(elem);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.usermode)
|
||||
{
|
||||
let css = [];
|
||||
// FIXME: Broken for imgspans.
|
||||
for (let [,{ doc: doc }] in Iterator(docs))
|
||||
{
|
||||
for (let elem in buffer.evaluateXPath("//*[@liberator:highlight and @number]", doc))
|
||||
{
|
||||
let group = elem.getAttributeNS(NS.uri, "highlight");
|
||||
css.push(highlight.selector(group) + "[number='" + elem.getAttribute("number") + "'] { " + elem.style.cssText + " }");
|
||||
}
|
||||
}
|
||||
styles.addSheet("hint-positions", "*", css.join("\n"), true, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function removeHints(timeout)
|
||||
{
|
||||
var firstElem = validHints[0] || null;
|
||||
|
||||
for (let [,{ doc: doc, start: start, end: end }] in Iterator(docs))
|
||||
{
|
||||
for (let elem in buffer.evaluateXPath("//*[@liberator:highlight='hints']", doc))
|
||||
elem.parentNode.removeChild(elem);
|
||||
for (let i in util.range(start, end + 1))
|
||||
{
|
||||
let hint = pageHints[i];
|
||||
if (!timeout || hint[ELEM] != firstElem)
|
||||
hint[ELEM].removeAttributeNS(NS.uri, "highlight");
|
||||
}
|
||||
|
||||
// animate the disappearance of the first hint
|
||||
if (timeout && firstElem)
|
||||
{
|
||||
// USE THIS FOR MAKING THE SELECTED ELEM RED
|
||||
// firstElem.style.backgroundColor = "red";
|
||||
// firstElem.style.color = "white";
|
||||
// setTimeout(function () {
|
||||
// firstElem.style.backgroundColor = firstElemBgColor;
|
||||
// firstElem.style.color = firstElemColor;
|
||||
// }, 200);
|
||||
// OR USE THIS FOR BLINKING:
|
||||
// var counter = 0;
|
||||
// var id = setInterval(function () {
|
||||
// firstElem.style.backgroundColor = "red";
|
||||
// if (counter % 2 == 0)
|
||||
// firstElem.style.backgroundColor = "yellow";
|
||||
// else
|
||||
// firstElem.style.backgroundColor = "#88FF00";
|
||||
//
|
||||
// if (counter++ >= 2)
|
||||
// {
|
||||
// firstElem.style.backgroundColor = firstElemBgColor;
|
||||
// firstElem.style.color = firstElemColor;
|
||||
// clearTimeout(id);
|
||||
// }
|
||||
// }, 100);
|
||||
setTimeout(function () { firstElem.removeAttributeNS(NS.uri, "highlight") }, timeout);
|
||||
}
|
||||
}
|
||||
styles.removeSheet("hint-positions", null, null, null, true);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
function processHints(followFirst)
|
||||
{
|
||||
if (validHints.length == 0)
|
||||
{
|
||||
liberator.beep();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options["followhints"] > 0)
|
||||
{
|
||||
if (!followFirst)
|
||||
return false; // no return hit; don't examine uniqueness
|
||||
|
||||
// OK. return hit. But there's more than one hint, and
|
||||
// there's no tab-selected current link. Do not follow in mode 2
|
||||
if (options["followhints"] == 2 && validHints.length > 1 && !hintNumber)
|
||||
return liberator.beep();
|
||||
}
|
||||
|
||||
if (!followFirst)
|
||||
{
|
||||
let firstHref = validHints[0].getAttribute("href") || null;
|
||||
if (firstHref)
|
||||
{
|
||||
if (validHints.some(function (e) e.getAttribute("href") != firstHref))
|
||||
return false;
|
||||
}
|
||||
else if (validHints.length > 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var timeout = followFirst || events.feedingKeys ? 0 : 500;
|
||||
var activeIndex = (hintNumber ? hintNumber - 1 : 0);
|
||||
var elem = validHints[activeIndex];
|
||||
removeHints(timeout);
|
||||
|
||||
if (timeout == 0)
|
||||
// force a possible mode change, based on wheter an input field has focus
|
||||
events.onFocusChange();
|
||||
setTimeout(function () {
|
||||
if (modes.extended & modes.HINTS)
|
||||
modes.reset();
|
||||
hintMode.action(elem, elem.href || "");
|
||||
}, timeout);
|
||||
return true;
|
||||
}
|
||||
|
||||
function onInput (event)
|
||||
{
|
||||
prevInput = "text";
|
||||
|
||||
// clear any timeout which might be active after pressing a number
|
||||
if (activeTimeout)
|
||||
{
|
||||
clearTimeout(activeTimeout);
|
||||
activeTimeout = null;
|
||||
}
|
||||
|
||||
hintNumber = 0;
|
||||
hintString = commandline.getCommand();
|
||||
updateStatusline();
|
||||
showHints();
|
||||
if (validHints.length == 1)
|
||||
processHints(false);
|
||||
}
|
||||
|
||||
function hintMatcher(hintString) //{{{
|
||||
{
|
||||
function containsMatcher(hintString) //{{{
|
||||
{
|
||||
var tokens = hintString.split(/ +/);
|
||||
return function (linkText) tokens.every(function (token) linkText.indexOf(token) >= 0);
|
||||
} //}}}
|
||||
|
||||
function wordStartsWithMatcher(hintString, allowWordOverleaping) //{{{
|
||||
{
|
||||
var hintStrings = hintString.split(/ +/);
|
||||
var wordSplitRegex = new RegExp(options["wordseparators"]);
|
||||
|
||||
// What the **** does this do? --Kris
|
||||
function charsAtBeginningOfWords(chars, words, allowWordOverleaping)
|
||||
{
|
||||
var charIdx = 0;
|
||||
var numMatchedWords = 0;
|
||||
for (let [,word] in Iterator(words))
|
||||
{
|
||||
if (word.length == 0)
|
||||
continue;
|
||||
|
||||
let wcIdx = 0;
|
||||
// Check if the current word matches same characters as the previous word.
|
||||
// Each already matched word has matched at least one character.
|
||||
if (charIdx > numMatchedWords)
|
||||
{
|
||||
let matchingStarted = false;
|
||||
for (let i in util.range(numMatchedWords, charIdx))
|
||||
{
|
||||
if (chars[i] == word[wcIdx])
|
||||
{
|
||||
matchingStarted = true;
|
||||
wcIdx++;
|
||||
}
|
||||
else if (matchingStarted)
|
||||
{
|
||||
wcIdx = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// the current word matches same characters as the previous word
|
||||
var prevCharIdx;
|
||||
if (wcIdx > 0)
|
||||
{
|
||||
prevCharIdx = charIdx;
|
||||
// now check if it matches additional characters
|
||||
for (; wcIdx < word.length && charIdx < chars.length; wcIdx++, charIdx++)
|
||||
{
|
||||
if (word[wcIdx] != chars[charIdx])
|
||||
break;
|
||||
}
|
||||
|
||||
// the word doesn't match additional characters, now check if the
|
||||
// already matched characters are equal to the next characters for matching,
|
||||
// if yes, then consume them
|
||||
if (prevCharIdx == charIdx)
|
||||
{
|
||||
for (let i = 0; i < wcIdx && charIdx < chars.length; i++, charIdx++)
|
||||
{
|
||||
if (word[i] != chars[charIdx])
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
numMatchedWords++;
|
||||
}
|
||||
// the current word doesn't match same characters as the previous word, just
|
||||
// try to match the next characters
|
||||
else
|
||||
{
|
||||
prevCharIdx = charIdx;
|
||||
for (let i = 0; i < word.length && charIdx < chars.length; i++, charIdx++)
|
||||
{
|
||||
if (word[i] != chars[charIdx])
|
||||
break;
|
||||
}
|
||||
|
||||
if (prevCharIdx == charIdx)
|
||||
{
|
||||
if (!allowWordOverleaping)
|
||||
return false;
|
||||
}
|
||||
else
|
||||
numMatchedWords++;
|
||||
}
|
||||
|
||||
if (charIdx == chars.length)
|
||||
return true;
|
||||
}
|
||||
|
||||
return (charIdx == chars.length);
|
||||
}
|
||||
|
||||
function stringsAtBeginningOfWords(strings, words, allowWordOverleaping)
|
||||
{
|
||||
var strIdx = 0;
|
||||
for (let [,word] in Iterator(words))
|
||||
{
|
||||
if (word.length == 0)
|
||||
continue;
|
||||
|
||||
let str = strings[strIdx];
|
||||
if (str.length == 0 || word.indexOf(str) == 0)
|
||||
strIdx++;
|
||||
else if (!allowWordOverleaping)
|
||||
return false;
|
||||
|
||||
if (strIdx == strings.length)
|
||||
return true;
|
||||
}
|
||||
|
||||
for (; strIdx < strings.length; strIdx++)
|
||||
{
|
||||
if (strings[strIdx].length != 0)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function wordStartsWith(linkText)
|
||||
{
|
||||
if (hintStrings.length == 1 && hintStrings[0].length == 0)
|
||||
return true;
|
||||
|
||||
let words = linkText.split(wordSplitRegex).map(String.toLowerCase);
|
||||
if (hintStrings.length == 1)
|
||||
return charsAtBeginningOfWords(hintStrings[0], words, allowWordOverleaping);
|
||||
else
|
||||
return stringsAtBeginningOfWords(hintStrings, words, allowWordOverleaping);
|
||||
}
|
||||
|
||||
return wordStartsWith;
|
||||
} //}}}
|
||||
|
||||
var hintMatching = options["hintmatching"];
|
||||
switch (hintMatching)
|
||||
{
|
||||
case "contains" : return containsMatcher(hintString);
|
||||
case "wordstartswith": return wordStartsWithMatcher(hintString, /*allowWordOverleaping=*/ true);
|
||||
case "firstletters" : return wordStartsWithMatcher(hintString, /*allowWordOverleaping=*/ false);
|
||||
case "custom" : return liberator.plugins.customHintMatcher(hintString);
|
||||
default : liberator.echoerr("Invalid hintmatching type: " + hintMatching);
|
||||
}
|
||||
return null;
|
||||
} //}}}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// OPTIONS /////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
const DEFAULT_HINTTAGS = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " +
|
||||
"//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " +
|
||||
"//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " +
|
||||
"//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | //xhtml:button | //xhtml:select";
|
||||
|
||||
options.add(["extendedhinttags", "eht"],
|
||||
"XPath string of hintable elements activated by ';'",
|
||||
"string", DEFAULT_HINTTAGS);
|
||||
|
||||
options.add(["hinttags", "ht"],
|
||||
"XPath string of hintable elements activated by 'f' and 'F'",
|
||||
"string", DEFAULT_HINTTAGS);
|
||||
|
||||
options.add(["hinttimeout", "hto"],
|
||||
"Timeout before automatically following a non-unique numerical hint",
|
||||
"number", 0,
|
||||
{ validator: function (value) value >= 0 });
|
||||
|
||||
options.add(["followhints", "fh"],
|
||||
// FIXME: this description isn't very clear but I can't think of a
|
||||
// better one right now.
|
||||
"Change the behaviour of <Return> in hint mode",
|
||||
"number", 0,
|
||||
{
|
||||
completer: function () [
|
||||
["0", "Follow the first hint as soon as typed text uniquely identifies it. Follow the selected hint on [m]<Return>[m]."],
|
||||
["1", "Follow the selected hint on [m]<Return>[m]."],
|
||||
["2", "Follow the selected hint on [m]<Return>[m] only it's been [m]<Tab>[m]-selected."]
|
||||
],
|
||||
validator: function (value) Option.validateCompleter
|
||||
});
|
||||
|
||||
options.add(["hintmatching", "hm"],
|
||||
"How links are matched",
|
||||
"string", "contains",
|
||||
{
|
||||
completer: function (filter)
|
||||
{
|
||||
return [[m, ""] for each (m in ["contains", "wordstartswith", "firstletters", "custom"])];
|
||||
},
|
||||
validator: Option.validateCompleter
|
||||
});
|
||||
|
||||
options.add(["wordseparators", "wsp"],
|
||||
"How words are split for hintmatching",
|
||||
"string", '[.,!?:;/"^$%&?()[\\]{}<>#*+|=~ _-]');
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// MAPPINGS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
mappings.add(myModes, ["f"],
|
||||
"Start QuickHint mode",
|
||||
function () { hints.show("o"); });
|
||||
|
||||
mappings.add(myModes, ["F"],
|
||||
"Start QuickHint mode, but open link in a new tab",
|
||||
function () { hints.show("t"); });
|
||||
|
||||
mappings.add(myModes, [";"],
|
||||
"Start an extended hint mode",
|
||||
function (arg) { hints.show(arg); },
|
||||
{ flags: Mappings.flags.ARGUMENT });
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
return {
|
||||
|
||||
addMode: function (mode)
|
||||
{
|
||||
hintModes[mode] = Mode.apply(Mode, Array.slice(arguments, 1));
|
||||
},
|
||||
|
||||
show: function (minor, filter, win)
|
||||
{
|
||||
hintMode = hintModes[minor];
|
||||
if (!hintMode)
|
||||
{
|
||||
liberator.beep();
|
||||
return;
|
||||
}
|
||||
commandline.input(hintMode.prompt + ":", null, { onChange: onInput });
|
||||
modes.extended = modes.HINTS;
|
||||
|
||||
submode = minor;
|
||||
hintString = filter || "";
|
||||
hintNumber = 0;
|
||||
usedTab = false;
|
||||
prevInput = "";
|
||||
canUpdate = false;
|
||||
|
||||
generate(win);
|
||||
|
||||
// get all keys from the input queue
|
||||
liberator.threadYield(true);
|
||||
|
||||
canUpdate = true;
|
||||
showHints();
|
||||
|
||||
if (validHints.length == 0)
|
||||
{
|
||||
liberator.beep();
|
||||
modes.reset();
|
||||
return false;
|
||||
}
|
||||
else if (validHints.length == 1)
|
||||
{
|
||||
processHints(true);
|
||||
return false;
|
||||
}
|
||||
else // still hints visible
|
||||
return true;
|
||||
},
|
||||
|
||||
hide: function ()
|
||||
{
|
||||
removeHints(0);
|
||||
},
|
||||
|
||||
onEvent: function (event)
|
||||
{
|
||||
var key = events.toString(event);
|
||||
var followFirst = false;
|
||||
|
||||
// clear any timeout which might be active after pressing a number
|
||||
if (activeTimeout)
|
||||
{
|
||||
clearTimeout(activeTimeout);
|
||||
activeTimeout = null;
|
||||
}
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case "<Return>":
|
||||
followFirst = true;
|
||||
break;
|
||||
|
||||
case "<Tab>":
|
||||
case "<S-Tab>":
|
||||
usedTabKey = true;
|
||||
if (hintNumber == 0)
|
||||
hintNumber = 1;
|
||||
|
||||
var oldID = hintNumber;
|
||||
if (key == "<Tab>")
|
||||
{
|
||||
if (++hintNumber > validHints.length)
|
||||
hintNumber = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (--hintNumber < 1)
|
||||
hintNumber = validHints.length;
|
||||
}
|
||||
showActiveHint(hintNumber, oldID);
|
||||
updateStatusline();
|
||||
return;
|
||||
|
||||
case "<BS>":
|
||||
if (hintNumber > 0 && !usedTabKey)
|
||||
{
|
||||
hintNumber = Math.floor(hintNumber / 10);
|
||||
if (hintNumber == 0)
|
||||
prevInput = "text";
|
||||
}
|
||||
else
|
||||
{
|
||||
usedTabKey = false;
|
||||
hintNumber = 0;
|
||||
liberator.beep();
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case mappings.getMapLeader():
|
||||
escapeNumbers = !escapeNumbers;
|
||||
if (escapeNumbers && usedTabKey) // hintNumber not used normally, but someone may wants to toggle
|
||||
hintNumber = 0; // <tab>s ? reset. Prevent to show numbers not entered.
|
||||
|
||||
updateStatusline();
|
||||
return;
|
||||
|
||||
default:
|
||||
if (/^\d$/.test(key))
|
||||
{
|
||||
// FIXME: Kludge.
|
||||
if (escapeNumbers)
|
||||
{
|
||||
let cmdline = document.getElementById("liberator-commandline-command");
|
||||
let start = cmdline.selectionStart;
|
||||
let end = cmdline.selectionEnd;
|
||||
cmdline.value = cmdline.value.substr(0, start) + key + cmdline.value.substr(start);
|
||||
cmdline.selectionStart = start + 1;
|
||||
cmdline.selectionEnd = end + 1;
|
||||
return;
|
||||
}
|
||||
|
||||
prevInput = "number";
|
||||
|
||||
var oldHintNumber = hintNumber;
|
||||
if (hintNumber == 0 || usedTabKey)
|
||||
{
|
||||
usedTabKey = false;
|
||||
hintNumber = parseInt(key, 10);
|
||||
}
|
||||
else
|
||||
hintNumber = (hintNumber * 10) + parseInt(key, 10);
|
||||
|
||||
updateStatusline();
|
||||
|
||||
if (!canUpdate)
|
||||
return;
|
||||
|
||||
if (docs.length == 0)
|
||||
{
|
||||
generate();
|
||||
showHints();
|
||||
}
|
||||
showActiveHint(hintNumber, oldHintNumber || 1);
|
||||
|
||||
if (hintNumber == 0 || hintNumber > validHints.length)
|
||||
{
|
||||
liberator.beep();
|
||||
return;
|
||||
}
|
||||
|
||||
// if we write a numeric part like 3, but we have 45 hints, only follow
|
||||
// the hint after a timeout, as the user might have wanted to follow link 34
|
||||
if (hintNumber > 0 && hintNumber * 10 <= validHints.length)
|
||||
{
|
||||
var timeout = options["hinttimeout"];
|
||||
if (timeout > 0)
|
||||
activeTimeout = setTimeout(function () { processHints(true); }, timeout);
|
||||
|
||||
return false;
|
||||
}
|
||||
// we have a unique hint
|
||||
processHints(true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateStatusline();
|
||||
|
||||
if (canUpdate)
|
||||
{
|
||||
if (docs.length == 0 && hintString.length > 0)
|
||||
generate();
|
||||
|
||||
showHints();
|
||||
processHints(followFirst);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: add resize support
|
||||
// window.addEventListener("resize", onResize, null);
|
||||
|
||||
// function onResize(event)
|
||||
// {
|
||||
// if (event)
|
||||
// doc = event.originalTarget;
|
||||
// else
|
||||
// doc = window.content.document;
|
||||
// }
|
||||
|
||||
//}}}
|
||||
}; //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
966
common/content/io.js
Normal file
966
common/content/io.js
Normal file
@@ -0,0 +1,966 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
Code based on venkman
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
plugins.contexts = {};
|
||||
function Script(file)
|
||||
{
|
||||
let self = plugins.contexts[file.path]
|
||||
if (self)
|
||||
{
|
||||
if (self.onUnload)
|
||||
self.onUnload();
|
||||
return self;
|
||||
}
|
||||
plugins.contexts[file.path] = this;
|
||||
this.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/, function (_0, _1) _1.toUpperCase());
|
||||
this.PATH = file.path;
|
||||
this.__context__ = this;
|
||||
|
||||
// This belongs elsewhere
|
||||
for (let [,dir] in Iterator(io.getRuntimeDirectories("plugin")))
|
||||
{
|
||||
if (dir.contains(file, false))
|
||||
plugins[name] = this.NAME;
|
||||
}
|
||||
}
|
||||
Script.prototype = plugins;
|
||||
|
||||
// TODO: why are we passing around strings rather than file objects?
|
||||
function IO() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
const WINDOWS = liberator.has("Win32");
|
||||
const EXTENSION_NAME = config.name.toLowerCase(); // "vimperator" or "muttator"
|
||||
|
||||
const ioService = Components.classes['@mozilla.org/network/io-service;1']
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
const environmentService = Components.classes["@mozilla.org/process/environment;1"]
|
||||
.getService(Components.interfaces.nsIEnvironment);
|
||||
const directoryService = Components.classes["@mozilla.org/file/directory_service;1"]
|
||||
.getService(Components.interfaces.nsIProperties);
|
||||
const downloadManager = Components.classes["@mozilla.org/download-manager;1"]
|
||||
.createInstance(Components.interfaces.nsIDownloadManager);
|
||||
|
||||
var processDir = directoryService.get("CurWorkD", Components.interfaces.nsIFile);
|
||||
var cwd = processDir;
|
||||
var oldcwd = null;
|
||||
|
||||
var lastRunCommand = ""; // updated whenever the users runs a command with :!
|
||||
var scriptNames = [];
|
||||
|
||||
// default option values
|
||||
var cdpath = "," + (environmentService.get("CDPATH").replace(/[:;]/g, ",") || ",");
|
||||
var runtimepath = "~/" + (WINDOWS ? "" : ".") + EXTENSION_NAME;
|
||||
var shell, shellcmdflag;
|
||||
|
||||
if (WINDOWS)
|
||||
{
|
||||
shell = "cmd.exe";
|
||||
// TODO: setting 'shell' to "something containing sh" updates
|
||||
// 'shellcmdflag' appropriately at startup on Windows in Vim
|
||||
shellcmdflag = "/c";
|
||||
}
|
||||
else
|
||||
{
|
||||
shell = environmentService.get("SHELL") || "sh";
|
||||
shellcmdflag = "-c";
|
||||
}
|
||||
|
||||
function expandPathList(list) list.split(",").map(io.expandPath).join(",")
|
||||
|
||||
function getPathsFromPathList(list)
|
||||
{
|
||||
if (!list)
|
||||
return [];
|
||||
else
|
||||
// empty list item means the current directory
|
||||
return list.replace(/,$/, "")
|
||||
.split(",")
|
||||
.map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir);
|
||||
}
|
||||
|
||||
function replacePathSep(path)
|
||||
{
|
||||
if (WINDOWS)
|
||||
return path.replace("/", "\\");
|
||||
return path;
|
||||
}
|
||||
|
||||
function joinPaths(head, tail)
|
||||
{
|
||||
let path = ioManager.getFile(head);
|
||||
path.appendRelativePath(ioManager.expandPath(tail)); // FIXME: should only expand env vars and normalise path separators
|
||||
return path;
|
||||
}
|
||||
|
||||
function isAbsolutePath(path)
|
||||
{
|
||||
try
|
||||
{
|
||||
Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile)
|
||||
.initWithPath(path);
|
||||
return true;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var downloadListener = {
|
||||
onDownloadStateChange: function (state, download)
|
||||
{
|
||||
if (download.state == downloadManager.DOWNLOAD_FINISHED)
|
||||
{
|
||||
let url = download.source.spec;
|
||||
let title = download.displayName;
|
||||
let file = download.targetFile.path;
|
||||
let size = download.size;
|
||||
|
||||
liberator.echomsg("Download of " + title + " to " + file + " finished", 1);
|
||||
autocommands.trigger("DownloadPost", { url: url, title: title, file: file, size: size });
|
||||
}
|
||||
},
|
||||
onStateChange: function () {},
|
||||
onProgressChange: function () {},
|
||||
onSecurityChange: function () {}
|
||||
};
|
||||
|
||||
downloadManager.addListener(downloadListener);
|
||||
liberator.registerObserver("shutdown", function () {
|
||||
downloadManager.removeListener(downloadListener);
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// OPTIONS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
options.add(["cdpath", "cd"],
|
||||
"List of directories searched when executing :cd",
|
||||
"stringlist", cdpath,
|
||||
{ setter: function (value) expandPathList(value) });
|
||||
|
||||
options.add(["runtimepath", "rtp"],
|
||||
"List of directories searched for runtime files",
|
||||
"stringlist", runtimepath,
|
||||
{ setter: function (value) expandPathList(value) });
|
||||
|
||||
options.add(["shell", "sh"],
|
||||
"Shell to use for executing :! and :run commands",
|
||||
"string", shell,
|
||||
{ setter: function (value) io.expandPath(value) });
|
||||
|
||||
options.add(["shellcmdflag", "shcf"],
|
||||
"Flag passed to shell when executing :! and :run commands",
|
||||
"string", shellcmdflag);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// COMMANDS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
commands.add(["cd", "chd[ir]"],
|
||||
"Change the current directory",
|
||||
function (args)
|
||||
{
|
||||
args = args.literalArg;
|
||||
|
||||
if (!args)
|
||||
{
|
||||
args = "~";
|
||||
}
|
||||
else if (args == "-")
|
||||
{
|
||||
if (oldcwd)
|
||||
{
|
||||
args = oldcwd.path;
|
||||
}
|
||||
else
|
||||
{
|
||||
liberator.echoerr("E186: No previous directory");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
args = io.expandPath(args);
|
||||
|
||||
// go directly to an absolute path or look for a relative path
|
||||
// match in 'cdpath'
|
||||
// TODO: handle ../ and ./ paths
|
||||
if (isAbsolutePath(args))
|
||||
{
|
||||
if (io.setCurrentDirectory(args))
|
||||
liberator.echo(io.getCurrentDirectory().path);
|
||||
}
|
||||
else
|
||||
{
|
||||
let dirs = getPathsFromPathList(options["cdpath"]);
|
||||
let found = false;
|
||||
|
||||
for (let [,dir] in Iterator(dirs))
|
||||
{
|
||||
dir = joinPaths(dir, args);
|
||||
|
||||
if (dir.exists() && dir.isDirectory() && dir.isReadable())
|
||||
{
|
||||
io.setCurrentDirectory(dir.path);
|
||||
liberator.echo(io.getCurrentDirectory().path);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
liberator.echoerr("E344: Can't find directory \"" + args + "\" in cdpath"
|
||||
+ "\n"
|
||||
+ "E472: Command failed");
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
argCount: "?",
|
||||
completer: function (context) completion.directory(context, true),
|
||||
literal: 0
|
||||
});
|
||||
|
||||
// NOTE: this command is only used in :source
|
||||
commands.add(["fini[sh]"],
|
||||
"Stop sourcing a script file",
|
||||
function () { liberator.echoerr("E168: :finish used outside of a sourced file"); },
|
||||
{ argCount: "0" });
|
||||
|
||||
commands.add(["pw[d]"],
|
||||
"Print the current directory name",
|
||||
function () { liberator.echo(io.getCurrentDirectory().path); },
|
||||
{ argCount: "0" });
|
||||
|
||||
// "mkv[imperatorrc]" or "mkm[uttatorrc]"
|
||||
commands.add([EXTENSION_NAME.replace(/(.)(.*)/, "mk$1[$2rc]")],
|
||||
"Write current key mappings and changed options to the config file",
|
||||
function (args)
|
||||
{
|
||||
// TODO: "E172: Only one file name allowed"
|
||||
let filename = args[0] || "~/" + (WINDOWS ? "_" : ".") + EXTENSION_NAME + "rc";
|
||||
let file = io.getFile(filename);
|
||||
|
||||
if (file.exists() && !args.bang)
|
||||
{
|
||||
liberator.echoerr("E189: \"" + filename + "\" exists (add ! to override)");
|
||||
return;
|
||||
}
|
||||
|
||||
// FIXME: Use a set/specifiable list here:
|
||||
let lines = [cmd.serial().map(commands.commandToString) for (cmd in commands) if (cmd.serial)];
|
||||
lines = util.Array.flatten(lines);
|
||||
|
||||
// :mkvimrc doesn't save autocommands, so we don't either - remove this code at some point
|
||||
// line += "\n\" Auto-Commands\n";
|
||||
// for (let item in autocommands)
|
||||
// line += "autocmd " + item.event + " " + item.pattern.source + " " + item.command + "\n";
|
||||
|
||||
// if (mappings.getMapLeader() != "\\")
|
||||
// line += "\nlet mapleader = \"" + mappings.getMapLeader() + "\"\n";
|
||||
|
||||
// source a user .vimperatorrc file
|
||||
lines.unshift('"' + liberator.version);
|
||||
lines.push("\nsource! " + filename + ".local");
|
||||
lines.push("\n\" vim: set ft=vimperator:");
|
||||
|
||||
try
|
||||
{
|
||||
io.writeFile(file, lines.join("\n"));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
liberator.echoerr("E190: Cannot open \"" + filename + "\" for writing");
|
||||
liberator.log("Could not write to " + file.path + ": " + e.message); // XXX
|
||||
}
|
||||
},
|
||||
{
|
||||
argCount: "?",
|
||||
bang: true,
|
||||
completer: function (context) completion.file(context, true)
|
||||
});
|
||||
|
||||
commands.add(["runt[ime]"],
|
||||
"Source the specified file from each directory in 'runtimepath'",
|
||||
function (args) { io.sourceFromRuntimePath(args, args.bang); },
|
||||
{
|
||||
argCount: "+",
|
||||
bang: true
|
||||
}
|
||||
);
|
||||
|
||||
commands.add(["scrip[tnames]"],
|
||||
"List all sourced script names",
|
||||
function ()
|
||||
{
|
||||
let list = template.tabular(["<SNR>", "Filename"], ["text-align: right; padding-right: 1em;"],
|
||||
([i + 1, file] for ([i, file] in Iterator(scriptNames)))); // TODO: add colon?
|
||||
|
||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
},
|
||||
{ argCount: "0" });
|
||||
|
||||
commands.add(["so[urce]"],
|
||||
"Read Ex commands from a file",
|
||||
function (args)
|
||||
{
|
||||
// FIXME: "E172: Only one file name allowed"
|
||||
io.source(args[0], args.bang);
|
||||
},
|
||||
{
|
||||
argCount: "1",
|
||||
bang: true,
|
||||
completer: function (context) completion.file(context, true)
|
||||
});
|
||||
|
||||
commands.add(["!", "run"],
|
||||
"Run a command",
|
||||
function (args)
|
||||
{
|
||||
let special = args.bang;
|
||||
args = args.string;
|
||||
|
||||
// :!! needs to be treated specially as the command parser sets the
|
||||
// special flag but removes the ! from args
|
||||
if (special)
|
||||
args = "!" + args;
|
||||
|
||||
// replaceable bang and no previous command?
|
||||
if (/((^|[^\\])(\\\\)*)!/.test(args) && !lastRunCommand)
|
||||
{
|
||||
liberator.echoerr("E34: No previous command");
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: Vim doesn't replace ! preceded by 2 or more backslashes and documents it - desirable?
|
||||
// pass through a raw bang when escaped or substitute the last command
|
||||
args = args.replace(/(\\)*!/g,
|
||||
function (m) /^\\(\\\\)*!$/.test(m) ? m.replace("\\!", "!") : m.replace("!", lastRunCommand)
|
||||
);
|
||||
|
||||
lastRunCommand = args;
|
||||
|
||||
let output = io.system(args);
|
||||
let command = ":" + util.escapeHTML(commandline.getCommand()) + "<br/>";
|
||||
|
||||
liberator.echo(template.generic(<span style="white-space: pre">{output}</span>))
|
||||
liberator.echo(command + util.escapeHTML(output));
|
||||
|
||||
autocommands.trigger("ShellCmdPost", {});
|
||||
},
|
||||
{
|
||||
argCount: "?", // TODO: "1" - probably not worth supporting weird Vim edge cases. The dream is dead. --djk
|
||||
bang: true,
|
||||
completer: function (context) completion.shellCommand(context),
|
||||
literal: 0
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
liberator.registerObserver("load_completion", function ()
|
||||
{
|
||||
completion.setFunctionCompleter([ioManager.getFile, ioManager.expandPath],
|
||||
[function (context, obj, args) {
|
||||
context.quote[2] = "";
|
||||
completion.file(context, true);
|
||||
}]);
|
||||
});
|
||||
|
||||
var ioManager = {
|
||||
|
||||
MODE_RDONLY: 0x01,
|
||||
MODE_WRONLY: 0x02,
|
||||
MODE_RDWR: 0x04,
|
||||
MODE_CREATE: 0x08,
|
||||
MODE_APPEND: 0x10,
|
||||
MODE_TRUNCATE: 0x20,
|
||||
MODE_SYNC: 0x40,
|
||||
MODE_EXCL: 0x80,
|
||||
|
||||
sourcing: null,
|
||||
|
||||
expandPath: function (path)
|
||||
{
|
||||
// TODO: proper pathname separator translation like Vim - this should be done elsewhere
|
||||
if (WINDOWS)
|
||||
path = path.replace("/", "\\", "g");
|
||||
|
||||
// expand "~" to VIMPERATOR_HOME or HOME (USERPROFILE or HOMEDRIVE\HOMEPATH on Windows if HOME is not set)
|
||||
if (/^~/.test(path))
|
||||
{
|
||||
let home = environmentService.get("VIMPERATOR_HOME");
|
||||
|
||||
if (!home)
|
||||
home = environmentService.get("HOME");
|
||||
|
||||
if (WINDOWS && !home)
|
||||
home = environmentService.get("USERPROFILE") ||
|
||||
environmentService.get("HOMEDRIVE") + environmentService.get("HOMEPATH");
|
||||
|
||||
path = home + path.substr(1);
|
||||
}
|
||||
|
||||
// expand any $ENV vars - this is naive but so is Vim and we like to be compatible
|
||||
// TODO: Vim does not expand variables set to an empty string (and documents it).
|
||||
// Kris reckons we shouldn't replicate this 'bug'. --djk
|
||||
// TODO: should we be doing this for all paths?
|
||||
path = path.replace(
|
||||
RegExp("\\$(\\w+)\\b|\\${(\\w+)}" + (WINDOWS ? "|%(\\w+)%" : ""), "g"),
|
||||
function (m, n1, n2, n3) environmentService.get(n1 || n2 || n3) || m
|
||||
);
|
||||
|
||||
return path.replace("\\ ", " ", "g");
|
||||
},
|
||||
|
||||
// TODO: there seems to be no way, short of a new component, to change
|
||||
// Firefox's CWD - see // https://bugzilla.mozilla.org/show_bug.cgi?id=280953
|
||||
getCurrentDirectory: function ()
|
||||
{
|
||||
let dir = ioManager.getFile(cwd.path);
|
||||
|
||||
// NOTE: the directory could have been deleted underneath us so
|
||||
// fallback to Firefox's CWD
|
||||
if (dir.exists() && dir.isDirectory())
|
||||
return dir;
|
||||
else
|
||||
return processDir;
|
||||
},
|
||||
|
||||
setCurrentDirectory: function (newdir)
|
||||
{
|
||||
newdir = newdir || "~";
|
||||
|
||||
if (newdir == "-")
|
||||
{
|
||||
[cwd, oldcwd] = [oldcwd, this.getCurrentDirectory()];
|
||||
}
|
||||
else
|
||||
{
|
||||
let dir = ioManager.getFile(newdir);
|
||||
|
||||
if (!dir.exists() || !dir.isDirectory())
|
||||
{
|
||||
liberator.echoerr("E344: Can't find directory \"" + dir.path + "\" in path");
|
||||
return null;
|
||||
}
|
||||
|
||||
[cwd, oldcwd] = [dir, this.getCurrentDirectory()];
|
||||
}
|
||||
|
||||
return ioManager.getCurrentDirectory();
|
||||
},
|
||||
|
||||
getRuntimeDirectories: function (specialDirectory)
|
||||
{
|
||||
let dirs = getPathsFromPathList(options["runtimepath"]);
|
||||
|
||||
dirs = dirs.map(function (dir) joinPaths(dir, specialDirectory))
|
||||
.filter(function (dir) dir.exists() && dir.isDirectory() && dir.isReadable());
|
||||
|
||||
return dirs;
|
||||
},
|
||||
|
||||
getRCFile: function (dir)
|
||||
{
|
||||
dir = dir || "~";
|
||||
|
||||
let rcFile1 = joinPaths(dir, "." + EXTENSION_NAME + "rc");
|
||||
let rcFile2 = joinPaths(dir, "_" + EXTENSION_NAME + "rc");
|
||||
|
||||
if (WINDOWS)
|
||||
[rcFile1, rcFile2] = [rcFile2, rcFile1];
|
||||
|
||||
if (rcFile1.exists() && rcFile1.isFile())
|
||||
return rcFile1;
|
||||
else if (rcFile2.exists() && rcFile2.isFile())
|
||||
return rcFile2;
|
||||
else
|
||||
return null;
|
||||
},
|
||||
|
||||
// return a nsILocalFile for path where you can call isDirectory(), etc. on
|
||||
// caller must check with .exists() if the returned file really exists
|
||||
// also expands relative paths
|
||||
getFile: function (path, noCheckPWD)
|
||||
{
|
||||
let file = Components.classes["@mozilla.org/file/local;1"]
|
||||
.createInstance(Components.interfaces.nsILocalFile);
|
||||
let protocolHandler = Components.classes["@mozilla.org/network/protocol;1?name=file"]
|
||||
.createInstance(Components.interfaces.nsIFileProtocolHandler);
|
||||
|
||||
if (/file:\/\//.test(path))
|
||||
{
|
||||
file = protocolHandler.getFileFromURLSpec(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
let expandedPath = ioManager.expandPath(path);
|
||||
|
||||
if (!isAbsolutePath(expandedPath) && !noCheckPWD)
|
||||
file = joinPaths(ioManager.getCurrentDirectory().path, expandedPath);
|
||||
else
|
||||
file.initWithPath(expandedPath);
|
||||
}
|
||||
|
||||
return file;
|
||||
},
|
||||
|
||||
// TODO: make secure
|
||||
// returns a nsILocalFile or null if it could not be created
|
||||
createTempFile: function ()
|
||||
{
|
||||
let tmpName = EXTENSION_NAME + ".tmp";
|
||||
|
||||
switch (EXTENSION_NAME)
|
||||
{
|
||||
case "muttator":
|
||||
tmpName = "mutt-ator-mail"; // to allow vim to :set ft=mail automatically
|
||||
break;
|
||||
case "vimperator":
|
||||
try
|
||||
{
|
||||
if (window.content.document.location.hostname)
|
||||
tmpName = EXTENSION_NAME + "-" + window.content.document.location.hostname + ".tmp";
|
||||
}
|
||||
catch (e) {}
|
||||
break;
|
||||
}
|
||||
|
||||
let file = directoryService.get("TmpD", Components.interfaces.nsIFile);
|
||||
file.append(tmpName);
|
||||
file.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0600);
|
||||
|
||||
if (file.exists())
|
||||
return file;
|
||||
else
|
||||
return null; // XXX
|
||||
|
||||
},
|
||||
|
||||
// file is either a full pathname or an instance of file instanceof nsILocalFile
|
||||
readDirectory: function (file, sort)
|
||||
{
|
||||
if (typeof file == "string")
|
||||
file = ioManager.getFile(file);
|
||||
else if (!(file instanceof Components.interfaces.nsILocalFile))
|
||||
throw Components.results.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined
|
||||
|
||||
if (file.isDirectory())
|
||||
{
|
||||
let entries = file.directoryEntries;
|
||||
let array = [];
|
||||
while (entries.hasMoreElements())
|
||||
{
|
||||
let entry = entries.getNext();
|
||||
entry.QueryInterface(Components.interfaces.nsIFile);
|
||||
array.push(entry);
|
||||
}
|
||||
if (sort)
|
||||
return array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
|
||||
return array;
|
||||
}
|
||||
else
|
||||
return []; // XXX: or should it throw an error, probably yes?
|
||||
},
|
||||
|
||||
// file is either a full pathname or an instance of file instanceof nsILocalFile
|
||||
// reads a file in "text" mode and returns the string
|
||||
readFile: function (file)
|
||||
{
|
||||
let ifstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileInputStream);
|
||||
let icstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterInputStream);
|
||||
|
||||
let charset = "UTF-8";
|
||||
if (typeof file == "string")
|
||||
file = ioManager.getFile(file);
|
||||
else if (!(file instanceof Components.interfaces.nsILocalFile))
|
||||
throw Components.results.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined
|
||||
|
||||
ifstream.init(file, -1, 0, 0);
|
||||
const replacementChar = Components.interfaces.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
|
||||
icstream.init(ifstream, charset, 4096, replacementChar); // 4096 bytes buffering
|
||||
|
||||
let buffer = "";
|
||||
let str = {};
|
||||
while (icstream.readString(4096, str) != 0)
|
||||
buffer += str.value;
|
||||
|
||||
icstream.close();
|
||||
ifstream.close();
|
||||
|
||||
return buffer;
|
||||
},
|
||||
|
||||
// file is either a full pathname or an instance of file instanceof nsILocalFile
|
||||
// default permission = 0644, only used when creating a new file, does not change permissions if the file exists
|
||||
// mode can be ">" or ">>" in addition to the normal MODE_* flags
|
||||
writeFile: function (file, buf, mode, perms)
|
||||
{
|
||||
let ofstream = Components.classes["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIFileOutputStream);
|
||||
let ocstream = Components.classes["@mozilla.org/intl/converter-output-stream;1"]
|
||||
.createInstance(Components.interfaces.nsIConverterOutputStream);
|
||||
|
||||
let charset = "UTF-8"; // Can be any character encoding name that Mozilla supports
|
||||
if (typeof file == "string")
|
||||
file = ioManager.getFile(file);
|
||||
else if (!(file instanceof Components.interfaces.nsILocalFile))
|
||||
throw Components.results.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined
|
||||
|
||||
if (mode == ">>")
|
||||
mode = ioManager.MODE_WRONLY | ioManager.MODE_CREATE | ioManager.MODE_APPEND;
|
||||
else if (!mode || mode == ">")
|
||||
mode = ioManager.MODE_WRONLY | ioManager.MODE_CREATE | ioManager.MODE_TRUNCATE;
|
||||
|
||||
if (!perms)
|
||||
perms = 0644;
|
||||
|
||||
ofstream.init(file, mode, perms, 0);
|
||||
ocstream.init(ofstream, charset, 0, 0x0000);
|
||||
ocstream.writeString(buf);
|
||||
|
||||
ocstream.close();
|
||||
ofstream.close();
|
||||
},
|
||||
|
||||
run: function (program, args, blocking)
|
||||
{
|
||||
args = args || [];
|
||||
blocking = !!blocking;
|
||||
|
||||
let file;
|
||||
|
||||
if (isAbsolutePath(program))
|
||||
{
|
||||
file = ioManager.getFile(program, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
let dirs = environmentService.get("PATH").split(WINDOWS ? ";" : ":");
|
||||
// Windows tries the cwd first TODO: desirable?
|
||||
if (WINDOWS)
|
||||
dirs = [io.getCurrentDirectory().path].concat(dirs);
|
||||
|
||||
lookup:
|
||||
for (let [,dir] in Iterator(dirs))
|
||||
{
|
||||
file = joinPaths(dir, program);
|
||||
try
|
||||
{
|
||||
if (file.exists())
|
||||
break;
|
||||
|
||||
// TODO: couldn't we just palm this off to the start command?
|
||||
// automatically try to add the executable path extensions on windows
|
||||
if (WINDOWS)
|
||||
{
|
||||
let extensions = environmentService.get("PATHEXT").split(";");
|
||||
for (let [,extension] in Iterator(extensions))
|
||||
{
|
||||
file = joinPaths(dir, program + extension);
|
||||
if (file.exists())
|
||||
break lookup;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!file || !file.exists())
|
||||
{
|
||||
liberator.echoerr("Command not found: " + program);
|
||||
return -1;
|
||||
}
|
||||
|
||||
let process = Components.classes["@mozilla.org/process/util;1"]
|
||||
.createInstance(Components.interfaces.nsIProcess);
|
||||
|
||||
process.init(file);
|
||||
process.run(blocking, args, args.length);
|
||||
|
||||
return process.exitValue;
|
||||
},
|
||||
|
||||
// when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is fixed
|
||||
// is fixed, should use that instead of a tmpfile
|
||||
system: function (command, input)
|
||||
{
|
||||
liberator.echomsg("Calling shell to execute: " + command, 4);
|
||||
|
||||
let stdoutFile = ioManager.createTempFile();
|
||||
let stderrFile = ioManager.createTempFile();
|
||||
|
||||
function escapeQuotes(str) str.replace('"', '\\"', "g");
|
||||
|
||||
if (!stdoutFile || !stderrFile) // FIXME: error reporting
|
||||
return "";
|
||||
|
||||
if (WINDOWS)
|
||||
command = "cd /D " + cwd.path + " && " + command + " > " + stdoutFile.path + " 2> " + stderrFile.path;
|
||||
else
|
||||
// TODO: should we only attempt the actual command conditionally on a successful cd?
|
||||
command = "cd " + escapeQuotes(cwd.path) + "; " + command + " > \"" + escapeQuotes(stdoutFile.path) + "\""
|
||||
+ " 2> \"" + escapeQuotes(stderrFile.path) + "\"";
|
||||
|
||||
let stdinFile = null;
|
||||
|
||||
if (input)
|
||||
{
|
||||
stdinFile = ioManager.createTempFile(); // FIXME: no returned file?
|
||||
ioManager.writeFile(stdinFile, input);
|
||||
command += " < \"" + escapeQuotes(stdinFile.path) + "\"";
|
||||
}
|
||||
|
||||
let res = ioManager.run(options["shell"], [options["shellcmdflag"], command], true);
|
||||
|
||||
if (res > 0)
|
||||
var output = ioManager.readFile(stderrFile) + "\nshell returned " + res;
|
||||
else
|
||||
var output = ioManager.readFile(stdoutFile);
|
||||
|
||||
stdoutFile.remove(false);
|
||||
stderrFile.remove(false);
|
||||
|
||||
if (stdinFile)
|
||||
stdinFile.remove(false);
|
||||
|
||||
// if there is only one \n at the end, chop it off
|
||||
if (output && output.indexOf("\n") == output.length - 1)
|
||||
output = output.substr(0, output.length - 1);
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
// FIXME: multiple paths?
|
||||
sourceFromRuntimePath: function (paths, all)
|
||||
{
|
||||
let dirs = getPathsFromPathList(options["runtimepath"]);
|
||||
let found = false;
|
||||
|
||||
// FIXME: should use original arg string
|
||||
liberator.echomsg("Searching for \"" + paths.join(" ") + "\" in \"" + options["runtimepath"] + "\"", 2);
|
||||
|
||||
outer:
|
||||
for (let [,dir] in Iterator(dirs))
|
||||
{
|
||||
for (let [,path] in Iterator(paths))
|
||||
{
|
||||
let file = joinPaths(dir, path);
|
||||
|
||||
liberator.echomsg("Searching for \"" + file.path, 3);
|
||||
|
||||
if (file.exists() && file.isFile() && file.isReadable())
|
||||
{
|
||||
io.source(file.path, false);
|
||||
found = true;
|
||||
|
||||
if (!all)
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
liberator.echomsg("not found in 'runtimepath': \"" + paths.join(" ") + "\"", 1); // FIXME: should use original arg string
|
||||
|
||||
return found;
|
||||
},
|
||||
|
||||
// files which end in .js are sourced as pure javascript files,
|
||||
// no need (actually forbidden) to add: js <<EOF ... EOF around those files
|
||||
source: function (filename, silent)
|
||||
{
|
||||
let wasSourcing = ioManager.sourcing;
|
||||
try
|
||||
{
|
||||
var file = ioManager.getFile(filename);
|
||||
ioManager.sourcing = {
|
||||
file: file.path,
|
||||
line: 0
|
||||
};
|
||||
|
||||
if (!file.exists() || !file.isReadable() || file.isDirectory())
|
||||
{
|
||||
if (!silent)
|
||||
{
|
||||
if (file.exists() && file.isDirectory())
|
||||
liberator.echomsg("Cannot source a directory: \"" + filename + "\"", 0);
|
||||
else
|
||||
liberator.echomsg("could not source: \"" + filename + "\"", 1);
|
||||
|
||||
liberator.echoerr("E484: Can't open file " + filename);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
liberator.echomsg("sourcing \"" + filename + "\"", 2);
|
||||
|
||||
let str = ioManager.readFile(file);
|
||||
let uri = ioService.newFileURI(file);
|
||||
|
||||
// handle pure javascript files specially
|
||||
if (/\.js$/.test(filename))
|
||||
{
|
||||
try
|
||||
{
|
||||
liberator.loadScript(uri.spec, new Script(file));
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
let err = new Error();
|
||||
for (let [k, v] in Iterator(e))
|
||||
err[k] = v;
|
||||
err.echoerr = file.path + ":" + e.lineNumber + ": " + e;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
else if (/\.css$/.test(filename))
|
||||
{
|
||||
storage.styles.registerSheet(uri.spec, !silent, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
let heredoc = "";
|
||||
let heredocEnd = null; // the string which ends the heredoc
|
||||
let lines = str.split("\n");
|
||||
|
||||
for (let [i, line] in Iterator(lines))
|
||||
{
|
||||
if (heredocEnd) // we already are in a heredoc
|
||||
{
|
||||
if (heredocEnd.test(line))
|
||||
{
|
||||
command.execute(heredoc, special, count);
|
||||
heredoc = "";
|
||||
heredocEnd = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
heredoc += line + "\n";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ioManager.sourcing.line = i + 1;
|
||||
// skip line comments and blank lines
|
||||
if (/^\s*(".*)?$/.test(line))
|
||||
continue;
|
||||
|
||||
var [count, cmd, special, args] = commands.parseCommand(line);
|
||||
var command = commands.get(cmd);
|
||||
|
||||
if (!command)
|
||||
{
|
||||
let lineNumber = i + 1;
|
||||
|
||||
// FIXME: messages need to be able to specify
|
||||
// whether they can be cleared/overwritten or
|
||||
// should be appended to and the MOW opened
|
||||
liberator.echoerr("Error detected while processing " + file.path,
|
||||
commandline.FORCE_MULTILINE);
|
||||
commandline.echo("line " + lineNumber + ":", commandline.HL_LINENR,
|
||||
commandline.APPEND_TO_MESSAGES);
|
||||
liberator.echoerr("E492: Not an editor command: " + line);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (command.name == "finish")
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (command.hereDoc)
|
||||
{
|
||||
// check for a heredoc
|
||||
let matches = args.match(/(.*)<<\s*(\S+)$/);
|
||||
|
||||
if (matches)
|
||||
{
|
||||
args = matches[1];
|
||||
heredocEnd = new RegExp("^" + matches[2] + "$", "m");
|
||||
if (matches[1])
|
||||
heredoc = matches[1] + "\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
command.execute(args, special, count);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// execute a normal liberator command
|
||||
liberator.execute(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if no heredoc-end delimiter is found before EOF then
|
||||
// process the heredoc anyway - Vim compatible ;-)
|
||||
if (heredocEnd)
|
||||
command.execute(heredoc, special, count);
|
||||
}
|
||||
|
||||
if (scriptNames.indexOf(file.path) == -1)
|
||||
scriptNames.push(file.path);
|
||||
|
||||
liberator.echomsg("finished sourcing \"" + filename + "\"", 2);
|
||||
|
||||
liberator.log("Sourced: " + file.path, 3);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
|
||||
liberator.reportError(e);
|
||||
if (!silent)
|
||||
liberator.echoerr(message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
ioManager.sourcing = wasSourcing;
|
||||
}
|
||||
}
|
||||
}; //}}}
|
||||
|
||||
return ioManager;
|
||||
|
||||
}; //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
58
common/content/liberator-overlay.js
Normal file
58
common/content/liberator-overlay.js
Normal file
@@ -0,0 +1,58 @@
|
||||
|
||||
(function () {
|
||||
const modules = {};
|
||||
const BASE = "chrome://liberator/content/";
|
||||
|
||||
modules.modules = modules;
|
||||
|
||||
const loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
|
||||
.getService(Components.interfaces.mozIJSSubScriptLoader);
|
||||
function load(script)
|
||||
{
|
||||
for (let [i, base] in Iterator(prefix))
|
||||
{
|
||||
try
|
||||
{
|
||||
loader.loadSubScript(base + script, modules)
|
||||
return;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (i + 1 < prefix.length)
|
||||
continue;
|
||||
if (Components.utils.reportError)
|
||||
Components.utils.reportError(e);
|
||||
dump("liberator: Loading script " + script + ": " + e + "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Components.utils.import("resource://liberator/storage.jsm", modules);
|
||||
|
||||
let prefix = [BASE];
|
||||
|
||||
["liberator.js",
|
||||
"config.js",
|
||||
"util.js",
|
||||
"style.js",
|
||||
"buffer.js",
|
||||
"commands.js",
|
||||
"completion.js",
|
||||
"editor.js",
|
||||
"events.js",
|
||||
"find.js",
|
||||
"hints.js",
|
||||
"io.js",
|
||||
"mappings.js",
|
||||
"modes.js",
|
||||
"options.js",
|
||||
"template.js",
|
||||
"ui.js"].forEach(load);
|
||||
|
||||
prefix.unshift("chrome://" + modules.config.name.toLowerCase() + "/content/");
|
||||
if (modules.config.scripts)
|
||||
modules.config.scripts.forEach(load);
|
||||
|
||||
})()
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1360
common/content/liberator.js
Normal file
1360
common/content/liberator.js
Normal file
File diff suppressed because it is too large
Load Diff
115
common/content/liberator.xul
Normal file
115
common/content/liberator.xul
Normal file
@@ -0,0 +1,115 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!-- ***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK ***** -->
|
||||
|
||||
<?xml-stylesheet href="chrome://liberator/skin/liberator.css" type="text/css"?>
|
||||
<!DOCTYPE overlay SYSTEM "liberator.dtd" [
|
||||
<!ENTITY liberator.content "chrome://liberator/content/">
|
||||
]>
|
||||
|
||||
<overlay id="liberator"
|
||||
xmlns:liberator="http://vimperator.org/namespaces/liberator"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:nc="http://home.netscape.com/NC-rdf#"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
<script type="application/x-javascript;version=1.8" src="&liberator.content;liberator-overlay.js"/>
|
||||
|
||||
<window id="&liberator.mainWindow;">
|
||||
|
||||
<keyset id="mainKeyset">
|
||||
<key id="key_open_vimbar" key=":" oncommand="liberator.modules.commandline.open(':', '', liberator.modules.modes.EX);" modifiers=""/>
|
||||
<key id="key_stop" keycode="VK_ESCAPE" oncommand="liberator.modules.events.onEscape();"/>
|
||||
<!-- other keys are handled inside the event loop in events.js -->
|
||||
</keyset>
|
||||
|
||||
<popupset>
|
||||
<panel id="liberator-visualbell" liberator:highlight="Bell"/>
|
||||
</popupset>
|
||||
|
||||
<!--this notifies us also of focus events in the XUL
|
||||
from: http://developer.mozilla.org/en/docs/XUL_Tutorial:Updating_Commands !-->
|
||||
<commandset id="onVimperatorFocus"
|
||||
commandupdater="true"
|
||||
events="focus"
|
||||
oncommandupdate="if (typeof liberator.modules.events != 'undefined') liberator.modules.events.onFocusChange(event);"/>
|
||||
<commandset id="onVimperatorSelect"
|
||||
commandupdater="true"
|
||||
events="select"
|
||||
oncommandupdate="if (typeof liberator.modules.events != 'undefined') liberator.modules.events.onSelectionChange(event);"/>
|
||||
|
||||
<!-- As of Firefox 3.1pre, <iframe>.height changes do not seem to have immediate effect,
|
||||
therefore we need to put them into a <vbox> for which that works just fine -->
|
||||
<vbox class="liberator-container" hidden="false" collapsed="true">
|
||||
<iframe id="liberator-multiline-output" src="chrome://liberator/content/buffer.xhtml"
|
||||
flex="1" hidden="false" collapsed="false"
|
||||
onclick="liberator.modules.commandline.onMultilineOutputEvent(event)"/>
|
||||
</vbox>
|
||||
|
||||
<vbox class="liberator-container" hidden="false" collapsed="true">
|
||||
<iframe id="liberator-completions" src="chrome://liberator/content/buffer.xhtml"
|
||||
flex="1" hidden="false" collapsed="false"
|
||||
onclick="liberator.modules.commandline.onMultilineOutputEvent(event)"/>
|
||||
</vbox>
|
||||
|
||||
<hbox id="liberator-commandline" hidden="false" liberator:highlight="Normal">
|
||||
<label class="plain" id="liberator-commandline-prompt" flex="0" crop="end" value="" collapsed="true"/>
|
||||
<textbox class="plain" id="liberator-commandline-command" flex="1" type="timed" timeout="100"
|
||||
oninput="liberator.modules.commandline.onEvent(event);"
|
||||
onfocus="liberator.modules.commandline.onEvent(event);"
|
||||
onblur="liberator.modules.commandline.onEvent(event);"/>
|
||||
</hbox>
|
||||
|
||||
<vbox class="liberator-container" hidden="false" collapsed="false">
|
||||
<textbox id="liberator-multiline-input" class="plain" flex="1" rows="1" hidden="false" collapsed="true" multiline="true"
|
||||
onkeypress="liberator.modules.commandline.onMultilineInputEvent(event);"
|
||||
oninput="liberator.modules.commandline.onMultilineInputEvent(event);"
|
||||
onblur="liberator.modules.commandline.onMultilineInputEvent(event);"/>
|
||||
</vbox>
|
||||
|
||||
</window>
|
||||
|
||||
<statusbar id="status-bar" liberator:highlight="StatusLine">
|
||||
<hbox insertbefore="&liberator.statusBefore;" insertafter="&liberator.statusAfter;"
|
||||
id="liberator-statusline" flex="1" hidden="false" align="center">
|
||||
<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-progress" flex="0"/>
|
||||
<label class="plain" id="liberator-statusline-field-tabcount" flex="0"/>
|
||||
<label class="plain" id="liberator-statusline-field-bufferposition" flex="0"/>
|
||||
</hbox>
|
||||
<!-- just hide them since other elements expect them -->
|
||||
<statusbarpanel id="statusbar-display" hidden="true"/>
|
||||
<statusbarpanel id="statusbar-progresspanel" hidden="true"/>
|
||||
</statusbar>
|
||||
|
||||
</overlay>
|
||||
|
||||
<!-- vim: set fdm=marker sw=4 ts=4 et: -->
|
||||
414
common/content/mappings.js
Normal file
414
common/content/mappings.js
Normal file
@@ -0,0 +1,414 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// Do NOT create instances of this class yourself, use the helper method
|
||||
// mappings.add() instead
|
||||
function Map(modes, cmds, description, action, extraInfo) //{{{
|
||||
{
|
||||
if (!modes || (!cmds || !cmds.length) || !action)
|
||||
return null;
|
||||
|
||||
if (!extraInfo)
|
||||
extraInfo = {};
|
||||
|
||||
this.modes = modes;
|
||||
// only store keysyms with uppercase modifier strings
|
||||
this.names = cmds.map(function (cmd) cmd.replace(/[casm]-/g, String.toUpperCase));
|
||||
this.action = action;
|
||||
|
||||
this.flags = extraInfo.flags || 0;
|
||||
this.description = description || "";
|
||||
this.rhs = extraInfo.rhs || null;
|
||||
this.noremap = extraInfo.noremap || false;
|
||||
this.silent = extraInfo.silent || false;
|
||||
};
|
||||
|
||||
Map.prototype = {
|
||||
|
||||
hasName: function (name)
|
||||
{
|
||||
return this.names.indexOf(name) >= 0;
|
||||
},
|
||||
|
||||
execute: function (motion, count, argument)
|
||||
{
|
||||
var args = [];
|
||||
|
||||
if (this.flags & Mappings.flags.MOTION)
|
||||
args.push(motion);
|
||||
if (this.flags & Mappings.flags.COUNT)
|
||||
args.push(count);
|
||||
if (this.flags & Mappings.flags.ARGUMENT)
|
||||
args.push(argument);
|
||||
|
||||
let self = this;
|
||||
// FIXME: Kludge.
|
||||
if (this.names[0] != ".")
|
||||
mappings.repeat = function () self.action.apply(self, args);
|
||||
|
||||
return this.action.apply(this, args);
|
||||
}
|
||||
|
||||
}; //}}}
|
||||
|
||||
function Mappings() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var main = []; // default mappings
|
||||
var user = []; // user created mappings
|
||||
|
||||
for (let mode in modes)
|
||||
{
|
||||
main[mode] = [];
|
||||
user[mode] = [];
|
||||
}
|
||||
|
||||
function addMap(map, userMap)
|
||||
{
|
||||
var where = userMap ? user : main;
|
||||
map.modes.forEach(function (mode) {
|
||||
if (!(mode in where))
|
||||
where[mode] = [];
|
||||
where[mode].push(map);
|
||||
});
|
||||
}
|
||||
|
||||
function getMap(mode, cmd, stack)
|
||||
{
|
||||
var maps = stack[mode] || [];
|
||||
|
||||
for (let [,map] in Iterator(maps))
|
||||
{
|
||||
if (map.hasName(cmd))
|
||||
return map;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function removeMap(mode, cmd)
|
||||
{
|
||||
var maps = user[mode] || [];
|
||||
var names;
|
||||
|
||||
for (let [i, map] in Iterator(maps))
|
||||
{
|
||||
for (let [j, name] in Iterator(map.names))
|
||||
{
|
||||
if (name == cmd)
|
||||
{
|
||||
map.names.splice(j, 1);
|
||||
if (map.names.length == 0)
|
||||
maps.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function expandLeader(keyString) keyString.replace(/<Leader>/i, mappings.getMapLeader())
|
||||
|
||||
// Return all mappings present in all @modes
|
||||
function mappingsIterator(modes, stack)
|
||||
{
|
||||
modes = modes.slice();
|
||||
return (map for ([i, map] in Iterator(stack[modes.shift()]))
|
||||
if (modes.every(function (mode) stack[mode].some(
|
||||
function (m) m.rhs == map.rhs && m.names[0] == map.names[0]))))
|
||||
}
|
||||
|
||||
function addMapCommands(ch, modes, modeDescription)
|
||||
{
|
||||
// 0 args -> list all maps
|
||||
// 1 arg -> list the maps starting with args
|
||||
// 2 args -> map arg1 to arg*
|
||||
function map(args, mode, noremap)
|
||||
{
|
||||
if (!args.length)
|
||||
{
|
||||
mappings.list(mode);
|
||||
return;
|
||||
}
|
||||
|
||||
// ?:\s+ <- don't remember; (...)? optional = rhs
|
||||
let [lhs, rhs] = args;
|
||||
|
||||
if (!rhs) // list the mapping
|
||||
{
|
||||
mappings.list(mode, expandLeader(lhs));
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let [,m] in Iterator(mode))
|
||||
{
|
||||
mappings.addUserMap([m], [lhs],
|
||||
"User defined mapping",
|
||||
function (count) { events.feedkeys((count > 1 ? count : "") + this.rhs, this.noremap, this.silent); },
|
||||
{
|
||||
flags: Mappings.flags.COUNT,
|
||||
rhs: rhs,
|
||||
noremap: !!noremap,
|
||||
silent: "<silent>" in args
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modeDescription = modeDescription ? " in " + modeDescription + " mode" : "";
|
||||
|
||||
const opts = {
|
||||
completer: function (context, args) completion.userMapping(context, args, modes),
|
||||
options: [
|
||||
[["<silent>", "<Silent>"], commands.OPTION_NOARG]
|
||||
],
|
||||
literal: 1,
|
||||
serial: function () {
|
||||
let noremap = this.name.indexOf("noremap") > -1;
|
||||
return [
|
||||
{
|
||||
command: this.name,
|
||||
options: map.silent ? { "<silent>": null } : {},
|
||||
arguments: [map.names[0]],
|
||||
literalArg: map.rhs
|
||||
}
|
||||
for (map in mappingsIterator(modes, user))
|
||||
if (map.rhs && map.noremap == noremap)
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
commands.add([ch ? ch + "m[ap]" : "map"],
|
||||
"Map a key sequence" + modeDescription,
|
||||
function (args) { map(args, modes, false); },
|
||||
opts);
|
||||
|
||||
commands.add([ch + "no[remap]"],
|
||||
"Map a key sequence without remapping keys" + modeDescription,
|
||||
function (args) { map(args, modes, true); },
|
||||
opts);
|
||||
|
||||
commands.add([ch + "mapc[lear]"],
|
||||
"Remove all mappings" + modeDescription,
|
||||
function () { modes.forEach(function (mode) { mappings.removeAll(mode); }); },
|
||||
{ argCount: "0" });
|
||||
|
||||
commands.add([ch + "unm[ap]"],
|
||||
"Remove a mapping" + modeDescription,
|
||||
function (args)
|
||||
{
|
||||
args = args[0];
|
||||
|
||||
let found = false;
|
||||
for (let [,mode] in Iterator(modes))
|
||||
{
|
||||
if (mappings.hasMap(mode, args))
|
||||
{
|
||||
mappings.remove(mode, args);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
liberator.echoerr("E31: No such mapping");
|
||||
},
|
||||
{
|
||||
argCount: "1",
|
||||
completer: function (context, args) completion.userMapping(context, args, modes)
|
||||
});
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// COMMANDS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
addMapCommands("", [modes.NORMAL], "");
|
||||
addMapCommands("c", [modes.COMMAND_LINE], "command line");
|
||||
addMapCommands("i", [modes.INSERT, modes.TEXTAREA], "insert");
|
||||
if (liberator.has("mail"))
|
||||
addMapCommands("m", [modes.MESSAGE], "message");
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
liberator.registerObserver("load_completion", function ()
|
||||
{
|
||||
completion.setFunctionCompleter(mappings.get,
|
||||
[
|
||||
null,
|
||||
function (context, obj, args)
|
||||
{
|
||||
let mode = args[0]
|
||||
return util.Array.flatten(
|
||||
[
|
||||
[[name, map.description] for ([i, name] in Iterator(map.names))]
|
||||
for ([i, map] in Iterator(user[mode].concat(main[mode])))
|
||||
])
|
||||
}
|
||||
]);
|
||||
});
|
||||
|
||||
// FIXME:
|
||||
Mappings.flags = {
|
||||
ALLOW_EVENT_ROUTING: 1 << 0, // if set, return true inside the map command to pass the event further to firefox
|
||||
MOTION: 1 << 1,
|
||||
COUNT: 1 << 2,
|
||||
ARGUMENT: 1 << 3
|
||||
};
|
||||
|
||||
return {
|
||||
|
||||
// NOTE: just normal mode for now
|
||||
__iterator__: function () mappingsIterator([modes.NORMAL], main),
|
||||
|
||||
// used by :mkvimperatorrc to save mappings
|
||||
getUserIterator: function (mode) mappingsIterator(mode, user),
|
||||
|
||||
add: function (modes, keys, description, action, extra)
|
||||
{
|
||||
addMap(new Map(modes, keys, description, action, extra), false);
|
||||
},
|
||||
|
||||
addUserMap: function (modes, keys, description, action, extra)
|
||||
{
|
||||
keys = keys.map(expandLeader);
|
||||
var map = new Map(modes, keys, description || "User defined mapping", action, extra);
|
||||
|
||||
// remove all old mappings to this key sequence
|
||||
for (let [,name] in Iterator(map.names))
|
||||
{
|
||||
for (let [,mode] in Iterator(map.modes))
|
||||
removeMap(mode, name);
|
||||
}
|
||||
|
||||
addMap(map, true);
|
||||
},
|
||||
|
||||
get: function (mode, cmd)
|
||||
{
|
||||
mode = mode || modes.NORMAL;
|
||||
return getMap(mode, cmd, user) || getMap(mode, cmd, main);
|
||||
},
|
||||
|
||||
getDefault: function (mode, cmd)
|
||||
{
|
||||
mode = mode || modes.NORMAL;
|
||||
return getMap(mode, cmd, main);
|
||||
},
|
||||
|
||||
// returns an array of mappings with names which START with "cmd" (but are NOT "cmd")
|
||||
getCandidates: function (mode, cmd)
|
||||
{
|
||||
let mappings = user[mode].concat(main[mode]);
|
||||
let matches = [];
|
||||
|
||||
for (let [,map] in Iterator(mappings))
|
||||
{
|
||||
for (let [,name] in Iterator(map.names))
|
||||
{
|
||||
if (name.indexOf(cmd) == 0 && name.length > cmd.length)
|
||||
{
|
||||
// for < only return a candidate if it doesn't look like a <c-x> mapping
|
||||
if (cmd != "<" || !/^<.+>/.test(name))
|
||||
matches.push(map);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
},
|
||||
|
||||
getMapLeader: function ()
|
||||
{
|
||||
var leaderRef = liberator.variableReference("mapleader");
|
||||
return leaderRef[0] ? leaderRef[0][leaderRef[1]] : "\\";
|
||||
},
|
||||
|
||||
// returns whether the user added a custom user map
|
||||
hasMap: function (mode, cmd)
|
||||
{
|
||||
return user[mode].some(function (map) map.hasName(cmd));
|
||||
},
|
||||
|
||||
remove: function (mode, cmd)
|
||||
{
|
||||
removeMap(mode, cmd);
|
||||
},
|
||||
|
||||
removeAll: function (mode)
|
||||
{
|
||||
user[mode] = [];
|
||||
},
|
||||
|
||||
list: function (modes, filter)
|
||||
{
|
||||
let modeSign = "";
|
||||
modes.forEach(function (mode)
|
||||
{
|
||||
if (mode == modes.NORMAL)
|
||||
modeSign += "n";
|
||||
if ((mode == modes.INSERT || mode == modes.TEXTAREA) && modeSign.indexOf("i") == -1)
|
||||
modeSign += "i";
|
||||
if (mode == modes.COMMAND_LINE)
|
||||
modeSign += "c";
|
||||
if (mode == modes.MESSAGRE)
|
||||
modeSign += "m";
|
||||
});
|
||||
|
||||
let maps = mappingsIterator(modes, user);
|
||||
if (filter)
|
||||
maps = [map for (map in maps) if (map.names[0] == filter)];
|
||||
|
||||
let list = <table>
|
||||
{
|
||||
template.map(maps, function (map)
|
||||
template.map(map.names, function (name)
|
||||
<tr>
|
||||
<td>{modeSign} {name}</td>
|
||||
<td>{map.noremap ? "*" : " "}</td>
|
||||
<td>{map.rhs || "function () { ... }"}</td>
|
||||
</tr>))
|
||||
}
|
||||
</table>;
|
||||
|
||||
// TODO: Move this to an ItemList to show this automatically
|
||||
if (list.*.length() == list.text().length())
|
||||
{
|
||||
liberator.echo(<div highlight="Title">No mappings found</div>, commandline.FORCE_MULTILINE);
|
||||
return;
|
||||
}
|
||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
}
|
||||
|
||||
};
|
||||
//}}}
|
||||
}; //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
294
common/content/modes.js
Normal file
294
common/content/modes.js
Normal file
@@ -0,0 +1,294 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
const modes = (function () //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var main = 1; // NORMAL
|
||||
var extended = 0; // NONE
|
||||
|
||||
var passNextKey = false;
|
||||
var passAllKeys = false;
|
||||
var isRecording = false;
|
||||
var isReplaying = false; // playing a macro
|
||||
|
||||
var modeStack = [];
|
||||
|
||||
function getModeMessage()
|
||||
{
|
||||
if (passNextKey && !passAllKeys)
|
||||
return "-- PASS THROUGH (next) --";
|
||||
else if (passAllKeys && !passNextKey)
|
||||
return "-- PASS THROUGH --";
|
||||
|
||||
// when recording a macro
|
||||
var macromode = "";
|
||||
if (modes.isRecording)
|
||||
macromode = "recording";
|
||||
else if (modes.isReplaying)
|
||||
macromode = "replaying";
|
||||
|
||||
var ext = "";
|
||||
if (extended & modes.MENU) // TODO: desirable?
|
||||
ext += " (menu)";
|
||||
ext += " --" + macromode;
|
||||
|
||||
if (main in modeMap && typeof modeMap[main].display == "function")
|
||||
return "-- " + modeMap[main].display() + ext;
|
||||
return macromode;
|
||||
}
|
||||
|
||||
// NOTE: Pay attention that you don't run into endless loops
|
||||
// Usually you should only indicate to leave a special mode like HINTS
|
||||
// by calling modes.reset() and adding the stuff which is needed
|
||||
// for its cleanup here
|
||||
function handleModeChange(oldMode, newMode)
|
||||
{
|
||||
// TODO: fix v.log() to work with verbosity level
|
||||
//liberator.log("switching from mode " + oldMode + " to mode " + newMode, 7);
|
||||
//liberator.dump("switching from mode " + oldMode + " to mode " + newMode + "\n");
|
||||
|
||||
switch (oldMode)
|
||||
{
|
||||
case modes.TEXTAREA:
|
||||
case modes.INSERT:
|
||||
editor.unselectText();
|
||||
break;
|
||||
|
||||
case modes.VISUAL:
|
||||
if (newMode == modes.CARET)
|
||||
{
|
||||
// clear any selection made
|
||||
var selection = window.content.getSelection();
|
||||
try
|
||||
{ // a simple if (selection) does not work
|
||||
selection.collapseToStart();
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
else
|
||||
editor.unselectText();
|
||||
break;
|
||||
|
||||
case modes.CUSTOM:
|
||||
plugins.stop();
|
||||
break;
|
||||
|
||||
case modes.COMMAND_LINE:
|
||||
// clean up for HINT mode
|
||||
if (modes.extended & modes.HINTS)
|
||||
hints.hide();
|
||||
commandline.close();
|
||||
break;
|
||||
}
|
||||
|
||||
if (newMode == modes.NORMAL)
|
||||
{
|
||||
// disable caret mode when we want to switch to normal mode
|
||||
var value = options.getPref("accessibility.browsewithcaret", false);
|
||||
if (value)
|
||||
options.setPref("accessibility.browsewithcaret", false);
|
||||
|
||||
statusline.updateUrl();
|
||||
// Kludge to prevent the input field losing focus on MS/Mac
|
||||
setTimeout(function () {
|
||||
let focus = document.commandDispatcher.focusedElement;
|
||||
let urlbar = document.getElementById("urlbar");
|
||||
if (!urlbar || focus != urlbar.inputField)
|
||||
liberator.focusContent(false);
|
||||
}, 100);
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var self = {
|
||||
NONE: 0,
|
||||
|
||||
__iterator__: function () util.Array.iterator(this.all),
|
||||
|
||||
get all() mainModes.slice(),
|
||||
|
||||
addMode: function (name, extended, display) {
|
||||
let disp = name.replace("_", " ", "g");
|
||||
this[name] = 1 << lastMode++;
|
||||
modeMap[name] = modeMap[this[name]] = {
|
||||
extended: extended,
|
||||
mask: this[name],
|
||||
name: name,
|
||||
display: display || function () disp
|
||||
};
|
||||
if (!extended)
|
||||
mainModes.push(this[name]);
|
||||
},
|
||||
|
||||
// show the current mode string in the command line
|
||||
show: function ()
|
||||
{
|
||||
if (!options["showmode"])
|
||||
return;
|
||||
|
||||
// never show mode messages if we are in command line mode
|
||||
if (main == modes.COMMAND_LINE)
|
||||
return;
|
||||
|
||||
commandline.echo(getModeMessage(), commandline.HL_MODEMSG,
|
||||
commandline.DISALLOW_MULTILINE);
|
||||
},
|
||||
|
||||
// add/remove always work on the extended mode only
|
||||
add: function (mode)
|
||||
{
|
||||
extended |= mode;
|
||||
this.show();
|
||||
},
|
||||
|
||||
// helper function to set both modes in one go
|
||||
// if silent == true, you also need to take care of the mode handling changes yourself
|
||||
set: function (mainMode, extendedMode, silent)
|
||||
{
|
||||
// if a main mode is set, the extended is always cleared
|
||||
if (typeof mainMode === "number")
|
||||
{
|
||||
main = mainMode;
|
||||
if (!extendedMode)
|
||||
extended = modes.NONE;
|
||||
|
||||
if (!silent && mainMode != main)
|
||||
handleModeChange(main, mainMode);
|
||||
}
|
||||
if (typeof extendedMode === "number")
|
||||
extended = extendedMode;
|
||||
|
||||
if (!silent)
|
||||
this.show();
|
||||
},
|
||||
|
||||
push: function (mainMode, extendedMode, silent)
|
||||
{
|
||||
modeStack.push([main, extended]);
|
||||
this.set(mainMode, extendedMode, silent);
|
||||
},
|
||||
|
||||
pop: function (silent)
|
||||
{
|
||||
var a = modeStack.pop();
|
||||
if (a)
|
||||
this.set(a[0], a[1], silent);
|
||||
else
|
||||
this.reset(silent);
|
||||
},
|
||||
|
||||
setCustomMode: function (modestr, oneventfunc, stopfunc)
|
||||
{
|
||||
// TODO this.plugin[id]... ('id' maybe submode or what..)
|
||||
plugins.mode = modestr;
|
||||
plugins.onEvent = oneventfunc;
|
||||
plugins.stop = stopfunc;
|
||||
},
|
||||
|
||||
// keeps recording state
|
||||
reset: function (silent)
|
||||
{
|
||||
modeStack = [];
|
||||
if (config.isComposeWindow)
|
||||
this.set(modes.COMPOSE, modes.NONE, silent);
|
||||
else
|
||||
this.set(modes.NORMAL, modes.NONE, silent);
|
||||
},
|
||||
|
||||
remove: function (mode)
|
||||
{
|
||||
extended &= ~mode;
|
||||
this.show();
|
||||
},
|
||||
|
||||
get passNextKey() passNextKey,
|
||||
set passNextKey(value) { passNextKey = value; this.show(); },
|
||||
|
||||
get passAllKeys() passAllKeys,
|
||||
set passAllKeys(value) { passAllKeys = value; this.show(); },
|
||||
|
||||
get isRecording() isRecording,
|
||||
set isRecording(value) { isRecording = value; this.show(); },
|
||||
|
||||
get isReplaying() isReplaying,
|
||||
set isReplaying(value) { isReplaying = value; this.show(); },
|
||||
|
||||
get main() main,
|
||||
set main(value) {
|
||||
if (value != main)
|
||||
handleModeChange(main, value);
|
||||
|
||||
main = value;
|
||||
// setting the main mode always resets any extended mode
|
||||
extended = modes.NONE;
|
||||
this.show();
|
||||
},
|
||||
|
||||
get extended() extended,
|
||||
set extended(value) { extended = value; this.show(); }
|
||||
|
||||
};
|
||||
|
||||
var mainModes = [self.NONE];
|
||||
var lastMode = 0;
|
||||
var modeMap = {};
|
||||
|
||||
// main modes, only one should ever be active
|
||||
self.addMode("NORMAL", false, -1);
|
||||
self.addMode("INSERT");
|
||||
self.addMode("VISUAL", false, function () "VISUAL" + (extended & modes.LINE ? " LINE" : ""));
|
||||
self.addMode("COMMAND_LINE");
|
||||
self.addMode("CARET"); // text cursor is visible
|
||||
self.addMode("TEXTAREA"); // text cursor is in a HTMLTextAreaElement
|
||||
self.addMode("MESSAGE"); // for now only used in Muttator when the message has focus
|
||||
self.addMode("COMPOSE");
|
||||
self.addMode("CUSTOM", false, function () plugins.mode);
|
||||
// extended modes, can include multiple modes, and even main modes
|
||||
self.addMode("EX", true);
|
||||
self.addMode("HINTS", true);
|
||||
self.addMode("INPUT_MULTILINE", true);
|
||||
self.addMode("OUTPUT_MULTILINE", true);
|
||||
self.addMode("SEARCH_FORWARD", true);
|
||||
self.addMode("SEARCH_BACKWARD", true);
|
||||
self.addMode("MENU", true); // a popupmenu is active
|
||||
self.addMode("LINE", true); // linewise visual mode
|
||||
self.addMode("RECORDING", true);
|
||||
self.addMode("PROMPT", true);
|
||||
|
||||
return self;
|
||||
//}}}
|
||||
})(); //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
984
common/content/options.js
Normal file
984
common/content/options.js
Normal file
@@ -0,0 +1,984 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
// do NOT create instances of this class yourself, use the helper method
|
||||
// options.add() instead
|
||||
function Option(names, description, type, defaultValue, extraInfo) //{{{
|
||||
{
|
||||
if (!names || !type)
|
||||
return null;
|
||||
|
||||
if (!extraInfo)
|
||||
extraInfo = {};
|
||||
|
||||
this.name = names[0];
|
||||
this.names = names;
|
||||
this.type = type;
|
||||
this.scope = (extraInfo.scope & options.OPTION_SCOPE_BOTH) ||
|
||||
options.OPTION_SCOPE_GLOBAL;
|
||||
// XXX set to BOTH by default someday? - kstep
|
||||
this.description = description || "";
|
||||
|
||||
// "", 0 are valid default values
|
||||
this.defaultValue = (defaultValue === undefined) ? null : defaultValue;
|
||||
|
||||
this.setter = extraInfo.setter || null;
|
||||
this.getter = extraInfo.getter || null;
|
||||
this.completer = extraInfo.completer || null;
|
||||
this.validator = extraInfo.validator || null;
|
||||
this.checkHas = extraInfo.checkHas || null;
|
||||
|
||||
// this property is set to true whenever the option is first set
|
||||
// useful to see whether it was changed by some rc file
|
||||
this.hasChanged = false;
|
||||
|
||||
// add no{option} variant of boolean {option} to this.names
|
||||
if (this.type == "boolean")
|
||||
{
|
||||
this.names = []; // reset since order is important
|
||||
for (let [,name] in Iterator(names))
|
||||
{
|
||||
this.names.push(name);
|
||||
this.names.push("no" + name);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.globalvalue == undefined)
|
||||
this.globalvalue = this.defaultValue;
|
||||
}
|
||||
Option.prototype = {
|
||||
get globalvalue() options.store.get(this.name),
|
||||
set globalvalue(val) { options.store.set(this.name, val) },
|
||||
|
||||
parseValues: function (value)
|
||||
{
|
||||
if (this.type == "stringlist")
|
||||
return value.split(",");
|
||||
if (this.type == "charlist")
|
||||
return Array.slice(value);
|
||||
return value;
|
||||
},
|
||||
|
||||
joinValues: function (values)
|
||||
{
|
||||
if (this.type == "stringlist")
|
||||
return values.join(",");
|
||||
if (this.type == "charlist")
|
||||
return values.join("");
|
||||
return values;
|
||||
},
|
||||
|
||||
get values() this.parseValues(this.value),
|
||||
set values(values) this.setValues(this.scope, values),
|
||||
|
||||
getValues: function (scope) this.parseValues(this.get(scope)),
|
||||
|
||||
setValues: function (values, scope)
|
||||
{
|
||||
this.set(this.joinValues(values), scope || this.scope);
|
||||
},
|
||||
|
||||
get: function (scope)
|
||||
{
|
||||
if (scope)
|
||||
{
|
||||
if ((scope & this.scope) == 0) // option doesn't exist in this scope
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
scope = this.scope;
|
||||
}
|
||||
|
||||
var aValue;
|
||||
|
||||
if (liberator.has("tabs") && (scope & options.OPTION_SCOPE_LOCAL))
|
||||
aValue = tabs.options[this.name];
|
||||
if ((scope & options.OPTION_SCOPE_GLOBAL) && (aValue == undefined))
|
||||
aValue = this.globalvalue;
|
||||
|
||||
if (this.getter)
|
||||
this.getter.call(this, aValue);
|
||||
|
||||
return aValue;
|
||||
},
|
||||
|
||||
set: function (newValue, scope)
|
||||
{
|
||||
scope = scope || this.scope;
|
||||
if ((scope & this.scope) == 0) // option doesn't exist in this scope
|
||||
return null;
|
||||
|
||||
if (this.setter)
|
||||
{
|
||||
let tmpValue = newValue;
|
||||
newValue = this.setter.call(this, newValue);
|
||||
|
||||
if (newValue === undefined)
|
||||
{
|
||||
newValue = tmpValue;
|
||||
liberator.log("DEPRECATED: '" + this.name + "' setter should return a value");
|
||||
}
|
||||
}
|
||||
|
||||
if (liberator.has("tabs") && (scope & options.OPTION_SCOPE_LOCAL))
|
||||
tabs.options[this.name] = newValue;
|
||||
if ((scope & options.OPTION_SCOPE_GLOBAL) && newValue != this.globalValue)
|
||||
this.globalvalue = newValue;
|
||||
|
||||
this.hasChanged = true;
|
||||
},
|
||||
|
||||
get value() this.get(),
|
||||
set value(val) this.set(val),
|
||||
|
||||
has: function ()
|
||||
{
|
||||
let self = this;
|
||||
let test = function (val) values.indexOf(val) >= 0;
|
||||
if (this.checkHas)
|
||||
test = function (val) values.some(function (value) self.checkHas(value, val));
|
||||
let values = this.values;
|
||||
/* Return whether some argument matches */
|
||||
return Array.some(arguments, function (val) test(val))
|
||||
},
|
||||
|
||||
hasName: function (name) this.names.indexOf(name) >= 0,
|
||||
|
||||
isValidValue: function (values)
|
||||
{
|
||||
if (this.validator)
|
||||
return this.validator(values);
|
||||
else
|
||||
return true;
|
||||
},
|
||||
|
||||
reset: function ()
|
||||
{
|
||||
this.value = this.defaultValue;
|
||||
},
|
||||
|
||||
op: function (operator, values, scope, invert)
|
||||
{
|
||||
let newValue = null;
|
||||
let self = this;
|
||||
|
||||
switch (this.type)
|
||||
{
|
||||
case "boolean":
|
||||
if (operator != "=")
|
||||
break;
|
||||
|
||||
if (invert)
|
||||
newValue = !this.value;
|
||||
else
|
||||
newValue = values;
|
||||
break;
|
||||
|
||||
case "number":
|
||||
let value = parseInt(values); // deduce radix
|
||||
|
||||
if (isNaN(value))
|
||||
return "E521: Number required";
|
||||
|
||||
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 `" + option.type + "' not supported";
|
||||
}
|
||||
|
||||
if (newValue == null)
|
||||
return "Operator " + operator + " not supported for option type " + this.type;
|
||||
if (!this.isValidValue(newValue))
|
||||
return "E474: Invalid argument: " + values;
|
||||
this.setValues(newValue, scope);
|
||||
}
|
||||
};
|
||||
// TODO: Run this by default?
|
||||
Option.validateCompleter = function (values)
|
||||
{
|
||||
let context = CompletionContext("");
|
||||
let res = context.fork("", 0, this, this.completer);
|
||||
if (!res)
|
||||
res = context.allItems.items.map(function (item) [item.text]);
|
||||
return Array.concat(values).every(
|
||||
function (value) res.some(function (item) item[0] == value));
|
||||
}; //}}}
|
||||
|
||||
function Options() //{{{
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
////////////////////// PRIVATE SECTION /////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch);
|
||||
var optionHash = {};
|
||||
|
||||
function optionObserver(key, event, option)
|
||||
{
|
||||
// Trigger any setters.
|
||||
let opt = options.get(option);
|
||||
if (event == "change" && opt)
|
||||
opt.set(opt.value, options.OPTION_SCOPE_GLOBAL);
|
||||
}
|
||||
|
||||
storage.newMap("options", false);
|
||||
storage.addObserver("options", optionObserver);
|
||||
liberator.registerObserver("shutdown", function () {
|
||||
storage.removeObserver("options", optionObserver);
|
||||
});
|
||||
|
||||
function storePreference(name, value)
|
||||
{
|
||||
var type = prefService.getPrefType(name);
|
||||
switch (typeof value)
|
||||
{
|
||||
case "string":
|
||||
if (type == prefService.PREF_INVALID || type == prefService.PREF_STRING)
|
||||
prefService.setCharPref(name, value);
|
||||
else if (type == prefService.PREF_INT)
|
||||
liberator.echoerr("E521: Number required after =: " + name + "=" + value);
|
||||
else
|
||||
liberator.echoerr("E474: Invalid argument: " + name + "=" + value);
|
||||
break;
|
||||
case "number":
|
||||
if (type == prefService.PREF_INVALID || type == prefService.PREF_INT)
|
||||
prefService.setIntPref(name, value);
|
||||
else
|
||||
liberator.echoerr("E474: Invalid argument: " + name + "=" + value);
|
||||
break;
|
||||
case "boolean":
|
||||
if (type == prefService.PREF_INVALID || type == prefService.PREF_BOOL)
|
||||
prefService.setBoolPref(name, value);
|
||||
else if (type == prefService.PREF_INT)
|
||||
liberator.echoerr("E521: Number required after =: " + name + "=" + value);
|
||||
else
|
||||
liberator.echoerr("E474: Invalid argument: " + name + "=" + value);
|
||||
break;
|
||||
default:
|
||||
liberator.echoerr("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
|
||||
}
|
||||
}
|
||||
|
||||
function loadPreference(name, forcedDefault, defaultBranch)
|
||||
{
|
||||
var defaultValue = null; // XXX
|
||||
if (forcedDefault != null) // this argument sets defaults for non-user settable options (like extensions.history.comp_history)
|
||||
defaultValue = forcedDefault;
|
||||
|
||||
var branch = defaultBranch ? prefService.getDefaultBranch("") : prefService;
|
||||
var type = prefService.getPrefType(name);
|
||||
try
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case prefService.PREF_STRING:
|
||||
var value = branch.getComplexValue(name, Components.interfaces.nsISupportsString).data;
|
||||
// try in case it's a localized string (will throw an exception if not)
|
||||
if (!prefService.prefIsLocked(name) && !prefService.prefHasUserValue(name) &&
|
||||
/^chrome:\/\/.+\/locale\/.+\.properties/.test(value))
|
||||
value = branch.getComplexValue(name, Components.interfaces.nsIPrefLocalizedString).data;
|
||||
return value;
|
||||
case prefService.PREF_INT:
|
||||
return branch.getIntPref(name);
|
||||
case prefService.PREF_BOOL:
|
||||
return branch.getBoolPref(name);
|
||||
default:
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// firefox preferences which need to be changed to work well with vimperator
|
||||
//
|
||||
|
||||
// work around firefox popup blocker
|
||||
var popupAllowedEvents = loadPreference("dom.popup_allowed_events", "change click dblclick mouseup reset submit");
|
||||
if (!/keypress/.test(popupAllowedEvents))
|
||||
{
|
||||
storePreference("dom.popup_allowed_events", popupAllowedEvents + " keypress");
|
||||
liberator.registerObserver("shutdown", function ()
|
||||
{
|
||||
if (loadPreference("dom.popup_allowed_events", "")
|
||||
== popupAllowedEvents + " keypress")
|
||||
storePreference("dom.popup_allowed_events", popupAllowedEvents);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: maybe reset in .destroy()?
|
||||
// TODO: move to vim.js or buffer.js
|
||||
// we have our own typeahead find implementation
|
||||
storePreference("accessibility.typeaheadfind.autostart", false);
|
||||
storePreference("accessibility.typeaheadfind", false); // actually the above setting should do it, but has no effect in firefox
|
||||
|
||||
// start with saved session
|
||||
storePreference("browser.startup.page", 3);
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// COMMANDS ////////////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
commands.add(["let"],
|
||||
"Set or list a variable",
|
||||
function (args)
|
||||
{
|
||||
args = args.string;
|
||||
|
||||
if (!args)
|
||||
{
|
||||
var str =
|
||||
<table>
|
||||
{
|
||||
template.map(liberator.globalVariables, function ([i, value]) {
|
||||
let prefix = typeof value == "number" ? "#" :
|
||||
typeof value == "function" ? "*" :
|
||||
" ";
|
||||
return <tr>
|
||||
<td style="width: 200px;">{i}</td>
|
||||
<td>{prefix}{value}</td>
|
||||
</tr>
|
||||
})
|
||||
}
|
||||
</table>;
|
||||
if (str.*.length())
|
||||
liberator.echo(str, commandline.FORCE_MULTILINE);
|
||||
else
|
||||
liberator.echo("No variables found");
|
||||
return;
|
||||
}
|
||||
|
||||
var matches;
|
||||
// 1 - type, 2 - name, 3 - +-., 4 - expr
|
||||
if (matches = args.match(/([$@&])?([\w:]+)\s*([-+.])?=\s*(.+)/))
|
||||
{
|
||||
if (!matches[1])
|
||||
{
|
||||
var reference = liberator.variableReference(matches[2]);
|
||||
if (!reference[0] && matches[3])
|
||||
{
|
||||
liberator.echoerr("E121: Undefined variable: " + matches[2]);
|
||||
return;
|
||||
}
|
||||
|
||||
var expr = liberator.evalExpression(matches[4]);
|
||||
if (expr === undefined)
|
||||
{
|
||||
liberator.echoerr("E15: Invalid expression: " + matches[4]);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!reference[0])
|
||||
{
|
||||
if (reference[2] == "g")
|
||||
reference[0] = liberator.globalVariables;
|
||||
else
|
||||
return; // for now
|
||||
}
|
||||
|
||||
if (matches[3])
|
||||
{
|
||||
if (matches[3] == "+")
|
||||
reference[0][reference[1]] += expr;
|
||||
else if (matches[3] == "-")
|
||||
reference[0][reference[1]] -= expr;
|
||||
else if (matches[3] == ".")
|
||||
reference[0][reference[1]] += expr.toString();
|
||||
}
|
||||
else
|
||||
reference[0][reference[1]] = expr;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 1 - name
|
||||
else if (matches = args.match(/^\s*([\w:]+)\s*$/))
|
||||
{
|
||||
var reference = liberator.variableReference(matches[1]);
|
||||
if (!reference[0])
|
||||
{
|
||||
liberator.echoerr("E121: Undefined variable: " + matches[1]);
|
||||
return;
|
||||
}
|
||||
|
||||
var value = reference[0][reference[1]];
|
||||
let prefix = typeof value == "number" ? "#" :
|
||||
typeof value == "function" ? "*" :
|
||||
" ";
|
||||
liberator.echo(reference[1] + "\t\t" + prefix + value);
|
||||
}
|
||||
});
|
||||
|
||||
commands.add(["pref[erences]", "prefs"],
|
||||
"Show " + config.hostApplication + " preferences",
|
||||
function (args)
|
||||
{
|
||||
if (args.bang) // open Firefox settings GUI dialog
|
||||
{
|
||||
liberator.open("about:config",
|
||||
(options["newtab"] && options.get("newtab").has("all", "prefs"))
|
||||
? liberator.NEW_TAB : liberator.CURRENT_TAB);
|
||||
}
|
||||
else
|
||||
{
|
||||
window.openPreferences();
|
||||
}
|
||||
},
|
||||
{
|
||||
argCount: "0",
|
||||
bang: true
|
||||
});
|
||||
|
||||
commands.add(["setl[ocal]"],
|
||||
"Set local option",
|
||||
function (args)
|
||||
{
|
||||
commands.get("set").execute(args.string, args.bang, args.count, { scope: options.OPTION_SCOPE_LOCAL });
|
||||
},
|
||||
{
|
||||
bang: true,
|
||||
count: true,
|
||||
completer: function (context, args)
|
||||
{
|
||||
return commands.get("set").completer(context.filter, args.bang, args.count, { scope: options.OPTION_SCOPE_LOCAL });
|
||||
},
|
||||
literal: 0
|
||||
}
|
||||
);
|
||||
|
||||
commands.add(["setg[lobal]"],
|
||||
"Set global option",
|
||||
function (args)
|
||||
{
|
||||
commands.get("set").execute(args.string, args.bang, args.count, { scope: options.OPTION_SCOPE_GLOBAL });
|
||||
},
|
||||
{
|
||||
bang: true,
|
||||
count: true,
|
||||
completer: function (context, args)
|
||||
{
|
||||
return commands.get("set").completer(context.filter, args.bang, args.count, { scope: options.OPTION_SCOPE_GLOBAL });
|
||||
},
|
||||
literal: 0
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: support setting multiple options at once
|
||||
commands.add(["se[t]"],
|
||||
"Set an option",
|
||||
function (args, modifiers)
|
||||
{
|
||||
let bang = args.bang;
|
||||
args = args.string;
|
||||
if (bang)
|
||||
{
|
||||
var onlyNonDefault = false;
|
||||
if (!args)
|
||||
{
|
||||
args = "all";
|
||||
onlyNonDefault = true;
|
||||
}
|
||||
|
||||
let [matches, name, postfix, valueGiven, operator, value] =
|
||||
args.match(/^\s*?([a-zA-Z0-9\.\-_{}]+)([?&!])?\s*(([-+^]?)=(.*))?\s*$/);
|
||||
let reset = (postfix == "&");
|
||||
let invertBoolean = (postfix == "!");
|
||||
|
||||
if (name == "all" && reset)
|
||||
liberator.echoerr("You can't reset all options, it could make " + config.hostApplication + " unusable.");
|
||||
else if (name == "all")
|
||||
options.listPrefs(onlyNonDefault, "");
|
||||
else if (reset)
|
||||
options.resetPref(name);
|
||||
else if (invertBoolean)
|
||||
options.invertPref(name);
|
||||
else if (valueGiven)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case undefined:
|
||||
value = "";
|
||||
break;
|
||||
case "true":
|
||||
value = true;
|
||||
break;
|
||||
case "false":
|
||||
value = false;
|
||||
break;
|
||||
default:
|
||||
if (/^\d+$/.test(value))
|
||||
value = parseInt(value, 10);
|
||||
}
|
||||
options.setPref(name, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
options.listPrefs(onlyNonDefault, name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let opt = options.parseOpt(args, modifiers);
|
||||
if (!opt)
|
||||
{
|
||||
liberator.echoerr("Error parsing :set command: " + args);
|
||||
return;
|
||||
}
|
||||
|
||||
let option = opt.option;
|
||||
if (option == null && !opt.all)
|
||||
{
|
||||
liberator.echoerr("No such option: " + opt.name);
|
||||
return;
|
||||
}
|
||||
|
||||
// reset a variable to its default value
|
||||
if (opt.reset)
|
||||
{
|
||||
if (opt.all)
|
||||
{
|
||||
for (let option in options)
|
||||
option.reset();
|
||||
}
|
||||
else
|
||||
{
|
||||
option.reset();
|
||||
}
|
||||
}
|
||||
// read access
|
||||
else if (opt.get)
|
||||
{
|
||||
if (opt.all)
|
||||
{
|
||||
options.list(opt.onlyNonDefault, opt.scope);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (option.type == "boolean")
|
||||
liberator.echo((opt.optionValue ? " " : "no") + option.name);
|
||||
else
|
||||
liberator.echo(" " + option.name + "=" + opt.optionValue);
|
||||
}
|
||||
}
|
||||
// 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
|
||||
{
|
||||
if (opt.option.type == "boolean")
|
||||
{
|
||||
if (opt.valueGiven)
|
||||
{
|
||||
liberator.echoerr("E474: Invalid argument: " + args);
|
||||
return;
|
||||
}
|
||||
opt.values = !opt.unsetBoolean;
|
||||
}
|
||||
let res = opt.option.op(opt.operator || "=", opt.values, opt.scope, opt.invert);
|
||||
if (res)
|
||||
liberator.echoerr(res);
|
||||
}
|
||||
},
|
||||
{
|
||||
bang: true,
|
||||
completer: function (context, args, modifiers)
|
||||
{
|
||||
let filter = context.filter;
|
||||
var optionCompletions = [];
|
||||
|
||||
if (args.bang) // list completions for about:config entries
|
||||
{
|
||||
if (filter[filter.length - 1] == "=")
|
||||
{
|
||||
context.advance(filter.length);
|
||||
context.completions = [options.getPref(filter.substr(0, filter.length - 1)), "Current Value"];
|
||||
return;
|
||||
}
|
||||
|
||||
return completion.preference(context);
|
||||
}
|
||||
|
||||
let opt = options.parseOpt(filter, modifiers);
|
||||
let prefix = opt.prefix;
|
||||
|
||||
if (context.filter.indexOf("=") == -1)
|
||||
{
|
||||
if (prefix)
|
||||
context.filters.push(function ({ item: opt }) opt.type == "boolean" || prefix == "inv" && opt.values instanceof Array);
|
||||
return completion.option(context, opt.scope);
|
||||
}
|
||||
else if (prefix == "no")
|
||||
return;
|
||||
|
||||
if (prefix)
|
||||
context.advance(prefix.length);
|
||||
|
||||
let option = opt.option;
|
||||
context.advance(context.filter.indexOf("=") + 1);
|
||||
|
||||
if (!option)
|
||||
{
|
||||
context.message = "No such option: " + opt.name;
|
||||
context.highlight(0, name.length, "SPELLCHECK");
|
||||
}
|
||||
|
||||
if (opt.get || opt.reset || !option || prefix)
|
||||
return;
|
||||
|
||||
if (!opt.value)
|
||||
{
|
||||
context.fork("default", 0, this, function (context) {
|
||||
context.title = ["Extra Completions"];
|
||||
context.completions = [[option.value, "Current value"], [option.defaultValue, "Default value"]].filter(function (f) f[0])
|
||||
});
|
||||
}
|
||||
|
||||
completion.optionValue(context, opt.name, opt.operator);
|
||||
},
|
||||
literal: 0,
|
||||
serial: function () [
|
||||
{
|
||||
command: this.name,
|
||||
literalArg: opt.type == "boolean" ? (opt.value ? "" : "no") + opt.name
|
||||
: opt.name + "=" + opt.value
|
||||
}
|
||||
for (opt in options)
|
||||
if (!opt.getter && opt.value != opt.defaultValue && (opt.scope & options.OPTION_SCOPE_GLOBAL))
|
||||
]
|
||||
});
|
||||
|
||||
commands.add(["unl[et]"],
|
||||
"Delete a variable",
|
||||
function (args)
|
||||
{
|
||||
//var names = args.split(/ /);
|
||||
//if (typeof names == "string") names = [names];
|
||||
|
||||
//var length = names.length;
|
||||
//for (let i = 0, name = names[i]; i < length; name = names[++i])
|
||||
for (let [,name] in args)
|
||||
{
|
||||
var name = args[i];
|
||||
var reference = liberator.variableReference(name);
|
||||
if (!reference[0])
|
||||
{
|
||||
if (!args.bang)
|
||||
liberator.echoerr("E108: No such variable: " + name);
|
||||
return;
|
||||
}
|
||||
|
||||
delete reference[0][reference[1]];
|
||||
}
|
||||
},
|
||||
{
|
||||
argCount: "+",
|
||||
bang: true
|
||||
});
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////}}}
|
||||
////////////////////// PUBLIC SECTION //////////////////////////////////////////
|
||||
/////////////////////////////////////////////////////////////////////////////{{{
|
||||
|
||||
// TODO: Does this belong elsewhere?
|
||||
liberator.registerObserver("load_completion", function ()
|
||||
{
|
||||
completion.setFunctionCompleter(options.get, [function () ([o.name, o.description] for (o in options))]);
|
||||
completion.setFunctionCompleter([options.getPref, options.setPref, options.resetPref, options.invertPref],
|
||||
[function () Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefBranch)
|
||||
.getChildList("", { value: 0 })
|
||||
.map(function (pref) [pref, ""])]);
|
||||
});
|
||||
|
||||
return {
|
||||
|
||||
OPTION_SCOPE_GLOBAL: 1,
|
||||
OPTION_SCOPE_LOCAL: 2,
|
||||
OPTION_SCOPE_BOTH: 3,
|
||||
|
||||
__iterator__: function ()
|
||||
{
|
||||
let sorted = [o for ([i, o] in Iterator(optionHash))].sort(function (a, b) String.localeCompare(a.name, b.name));
|
||||
return (v for ([k, v] in Iterator(sorted)));
|
||||
},
|
||||
|
||||
add: function (names, description, type, defaultValue, extraInfo)
|
||||
{
|
||||
if (!extraInfo)
|
||||
extraInfo = {};
|
||||
|
||||
let option = new Option(names, description, type, defaultValue, extraInfo);
|
||||
|
||||
if (!option)
|
||||
return false;
|
||||
|
||||
if (option.name in optionHash)
|
||||
{
|
||||
// never replace for now
|
||||
liberator.log("Warning: '" + names[0] + "' already exists, NOT replacing existing option.", 1);
|
||||
return false;
|
||||
}
|
||||
|
||||
// quickly access options with options["wildmode"]:
|
||||
this.__defineGetter__(option.name, function () option.value);
|
||||
this.__defineSetter__(option.name, function (value) { option.value = value; });
|
||||
|
||||
optionHash[option.name] = option;
|
||||
return true;
|
||||
},
|
||||
|
||||
get: function (name, scope)
|
||||
{
|
||||
if (!scope)
|
||||
scope = options.OPTION_SCOPE_BOTH;
|
||||
|
||||
if (name in optionHash)
|
||||
return (optionHash[name].scope & scope) && optionHash[name];
|
||||
|
||||
for (let opt in Iterator(options))
|
||||
{
|
||||
if (opt.hasName(name))
|
||||
return (opt.scope & scope) && opt;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
list: function (onlyNonDefault, scope)
|
||||
{
|
||||
if (!scope)
|
||||
scope = options.OPTION_SCOPE_BOTH;
|
||||
|
||||
let opts = function (opt) {
|
||||
for (let opt in Iterator(options))
|
||||
{
|
||||
let option = {
|
||||
isDefault: opt.value == opt.defaultValue,
|
||||
name: opt.name,
|
||||
default: opt.defaultValue,
|
||||
pre: "\u00a0\u00a0", /* Unicode nonbreaking space. */
|
||||
value: <></>
|
||||
};
|
||||
|
||||
if (onlyNonDefault && option.isDefault)
|
||||
continue;
|
||||
if (!(opt.scope & scope))
|
||||
continue;
|
||||
|
||||
if (opt.type == "boolean")
|
||||
{
|
||||
if (!opt.value)
|
||||
option.pre = "no";
|
||||
option.default = (option.default ? "" : "no") + opt.name;
|
||||
}
|
||||
else
|
||||
{
|
||||
option.value = <>={template.highlight(opt.value)}</>;
|
||||
}
|
||||
yield option;
|
||||
}
|
||||
};
|
||||
|
||||
let list = template.options("Options", opts());
|
||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
},
|
||||
|
||||
listPrefs: function (onlyNonDefault, filter)
|
||||
{
|
||||
if (!filter)
|
||||
filter = "";
|
||||
|
||||
var prefArray = prefService.getChildList("", { value: 0 });
|
||||
prefArray.sort();
|
||||
let prefs = function () {
|
||||
for each (let pref in prefArray)
|
||||
{
|
||||
var userValue = prefService.prefHasUserValue(pref);
|
||||
if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
|
||||
continue;
|
||||
|
||||
value = options.getPref(pref);
|
||||
|
||||
let option = {
|
||||
isDefault: !userValue,
|
||||
default: loadPreference(pref, null, true),
|
||||
value: <>={template.highlight(value, true, 100)}</>,
|
||||
name: pref,
|
||||
pre: "\u00a0\u00a0" /* Unicode nonbreaking space. */
|
||||
};
|
||||
|
||||
yield option;
|
||||
}
|
||||
};
|
||||
|
||||
let list = template.options(config.hostApplication + " Options", prefs());
|
||||
commandline.echo(list, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
},
|
||||
|
||||
parseOpt: function parseOpt(args, modifiers)
|
||||
{
|
||||
let ret = {};
|
||||
let matches, prefix, postfix, valueGiven;
|
||||
|
||||
[matches, prefix, ret.name, postfix, valueGiven, ret.operator, ret.value] =
|
||||
args.match(/^\s*(no|inv)?([a-z_]*)([?&!])?\s*(([-+^]?)=(.*))?\s*$/) || [];
|
||||
|
||||
ret.args = args;
|
||||
ret.onlyNonDefault = false; // used for :set to print non-default options
|
||||
if (!args)
|
||||
{
|
||||
ret.name = "all";
|
||||
ret.onlyNonDefault = true;
|
||||
}
|
||||
|
||||
if (matches)
|
||||
ret.option = options.get(ret.name, ret.scope);
|
||||
|
||||
ret.prefix = prefix;
|
||||
ret.postfix = postfix;
|
||||
|
||||
ret.all = (ret.name == "all");
|
||||
ret.get = (ret.all || postfix == "?" || (ret.option && ret.option.type != "boolean" && !valueGiven));
|
||||
ret.invert = (prefix == "inv" || postfix == "!");
|
||||
ret.reset = (postfix == "&");
|
||||
ret.unsetBoolean = (prefix == "no");
|
||||
|
||||
ret.scope = modifiers && modifiers.scope;
|
||||
|
||||
if (!ret.option)
|
||||
return ret;
|
||||
|
||||
if (ret.value === undefined)
|
||||
ret.value = "";
|
||||
|
||||
ret.optionValue = ret.option.get(ret.scope);
|
||||
ret.optionValues = ret.option.getValues(ret.scope);
|
||||
|
||||
ret.values = ret.option.parseValues(ret.value);
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
get store() storage.options,
|
||||
|
||||
getPref: function (name, forcedDefault)
|
||||
{
|
||||
return loadPreference(name, forcedDefault);
|
||||
},
|
||||
|
||||
setPref: function (name, value)
|
||||
{
|
||||
return storePreference(name, value);
|
||||
},
|
||||
|
||||
resetPref: function (name)
|
||||
{
|
||||
return prefService.clearUserPref(name);
|
||||
},
|
||||
|
||||
// this works only for booleans
|
||||
invertPref: function (name)
|
||||
{
|
||||
if (prefService.getPrefType(name) == prefService.PREF_BOOL)
|
||||
this.setPref(name, !this.getPref(name));
|
||||
else
|
||||
liberator.echoerr("E488: Trailing characters: " + name + "!");
|
||||
}
|
||||
};
|
||||
//}}}
|
||||
}; //}}}
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
582
common/content/style.js
Normal file
582
common/content/style.js
Normal file
@@ -0,0 +1,582 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
©2008 Kris Maglione <maglione.k at Gmail>
|
||||
Distributable under the terms of the MIT license, which allows
|
||||
for sublicensing under any compatible license, including the MPL,
|
||||
GPL, and MPL. Anyone who changes this file is welcome to relicense
|
||||
it under any or all of those licenseses.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
Highlights.prototype.CSS = <![CDATA[
|
||||
Boolean color: red;
|
||||
Function color: navy;
|
||||
Null color: blue;
|
||||
Number color: blue;
|
||||
Object color: maroon;
|
||||
String color: green;
|
||||
|
||||
Normal color: black; background: white;
|
||||
ErrorMsg color: white; background: red; font-weight: bold;
|
||||
InfoMsg color: black; background: white;
|
||||
ModeMsg color: black; background: white;
|
||||
MoreMsg color: green; background: white;
|
||||
WarningMsg color: red; background: white;
|
||||
Message white-space: normal; min-width: 100%; padding-left: 2em; text-indent: -2em; display: block;
|
||||
NonText color: blue; min-height: 16px; padding-left: 2px;
|
||||
Preview color: gray;
|
||||
|
||||
CompGroup
|
||||
CompGroup:not(:first-of-type) margin-top: .5em;
|
||||
CompTitle color: magenta; background: white; font-weight: bold;
|
||||
CompTitle>* /* border-bottom: 1px dashed magenta; */
|
||||
CompMsg font-style: italic; margin-left: 16px;
|
||||
CompItem
|
||||
CompItem[selected] background: yellow;
|
||||
CompItem>* padding: 0 .5ex;
|
||||
CompIcon width: 16px; min-width: 16px; display: inline-block; margin-right: .5ex;
|
||||
CompIcon>img max-width: 16px; max-height: 16px; vertical-align: middle;
|
||||
CompResult width: 45%; overflow: hidden;
|
||||
CompDesc color: gray; width: 50%;
|
||||
CompLess text-align: center; height: 0; line-height: .5ex; padding-top: 1ex;
|
||||
CompLess::after content: "\2303" /* Unicode up arrowhead */
|
||||
CompMore text-align: center; height: .5ex; line-height: .5ex; margin-bottom: -.5ex;
|
||||
CompMore::after content: "\2304" /* Unicode down arrowhead */
|
||||
|
||||
Gradient height: 1px; margin-bottom: -1px; margin-top: -1px;
|
||||
GradientLeft background-color: magenta;
|
||||
GradientRight background-color: white;
|
||||
|
||||
Indicator color: blue;
|
||||
Filter font-weight: bold;
|
||||
|
||||
Keyword color: red;
|
||||
Tag color: blue;
|
||||
|
||||
LineNr color: orange; background: white;
|
||||
Question color: green; background: white; font-weight: bold;
|
||||
|
||||
StatusLine color: white; background: black;
|
||||
StatusLineBroken color: black; background: #FF6060; /* light-red */
|
||||
StatusLineSecure color: black; background: #B0FF00; /* light-green */
|
||||
|
||||
TabClose
|
||||
TabIcon
|
||||
TabText
|
||||
TabNumber font-weight: bold; margin: 0px; padding-right: .3ex;
|
||||
TabIconNumber {
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
text-align: center;
|
||||
text-shadow: black -1px 0 1px, black 0 1px 1px, black 1px 0 1px, black 0 -1px 1px;
|
||||
}
|
||||
|
||||
Title color: magenta; background: white; font-weight: bold;
|
||||
URL text-decoration: none; color: green; background: inherit;
|
||||
URL:hover text-decoration: underline; cursor: pointer;
|
||||
|
||||
FrameIndicator,,* {
|
||||
background-color: red;
|
||||
opacity: 0.5;
|
||||
z-index: 999;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
Bell border: none; background-color: black;
|
||||
Hint,,* {
|
||||
font-family: monospace;
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
background-color: red;
|
||||
border-color: ButtonShadow;
|
||||
border-width: 0px;
|
||||
border-style: solid;
|
||||
padding: 0px 1px 0px 1px;
|
||||
}
|
||||
Hint::after,,* content: attr(number);
|
||||
HintElem,,* background-color: yellow; color: black;
|
||||
HintActive,,* background-color: #88FF00; color: black;
|
||||
HintImage,,* opacity: .5;
|
||||
|
||||
Search,,* {
|
||||
font-size: inherit;
|
||||
padding: 0;
|
||||
color: black;
|
||||
background-color: yellow;
|
||||
padding: 0;
|
||||
}
|
||||
]]>.toString();
|
||||
function Highlights(name, store, serial)
|
||||
{
|
||||
var self = this;
|
||||
var highlight = {};
|
||||
var styles = storage.styles;
|
||||
|
||||
const Highlight = Struct("class", "selector", "filter", "default", "value");
|
||||
Highlight.defaultValue("filter", function () "chrome://liberator/content/buffer.xhtml" + "," + config.styleableChrome);
|
||||
Highlight.defaultValue("selector", function () self.selector(this.class));
|
||||
Highlight.defaultValue("value", function () this.default);
|
||||
Highlight.prototype.toString = function () "Highlight(" + this.class + ")\n\t" + [k + ": " + util.escapeString(v || "undefined") for ([k, v] in this)].join("\n\t");
|
||||
|
||||
function keys() [k for ([k, v] in Iterator(highlight))].sort();
|
||||
this.__iterator__ = function () (highlight[v] for ([k, v] in Iterator(keys())));
|
||||
|
||||
this.get = function (k) highlight[k];
|
||||
this.set = function (key, newStyle, force, append)
|
||||
{
|
||||
let [, class, selectors] = key.match(/^([a-zA-Z_-]+)(.*)/);
|
||||
|
||||
if (!(class in highlight))
|
||||
return "Unknown highlight keyword: " + class;
|
||||
|
||||
let style = highlight[key] || new Highlight(key);
|
||||
styles.removeSheet(style.selector, null, null, null, true);
|
||||
|
||||
if (append)
|
||||
newStyle = (style.value || "").replace(/;?\s*$/, "; " + newStyle);
|
||||
if (/^\s*$/.test(newStyle))
|
||||
newStyle = null;
|
||||
if (newStyle == null)
|
||||
{
|
||||
if (style.default == null)
|
||||
{
|
||||
delete highlight[style.class];
|
||||
styles.removeSheet(style.selector, null, null, null, true);
|
||||
return null;
|
||||
}
|
||||
newStyle = style.default;
|
||||
force = true;
|
||||
}
|
||||
|
||||
let css = newStyle.replace(/(?:!\s*important\s*)?(?:;?\s*$|;)/g, "!important;")
|
||||
.replace(";!important;", ";", "g"); // Seeming Spidermonkey bug
|
||||
css = style.selector + " { " + css + " }";
|
||||
|
||||
let error = styles.addSheet(style.selector, style.filter, css, true, force);
|
||||
if (error)
|
||||
return error;
|
||||
style.value = newStyle;
|
||||
highlight[style.class] = style;
|
||||
}
|
||||
|
||||
this.selector = function (class)
|
||||
{
|
||||
let [, hl, rest] = class.match(/^(\w*)(.*)/);
|
||||
return "[liberator|highlight~=" + hl + "]" + rest
|
||||
};
|
||||
|
||||
this.reload = function ()
|
||||
{
|
||||
this.CSS.replace(/\{((?:.|\n)*?)\}/g, function (_, _1) _1.replace(/\n\s*/g, " "))
|
||||
.split("\n").filter(function (s) /\S/.test(s))
|
||||
.forEach(function (style)
|
||||
{
|
||||
style = Highlight.apply(Highlight, Array.slice(style.match(/^\s*([^,\s]+)(?:,([^,\s]+)?)?(?:,([^,\s]+))?\s*(.*)$/), 1));
|
||||
let old = highlight[style.class];
|
||||
highlight[style.class] = style;
|
||||
if (old && old.value != old.default)
|
||||
style.value = old.value;
|
||||
});
|
||||
for (let [class, hl] in Iterator(highlight))
|
||||
{
|
||||
if (hl.value == hl.default)
|
||||
this.set(class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Styles(name, store, serial)
|
||||
{
|
||||
/* Can't reference liberator or Components inside Styles --
|
||||
* they're members of the window object, which disappear
|
||||
* with this window.
|
||||
*/
|
||||
const util = modules.util;
|
||||
const sleep = liberator.sleep;
|
||||
const storage = modules.storage;
|
||||
const consoleService = Components.classes["@mozilla.org/consoleservice;1"]
|
||||
.getService(Components.interfaces.nsIConsoleService);
|
||||
const ios = Components.classes["@mozilla.org/network/io-service;1"]
|
||||
.getService(Components.interfaces.nsIIOService);
|
||||
const sss = Components.classes["@mozilla.org/content/style-sheet-service;1"]
|
||||
.getService(Components.interfaces.nsIStyleSheetService);
|
||||
const namespace = '@namespace html "' + XHTML + '";\n' +
|
||||
'@namespace xul "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";\n' +
|
||||
'@namespace liberator "' + NS.uri + '";\n';
|
||||
const Sheet = new Struct("name", "sites", "css", "ref");
|
||||
|
||||
let cssUri = function (css) "chrome-data:text/css," + encodeURI(css);
|
||||
|
||||
let userSheets = [];
|
||||
let systemSheets = [];
|
||||
let userNames = {};
|
||||
let systemNames = {};
|
||||
|
||||
this.__iterator__ = function () Iterator(userSheets.concat(systemSheets));
|
||||
this.__defineGetter__("systemSheets", function () Iterator(systemSheets));
|
||||
this.__defineGetter__("userSheets", function () Iterator(userSheets));
|
||||
this.__defineGetter__("systemNames", function () Iterator(systemNames));
|
||||
this.__defineGetter__("userNames", function () Iterator(userNames));
|
||||
|
||||
this.addSheet = function (name, filter, css, system, force)
|
||||
{
|
||||
let sheets = system ? systemSheets : userSheets;
|
||||
let names = system ? systemNames : userNames;
|
||||
if (name && name in names)
|
||||
this.removeSheet(name, null, null, null, system);
|
||||
|
||||
let sheet = sheets.filter(function (s) s.sites.join(",") == filter && s.css == css)[0];
|
||||
if (!sheet)
|
||||
sheet = new Sheet(name, filter.split(",").filter(util.identity), css, null);
|
||||
|
||||
if (sheet.ref == null) // Not registered yet
|
||||
{
|
||||
sheet.ref = [];
|
||||
try
|
||||
{
|
||||
this.registerSheet(cssUri(wrapCSS(sheet)), !force);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return e.echoerr || e;
|
||||
}
|
||||
sheets.push(sheet);
|
||||
}
|
||||
if (name)
|
||||
{
|
||||
sheet.ref.push(name);
|
||||
names[name] = sheet;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
this.findSheets = function (name, filter, css, index, system)
|
||||
{
|
||||
let sheets = system ? systemSheets : userSheets;
|
||||
let names = system ? systemNames : userNames;
|
||||
|
||||
// Grossly inefficient.
|
||||
let matches = [k for ([k, v] in Iterator(sheets))];
|
||||
if (index)
|
||||
matches = String(index).split(",").filter(function (i) i in sheets);
|
||||
if (name)
|
||||
matches = matches.filter(function (i) sheets[i] == names[name]);
|
||||
if (css)
|
||||
matches = matches.filter(function (i) sheets[i].css == css);
|
||||
if (filter)
|
||||
matches = matches.filter(function (i) sheets[i].sites.indexOf(filter) >= 0);
|
||||
return matches.map(function (i) sheets[i]);
|
||||
};
|
||||
|
||||
this.removeSheet = function (name, filter, css, index, system)
|
||||
{
|
||||
let self = this;
|
||||
let sheets = system ? systemSheets : userSheets;
|
||||
let names = system ? systemNames : userNames;
|
||||
|
||||
if (filter && filter.indexOf(",") > -1)
|
||||
return filter.split(",").reduce(
|
||||
function (n, f) n + self.removeSheet(name, f, index, system), 0);
|
||||
|
||||
if (filter == undefined)
|
||||
filter = "";
|
||||
|
||||
let matches = this.findSheets(name, filter, css, index, system);
|
||||
if (matches.length == 0)
|
||||
return;
|
||||
|
||||
for (let [,sheet] in Iterator(matches.reverse()))
|
||||
{
|
||||
if (name)
|
||||
{
|
||||
if (sheet.ref.indexOf(name) > -1)
|
||||
sheet.ref.splice(sheet.ref.indexOf(name), 1);
|
||||
delete names[name];
|
||||
}
|
||||
if (!sheet.ref.length)
|
||||
{
|
||||
this.unregisterSheet(cssUri(wrapCSS(sheet)));
|
||||
if (sheets.indexOf(sheet) > -1)
|
||||
sheets.splice(sheets.indexOf(sheet), 1);
|
||||
}
|
||||
if (filter)
|
||||
{
|
||||
let sites = sheet.sites.filter(function (f) f != filter);
|
||||
if (sites.length)
|
||||
this.addSheet(name, sites.join(","), css, system, true);
|
||||
}
|
||||
}
|
||||
return matches.length;
|
||||
}
|
||||
|
||||
this.registerSheet = function (uri, doCheckSyntax, reload)
|
||||
{
|
||||
//dump (uri + "\n\n");
|
||||
if (doCheckSyntax)
|
||||
checkSyntax(uri);
|
||||
if (reload)
|
||||
this.unregisterSheet(uri);
|
||||
uri = ios.newURI(uri, null, null);
|
||||
if (reload || !sss.sheetRegistered(uri, sss.USER_SHEET))
|
||||
sss.loadAndRegisterSheet(uri, sss.USER_SHEET);
|
||||
}
|
||||
|
||||
this.unregisterSheet = function (uri)
|
||||
{
|
||||
uri = ios.newURI(uri, null, null);
|
||||
if (sss.sheetRegistered(uri, sss.USER_SHEET))
|
||||
sss.unregisterSheet(uri, sss.USER_SHEET);
|
||||
}
|
||||
|
||||
function wrapCSS(sheet)
|
||||
{
|
||||
let filter = sheet.sites;
|
||||
let css = sheet.css;
|
||||
if (filter[0] == "*")
|
||||
return namespace + css;
|
||||
let selectors = filter.map(function (part) (/[*]$/.test(part) ? "url-prefix" :
|
||||
/[\/:]/.test(part) ? "url"
|
||||
: "domain")
|
||||
+ '("' + part.replace(/"/g, "%22").replace(/[*]$/, "") + '")')
|
||||
.join(", ");
|
||||
return namespace + "@-moz-document " + selectors + "{\n" + css + "\n}\n";
|
||||
}
|
||||
|
||||
let queryinterface = XPCOMUtils.generateQI([Components.interfaces.nsIConsoleListener]);
|
||||
/* What happens if more than one thread tries to use this? */
|
||||
let testDoc = document.implementation.createDocument(XHTML, "doc", null);
|
||||
function checkSyntax(uri)
|
||||
{
|
||||
let errors = [];
|
||||
let listener = {
|
||||
QueryInterface: queryinterface,
|
||||
observe: function (message)
|
||||
{
|
||||
try
|
||||
{
|
||||
message = message.QueryInterface(Components.interfaces.nsIScriptError);
|
||||
if (message.sourceName == uri)
|
||||
errors.push(message);
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
consoleService.registerListener(listener);
|
||||
if (testDoc.documentElement.firstChild)
|
||||
testDoc.documentElement.removeChild(testDoc.documentElement.firstChild);
|
||||
testDoc.documentElement.appendChild(util.xmlToDom(
|
||||
<html><head><link type="text/css" rel="stylesheet" href={uri}/></head></html>, testDoc));
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Throws NS_ERROR_DOM_INVALID_ACCESS_ERR if not finished loading
|
||||
testDoc.styleSheets[0].cssRules.length;
|
||||
break;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
if (e.name != "NS_ERROR_DOM_INVALID_ACCESS_ERR")
|
||||
return [e.toString()];
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
consoleService.unregisterListener(listener);
|
||||
}
|
||||
if (errors.length)
|
||||
{
|
||||
let err = new Error("", errors[0].sourceName.replace(/^(chrome-data:text\/css,).*/, "$1..."), errors[0].lineNumber);
|
||||
err.name = "CSSError"
|
||||
err.message = errors.reduce(function (msg, e) msg + "; " + e.lineNumber + ": " + e.errorMessage,
|
||||
errors.shift().errorMessage);
|
||||
err.echoerr = err.fileName + ":" + err.lineNumber + ": " + err.message;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
let (array = util.Array)
|
||||
{
|
||||
Styles.prototype = {
|
||||
get sites() array.uniq(array.flatten([v.sites for ([k, v] in this.userSheets)]))
|
||||
};
|
||||
}
|
||||
|
||||
const styles = storage.newObject("styles", Styles, false);
|
||||
const highlight = storage.newObject("highlight", Highlights, false);
|
||||
highlight.CSS = Highlights.prototype.CSS;
|
||||
highlight.reload();
|
||||
|
||||
liberator.triggerObserver("load_styles", "styles");
|
||||
liberator.triggerObserver("load_highlight", "highlight");
|
||||
|
||||
liberator.registerObserver("load_commands", function ()
|
||||
{
|
||||
// TODO: :colo default needs :hi clear
|
||||
commands.add(["colo[rscheme]"],
|
||||
"Load a color scheme",
|
||||
function (args)
|
||||
{
|
||||
let scheme = args[0];
|
||||
|
||||
if (io.sourceFromRuntimePath(["colors/" + scheme + ".vimp"]))
|
||||
autocommands.trigger("ColorScheme", { name: scheme });
|
||||
else
|
||||
liberator.echoerr("E185: Cannot find color scheme " + scheme);
|
||||
},
|
||||
{
|
||||
argCount: "1",
|
||||
completer: function (context) completion.colorScheme(context)
|
||||
});
|
||||
|
||||
commands.add(["sty[le]"],
|
||||
"Add or list user styles",
|
||||
function (args)
|
||||
{
|
||||
let [filter, css] = args;
|
||||
let name = args["-name"];
|
||||
|
||||
if (!css)
|
||||
{
|
||||
let list = Array.concat([i for (i in styles.userNames)],
|
||||
[i for (i in styles.userSheets) if (!i[1].ref.length)]);
|
||||
let str = template.tabular(["", "Filter", "CSS"],
|
||||
["padding: 0 1em 0 1ex; vertical-align: top", "padding: 0 1em 0 0; vertical-align: top"],
|
||||
([k, v[1].join(","), v[2]]
|
||||
for ([i, [k, v]] in Iterator(list))
|
||||
if ((!filter || v[1].indexOf(filter) >= 0) && (!name || v[0] == name))));
|
||||
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
}
|
||||
else
|
||||
{
|
||||
if ("-append" in args)
|
||||
{
|
||||
let sheet = styles.findSheets(name, null, null, null, false)[0];
|
||||
if (sheet)
|
||||
{
|
||||
filter = sheet.sites.concat(filter).join(",");
|
||||
css = sheet.css.replace(/;?\s*$/, "; " + css);
|
||||
}
|
||||
}
|
||||
let err = styles.addSheet(name, filter, css, false, args.bang);
|
||||
if (err)
|
||||
liberator.echoerr(err);
|
||||
}
|
||||
},
|
||||
{
|
||||
bang: true,
|
||||
completer: function (context, args) {
|
||||
let compl = [];
|
||||
if (args.completeArg == 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
compl.push([content.location.host, "Current Host"]);
|
||||
compl.push([content.location.href, "Current URL"]);
|
||||
}
|
||||
catch (e) {}
|
||||
context.completions = compl.concat([[s, ""] for each (s in styles.sites)])
|
||||
}
|
||||
else if (args.completeArg == 1)
|
||||
{
|
||||
let sheet = styles.findSheets(args["-name"], null, null, null, false)[0];
|
||||
if (sheet)
|
||||
context.completions = [[sheet.css, "Current Value"]];
|
||||
}
|
||||
},
|
||||
hereDoc: true,
|
||||
literal: 1,
|
||||
options: [[["-name", "-n"], commands.OPTION_STRING, null, function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))]],
|
||||
[["-append", "-a"], commands.OPTION_NOARG]],
|
||||
serial: function () [
|
||||
{
|
||||
command: this.name,
|
||||
bang: true,
|
||||
options: sty.name ? { "-name": sty.name } : {},
|
||||
arguments: [sty.sites.join(",")],
|
||||
literalArg: sty.css
|
||||
} for ([k, sty] in styles.userSheets)
|
||||
]
|
||||
});
|
||||
|
||||
commands.add(["dels[tyle]"],
|
||||
"Remove a user stylesheet",
|
||||
function (args) {
|
||||
styles.removeSheet(args["-name"], args[0], args.literalArg, args["-index"], false);
|
||||
},
|
||||
{
|
||||
completer: function (context) { context.completions = styles.sites.map(function (site) [site, ""]); },
|
||||
literal: 1,
|
||||
options: [[["-index", "-i"], commands.OPTION_INT, null, function () [[i, <>{s.sites.join(",")}: {s.css.replace("\n", "\\n")}</>] for ([i, s] in styles.userSheets)]],
|
||||
[["-name", "-n"], commands.OPTION_STRING, null, function () [[k, v.css] for ([k, v] in Iterator(styles.userNames))]]]
|
||||
});
|
||||
|
||||
commands.add(["hi[ghlight]"],
|
||||
"Set the style of certain display elements",
|
||||
function (args)
|
||||
{
|
||||
let style = <![CDATA[
|
||||
;
|
||||
display: inline-block !important;
|
||||
position: static !important;
|
||||
margin: 0px !important; padding: 0px !important;
|
||||
width: 3em !important; min-width: 3em !important; max-width: 3em !important;
|
||||
height: 1em !important; min-height: 1em !important; max-height: 1em !important;
|
||||
overflow: hidden !important;
|
||||
]]>;
|
||||
let [key, css] = args;
|
||||
if (!css && !(key && args.bang))
|
||||
{
|
||||
let str = template.tabular(["Key", "Sample", "CSS"],
|
||||
["padding: 0 1em 0 0; vertical-align: top", "text-align: center"],
|
||||
([h.class,
|
||||
<span style={"text-align: center; line-height: 1em;" + h.value + style}>XXX</span>,
|
||||
template.highlightRegexp(h.value, /\b[-\w]+(?=:)/g)]
|
||||
for (h in highlight)
|
||||
if (!key || h.class.indexOf(key) > -1)));
|
||||
commandline.echo(str, commandline.HL_NORMAL, commandline.FORCE_MULTILINE);
|
||||
return;
|
||||
}
|
||||
let error = highlight.set(key, css, args.bang, "-append" in args);
|
||||
if (error)
|
||||
liberator.echoerr(error);
|
||||
},
|
||||
{
|
||||
bang: true,
|
||||
// TODO: add this as a standard highlight completion function?
|
||||
completer: function (context, args)
|
||||
{
|
||||
if (args.completeArg == 0)
|
||||
context.completions = [[v.class, ""] for (v in highlight)];
|
||||
else if (args.completeArg == 1)
|
||||
{
|
||||
let hl = highlight.get(args[0]);
|
||||
if (hl)
|
||||
context.completions = [[hl.value, "Current Value"], [hl.default || "", "Default Value"]];
|
||||
}
|
||||
},
|
||||
hereDoc: true,
|
||||
literal: 1,
|
||||
options: [[["-append", "-a"], commands.OPTION_NOARG]],
|
||||
serial: function () [
|
||||
{
|
||||
command: this.name,
|
||||
arguments: [k],
|
||||
literalArg: v
|
||||
}
|
||||
for ([k, v] in Iterator(highlight))
|
||||
if (v.value != v.default)
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1000
common/content/tabs.js
Normal file
1000
common/content/tabs.js
Normal file
File diff suppressed because it is too large
Load Diff
305
common/content/template.js
Normal file
305
common/content/template.js
Normal file
@@ -0,0 +1,305 @@
|
||||
const template = {
|
||||
add: function add(a, b) a + b,
|
||||
join: function join(c) function (a, b) a + c + b,
|
||||
|
||||
map: function map(iter, fn, sep, interruptable)
|
||||
{
|
||||
if (iter.length) /* Kludge? */
|
||||
iter = util.Array.iterator(iter);
|
||||
let ret = <></>;
|
||||
let n = 0;
|
||||
for each (let i in Iterator(iter))
|
||||
{
|
||||
let val = fn(i);
|
||||
if (val == undefined)
|
||||
continue;
|
||||
if (sep && n++)
|
||||
ret += sep;
|
||||
if (interruptable && n % interruptable == 0)
|
||||
liberator.threadYield(true, true);
|
||||
ret += val;
|
||||
}
|
||||
return ret;
|
||||
},
|
||||
|
||||
maybeXML: function maybeXML(xml)
|
||||
{
|
||||
if (typeof xml == "xml")
|
||||
return xml;
|
||||
try
|
||||
{
|
||||
return new XMLList(xml);
|
||||
}
|
||||
catch (e) {}
|
||||
return <>{xml}</>;
|
||||
},
|
||||
|
||||
completionRow: function completionRow(item, class)
|
||||
{
|
||||
if (typeof icon == "function")
|
||||
icon = icon();
|
||||
|
||||
if (class)
|
||||
{
|
||||
var text = item[0] || "";
|
||||
var desc = item[1] || "";
|
||||
}
|
||||
else
|
||||
{
|
||||
var text = this.process[0].call(this, item, item.text || this.getKey(item, "text"));
|
||||
var desc = this.process[1].call(this, item, this.getKey(item, "description"));
|
||||
}
|
||||
|
||||
return <div highlight={class || "CompItem"} style="white-space: nowrap">
|
||||
<!-- The non-breaking spaces prevent empty elements
|
||||
- from pushing the baseline down and enlarging
|
||||
- the row.
|
||||
-->
|
||||
<li highlight="CompResult">{text} </li>
|
||||
<li highlight="CompDesc">{desc} </li>
|
||||
</div>;
|
||||
},
|
||||
|
||||
bookmarkDescription: function (item, text)
|
||||
{
|
||||
let extra = this.getKey(item, "extra");
|
||||
return <>
|
||||
<a href="#" highlight="URL">{text}</a> 
|
||||
{
|
||||
!(extra && extra.length) ? "" :
|
||||
<span class="extra-info">
|
||||
({
|
||||
template.map(extra, function (e)
|
||||
<>{e[0]}: <span highlight={e[2]}>{e[1]}</span></>,
|
||||
<> </>/* Non-breaking space */)
|
||||
})
|
||||
</span>
|
||||
}
|
||||
</>
|
||||
},
|
||||
|
||||
icon: function (item, text)
|
||||
{
|
||||
let icon = this.getKey(item, "icon");
|
||||
return <><span highlight="CompIcon">{icon ? <img src={icon}/> : <></>}</span><span class="td-strut"/>{text}</>
|
||||
},
|
||||
|
||||
filter: function (str) <span highlight="Filter">{str}</span>,
|
||||
|
||||
// if "processStrings" is true, any passed strings will be surrounded by " and
|
||||
// any line breaks are displayed as \n
|
||||
highlight: function highlight(arg, processStrings, clip)
|
||||
{
|
||||
// some objects like window.JSON or getBrowsers()._browsers need the try/catch
|
||||
let str = clip ? util.clip(String(arg), clip) : String(arg);
|
||||
try
|
||||
{
|
||||
switch (arg == null ? "undefined" : typeof arg)
|
||||
{
|
||||
case "number":
|
||||
return <span highlight="Number">{str}</span>;
|
||||
case "string":
|
||||
if (processStrings)
|
||||
str = str.quote();
|
||||
return <span highlight="String">{str}</span>;
|
||||
case "boolean":
|
||||
return <span highlight="Boolean">{str}</span>;
|
||||
case "function":
|
||||
// Vim generally doesn't like /foo*/, because */ looks like a comment terminator.
|
||||
// Using /foo*(:?)/ instead.
|
||||
if (processStrings)
|
||||
return <span highlight="Function">{str.replace(/\{(.|\n)*(?:)/g, "{ ... }")}</span>;
|
||||
return <>{arg}</>;
|
||||
case "undefined":
|
||||
return <span highlight="Null">{arg}</span>;
|
||||
case "object":
|
||||
// for java packages value.toString() would crash so badly
|
||||
// that we cannot even try/catch it
|
||||
if (/^\[JavaPackage.*\]$/.test(arg))
|
||||
return <>[JavaPackage]</>;
|
||||
if (processStrings && false)
|
||||
str = template.highlightFilter(str, "\n", function () <span highlight="NonText">^J</span>);
|
||||
return <span highlight="Object">{str}</span>;
|
||||
case "xml":
|
||||
return arg;
|
||||
default:
|
||||
return <![CDATA[<unknown type>]]>;
|
||||
}
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
return<![CDATA[<unknown>]]>;
|
||||
}
|
||||
},
|
||||
|
||||
highlightFilter: function highlightFilter(str, filter, highlight)
|
||||
{
|
||||
return this.highlightSubstrings(str, (function ()
|
||||
{
|
||||
if (filter.length == 0)
|
||||
return;
|
||||
let lcstr = String.toLowerCase(str);
|
||||
let lcfilter = filter.toLowerCase();
|
||||
let start = 0;
|
||||
while ((start = lcstr.indexOf(lcfilter, start)) > -1)
|
||||
{
|
||||
yield [start, filter.length];
|
||||
start += filter.length;
|
||||
}
|
||||
})(), highlight || template.filter);
|
||||
},
|
||||
|
||||
highlightRegexp: function highlightRegexp(str, re, highlight)
|
||||
{
|
||||
return this.highlightSubstrings(str, (function ()
|
||||
{
|
||||
let res;
|
||||
while ((res = re.exec(str)) && res[0].length)
|
||||
yield [res.index, res[0].length];
|
||||
})(), highlight || template.filter);
|
||||
},
|
||||
|
||||
highlightSubstrings: function highlightSubstrings(str, iter, highlight)
|
||||
{
|
||||
if (typeof str == "xml")
|
||||
return str;
|
||||
if (str == "")
|
||||
return <>{str}</>;
|
||||
|
||||
str = String(str).replace(" ", "\u00a0");
|
||||
let s = <></>;
|
||||
let start = 0;
|
||||
let n = 0;
|
||||
for (let [i, length] in iter)
|
||||
{
|
||||
if (n++ > 50) // Prevent infinite loops.
|
||||
return s + <>{str.substr(start)}</>;
|
||||
XML.ignoreWhitespace = false;
|
||||
s += <>{str.substring(start, i)}</>;
|
||||
s += highlight(str.substr(i, length));
|
||||
start = i + length;
|
||||
}
|
||||
return s + <>{str.substr(start)}</>;
|
||||
},
|
||||
|
||||
highlightURL: function highlightURL(str, force)
|
||||
{
|
||||
if (force || /^[a-zA-Z]+:\/\//.test(str))
|
||||
return <a highlight="URL" href="#">{str}</a>;
|
||||
else
|
||||
return str;
|
||||
},
|
||||
|
||||
generic: function generic(xml)
|
||||
{
|
||||
return <>:{commandline.getCommand()}<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)
|
||||
{
|
||||
this.listCompleter(function (context) {
|
||||
context.filterFunc = null;
|
||||
if (format)
|
||||
context.format = format;
|
||||
context.completions = items;
|
||||
});
|
||||
},
|
||||
|
||||
jumps: function jumps(index, elems)
|
||||
{
|
||||
return this.generic(
|
||||
<table>
|
||||
<tr style="text-align: left;" highlight="Title">
|
||||
<th colspan="2">jump</th><th>title</th><th>URI</th>
|
||||
</tr>
|
||||
{
|
||||
this.map(Iterator(elems), function ([idx, val])
|
||||
<tr>
|
||||
<td class="indicator">{idx == index ? ">" : ""}</td>
|
||||
<td>{Math.abs(idx - index)}</td>
|
||||
<td style="width: 250px; max-width: 500px; overflow: hidden;">{val.title}</td>
|
||||
<td><a href="#" highlight="URL jump-list">{val.URI.spec}</a></td>
|
||||
</tr>)
|
||||
}
|
||||
</table>);
|
||||
},
|
||||
|
||||
options: function options(title, opts)
|
||||
{
|
||||
return this.generic(
|
||||
<table>
|
||||
<tr highlight="Title" align="left">
|
||||
<th>--- {title} ---</th>
|
||||
</tr>
|
||||
{
|
||||
this.map(opts, function (opt)
|
||||
<tr>
|
||||
<td>
|
||||
<span style={opt.isDefault ? "" : "font-weight: bold"}>{opt.pre}{opt.name}</span><span>{opt.value}</span>
|
||||
{opt.isDefault || opt.default == null ? "" : <span class="extra-info"> (default: {opt.default})</span>}
|
||||
</td>
|
||||
</tr>)
|
||||
}
|
||||
</table>);
|
||||
},
|
||||
|
||||
table: function table(title, data, indent)
|
||||
{
|
||||
let table =
|
||||
<table>
|
||||
<tr highlight="Title" align="left">
|
||||
<th colspan="2">{title}</th>
|
||||
</tr>
|
||||
{
|
||||
this.map(data, function (datum)
|
||||
<tr>
|
||||
<td style={"font-weight: bold; min-width: 150px; padding-left: " + (indent || "2ex")}>{datum[0]}</td>
|
||||
<td>{template.maybeXML(datum[1])}</td>
|
||||
</tr>)
|
||||
}
|
||||
</table>;
|
||||
if (table.tr.length() > 1)
|
||||
return table;
|
||||
},
|
||||
|
||||
tabular: function tabular(headings, style, iter)
|
||||
{
|
||||
/* This might be mind-bogglingly slow. We'll see. */
|
||||
return this.generic(
|
||||
<table>
|
||||
<tr highlight="Title" align="left">
|
||||
{
|
||||
this.map(headings, function (h)
|
||||
<th>{h}</th>)
|
||||
}
|
||||
</tr>
|
||||
{
|
||||
this.map(iter, function (row)
|
||||
<tr>
|
||||
{
|
||||
template.map(Iterator(row), function ([i, d])
|
||||
<td style={style[i] || ""}>{d}</td>)
|
||||
}
|
||||
</tr>)
|
||||
}
|
||||
</table>);
|
||||
},
|
||||
|
||||
usage: function usage(iter)
|
||||
{
|
||||
return this.generic(
|
||||
<table>
|
||||
{
|
||||
this.map(iter, function (item)
|
||||
<tr>
|
||||
<td highlight="Title" style="padding-right: 20px">{item.name || item.names[0]}</td>
|
||||
<td>{item.description}</td>
|
||||
</tr>)
|
||||
}
|
||||
</table>);
|
||||
}
|
||||
};
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
1860
common/content/ui.js
Normal file
1860
common/content/ui.js
Normal file
File diff suppressed because it is too large
Load Diff
597
common/content/util.js
Normal file
597
common/content/util.js
Normal file
@@ -0,0 +1,597 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
const XHTML = "http://www.w3.org/1999/xhtml";
|
||||
const NS = Namespace("liberator", "http://vimperator.org/namespaces/liberator");
|
||||
default xml namespace = XHTML;
|
||||
|
||||
const util = { //{{{
|
||||
|
||||
Array: {
|
||||
// [["a", "b"], ["c", "d"]] -> { a: "b", c: "d" }
|
||||
// From Common Lisp, more or less
|
||||
assocToObj: function assocToObj(assoc)
|
||||
{
|
||||
let obj = {};
|
||||
assoc.forEach(function ([k, v]) { obj[k] = v });
|
||||
return obj;
|
||||
},
|
||||
|
||||
// flatten an array: [["foo", ["bar"]], ["baz"], "quux"] -> ["foo", ["bar"], "baz", "quux"]
|
||||
flatten: function flatten(ary) Array.concat.apply([], ary),
|
||||
|
||||
iterator: function iterator(ary)
|
||||
{
|
||||
let length = ary.length;
|
||||
for (let i = 0; i < length; i++)
|
||||
yield ary[i];
|
||||
},
|
||||
|
||||
iterator2: function (ary)
|
||||
{
|
||||
let length = ary.length;
|
||||
for (let i = 0; i < length; i++)
|
||||
yield [i, ary[i]];
|
||||
},
|
||||
|
||||
uniq: function uniq(ary, unsorted)
|
||||
{
|
||||
let ret = [];
|
||||
if (unsorted)
|
||||
{
|
||||
for (let [,item] in Iterator(ary))
|
||||
if (ret.indexOf(item) == -1)
|
||||
ret.push(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (let [,item] in Iterator(ary.sort()))
|
||||
{
|
||||
if (item != last || !ret.length)
|
||||
ret.push(item);
|
||||
var last = item;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: class could have better variable names/documentation
|
||||
Timer: function Timer(minInterval, maxInterval, callback)
|
||||
{
|
||||
let timer = Components.classes["@mozilla.org/timer;1"]
|
||||
.createInstance(Components.interfaces.nsITimer);
|
||||
this.doneAt = 0;
|
||||
this.latest = 0;
|
||||
this.notify = function (aTimer)
|
||||
{
|
||||
timer.cancel();
|
||||
this.latest = 0;
|
||||
/* minInterval is the time between the completion of the command and the next firing. */
|
||||
this.doneAt = Date.now() + minInterval;
|
||||
|
||||
try
|
||||
{
|
||||
callback(this.arg);
|
||||
}
|
||||
finally
|
||||
{
|
||||
this.doneAt = Date.now() + minInterval;
|
||||
}
|
||||
};
|
||||
this.tell = function (arg)
|
||||
{
|
||||
if (arg !== undefined)
|
||||
this.arg = arg;
|
||||
|
||||
let now = Date.now();
|
||||
if (this.doneAt == -1)
|
||||
timer.cancel();
|
||||
|
||||
let timeout = minInterval;
|
||||
if (now > this.doneAt && this.doneAt > -1)
|
||||
timeout = 0;
|
||||
else if (this.latest)
|
||||
timeout = Math.min(timeout, this.latest - now);
|
||||
else
|
||||
this.latest = now + maxInterval;
|
||||
|
||||
timer.initWithCallback(this, Math.max(timeout, 0), timer.TYPE_ONE_SHOT);
|
||||
this.doneAt = -1;
|
||||
};
|
||||
this.reset = function ()
|
||||
{
|
||||
timer.cancel();
|
||||
this.doneAt = 0;
|
||||
};
|
||||
this.flush = function ()
|
||||
{
|
||||
if (this.latest)
|
||||
this.notify();
|
||||
};
|
||||
},
|
||||
|
||||
cloneObject: function cloneObject(obj)
|
||||
{
|
||||
if (obj instanceof Array)
|
||||
return obj.slice();
|
||||
let newObj = {};
|
||||
for (let [k, v] in Iterator(obj))
|
||||
newObj[k] = v;
|
||||
return newObj;
|
||||
},
|
||||
|
||||
clip: function clip(str, length)
|
||||
{
|
||||
return str.length <= length ? str : str.substr(0, length - 3) + "...";
|
||||
},
|
||||
|
||||
compareIgnoreCase: function compareIgnoreCase(a, b) String.localeCompare(a.toLowerCase(), b.toLowerCase()),
|
||||
|
||||
computedStyle: function computedStyle(node)
|
||||
{
|
||||
while (node instanceof Text && node.parentNode)
|
||||
node = node.parentNode;
|
||||
return node.ownerDocument.defaultView.getComputedStyle(node, null);
|
||||
},
|
||||
|
||||
copyToClipboard: function copyToClipboard(str, verbose)
|
||||
{
|
||||
const clipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
|
||||
.getService(Components.interfaces.nsIClipboardHelper);
|
||||
clipboardHelper.copyString(str);
|
||||
|
||||
if (verbose)
|
||||
liberator.echo("Yanked " + str, commandline.FORCE_SINGLELINE);
|
||||
},
|
||||
|
||||
createURI: function createURI(str)
|
||||
{
|
||||
const fixup = Components.classes["@mozilla.org/docshell/urifixup;1"]
|
||||
.getService(Components.interfaces.nsIURIFixup);
|
||||
return fixup.createFixupURI(str, fixup.FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP);
|
||||
},
|
||||
|
||||
escapeHTML: function escapeHTML(str)
|
||||
{
|
||||
// XXX: the following code is _much_ slower than a simple .replace()
|
||||
// :history display went down from 2 to 1 second after changing
|
||||
//
|
||||
// var e = window.content.document.createElement("div");
|
||||
// e.appendChild(window.content.document.createTextNode(str));
|
||||
// return e.innerHTML;
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
},
|
||||
|
||||
escapeRegex: function escapeRegex(str)
|
||||
{
|
||||
return str.replace(/([\\{}()[\].?*+])/g, "\\$1");
|
||||
},
|
||||
|
||||
escapeString: function escapeString(str, delimiter)
|
||||
{
|
||||
if (delimiter == undefined)
|
||||
delimiter = '"';
|
||||
return delimiter + str.replace(/([\\'"])/g, "\\$1").replace("\n", "\\n", "g").replace("\t", "\\t", "g") + delimiter;
|
||||
},
|
||||
|
||||
formatBytes: function formatBytes(num, decimalPlaces, humanReadable)
|
||||
{
|
||||
const unitVal = ["Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"];
|
||||
let unitIndex = 0;
|
||||
let tmpNum = parseInt(num, 10) || 0;
|
||||
let strNum = [tmpNum + ""];
|
||||
|
||||
if (humanReadable)
|
||||
{
|
||||
while (tmpNum >= 1024)
|
||||
{
|
||||
tmpNum /= 1024;
|
||||
if (++unitIndex > (unitVal.length - 1))
|
||||
break;
|
||||
}
|
||||
|
||||
let decPower = Math.pow(10, decimalPlaces);
|
||||
strNum = ((Math.round(tmpNum * decPower) / decPower) + "").split(".", 2);
|
||||
|
||||
if (!strNum[1])
|
||||
strNum[1] = "";
|
||||
|
||||
while (strNum[1].length < decimalPlaces) // pad with "0" to the desired decimalPlaces)
|
||||
strNum[1] += "0";
|
||||
}
|
||||
|
||||
for (let u = strNum[0].length - 3; u > 0; u -= 3) // make a 10000 a 10,000
|
||||
strNum[0] = strNum[0].substr(0, u) + "," + strNum[0].substr(u);
|
||||
|
||||
if (unitIndex) // decimalPlaces only when > Bytes
|
||||
strNum[0] += "." + strNum[1];
|
||||
|
||||
return strNum[0] + " " + unitVal[unitIndex];
|
||||
},
|
||||
|
||||
// generates an Asciidoc help entry, "command" can also be a mapping
|
||||
generateHelp: function generateHelp(command, extraHelp)
|
||||
{
|
||||
let start = "", end = "";
|
||||
if (command instanceof liberator.Command)
|
||||
start = ":";
|
||||
else if (command instanceof liberator.Option)
|
||||
start = end = "'";
|
||||
|
||||
let ret = "";
|
||||
let longHelp = false;
|
||||
if ((command.help && command.description) && (command.help.length + command.description.length) > 50)
|
||||
longHelp = true;
|
||||
|
||||
// the tags which are printed on the top right
|
||||
for (let j = command.names.length - 1; j >= 0; j--)
|
||||
ret += "|" + start + command.names[j] + end + "| ";
|
||||
|
||||
if (longHelp)
|
||||
ret += "+";
|
||||
|
||||
ret += "\n";
|
||||
|
||||
// the usage information for the command
|
||||
let usage = command.names[0];
|
||||
if (command.specs) // for :commands
|
||||
usage = command.specs[0];
|
||||
|
||||
usage = usage.replace(/{/, "\\\\{").replace(/}/, "\\\\}");
|
||||
usage = usage.replace(/'/, "\\'").replace(/`/, "\\`");
|
||||
ret += "||" + start + usage + end + "||";
|
||||
if (usage.length > 15)
|
||||
ret += " +";
|
||||
|
||||
ret += "\n________________________________________________________________________________\n";
|
||||
|
||||
// the actual help text
|
||||
if (command.description)
|
||||
{
|
||||
ret += command.description + "."; // the help description
|
||||
if (extraHelp)
|
||||
ret += " +\n" + extraHelp;
|
||||
}
|
||||
else
|
||||
ret += "Sorry, no help available";
|
||||
|
||||
// add more space between entries
|
||||
ret += "\n________________________________________________________________________________\n\n\n";
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
httpGet: function httpGet(url, callback)
|
||||
{
|
||||
try
|
||||
{
|
||||
let xmlhttp = new XMLHttpRequest();
|
||||
xmlhttp.mozBackgroundRequest = true;
|
||||
if (callback)
|
||||
{
|
||||
xmlhttp.onreadystatechange = function () {
|
||||
if (xmlhttp.readyState == 4)
|
||||
callback(xmlhttp)
|
||||
}
|
||||
}
|
||||
xmlhttp.open("GET", url, !!callback);
|
||||
xmlhttp.send(null);
|
||||
return xmlhttp;
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
liberator.log("Error opening " + url + ": " + e, 1);
|
||||
}
|
||||
},
|
||||
|
||||
identity: function identity(k) k,
|
||||
|
||||
map: function map(obj, fn)
|
||||
{
|
||||
let ary = [];
|
||||
for (let i in Iterator(obj))
|
||||
ary.push(fn(i));
|
||||
return ary;
|
||||
},
|
||||
|
||||
// if color = true it uses HTML markup to color certain items
|
||||
objectToString: function objectToString(object, color)
|
||||
{
|
||||
/* Use E4X literals so html is automatically quoted
|
||||
* only when it's asked for. Noone wants to see <
|
||||
* on their console or :map :foo in their buffer
|
||||
* when they expect :map <C-f> :foo.
|
||||
*/
|
||||
XML.prettyPrinting = false;
|
||||
XML.ignoreWhitespace = false;
|
||||
|
||||
if (object === null)
|
||||
return "null\n";
|
||||
|
||||
if (typeof object != "object")
|
||||
return false;
|
||||
|
||||
try
|
||||
{ // for window.JSON
|
||||
var obj = String(object);
|
||||
}
|
||||
catch (e)
|
||||
{
|
||||
obj = "[Object]";
|
||||
}
|
||||
obj = template.highlightFilter(util.clip(obj, 150), "\n", !color ? function () "^J" : function () <span highlight="NonText">^J</span>);
|
||||
let string = <><span highlight="Title Object">{obj}</span>::<br/>
</>;
|
||||
|
||||
let keys = [];
|
||||
try // window.content often does not want to be queried with "var i in object"
|
||||
{
|
||||
let hasValue = !("__iterator__" in object);
|
||||
if (modules.isPrototypeOf(object))
|
||||
{
|
||||
object = Iterator(object);
|
||||
hasValue = false;
|
||||
}
|
||||
for (let i in object)
|
||||
{
|
||||
let value = <![CDATA[<no value>]]>;
|
||||
try
|
||||
{
|
||||
value = object[i];
|
||||
}
|
||||
catch (e) {}
|
||||
if (!hasValue)
|
||||
{
|
||||
if (i instanceof Array && i.length == 2)
|
||||
[i, value] = i;
|
||||
else
|
||||
var noVal = true;
|
||||
}
|
||||
|
||||
value = template.highlight(value, true, 150);
|
||||
// FIXME: Inline style.
|
||||
key = <span style="font-weight: bold;">{i}</span>;
|
||||
if (!isNaN(i))
|
||||
i = parseInt(i);
|
||||
else if (/^[A-Z_]+$/.test(i))
|
||||
i = "";
|
||||
keys.push([i, <>{key}{noVal ? "" : <>: {value}</> // Vim /
|
||||
}<br/>
</>]);
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
function compare(a, b)
|
||||
{
|
||||
if (!isNaN(a[0]) && !isNaN(b[0]))
|
||||
return a[0] - b[0];
|
||||
return String.localeCompare(a[0], b[0]);
|
||||
}
|
||||
string += template.map(keys.sort(compare), function (f) f[1]);
|
||||
return color ? string : [s for each (s in string)].join("");
|
||||
},
|
||||
|
||||
range: function range(start, end, reverse)
|
||||
{
|
||||
if (!reverse)
|
||||
{
|
||||
while (start < end)
|
||||
yield start++;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (start > end)
|
||||
yield --start;
|
||||
}
|
||||
},
|
||||
|
||||
interruptableRange: function interruptableRange(start, end, time)
|
||||
{
|
||||
let endTime = Date.now() + time;
|
||||
while (start < end)
|
||||
{
|
||||
if (Date.now() > endTime)
|
||||
{
|
||||
liberator.threadYield(true, true);
|
||||
endTime = Date.now() + time;
|
||||
}
|
||||
yield start++;
|
||||
}
|
||||
},
|
||||
|
||||
// same as Firefox's readFromClipboard function, but needed for apps like Thunderbird
|
||||
readFromClipboard: function readFromClipboard()
|
||||
{
|
||||
let url;
|
||||
|
||||
try
|
||||
{
|
||||
const clipboard = Components.classes['@mozilla.org/widget/clipboard;1']
|
||||
.getService(Components.interfaces.nsIClipboard);
|
||||
const transferable = Components.classes['@mozilla.org/widget/transferable;1']
|
||||
.createInstance(Components.interfaces.nsITransferable);
|
||||
|
||||
transferable.addDataFlavor("text/unicode");
|
||||
|
||||
if (clipboard.supportsSelectionClipboard())
|
||||
clipboard.getData(transferable, clipboard.kSelectionClipboard);
|
||||
else
|
||||
clipboard.getData(transferable, clipboard.kGlobalClipboard);
|
||||
|
||||
let data = {};
|
||||
let dataLen = {};
|
||||
|
||||
transferable.getTransferData("text/unicode", data, dataLen);
|
||||
|
||||
if (data)
|
||||
{
|
||||
data = data.value.QueryInterface(Components.interfaces.nsISupportsString);
|
||||
url = data.data.substring(0, dataLen.value / 2);
|
||||
}
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
return url;
|
||||
},
|
||||
|
||||
// takes a string like 'google bla, www.osnews.com'
|
||||
// and returns an array ['www.google.com/search?q=bla', 'www.osnews.com']
|
||||
stringToURLArray: function stringToURLArray(str)
|
||||
{
|
||||
let urls = str.split(new RegExp("\s*" + options["urlseparator"] + "\s*"));
|
||||
|
||||
return urls.map(function (url) {
|
||||
try
|
||||
{
|
||||
let file = io.getFile(url);
|
||||
if (file.exists() && file.isReadable())
|
||||
return file.path;
|
||||
}
|
||||
catch (e) {}
|
||||
|
||||
// removes spaces from the string if it starts with http:// or something like that
|
||||
if (/^\w+:\/\//.test(url))
|
||||
url = url.replace(/\s+/g, "");
|
||||
|
||||
// strip each 'URL' - makes things simpler later on
|
||||
url = url.replace(/^\s+|\s+$/, "");
|
||||
|
||||
// if the string doesn't look like a valid URL (i.e. contains a space
|
||||
// or does not contain any of: .:/) try opening it with a search engine
|
||||
// or keyword bookmark
|
||||
if (/\s/.test(url) || !/[.:\/]/.test(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 - much
|
||||
// like the comments below ;-)
|
||||
|
||||
// check for a search engine match in the string
|
||||
let searchURL = bookmarks.getSearchURL(url, false);
|
||||
if (searchURL)
|
||||
{
|
||||
return searchURL;
|
||||
}
|
||||
else // no search engine match, search for the whole string in the default engine
|
||||
{
|
||||
searchURL = bookmarks.getSearchURL(url, true);
|
||||
if (searchURL)
|
||||
return searchURL;
|
||||
}
|
||||
}
|
||||
// if we are here let Firefox handle the url and hope it does
|
||||
// something useful with it :)
|
||||
return url;
|
||||
});
|
||||
},
|
||||
|
||||
xmlToDom: function xmlToDom(node, doc, nodes)
|
||||
{
|
||||
XML.prettyPrinting = false;
|
||||
switch (node.nodeKind())
|
||||
{
|
||||
case "text":
|
||||
return doc.createTextNode(node);
|
||||
case "element":
|
||||
let domnode = doc.createElementNS(node.namespace(), node.localName());
|
||||
for each (let attr in node.@*)
|
||||
domnode.setAttributeNS(attr.name() == "highlight" ? NS.uri : attr.namespace(), attr.name(), String(attr));
|
||||
for each (let child in node.*)
|
||||
domnode.appendChild(arguments.callee(child, doc, nodes));
|
||||
if (nodes && node.@key)
|
||||
nodes[node.@key] = domnode;
|
||||
return domnode;
|
||||
}
|
||||
}
|
||||
}; //}}}
|
||||
|
||||
// Struct is really slow, AT LEAST 5 times slower than using structs or simple Objects
|
||||
// main reason is the function ConStructor(), which i couldn't get faster.
|
||||
// Maybe it's a TraceMonkey problem, for now don't use it for anything which must be fast (like bookmarks or history)
|
||||
function Struct()
|
||||
{
|
||||
let self = this instanceof Struct ? this : new Struct();
|
||||
if (!arguments.length)
|
||||
return self;
|
||||
|
||||
let args = Array.slice(arguments);
|
||||
self.__defineGetter__("length", function () args.length);
|
||||
self.__defineGetter__("members", function () args.slice());
|
||||
for (let arg in Iterator(args))
|
||||
{
|
||||
let [i, name] = arg;
|
||||
self.__defineGetter__(name, function () this[i]);
|
||||
self.__defineSetter__(name, function (val) { this[i] = val; });
|
||||
}
|
||||
function ConStructor()
|
||||
{
|
||||
let self = this instanceof arguments.callee ? this : new arguments.callee();
|
||||
//for (let [k, v] in Iterator(Array.slice(arguments))) // That is makes using struct twice as slow as the following code:
|
||||
for (let i = 0; i < arguments.length; i++)
|
||||
{
|
||||
if (arguments[i] != undefined)
|
||||
self[i] = arguments[i];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
ConStructor.prototype = self;
|
||||
ConStructor.defaultValue = function (key, val)
|
||||
{
|
||||
let i = args.indexOf(key);
|
||||
ConStructor.prototype.__defineGetter__(i, function () (this[i] = val.call(this), this[i])); // Kludge for FF 3.0
|
||||
ConStructor.prototype.__defineSetter__(i, function (val) {
|
||||
let value = val;
|
||||
this.__defineGetter__(i, function () value);
|
||||
this.__defineSetter__(i, function (val) { value = val });
|
||||
});
|
||||
};
|
||||
return self.constructor = ConStructor;
|
||||
}
|
||||
|
||||
Struct.prototype = {
|
||||
clone: function clone()
|
||||
{
|
||||
return this.constructor.apply(null, this.slice());
|
||||
},
|
||||
// Iterator over our named members
|
||||
__iterator__: function ()
|
||||
{
|
||||
let self = this;
|
||||
return ([v, self[v]] for ([k, v] in Iterator(self.members)))
|
||||
}
|
||||
}
|
||||
|
||||
// Add no-sideeffect array methods. Can't set new Array() as the prototype or
|
||||
// get length() won't work.
|
||||
for (let [,k] in Iterator(["concat", "every", "filter", "forEach", "indexOf", "join", "lastIndexOf",
|
||||
"map", "reduce", "reduceRight", "reverse", "slice", "some", "sort"]))
|
||||
Struct.prototype[k] = Array.prototype[k];
|
||||
|
||||
// vim: set fdm=marker sw=4 ts=4 et:
|
||||
56
common/make_jar.sh
Normal file
56
common/make_jar.sh
Normal file
@@ -0,0 +1,56 @@
|
||||
#!/bin/dash
|
||||
set -e
|
||||
|
||||
top=$(pwd)
|
||||
jar=$1
|
||||
bases=$2
|
||||
dirs=$3
|
||||
text=$4
|
||||
bin=$5
|
||||
shift 5;
|
||||
files="$@"
|
||||
|
||||
stage="$top/${jar%.*}"
|
||||
mkdir -p $stage
|
||||
|
||||
getfiles () {
|
||||
filter="\.($(echo $1 | tr ' ' '|'))$"; shift
|
||||
find "$@" -not -path '*CVS*' 2>/dev/null | grep -E "$filter" || true
|
||||
}
|
||||
copytext () {
|
||||
sed -e "s,###VERSION###,$VERSION,g" \
|
||||
-e "s,###DATE###,$DATE,g" \
|
||||
<"$1" >"$2"
|
||||
cmp -s "$1" "$2" ||
|
||||
( echo "modified: $1"; diff -u "$1" "$2" | grep '^[-+][^-+]' )
|
||||
}
|
||||
|
||||
for base in $bases
|
||||
do
|
||||
(
|
||||
set -e
|
||||
cd $base
|
||||
[ ${jar##*.} = jar ] && stage="$stage/${base##*/}"
|
||||
for dir in $dirs
|
||||
do
|
||||
for f in $(getfiles "$bin" "$dir")
|
||||
do
|
||||
mkdir -p "$stage/${f%/*}"
|
||||
cp $f "$stage/$f"
|
||||
done
|
||||
for f in $(getfiles "$text" "$dir")
|
||||
do
|
||||
mkdir -p "$stage/${f%/*}"
|
||||
copytext "$f" "$stage/$f"
|
||||
done
|
||||
done
|
||||
for f in $files
|
||||
do
|
||||
[ -f "$f" ] && copytext "$f" "$stage/$f"
|
||||
done
|
||||
)
|
||||
done
|
||||
|
||||
cd $stage; zip -r "$top/$jar" *
|
||||
rm -rf "$stage"
|
||||
|
||||
234
common/modules/storage.jsm
Normal file
234
common/modules/storage.jsm
Normal file
@@ -0,0 +1,234 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
© 2008: Kris Maglione <maglione.k at Gmail>
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
var EXPORTED_SYMBOLS = ["storage"];
|
||||
|
||||
var prefService = Components.classes["@mozilla.org/preferences-service;1"]
|
||||
.getService(Components.interfaces.nsIPrefService)
|
||||
.getBranch("extensions.liberator.datastore.");
|
||||
var json = Components.classes["@mozilla.org/dom/json;1"]
|
||||
.createInstance(Components.interfaces.nsIJSON);
|
||||
|
||||
function getCharPref(name)
|
||||
{
|
||||
try
|
||||
{
|
||||
return prefService.getComplexValue(name, Components.interfaces.nsISupportsString).data;
|
||||
}
|
||||
catch (e) {}
|
||||
}
|
||||
|
||||
function setCharPref(name, value)
|
||||
{
|
||||
var str = Components.classes['@mozilla.org/supports-string;1']
|
||||
.createInstance(Components.interfaces.nsISupportsString);
|
||||
str.data = value;
|
||||
return prefService.setComplexValue(name, Components.interfaces.nsISupportsString, str);
|
||||
}
|
||||
|
||||
function loadPref(name, store, type)
|
||||
{
|
||||
if (store)
|
||||
var pref = getCharPref(name);
|
||||
if (pref)
|
||||
var result = json.decode(pref);
|
||||
if (result instanceof type)
|
||||
return result;
|
||||
}
|
||||
|
||||
function savePref(obj)
|
||||
{
|
||||
if (obj.store)
|
||||
setCharPref(obj.name, obj.serial)
|
||||
}
|
||||
|
||||
var prototype = {
|
||||
fireEvent: function (event, arg) { storage.fireEvent(this.name, event, arg) },
|
||||
save: function () { savePref(this) },
|
||||
};
|
||||
|
||||
function ObjectStore(name, store, data)
|
||||
{
|
||||
var object = data || {};
|
||||
|
||||
this.__defineGetter__("store", function () store);
|
||||
this.__defineGetter__("name", function () name);
|
||||
this.__defineGetter__("serial", function () json.encode(object));
|
||||
|
||||
this.set = function set(key, val)
|
||||
{
|
||||
var defined = key in object;
|
||||
var orig = object[key];
|
||||
object[key] = val;
|
||||
if (!defined)
|
||||
this.fireEvent("add", key);
|
||||
else if (orig != val)
|
||||
this.fireEvent("change", key);
|
||||
};
|
||||
|
||||
this.remove = function remove(key)
|
||||
{
|
||||
var ret = object[key];
|
||||
delete object[key];
|
||||
this.fireEvent("remove", key);
|
||||
return ret;
|
||||
};
|
||||
|
||||
this.get = function get(val) object[val];
|
||||
|
||||
this.clear = function ()
|
||||
{
|
||||
object = {};
|
||||
};
|
||||
|
||||
this.__iterator__ = function () Iterator(object);
|
||||
}
|
||||
ObjectStore.prototype = prototype;
|
||||
|
||||
function ArrayStore(name, store, data)
|
||||
{
|
||||
var array = data || [];
|
||||
|
||||
this.__defineGetter__("store", function () store);
|
||||
this.__defineGetter__("name", function () name);
|
||||
this.__defineGetter__("serial", function () json.encode(array));
|
||||
this.__defineGetter__("length", function () array.length);
|
||||
|
||||
this.set = function set(index, value)
|
||||
{
|
||||
var orig = array[index];
|
||||
array[index] = value;
|
||||
this.fireEvent("change", index);
|
||||
};
|
||||
|
||||
this.push = function push(value)
|
||||
{
|
||||
array.push(value);
|
||||
this.fireEvent("push", array.length);
|
||||
};
|
||||
|
||||
this.pop = function pop(value)
|
||||
{
|
||||
var ret = array.pop();
|
||||
this.fireEvent("pop", array.length);
|
||||
return ret;
|
||||
};
|
||||
|
||||
this.truncate = function truncate(length, fromEnd)
|
||||
{
|
||||
var ret = array.length;
|
||||
if (array.length > length)
|
||||
{
|
||||
if (fromEnd)
|
||||
array.splice(0, array.length - length);
|
||||
array.length = length;
|
||||
this.fireEvent("truncate", length);
|
||||
}
|
||||
return ret;
|
||||
};
|
||||
|
||||
// XXX: Awkward.
|
||||
this.mutate = function mutate(aFuncName)
|
||||
{
|
||||
var funcName = aFuncName;
|
||||
arguments[0] = array;
|
||||
array = Array[funcName].apply(Array, arguments);
|
||||
};
|
||||
|
||||
this.get = function get(index)
|
||||
{
|
||||
return index >= 0 ? array[index] : array[array.length + index];
|
||||
};
|
||||
|
||||
this.__iterator__ = function () Iterator(array);
|
||||
}
|
||||
ArrayStore.prototype = prototype;
|
||||
|
||||
var keys = {};
|
||||
var observers = {};
|
||||
|
||||
var storage = {
|
||||
newObject: function newObject(key, constructor, store, type, reload)
|
||||
{
|
||||
if (!(key in keys) || reload)
|
||||
{
|
||||
if (key in this && !reload)
|
||||
throw Error;
|
||||
keys[key] = new constructor(key, store, loadPref(key, store, type || Object));
|
||||
this.__defineGetter__(key, function () keys[key]);
|
||||
}
|
||||
return keys[key];
|
||||
},
|
||||
|
||||
newMap: function newMap(key, store)
|
||||
{
|
||||
return this.newObject(key, ObjectStore, store);
|
||||
},
|
||||
|
||||
newArray: function newArray(key, store)
|
||||
{
|
||||
return this.newObject(key, ArrayStore, store, Array);
|
||||
},
|
||||
|
||||
addObserver: function addObserver(key, callback)
|
||||
{
|
||||
if (!(key in observers))
|
||||
observers[key] = [];
|
||||
if (observers[key].indexOf(callback) == -1)
|
||||
observers[key].push(callback);
|
||||
},
|
||||
|
||||
removeObserver: function (key, callback)
|
||||
{
|
||||
if (!(key in observers))
|
||||
return;
|
||||
observers[key] = observers[key].filter(function (elem) elem != callback);
|
||||
if (observers[key].length == 0)
|
||||
delete obsevers[key];
|
||||
},
|
||||
|
||||
fireEvent: function fireEvent(key, event, arg)
|
||||
{
|
||||
for each (callback in observers[key])
|
||||
callback(key, event, arg);
|
||||
},
|
||||
|
||||
save: function save(key)
|
||||
{
|
||||
savePref(keys[key]);
|
||||
},
|
||||
|
||||
saveAll: function storeAll()
|
||||
{
|
||||
for each (obj in keys)
|
||||
savePref(obj);
|
||||
},
|
||||
};
|
||||
|
||||
// vim: set fdm=marker sw=4 sts=4 et ft=javascript:
|
||||
11
common/process_manifest.awk
Normal file
11
common/process_manifest.awk
Normal file
@@ -0,0 +1,11 @@
|
||||
{ content = $1 ~ /^(content|skin|locale)$/ }
|
||||
content && $NF ~ /^[a-z]/ { $NF = "/" name "/" $NF }
|
||||
content {
|
||||
sub(/^\.\./, "", $NF);
|
||||
$NF = "jar:chrome/" name ".jar!" $NF
|
||||
}
|
||||
{
|
||||
sub("^\\.\\./liberator/", "", $NF)
|
||||
print
|
||||
}
|
||||
|
||||
BIN
common/skin/icon.png
Normal file
BIN
common/skin/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 524 B |
204
common/skin/liberator.css
Normal file
204
common/skin/liberator.css
Normal file
@@ -0,0 +1,204 @@
|
||||
/***** BEGIN LICENSE BLOCK ***** {{{
|
||||
Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
|
||||
The contents of this file are subject to the Mozilla Public License Version
|
||||
1.1 (the "License"); you may not use this file except in compliance with
|
||||
the License. You may obtain a copy of the License at
|
||||
http://www.mozilla.org/MPL/
|
||||
|
||||
Software distributed under the License is distributed on an "AS IS" basis,
|
||||
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
for the specific language governing rights and limitations under the
|
||||
License.
|
||||
|
||||
(c) 2006-2008: Martin Stubenschrott <stubenschrott@gmx.net>
|
||||
|
||||
Alternatively, the contents of this file may be used under the terms of
|
||||
either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
of those above. If you wish to allow use of your version of this file only
|
||||
under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
use your version of this file under the terms of the MPL, indicate your
|
||||
decision by deleting the provisions above and replace them with the notice
|
||||
and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
the provisions above, a recipient may use your version of this file under
|
||||
the terms of any one of the MPL, the GPL or the LGPL.
|
||||
}}} ***** END LICENSE BLOCK *****/
|
||||
|
||||
@namespace liberator url("http://vimperator.org/namespaces/liberator");
|
||||
|
||||
/* Applied to all content */
|
||||
[liberator|activeframe] {
|
||||
-moz-binding: url(chrome://liberator/content/bindings.xml#frame);
|
||||
}
|
||||
|
||||
[liberator|highlight~=HintImage],
|
||||
[liberator|highlight~=Hint] {
|
||||
z-index: 5000;
|
||||
position: absolute;
|
||||
}
|
||||
[liberator|highlight~=Search] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
/* Applied only to completion buffer and MOW */
|
||||
@-moz-document
|
||||
url-prefix(chrome://liberator/) {
|
||||
|
||||
*:-moz-loading, *:-moz-broken { display: none !important; }
|
||||
|
||||
[liberator|highlight~=Completions] {
|
||||
width: 100%;
|
||||
display: table;
|
||||
}
|
||||
[liberator|highlight~=CompItem],
|
||||
[liberator|highlight~=CompTitle] {
|
||||
display: table-row;
|
||||
}
|
||||
[liberator|highlight~=Completions] > ul {
|
||||
display: table-row;
|
||||
}
|
||||
[liberator|highlight~=CompItem] > *,
|
||||
[liberator|highlight~=CompTitle] > * {
|
||||
-moz-binding: url(chrome://liberator/content/bindings.xml#compitem-td);
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[liberator|highlight~=CompMsg] {
|
||||
height: 1.5em;
|
||||
line-height: 1.5em !important;
|
||||
}
|
||||
|
||||
.td-span {
|
||||
display: inline-block;
|
||||
overflow: visible;
|
||||
width: 0px;
|
||||
height: 1.5em;
|
||||
line-height: 1.5em !important;
|
||||
}
|
||||
.td-strut {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
height: 16px;
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
.extra-info { color: gray; }
|
||||
|
||||
.times-executed, .time-average { color: green; }
|
||||
.time-total { color: red; }
|
||||
|
||||
}
|
||||
|
||||
/* Applied to completion buffer, MOW, browser window */
|
||||
@-moz-document
|
||||
url-prefix(chrome://) {
|
||||
|
||||
#liberator-container {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
#liberator-completions {
|
||||
-moz-user-focus: ignore;
|
||||
overflow: -moz-scrollbars-none !important; /* does not seem to work fully */
|
||||
border-width: 0px !important;
|
||||
/*-moz-appearance: none !important; /* prevent an ugly 3D border */
|
||||
}
|
||||
|
||||
/* fixes the min-height: 22px from firefox */
|
||||
#status-bar, statusbarpanel {
|
||||
-moz-appearance: none !important;
|
||||
min-height: 18px !important;
|
||||
border: none !important;
|
||||
font-weight: bold;
|
||||
font-family: monospace;
|
||||
}
|
||||
#liberator-statusline {
|
||||
font-family: monospace;
|
||||
margin: 0px;
|
||||
}
|
||||
#liberator-statusline > label {
|
||||
padding: 0px 0px 0px 8px;
|
||||
}
|
||||
#liberator-statusline > label:first-child {
|
||||
padding: 0px;
|
||||
}
|
||||
#liberator-statusline-field-url {
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* no longer at the window's bottom right corner */
|
||||
.statusbar-resizerpanel {
|
||||
display: none;
|
||||
}
|
||||
#statusbar-display, #statusbar-progresspanel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#liberator-commandline {
|
||||
padding: 1px;
|
||||
font-family: monospace;
|
||||
/*
|
||||
background-color: white;
|
||||
color: black;
|
||||
*/
|
||||
}
|
||||
#liberator-commandline-prompt {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
background-color: inherit;
|
||||
}
|
||||
#liberator-commandline-command {
|
||||
background-color: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
#liberator-visualbell {
|
||||
border: none;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
max-width: 90% !important;
|
||||
min-width: 10% !important;
|
||||
}
|
||||
|
||||
/* MOW */
|
||||
|
||||
#liberator-completions, #liberator-multiline-output, #liberator-multiline-input {
|
||||
overflow: hidden;
|
||||
background-color: white;
|
||||
color: black;
|
||||
}
|
||||
|
||||
#liberator-completions-content, #liberator-multiline-output-content, #liberator-multiline-input {
|
||||
white-space: pre;
|
||||
font-family: -moz-fixed;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
#liberator-completions-content *, #liberator-multiline-output-content * {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
#liberator-completions-content table, #liberator-multiline-output-content table {
|
||||
white-space: inherit;
|
||||
border-spacing: 0px;
|
||||
}
|
||||
|
||||
#liberator-completions-content td, #liberator-multiline-output-content td,
|
||||
#liberator-completions-content th, #liberator-multiline-output-content th {
|
||||
padding: 0px 2px;
|
||||
}
|
||||
|
||||
/* for muttator's composer */
|
||||
#content-frame, #appcontent {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* vim: set fdm=marker sw=4 ts=4 et: */
|
||||
Reference in New Issue
Block a user