diff --git a/.gitignore b/.gitignore index e72f960a..70f6032d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,31 @@ +## To see if new rules exclude any existing files, run +## +## git ls-files -i --exclude-standard +## +## after modifying this file. + +## Generated by the build process *.xpi */locale/*/*.html */chrome + +## Editor backup and swap files *~ -.*.swp -.*.swo -.swp +.\#* +\#**\# +.*.sw[op] +.sw[op] + +## Generated by Mac filesystem .DS_Store + +## For rejects +*.orig +*.rej +*.ancestor +*.current +*.patched + +## Generated by StGit +patches-* +.stgit-edit.txt diff --git a/HACKING b/HACKING index 85cc55c0..2c92428b 100644 --- a/HACKING +++ b/HACKING @@ -79,7 +79,9 @@ We try to be quite consistent, but of course, that's not always possible. * Prefer // over /* */ comments (exceptions for big comments are usually OK) Right: if (HACK) // TODO: remove hack Wrong: if (HACK) /* TODO: remove hack */ - Documentation comment blocks use /** ... */ + +* Documentation comment blocks use /** ... */ Wrap these lines at 80 + characters. * Only wrap lines if it makes the code obviously clearer. Lines longer than 132 characters should probably be broken up rather than wrapped anyway. @@ -121,6 +123,43 @@ We try to be quite consistent, but of course, that's not always possible. I don't like unnecessary use of 'new', I don't like 'new'. --djk + There is semantic value to using "new." It's good CBSE. --Ted + + There's no semantic value. It's reasonable to infer that any function + named in CamelCase is a constructor. That's the case with all such + internal functions. As far as I'm concerned, 'new' is a hack. Actually, + most of JS is a hack... + --Kris + + What he said. Although, I would have said "a pretty nifty language + with some regrettable design decisions" now that I'm in therapy. + --djk + + There is semantic value: With new you know for SURE it's calling the + constructor of a class, with CamelCase only you just ASSUME you do. + I am all about making code clearer also to new developers. And this + includes getting rid of as many assumptions as possible, by making + things explicit, when they don't hurt readability (and new doesn't + for me). It's not so important, that i'll change all instances to + new immediately, but will probably do so over time, when I see it. + --mst + + JavaScript doesn't have classes... What about all the other + 'constructor' functions such as Commands? And I think it does hurt + readability because it's pointless. + Anyway, if it's important enough to change it should all be + changed at once. I've removed most of these sorts of + inconsistencies and I wouldn't like to see them reintroduced. + --djk + + Actually, you're not sure of anything. You can call new (function (a) + a.substr(2)), and you don't get a new object. The only difference is + that it's called with 'this' set. Given that it's uncouth to name a + non-constructor function in CamelCase, and that most internal + constructors don't require new (and some, like String, break when + you use it), it just seems superfluous and distracting. + --Kris + == Testing/Optimization == TODO: Add some information here about testing/validation/etc. @@ -142,7 +181,16 @@ TODO: Document the existence of remote branches and discuss when and how countless git walkthroughs, FAQs, tips pages (not to mention 'git help') that I don't see the need to duplicate them here. As for branches, 'git branch' should be sufficient, and, if not, there's - a list on gitweb. - --Kris + a list on gitweb. --Kris -# vim: set fdm=marker sw=4 ts=4 et ai: + I wasn't trying to say that git was a problem (though other DVCS + have more accessible help systems; except for very complex + projects, I think Mercurial is a much more comfortable DVCS to + learn, use, and navigate). I was saying that it might be nice if + the remote branches (and related polices) were documented. Yes, + anyone can do a "git branch -r", but seeing that a branch exists + is not the same as understanding why it's there. --Ted + + Sure, I agree. --djk + +// vim: set ft=asciidoc fdm=marker sw=4 ts=4 et ai: diff --git a/License.txt b/License.txt index cd24cb86..7efdb050 100644 --- a/License.txt +++ b/License.txt @@ -10,7 +10,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 diff --git a/Makefile b/Makefile index ebdc570e..2d0618d6 100644 --- a/Makefile +++ b/Makefile @@ -8,4 +8,3 @@ $(TARGETS:%=\%.%): $(MAKE) -C $* $(@:$*.%=%) $(TARGETS): %: $(DIRS:%=%.%) - diff --git a/README.E4X b/README.E4X index 0aea4d53..5e0b8a93 100644 --- a/README.E4X +++ b/README.E4X @@ -95,7 +95,7 @@ usage you'll see herein: // Adding a quux attribute to the root -> xml.@quux = "foo" +> xml.@quux = "foo" foo > xml diff --git a/common/Makefile.common b/common/Makefile.common index 8bb4edaf..38606885 100644 --- a/common/Makefile.common +++ b/common/Makefile.common @@ -5,8 +5,8 @@ OS = $(shell uname -s) BUILD_DATE = $(shell date "+%Y/%m/%d %H:%M:%S") BASE = $(TOP)/../common -DOC_SRC_FILES = $(wildcard locale/*/*.txt) -DOC_FILES = ${DOC_SRC_FILES:%.txt=%.html} +DOC_SRC_FILES = $(wildcard locale/*/*.txt) $(wildcard locale/*/*.t2t) +LOCALES = $(wildcard locale/*) MAKE_JAR = VERSION="$(VERSION)" DATE="$(BUILD_DATE)" sh $(BASE)/make_jar.sh @@ -63,8 +63,6 @@ info: @echo -e "jar files $(shell echo ${JAR_FILES} | sed 's/ /\\n /g' )" @echo "xpi files ${XPI_FILES}" -.PHONY: check-asciidoc -doc: check-asciidoc ${DOC_FILES} xpi: ${XPI} jar: ${JAR} @@ -80,11 +78,10 @@ ${RDF}: ${RDF_IN} Makefile clean: @echo "Cleanup..." rm -f ${JAR} ${XPI} - find . -name '*~' -exec rm -f {} \; distclean: clean @echo "More cleanup..." - rm -f ${DOC_FILES} + @set -e; for locale in $(LOCALES); do $(MAKE) -C clean; doc; done rm -rf ${BUILD_DIR} #### xpi @@ -103,21 +100,7 @@ $(JAR): doc $(MAKE_JAR) "$(JAR)" "$(JAR_BASES)" "$(JAR_DIRS)" "$(JAR_TEXTS)" "$(JAR_BINS)" "$(JAR_FILES)" @echo "SUCCESS: $@" -#### doc - -check-asciidoc: - @asciidoc --version | awk '{ exit $$2 !~ /^8\.2\./ }' || \ - echo >&2 "Warning: asciidoc versions other than 8.2.x are unsupported" - -${DOC_FILES}: %.html: %.txt $(BASE)/Makefile.common locale/en-US/asciidoc.conf - @echo "DOC $@" - ${ASCIIDOC} --unsafe -a linkcss -a quirks! -a doctitle="$(shell basename $@)" -o $@ $< - -T2T = $(wildcard locale/*/*.t2t) -t2t: $(T2T:%.t2t=%.xhtml) -$(T2T:%.t2t=%.xhtml): locale/en-US/config.t2t - -%.xhtml: %.t2t - @echo "T2T $@" - txt2tags --quiet $< +#### doc (see Makefile.doc) +doc: + @set -e; for locale in $(LOCALES); do $(MAKE) -C $$locale doc; done diff --git a/common/Makefile.doc b/common/Makefile.doc new file mode 100644 index 00000000..59898e8a --- /dev/null +++ b/common/Makefile.doc @@ -0,0 +1,75 @@ +# Symlink me to locale/*/Makefile + +#### configuration + +BASE = ../../../common + +THIS_LOCALE = $(notdir $(shell pwd)) + +ADC_SRC_FILES = $(wildcard *.txt) +ADC_FILES = $(ADC_SRC_FILES:%.txt=%.html) +ADC_DEPS = $(wildcard asciidoc.conf) + +T2T_SRC_FILES = $(wildcard *.t2t) +T2T_FILES = $(T2T_SRC_FILES:%.t2t=%.xhtml) +T2T_DEPS = $(wildcard config.t2t) + +DOC_FILES = $(ADC_FILES) $(T2T_FILES) + +ASCIIDOC = asciidoc +TXT2TAGS = txt2tags +AWK = awk + +.SILENT: + +#### rules + +.PHONY: all help doc asciidoc check-asciidoc clean distclean +all: doc + +doc: asciidoc t2t + +help: + @echo "${NAME} ${VERSION} build" + @echo + @echo " make help - display this help" + @echo " make doc - build doc files" + @echo " make asciidoc - build asciidoc'd files only" + @echo " make t2t - build txt2tags'd files only" + @echo " make clean - clean up" + @echo " make distclean - clean up more" + +clean: + @echo "Cleanup..." + +distclean: clean + @echo "More cleanup..." + rm -f $(DOC_FILES) + +#### Makes single-file makes easier to type + +%: %.html %.t2t %.xhtml %.t2t ; + +%: %.html %.txt ; + +%: %.xhtml %.t2t ; + +#### asciidoc + +asciidoc: check-asciidoc $(ADC_FILES) + +check-asciidoc: + @$(ASCIIDOC) --version | $(AWK) '{ exit $$2 !~ /^8\.2\./ }' || \ + echo >&2 "Warning: asciidoc versions other than 8.2.x are unsupported" + +$(ADC_FILES): %.html: %.txt $(BASE)/Makefile.doc $(ADC_DEPS) + @echo "DOC locale/$(THIS_LOCALE)/$@" + $(ASCIIDOC) --unsafe -a linkcss -a quirks! -a doctitle="$(shell basename $@)" -o $@ $< + +#### txt2tags + +t2t: $(T2T_FILES) + +$(T2T_FILES): %.xhtml: %.t2t $(BASE)/Makefile.doc $(T2T_DEPS) + @echo "T2T locale/$(THIS_LOCALE)/$@" + txt2tags --quiet $< diff --git a/common/content/buffer.js b/common/content/buffer.js index 76bfc36d..6341243c 100644 --- a/common/content/buffer.js +++ b/common/content/buffer.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -157,7 +157,7 @@ function Buffer() //{{{ options.add(["pageinfo", "pa"], "Desired info on :pa[geinfo]", "charlist", "gfm", { - completer: function (filter) [[k, v[1]] for ([k, v] in Iterator(pageInfo))], + completer: function (context) [[k, v[1]] for ([k, v] in Iterator(pageInfo))], validator: Option.validateCompleter }); @@ -170,7 +170,7 @@ function Buffer() //{{{ "Show the destination of the link under the cursor in the status bar", "number", 1, { - completer: function (filter) [ + completer: function (context) [ ["0", "Don't show link destination"], ["1", "Show the link in the status line"], ["2", "Show the link in the command line"] @@ -356,8 +356,7 @@ function Buffer() //{{{ function () { liberator.open(util.readFromClipboard(), - /\bpaste\b/.test(options["activate"]) ? - liberator.NEW_BACKGROUND_TAB : liberator.NEW_TAB); + liberator[options.get("activate").has("paste") ? "NEW_BACKGROUND_TAB" : "NEW_TAB"]); }); mappings.add(myModes, ["p", ""], @@ -849,7 +848,7 @@ function Buffer() //{{{ }, /** - * @property {Object} The last focused input field in the buffer. Used + * @property {Node} The last focused input field in the buffer. Used * by the "gi" key binding. */ get lastInputField() @@ -987,6 +986,7 @@ function Buffer() //{{{ selController.wordMove(false, false); selController.wordMove(true, true); selController.setCaretEnabled(caretmode); + return String.match(selection, /\w*/)[0]; } let range = selection.getRangeAt(0); if (util.computedStyle(range.startContainer).whiteSpace == "pre" @@ -1139,15 +1139,18 @@ function Buffer() //{{{ elem.focus(); let evt = doc.createEvent("MouseEvents"); - ["mousedown", "mouseup", "click"].forEach(function (event) { - evt.initMouseEvent(event, true, true, view, 1, offsetX, offsetY, 0, 0, - ctrlKey, /*altKey*/0, shiftKey, /*metaKey*/ ctrlKey, 0, null); - elem.dispatchEvent(evt); + options.withContext(function () { + options.setPref("browser.tabs.loadInBackground", true); + ["mousedown", "mouseup", "click"].forEach(function (event) { + evt.initMouseEvent(event, true, true, view, 1, offsetX, offsetY, 0, 0, + ctrlKey, /*altKey*/0, shiftKey, /*metaKey*/ ctrlKey, 0, null); + elem.dispatchEvent(evt); + }); }); }, /** - * @property {Object} The current document's selection controller. + * @property {nsISelectionController} The current document's selection controller. */ get selectionController() getBrowser().docShell .QueryInterface(Ci.nsIInterfaceRequestor) @@ -1157,8 +1160,8 @@ function Buffer() //{{{ /** * Saves a page link to disk. * - * @param {Object} elem The page link to save. - * @param {boolean} skipPrompt Whether to open the "Save Link As..." dialog + * @param {HTMLAnchorElement} elem The page link to save. + * @param {boolean} skipPrompt Whether to open the "Save Link As..." dialog. */ saveLink: function (elem, skipPrompt) { @@ -1381,7 +1384,7 @@ function Buffer() //{{{ /** * Displays information about the specified element. * - * @param {Object} elem + * @param {Node} elem */ showElementInfo: function (elem) { @@ -1662,7 +1665,10 @@ function Marks() //{{{ marks.remove(args, special); }, - { bang: true }); + { + bang: true, + completer: function (context) completion.mark(context) + }); commands.add(["ma[rk]"], "Mark current location within the web page", @@ -1707,6 +1713,8 @@ function Marks() //{{{ return { + get all() getSortedMarks(), + /** * Add a named mark for the current buffer, at its current position. * If mark matches [A-Z], it's considered a URL mark, and will jump to diff --git a/common/content/commands.js b/common/content/commands.js index e8a0a50f..d78cf393 100644 --- a/common/content/commands.js +++ b/common/content/commands.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -150,7 +150,7 @@ Command.prototype = { * @param {number} count @deprecated Whether this command was * executed with a leading count. * @param modifiers Any modifiers to be passed to - * {@link action} + * {@link #action}. */ execute: function (args, bang, count, modifiers) { @@ -233,15 +233,6 @@ function Commands() //{{{ 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 QUOTE_STYLE = "vimperator"; const quoteMap = { @@ -264,16 +255,24 @@ function Commands() //{{{ "": quote("", "\\\\ ") }; + function parseBool(arg) + { + if (/^(true|1|on)$/i.test(arg)) + return true; + if (/^(false|0|off)$/i.test(arg)) + return false; + return NaN; + } 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)); + ArgType("no arg", function (arg) !arg || null), + ArgType("boolean", parseBool), + ArgType("string", function (val) val), + ArgType("int", parseInt), + ArgType("float", parseFloat), + ArgType("list", function (arg) arg && arg.split(/\s*,\s*/)) + ]; function addCommand(command, isUserCommand, replace) { @@ -358,7 +357,7 @@ function Commands() //{{{ let str = args.literalArg; if (str) - res.push(/\n/.test(str) ? "< +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -159,7 +159,7 @@ function CompletionContext(editor, name, offset) //{{{ * contain inactive contexts. For active contexts, see * {@link #contextList}. */ - this.contexts = { "/": this }; + this.contexts = { "": this }; /** * @property {Object} A mapping of keys, for {@link #getKey}. Given * { key: value }, getKey(item, key) will return values as such: @@ -285,7 +285,7 @@ CompletionContext.prototype = { set completions(items) { // Accept a generator - if (!(items instanceof Array)) + if (!("length" in items)) items = [x for (x in Iterator(items))]; delete this.cache.filtered; delete this.cache.filter; @@ -1388,7 +1388,6 @@ function Completion() //{{{ { process.call(this, item, text) } ]; - context.completions = util.map(tabs.browsers, function ([i, browser]) { let indicator = " "; if (i == tabs.index()) @@ -1608,7 +1607,23 @@ function Completion() //{{{ context.completions = [item for (item in events.getMacros())]; }, - menuItem: function menuItem(filter) commands.get("emenu").completer(filter), // XXX + mark: function mark(context) + { + function percent(i) Math.round(i * 100); + + // FIXME: Line/Column doesn't make sense with % + context.title = ["Mark", "Line Column File"]; + context.keys.description = function ([,m]) percent(m.position.y) + "% " + percent(m.position.x) + "% " + m.location; + context.completions = marks.all; + }, + + menuItem: function menuItem(context) + { + context.title = ["Menu Path", "Label"]; + context.anchored = false; + context.keys = { text: "fullMenuPath", description: "label" }; + context.completions = liberator.menuItems; + }, option: function option(context, scope) { diff --git a/common/content/editor.js b/common/content/editor.js index 237d0bcc..8f2f830f 100644 --- a/common/content/editor.js +++ b/common/content/editor.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 diff --git a/common/content/events.js b/common/content/events.js index 685754fe..5478322a 100644 --- a/common/content/events.js +++ b/common/content/events.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -189,8 +189,8 @@ function AutoCommands() //{{{ * * @param {Array} events The array of event names for which this * autocommand should be executed. - * @param {string} regex The URL pattern to match against the buffer URL - * @param {string} cmd The Ex command to run + * @param {string} regex The URL pattern to match against the buffer URL. + * @param {string} cmd The Ex command to run. */ add: function (events, regex, cmd) { @@ -630,7 +630,7 @@ function Events() //{{{ // load all macros inside ~/.vimperator/macros/ // setTimeout needed since io. is loaded after events. - setTimeout (function () { + setTimeout(function () { try { let dirs = io.getRuntimeDirectories("macros"); @@ -769,8 +769,11 @@ function Events() //{{{ // removeEventListeners() to avoid mem leaks liberator.dump("TODO: remove all eventlisteners"); - if (typeof getBrowser != "undefined") + try + { getBrowser().removeProgressListener(this.progressListener); + } + catch (e) {} window.removeEventListener("popupshown", enterPopupMode, true); window.removeEventListener("popuphidden", exitPopupMode, true); @@ -879,7 +882,7 @@ function Events() //{{{ * Pushes keys into the event queue from liberator it is similar to * Vim's feedkeys() method, but cannot cope with 2 partially-fed * strings, you have to feed one parsable string. - * + * * @param {string} keys A string like "2" to pass if you want "<" * to be taken literally, prepend it with a "\\". * @param {boolean} noremap Allow recursive mappings. @@ -915,7 +918,7 @@ function Events() //{{{ //if (keys[i] == "\\") // FIXME: support the escape key if (keys[i] == "<" && !escapeKey) // start a complex key { - let [match, modifier, keyname] = keys.substr(i).match(/<([CSMA]-)*(.+?)>/i) || []; + let [match, modifier, keyname] = keys.substr(i).match(/<((?:[CSMA]-)*)(.+?)>/i) || []; if (keyname) { if (modifier) // check for modifiers diff --git a/common/content/find.js b/common/content/find.js index 0bb125cb..fe65eb17 100644 --- a/common/content/find.js +++ b/common/content/find.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -123,8 +123,8 @@ function Search() //{{{ * * 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. + * Portions created by the Initial Developer are Copyright (c) 2003 + * by the Initial Developer. All Rights Reserved. * * Contributor(s): * Blake Ross (Original Author) @@ -436,7 +436,6 @@ function Search() //{{{ // escape while typing a search searchCanceled: function () { - this.clear(); // TODO: code to reposition the document to the place before search started }, diff --git a/common/content/help.css b/common/content/help.css index e35994ae..67bdba86 100644 --- a/common/content/help.css +++ b/common/content/help.css @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 diff --git a/common/content/hints.js b/common/content/hints.js index cdc90dfb..343cc10f 100644 --- a/common/content/hints.js +++ b/common/content/hints.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -73,10 +73,10 @@ function Hints() //{{{ t: Mode("Follow hint in a new tab", function (elem) buffer.followLink(elem, liberator.NEW_TAB)), b: Mode("Follow hint in a background tab", function (elem) buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB)), w: Mode("Follow hint in a new window", function (elem) buffer.followLink(elem, liberator.NEW_WINDOW), extended), - F: Mode("Follow hint sequence in tabs", hintSequenceElement), - O: Mode("Preselect hint in an :open query", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)), - T: Mode("Preselect hint in a :tabopen query", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)), - W: Mode("Preselect hint in a :winopen query", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)), + F: Mode("Open multiple hints in tabs", hintAction_F), + O: Mode(":open URL based on hint location", function (elem, loc) commandline.open(":", "open " + loc, modes.EX)), + T: Mode(":tabopen URL based on hint location", function (elem, loc) commandline.open(":", "tabopen " + loc, modes.EX)), + W: Mode(":winopen URL based on hint location", function (elem, loc) commandline.open(":", "winopen " + loc, modes.EX)), v: Mode("View hint source", function (elem, loc) buffer.viewSource(loc, false), extended), V: Mode("View hint source in external editor", function (elem, loc) buffer.viewSource(loc, true), extended), y: Mode("Yank hint location", function (elem, loc) util.copyToClipboard(loc, true)), @@ -84,18 +84,11 @@ function Hints() //{{{ }; // Used to open multiple hints - function hintSequenceElement(elem) + function hintAction_F(elem) { - // Want to always open sequence hints in background - // (remember: NEW_BACKGROUND_TAB and NEW_TAB semantics assume - // that loadInBackground=true) - if (options.getPref("browser.tabs.loadInBackground")) - buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB); - else - buffer.followLink(elem, liberator.NEW_TAB); + buffer.followLink(elem, liberator.NEW_BACKGROUND_TAB); - // Move to next element in sequence - // TODO: Maybe we find a *simple* way to keep the hints displayed rather than + // TODO: Maybe we find a *simple* way to keep the hints displayed rather than // showing them again, or is this short flash actually needed as a "usability // feature"? --mst hints.show("F"); @@ -580,10 +573,12 @@ function Hints() //{{{ "How links are matched", "string", "contains", { - completer: function (filter) - { - return [[m, ""] for each (m in ["contains", "wordstartswith", "firstletters", "custom"])]; - }, + completer: function (context) [ + ["contains", "The typed characters are split on whitespace. The resulting groups must all appear in the hint."], + ["wordstartswith", "The typed characters are split on whitespace. The resulting groups must all match the beginings of words, in order."], + ["firstletters", "Behaves like wordstartswith, but all groups much match a sequence of words."], + ["custom", "Delegate to a custom function: liberator.plugins.customHintMatcher(hintString)"], + ], validator: Option.validateCompleter }); @@ -599,9 +594,19 @@ function Hints() //{{{ "Start QuickHint mode", function () { hints.show("o"); }); + // At the moment, "F" calls + // buffer.followLink(clicked_element, DO_WHAT_FIREFOX_DOES_WITH_CNTRL_CLICK) + // It is not clear that it shouldn't be: + // buffer.followLink(clicked_element, !DO_WHAT_FIREFOX_DOES_WITH_CNTRL_CLICK) + // In fact, it might be nice if there was a "dual" to F (like H and + // gH, except that gF is already taken). --tpp + // + // Likewise, it might be nice to have a liberator.NEW_FOREGROUND_TAB + // and then make liberator.NEW_TAB always do what a Cntrl+Click + // does. --tpp mappings.add(myModes, ["F"], "Start QuickHint mode, but open link in a new tab", - function () { hints.show("t"); }); + function () { hints.show(options.getPref("browser.tabs.loadInBackground") ? "b" : "t"); }); mappings.add(myModes, [";"], "Start an extended hint mode", diff --git a/common/content/io.js b/common/content/io.js index c98ed57a..96fc077c 100644 --- a/common/content/io.js +++ b/common/content/io.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott Code based on venkman Alternatively, the contents of this file may be used under the terms of @@ -40,7 +40,7 @@ function Script(file) return self; } plugins.contexts[file.path] = this; - this.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/, function (_0, _1) _1.toUpperCase()); + this.NAME = file.leafName.replace(/\..*/, "").replace(/-([a-z])/g, function (m, n1) n1.toUpperCase()); this.PATH = file.path; this.__context__ = this; @@ -48,13 +48,14 @@ function Script(file) for (let [,dir] in Iterator(io.getRuntimeDirectories("plugin"))) { if (dir.contains(file, false)) - plugins[name] = this.NAME; + plugins[this.NAME] = this; } } Script.prototype = plugins; // TODO: why are we passing around strings rather than file objects? /** + * Provides a basic interface to common system I/O operations. * @instance io */ function IO() //{{{ @@ -388,21 +389,94 @@ function IO() //{{{ const self = { + /** + * @property {number} Open for reading only. + * @final + */ MODE_RDONLY: 0x01, + + /** + * @property {number} Open for writing only. + * @final + */ MODE_WRONLY: 0x02, + + /** + * @property {number} Open for reading and writing. + * @final + */ MODE_RDWR: 0x04, + + /** + * @property {number} If the file does not exist, the file is created. + * If the file exists, this flag has no effect. + * @final + */ MODE_CREATE: 0x08, + + /** + * @property {number} The file pointer is set to the end of the file + * prior to each write. + * @final + */ MODE_APPEND: 0x10, + + /** + * @property {number} If the file exists, its length is truncated to 0. + * @final + */ MODE_TRUNCATE: 0x20, + + /** + * @property {number} If set, each write will wait for both the file + * data and file status to be physically updated. + * @final + */ MODE_SYNC: 0x40, + + /** + * @property {number} With MODE_CREATE, if the file does not exist, the + * file is created. If the file already exists, no action and NULL + * is returned. + * @final + */ MODE_EXCL: 0x80, + /** + * @property {Object} The current file sourcing context. As a file is + * being sourced the 'file' and 'line' properties of this context + * object are updated appropriately. + */ sourcing: null, + /** + * Expands "~" and environment variables in path. + * + * "~" is expanded to to the value of $HOME. On Windows if this is not + * set then the following are tried in order: + * $USERPROFILE + * ${HOMDRIVE}$HOMEPATH + * + * The variable notation is $VAR (terminated by a non-word character) + * or ${VAR}. %VAR% is also supported on Windows. + * + * @param {string} path The unexpanded path string. + * @param {boolean} relative Whether the path is relative or absolute. + * @returns {string} + */ expandPath: IO.expandPath, // 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 + /** + * Returns the current working directory. + * + * It's not possible to change the real CWD of Firefox so this state is + * maintained internally. External commands run via {@link #system} are + * executed in this directory. + * + * @returns {nsIFile} + */ getCurrentDirectory: function () { let dir = self.getFile(cwd.path); @@ -415,17 +489,23 @@ function IO() //{{{ return processDir; }, - setCurrentDirectory: function (newdir) + /** + * Sets the current working directory. + * + * @param {string} newDir The new CWD. This may be a relative or + * absolute path and is expanded by {@link #expandPath}. + */ + setCurrentDirectory: function (newDir) { - newdir = newdir || "~"; + newDir = newDir || "~"; - if (newdir == "-") + if (newDir == "-") { [cwd, oldcwd] = [oldcwd, this.getCurrentDirectory()]; } else { - let dir = self.getFile(newdir); + let dir = self.getFile(newDir); if (!dir.exists() || !dir.isDirectory()) { @@ -439,16 +519,30 @@ function IO() //{{{ return self.getCurrentDirectory(); }, - getRuntimeDirectories: function (specialDirectory) + /** + * Returns all directories named name in 'runtimepath'. + * + * @param {string} name + * @returns {nsIFile[]) + */ + getRuntimeDirectories: function (name) { let dirs = getPathsFromPathList(options["runtimepath"]); - dirs = dirs.map(function (dir) joinPaths(dir, specialDirectory)) + dirs = dirs.map(function (dir) joinPaths(dir, name)) .filter(function (dir) dir.exists() && dir.isDirectory() && dir.isReadable()); return dirs; }, + /** + * Returns the first user RC file found in dir. + * + * @param {string} dir The directory to search. + * @param {boolean} always When true, return a path whether + * the file exists or not. + * @default $HOME. + */ getRCFile: function (dir, always) { dir = dir || "~"; @@ -471,6 +565,14 @@ function IO() //{{{ // 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 + /** + * Returns an nsIFile object for path, which is expanded + * according to {@link #expandPath}. + * + * @param {string} path The path used to create the file object. + * @param {boolean} noCheckPWD Whether to allow a relative path. + * @returns {nsIFile} + */ getFile: function (path, noCheckPWD) { let file = services.create("file"); @@ -494,7 +596,11 @@ function IO() //{{{ }, // TODO: make secure - // returns a nsILocalFile or null if it could not be created + /** + * Creates a temporary file. + * + * @returns {nsIFile} + */ createTempFile: function () { let tmpName = EXTENSION_NAME + ".tmp"; @@ -525,17 +631,25 @@ function IO() //{{{ }, - // file is either a full pathname or an instance of file instanceof nsILocalFile - readDirectory: function (file, sort) + /** + * Returns the list of files in dir. + * + * @param {nsIFile|string} dir The directory to read, either a full + * pathname or an instance of nsIFile. + * @param {boolean} sort Whether to sort the returned directory + * entries. + * @returns {nsIFile[]} + */ + readDirectory: function (dir, sort) { - if (typeof file == "string") - file = self.getFile(file); - else if (!(file instanceof Ci.nsILocalFile)) + if (typeof dir == "string") + dir = self.getFile(dir); + else if (!(dir instanceof Ci.nsILocalFile)) throw Cr.NS_ERROR_INVALID_ARG; // FIXME: does not work as expected, just shows undefined: undefined - if (file.isDirectory()) + if (dir.isDirectory()) { - let entries = file.directoryEntries; + let entries = dir.directoryEntries; let array = []; while (entries.hasMoreElements()) { @@ -551,8 +665,13 @@ function IO() //{{{ // Yes --djk }, - // file is either a full pathname or an instance of file instanceof nsILocalFile - // reads a file in "text" mode and returns the string + /** + * Reads a file in "text" mode and returns the content as a string. + * + * @param {nsIFile|string} file The file to read, either a full + * pathname or an instance of nsIFile. + * @returns {string} + */ readFile: function (file) { let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream); @@ -578,9 +697,30 @@ function IO() //{{{ 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 + /** + * Writes the string buf to a file. + * + * @param {nsIFile|string} file The file to write, either a full + * pathname or an instance of nsIFile. + * @param {string} buf The file content. + * @param {string|number} mode The file access mode, a bitwise OR of + * the following flags: + * {@link #MODE_RDONLY}: 0x01 + * {@link #MODE_WRONLY}: 0x02 + * {@link #MODE_RDWR}: 0x04 + * {@link #MODE_CREATE}: 0x08 + * {@link #MODE_APPEND}: 0x10 + * {@link #MODE_TRUNCATE}: 0x20 + * {@link #MODE_SYNC}: 0x40 + * Alternatively, the following abbreviations may be used: + * ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE} + * ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND} + * @default ">" + * @param {number} perms The file mode bits of the created file. This + * is only used when creating a new file and does not change + * permissions if the file exists. + * @default 0644 + */ writeFile: function (file, buf, mode, perms) { let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream); @@ -608,6 +748,13 @@ function IO() //{{{ ofstream.close(); }, + /** + * Runs an external program. + * + * @param {string} program The program to run. + * @param {string[]} args An array of arguments to pass to program. + * @param {boolean} blocking Whether to wait until the process terminates. + */ run: function (program, args, blocking) { args = args || []; @@ -666,47 +813,17 @@ lookup: 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); - - function escape(str) '"' + str.replace(/[\\"$]/g, "\\$&") + '"'; - - return this.withTempFiles(function (stdin, stdout, stderr, cmd) { - - if (input) - this.writeFile(stdin, input); - - if (WINDOWS) - { - command = "cd /D " + cwd.path + " && " + command + " > " + stdout.path + " 2> " + stderr.path + " < " + stdin.path; - var res = this.run(options["shell"], options["shellcmdflag"].split(/\s+/).concat(command), true); - } - else - { - - this.writeFile(cmd, "cd " + escape(cwd.path) + "\n" + - ["exec", ">" + escape(stdout.path), "2>" + escape(stderr.path), "<" + escape(stdin.path), - escape(options["shell"]), options["shellcmdflag"], escape(command)].join(" ")); - res = this.run("/bin/sh", ["-e", cmd.path], true); - } - - if (res > 0) // FIXME: Is this really right? Shouldn't we always show both? - var output = self.readFile(stderr) + "\nshell returned " + res; - else - output = self.readFile(stdout); - - // 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? + /** + * Sources files found in 'runtimepath'. For each relative path in + * paths each directory in 'runtimepath' is searched and if a + * matching file is found it is sourced. Only the first file found (per + * specified path) is sourced unless all is specified, then + * all found files are sourced. + * + * @param {string[]} paths An array of relative paths to source. + * @param {boolean} all Whether all found files should be sourced. + */ sourceFromRuntimePath: function (paths, all) { let dirs = getPathsFromPathList(options["runtimepath"]); @@ -741,8 +858,12 @@ lookup: return found; }, - // files which end in .js are sourced as pure JavaScript files, - // no need (actually forbidden) to add: js <filename. + * + * @param {string} filename The name of the file to source. + * @param {boolean} silent Whether errors should be reported. + */ source: function (filename, silent) { let wasSourcing = self.sourcing; @@ -772,7 +893,7 @@ lookup: liberator.echomsg("sourcing " + filename.quote(), 2); let str = self.readFile(file); - let uri = ioService.newFileURI(file); + let uri = services.get("io").newFileURI(file); // handle pure JavaScript files specially if (/\.js$/.test(filename)) @@ -896,11 +1017,68 @@ lookup: } }, + // TODO: when https://bugzilla.mozilla.org/show_bug.cgi?id=68702 is + // fixed is fixed, should use that instead of a tmpfile + /** + * Runs command in a subshell and returns the output in a + * string. The shell used is that specified by the 'shell' option. + * + * @param {string} command The command to run. + * @param {string} input Any input to be provided to the command on stdin. + * @returns {string} + */ + system: function (command, input) + { + liberator.echomsg("Calling shell to execute: " + command, 4); + + function escape(str) '"' + str.replace(/[\\"$]/g, "\\$&") + '"'; + + return this.withTempFiles(function (stdin, stdout, cmd) { + if (input) + this.writeFile(stdin, input); + + // TODO: implement 'shellredir' + if (WINDOWS) + { + command = "cd /D " + cwd.path + " && " + command + " > " + stdout.path + " 2>&1" + " < " + stdin.path; + var res = this.run(options["shell"], options["shellcmdflag"].split(/\s+/).concat(command), true); + } + else + { + this.writeFile(cmd, "cd " + escape(cwd.path) + "\n" + + ["exec", ">" + escape(stdout.path), "2>&1", "<" + escape(stdin.path), + escape(options["shell"]), options["shellcmdflag"], escape(command)].join(" ")); + res = this.run("/bin/sh", ["-e", cmd.path], true); + } + + let output = self.readFile(stdout); + if (res > 0) + output += "\nshell returned " + res; + // if there is only one \n at the end, chop it off + else if (output && output.indexOf("\n") == output.length - 1) + output = output.substr(0, output.length - 1); + + return output; + }) || ""; + }, + + /** + * Creates a temporary file context for executing external commands. + * fn is called with a temp file, created with + * {@link #createTempFile}, for each explicit argument. Ensures that + * all files are removed when fn returns. + * + * @param {function} fn The function to execute. + * @param {Object} self The 'this' object used when executing fn. + * @return {boolean} false if temp files couldn't be created, + * otherwise, the return value of fn. + */ withTempFiles: function (fn, self) { let args = util.map(util.range(0, fn.length), this.createTempFile); if (!args.every(util.identity)) return false; + try { return fn.apply(self || this, args); @@ -922,6 +1100,10 @@ IO.PATH_SEP = (function () { return file.path[0]; })(); +/** + * @property {string} The value of the $VIMPERATOR_RUNTIME environment + * variable. + */ IO.__defineGetter__("runtimePath", function () { const rtpvar = config.name.toUpperCase() + "_RUNTIME"; let rtp = services.get("environment").get(rtpvar); diff --git a/common/content/liberator.js b/common/content/liberator.js index 864472f0..9dd6fa62 100644 --- a/common/content/liberator.js +++ b/common/content/liberator.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -28,7 +28,7 @@ the terms of any one of the MPL, the GPL or the LGPL. /** @scope modules */ -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/XPCOMUtils.jsm", modules); const plugins = {}; plugins.__proto__ = modules; @@ -153,7 +153,7 @@ const liberator = (function () //{{{ group.setter(value); return value; }, - completer: function (filter) + completer: function (context) { let opts = [v.opts for ([k, v] in Iterator(groups))]; opts = opts.map(function (opt) [[k, v[0]] for ([k, v] in Iterator(opt))]); @@ -206,6 +206,34 @@ const liberator = (function () //{{{ function () { liberator.quit(true); }); }); + // TODO: move this + function getMenuItems() + { + function addChildren(node, parent) + { + for (let [,item] in Iterator(node.childNodes)) + { + if (item.childNodes.length == 0 && item.localName == "menuitem" + && !/rdf:http:/.test(item.label)) // FIXME + { + item.fullMenuPath = parent + item.label; + items.push(item); + } + else + { + let path = parent; + if (item.localName == "menu") + path += item.label + "."; + addChildren(item, path); + } + } + } + + let items = []; + addChildren(document.getElementById(config.guioptions["m"][1]), ""); + return items; + } + registerObserver("load_commands", function () { commands.add(["addo[ns]"], @@ -256,34 +284,6 @@ const liberator = (function () //{{{ completer: function (context, args) completion.dialog(context) }); - // TODO: move this - function getMenuItems() - { - function addChildren(node, parent) - { - for (let [,item] in Iterator(node.childNodes)) - { - if (item.childNodes.length == 0 && item.localName == "menuitem" - && !/rdf:http:/.test(item.label)) // FIXME - { - item.fullMenuPath = parent + item.label; - items.push(item); - } - else - { - let path = parent; - if (item.localName == "menu") - path += item.label + "."; - addChildren(item, path); - } - } - } - - let items = []; - addChildren(document.getElementById(config.guioptions["m"][1]), ""); - return items; - } - commands.add(["em[enu]"], "Execute the specified menu item from the command line", function (args) @@ -305,13 +305,7 @@ const liberator = (function () //{{{ }, { argCount: "1", - // TODO: add this as a standard menu completion function - completer: function (context) - { - context.title = ["Menu Path", "Label"]; - context.keys = { text: "fullMenuPath", description: "label" }; - context.completions = getMenuItems(); - }, + completer: function (context) completion.menuItem(context), literal: 0 }); @@ -597,6 +591,8 @@ const liberator = (function () //{{{ get mode() modes.main, set mode(value) modes.main = value, + get menuItems() getMenuItems(), + // Global constants CURRENT_TAB: 1, NEW_TAB: 2, @@ -652,8 +648,9 @@ const liberator = (function () //{{{ triggerObserver: function (type) { + let args = Array.slice(arguments, 1); for (let [,fn] in Iterator(observers[type] || [])) - fn.apply(null, Array.slice(arguments, 1)); + fn.apply(null, args); }, beep: function () diff --git a/common/content/liberator.xul b/common/content/liberator.xul index 3f6ae0ca..e7609f52 100644 --- a/common/content/liberator.xul +++ b/common/content/liberator.xul @@ -13,7 +13,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -48,7 +48,7 @@ the terms of any one of the MPL, the GPL or the LGPL. - + diff --git a/common/content/mappings.js b/common/content/mappings.js index ed3d78eb..0f222d64 100644 --- a/common/content/mappings.js +++ b/common/content/mappings.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 diff --git a/common/content/modes.js b/common/content/modes.js index df20bff0..b00ec691 100644 --- a/common/content/modes.js +++ b/common/content/modes.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -113,8 +113,7 @@ const modes = (function () //{{{ if (newMode == modes.NORMAL) { // disable caret mode when we want to switch to normal mode - let value = options.getPref("accessibility.browsewithcaret", false); - if (value) + if (options.getPref("accessibility.browsewithcaret")) options.setPref("accessibility.browsewithcaret", false); statusline.updateUrl(); @@ -171,7 +170,7 @@ const modes = (function () //{{{ // 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) + set: function (mainMode, extendedMode, silent, stack) { silent = (silent || main == mainMode && extended == extendedMode); // if a main mode is set, the extended is always cleared @@ -187,6 +186,7 @@ const modes = (function () //{{{ if (main != oldMain) handleModeChange(oldMain, mainMode, oldExtended); } + liberator.triggerObserver("modeChange", [oldMain, oldExtended], [main, extended], stack); if (!silent) this.show(); @@ -195,18 +195,19 @@ const modes = (function () //{{{ push: function (mainMode, extendedMode, silent) { modeStack.push([main, extended]); - this.set(mainMode, extendedMode, silent); + this.set(mainMode, extendedMode, silent, { push: modeStack[modeStack.length - 1] }); }, pop: function (silent) { let a = modeStack.pop(); if (a) - this.set(a[0], a[1], silent); + this.set(a[0], a[1], silent, { pop: a }); else this.reset(silent); }, + // TODO: Deprecate this in favor of addMode? --Kris setCustomMode: function (modestr, oneventfunc, stopfunc) { // TODO this.plugin[id]... ('id' maybe submode or what..) @@ -247,18 +248,10 @@ const modes = (function () //{{{ 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(); - }, + set main(value) { this.set(value); }, get extended() extended, - set extended(value) { extended = value; this.show(); } + set extended(value) { this.set(null, value) } }; @@ -285,7 +278,6 @@ const modes = (function () //{{{ 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; diff --git a/common/content/options.js b/common/content/options.js index 2b451649..584fc344 100644 --- a/common/content/options.js +++ b/common/content/options.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -1015,17 +1015,38 @@ function Options() //{{{ liberator.echoerr("E488: Trailing characters: " + name + "!"); }, + /** + * Pushes a new preference context onto the context stack. + * + * @see #withContext + */ pushContext: function () { prefContexts.push({}); }, + /** + * Pops the top preference context from the stack. + * + * @see #withContext + */ popContext: function () { for (let [k, v] in Iterator(prefContexts.pop())) storePreference(k, v); }, + /** + * Executes fn with a new preference context. When fn + * returns, the context is popped and any preferences set via + * {@link #setPref} or {@link #invertPref} are restored to their + * previous values. + * + * @param {function} fn The function to call. + * @param {object} fn The 'this' object with which to call fn + * @see #pushContext + * @see #popContext + */ withContext: function (fn, self) { try diff --git a/common/content/services.js b/common/content/services.js index 79bc3e73..925be0a8 100644 --- a/common/content/services.js +++ b/common/content/services.js @@ -1,5 +1,5 @@ /***** BEGIN LICENSE BLOCK ***** {{{ - ©2008 Kris Maglione + Copyright © 2008-2009 by Kris Maglione 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 diff --git a/common/content/style.js b/common/content/style.js index 6da2d5c3..50a38f5c 100644 --- a/common/content/style.js +++ b/common/content/style.js @@ -1,5 +1,5 @@ /***** BEGIN LICENSE BLOCK ***** {{{ - ©2008 Kris Maglione + Copyright © 2008-2009 by Kris Maglione 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 diff --git a/common/content/tabs.js b/common/content/tabs.js index 0fcefb70..bb68506f 100644 --- a/common/content/tabs.js +++ b/common/content/tabs.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -140,14 +140,11 @@ function Tabs() //{{{ return value; }, - completer: function (filter) - { - return [ - ["0", "Never show tab bar"], - ["1", "Show tab bar only if more than one tab is open"], - ["2", "Always show tab bar"] - ]; - }, + completer: function (context) [ + ["0", "Never show tab bar"], + ["1", "Show tab bar only if more than one tab is open"], + ["2", "Always show tab bar"] + ], validator: Option.validateCompleter }); @@ -157,15 +154,12 @@ function Tabs() //{{{ "Define when tabs are automatically activated", "stringlist", "homepage,quickmark,tabopen,paste", { - completer: function (filter) - { - return [ - ["homepage", "gH mapping"], - ["quickmark", "go and gn mappings"], - ["tabopen", ":tabopen[!] command"], - ["paste", "P and gP mappings"] - ]; - }, + completer: function (context) [ + ["homepage", "gH mapping"], + ["quickmark", "go and gn mappings"], + ["tabopen", ":tabopen[!] command"], + ["paste", "P and gP mappings"] + ], validator: Option.validateCompleter }); @@ -173,17 +167,14 @@ function Tabs() //{{{ "Define which commands should output in a new tab by default", "stringlist", "", { - completer: function (filter) - { - return [ - ["all", "All commands"], - ["addons", ":addo[ns] command"], - ["downloads", ":downl[oads] command"], - ["help", ":h[elp] command"], - ["javascript", ":javascript! or :js! command"], - ["prefs", ":pref[erences]! or :prefs! command"] - ]; - }, + completer: function (context) [ + ["all", "All commands"], + ["addons", ":addo[ns] command"], + ["downloads", ":downl[oads] command"], + ["help", ":h[elp] command"], + ["javascript", ":javascript! or :js! command"], + ["prefs", ":pref[erences]! or :prefs! command"] + ], validator: Option.validateCompleter }); @@ -204,16 +195,13 @@ function Tabs() //{{{ return value; }, - completer: function (filter) - { - return [ - ["0", "Force to open in the current tab"], - ["1", "Always open in a new tab"], - ["2", "Open in a new window if it has a specific requested size (default in Firefox)"], - ["3", "Always open in a new window"], - ["4", "Open in the same tab unless it has a specific requested size"] - ]; - }, + completer: function (context) [ + ["0", "Force to open in the current tab"], + ["1", "Always open in a new tab"], + ["2", "Open in a new window if it has a specific requested size (default in Firefox)"], + ["3", "Always open in a new window"], + ["4", "Open in the same tab unless it has a specific requested size"] + ], validator: Option.validateCompleter }); let fragment = liberator.has("MacUnix") ? "tab-mac" : "tab"; @@ -629,6 +617,7 @@ function Tabs() //{{{ argCount: "?", completer: function (context) { + context.anchored = false; context.keys = { text: function (item) item.state.entries[0].url, description: "title" }; context.completions = tabs.closedTabs; }, diff --git a/common/content/template.js b/common/content/template.js index 60f986df..981f72f8 100644 --- a/common/content/template.js +++ b/common/content/template.js @@ -1,3 +1,30 @@ +/***** 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. + +Copyright (c) 2006-2009 by Martin Stubenschrott + +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 *****/ /** @scope modules */ diff --git a/common/content/ui.js b/common/content/ui.js index 8f1cd0a5..862cd461 100644 --- a/common/content/ui.js +++ b/common/content/ui.js @@ -11,7 +11,7 @@ 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 +Copyright (c) 2006-2009 by Martin Stubenschrott 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 @@ -29,10 +29,10 @@ the terms of any one of the MPL, the GPL or the LGPL. /** @scope modules */ /** - * This class is used for prompting of user input and echoing of messages + * This class is used for prompting of user input and echoing of messages. * - * it consists of a prompt and command field - * be sure to only create objects of this class when the chrome is ready + * It consists of a prompt and command field be sure to only create objects of + * this class when the chrome is ready. */ function CommandLine() //{{{ { @@ -43,7 +43,7 @@ function CommandLine() //{{{ storage.newArray("history-search", true); storage.newArray("history-command", true); - var messageHistory = { + var messageHistory = { // {{{ _messages: [], get messages() { @@ -68,7 +68,7 @@ function CommandLine() //{{{ this._messages.push(message); } - }; + }; // }}} var lastMowOutput = null; var silent = false; @@ -76,12 +76,12 @@ function CommandLine() //{{{ var lastEcho = null; /** - * A class for managing the history of an inputField + * A class for managing the history of an inputField. * - * @param {Object} inputField - * @param {string} mode + * @param {HTMLInputElement} inputField + * @param {string} mode The mode for which we need history. */ - function History(inputField, mode) + function History(inputField, mode) // {{{ { if (!(this instanceof arguments.callee)) return new arguments.callee(inputField, mode); @@ -92,14 +92,15 @@ function CommandLine() //{{{ } History.prototype = { /** - * Empties the history. + * Reset the history index to the first entry. */ reset: function () { this.index = null; }, /** - * Permanently save the history + * Save the last entry to the permanent store. All duplicate entries + * are removed and the list is truncated, if necessary. */ save: function () { @@ -111,9 +112,9 @@ function CommandLine() //{{{ this.store.truncate(options["history"], true); }, /** - * Set the current match to val + * Replace the current input field value. * - * @param {string} val + * @param {string} val The new value. */ replace: function (val) { @@ -122,10 +123,11 @@ function CommandLine() //{{{ }, /** - * move up or (if backward) down in the history + * Move forward or backward in history. * - * @param {boolean} backward - * @param {boolean} matchCurrent XXX: what? + * @param {boolean} backward Direction to move. + * @param {boolean} matchCurrent Search for matches starting + * with the current input value. */ select: function (backward, matchCurrent) { @@ -173,14 +175,14 @@ function CommandLine() //{{{ } } } - }; + }; // }}} /** - * A class for tab completions on an input field + * A class for tab completions on an input field. * * @param {Object} input */ - function Completions(input) + function Completions(input) // {{{ { if (!(this instanceof arguments.callee)) return new arguments.callee(input); @@ -380,24 +382,53 @@ function CommandLine() //{{{ { // Wait for contexts to complete if necessary. // FIXME: Need to make idx relative to individual contexts. - let list = this.context.contextList.reverse(); + let list = this.context.contextList; if (idx == -2) list = list.slice().reverse(); let n = 0; - for (let [,context] in Iterator(list)) + try { - function done() !(idx >= n + context.items.length || idx == -2 && !context.items.length); - while (context.incomplete && !done()) - liberator.threadYield(true, true); - if (done()) - break; - n += context.items.length; + this.waiting = true; + for (let [,context] in Iterator(list)) + { + function done() !(idx >= n + context.items.length || idx == -2 && !context.items.length); + while (context.incomplete && !done()) + // threadYield(true, true) would be better, but it does not return on my + // machine until all awesomebar completions were reported, making + // :open foo nearly unusable, if the first 2 foo-completions would + // be there fast, but it takes up to 20 sec to find more foo-completions + // + // The strange thing is, I tested the 2009-01-07 nightly at work in Windows + // and it seemed to work perfectly there. Will have to see if it's a + // hardware (dual core there, vs. P4 at home) issue or an OS issue. + // + // While I *really* prefer this solution over my hack + // when it works, we can't have a nearly-defect :open + // prompt when releasing vimp 2.0, even not just on certain + // computers, as :open is probably the most often used ex-command + // in vimperator + // + // liberator.threadYield(false, true); is just a temporary measure as + // it has other problems (hitting tab often in a row), until we find the + // source of the problem (which we hopefully do, as I really don't want to + // have to revert to my hack when better solutions exist) + liberator.threadYield(false, true); + if (done()) + break; + n += context.items.length; + } + } + finally + { + this.waiting = false; } // See previous FIXME. This will break if new items in // a previous context come in. if (idx < 0) idx = this.items.length - 1; + if (this.items.length == 0) + return; this.selected = idx; this.completion = this.items[idx].text; @@ -406,6 +437,8 @@ function CommandLine() //{{{ this.itemList.selectItem(idx); }, + tabs: [], + tab: function tab(reverse) { autocompleteTimer.flush(); @@ -413,36 +446,44 @@ function CommandLine() //{{{ if (this.context.waitingForTab || this.wildIndex == -1) this.complete(true, true); - switch (this.wildtype.replace(/.*:/, "")) + this.tabs.push(reverse); + if (this.waiting) + return; + + while (this.tabs.length) { - case "": - this.select(0); - break; - case "longest": - if (this.items.length > 1) - { - if (this.substring && this.substring != this.completion) - this.completion = this.substring; + reverse = this.tabs.shift(); + switch (this.wildtype.replace(/.*:/, "")) + { + case "": + this.select(0); break; - } - // Fallthrough - case "full": - this.select(reverse ? this.UP : this.DOWN) - break; + case "longest": + if (this.items.length > 1) + { + if (this.substring && this.substring != this.completion) + this.completion = this.substring; + break; + } + // Fallthrough + case "full": + this.select(reverse ? this.UP : this.DOWN) + break; + } + + if (this.type.list) + completionList.show(); + + this.wildIndex = Math.max(0, Math.min(this.wildtypes.length - 1, this.wildIndex + 1)); + this.preview(); + + statusTimer.tell(); } if (this.items.length == 0) - return void liberator.beep(); - - if (this.type.list) - completionList.show(); - - this.wildIndex = Math.max(0, Math.min(this.wildtypes.length - 1, this.wildIndex + 1)); - this.preview(); - - statusTimer.tell(); + liberator.beep(); } - } + }; // }}} /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// TIMERS ////////////////////////////////////////////////// @@ -455,13 +496,18 @@ function CommandLine() //{{{ statusline.updateProgress("match " + (completions.selected + 1) + " of " + completions.items.length); }); - var autocompleteTimer = new Timer(201, 500, function autocompleteTell(tabPressed) { - if (events.feedingKeys || !completions) - return; - completions.complete(true, false); - completions.itemList.show(); + var autocompleteTimer = new Timer(200, 500, function autocompleteTell(tabPressed) { + if (!events.feedingKeys && completions && options.get("wildoptions").has("auto")) + { + completions.complete(true, false); + completions.itemList.show(); + } }); + // This timer just prevents s from queueing up when the + // system is under load (and, thus, giving us several minutes of + // the completion list scrolling). Multiple presses are + // still processed normally, as the time is flushed on "keyup". var tabTimer = new Timer(0, 0, function tabTell(event) { if (completions) completions.tab(event.shiftKey); @@ -471,10 +517,7 @@ function CommandLine() //{{{ ////////////////////// CALLBACKS /////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ - // callback for prompt mode - var promptSubmitCallback = null; - var promptChangeCallback = null; - var promptCompleter = null; + var input = {}; liberator.registerCallback("submit", modes.EX, function (command) { liberator.execute(command); @@ -483,28 +526,28 @@ function CommandLine() //{{{ context.fork("ex", 0, completion, "ex"); }); liberator.registerCallback("change", modes.EX, function (command) { - if (options.get("wildoptions").has("auto")) - autocompleteTimer.tell(false); + autocompleteTimer.tell(false); }); liberator.registerCallback("cancel", modes.PROMPT, closePrompt); liberator.registerCallback("submit", modes.PROMPT, closePrompt); liberator.registerCallback("change", modes.PROMPT, function (str) { - liberator.triggerCallback("change", modes.EX, str); - if (promptChangeCallback) - return promptChangeCallback.call(commandline, str); + if (input.complete) + autocompleteTimer.tell(false); + if (input.change) + return input.change.call(commandline, str); }); liberator.registerCallback("complete", modes.PROMPT, function (context) { - if (promptCompleter) - context.fork("input", 0, commandline, promptCompleter); + if (input.complete) + context.fork("input", 0, commandline, input.complete); }); function closePrompt(value) { - let callback = promptSubmitCallback; - promptSubmitCallback = null; + let callback = input.submit; + input = {}; if (callback) - callback.call(commandline, value == null ? commandline.command : value); + callback.call(commandline, value != null ? value : commandline.command); } /////////////////////////////////////////////////////////////////////////////}}} @@ -554,7 +597,7 @@ function CommandLine() //{{{ var multilineCallback = null; /** - * @private - highlight the messageBox according to group + * Highlight the messageBox according to group. */ function setHighlightGroup(group) { @@ -562,7 +605,7 @@ function CommandLine() //{{{ } /** - * @private - Determines whether the command line should be visible. + * Determines whether the command-line should be visible. * * @return {boolean} */ @@ -570,7 +613,7 @@ function CommandLine() //{{{ !(modes.extended & (modes.INPUT_MULTILINE | modes.OUTPUT_MULTILINE)); /** - * @private - set the prompt to val styled with highlightGroup + * Set the command-line prompt. * * @param {string} val * @param {string} highlightGroup @@ -584,7 +627,8 @@ function CommandLine() //{{{ } /** - * @private - set the command to cmd and move the user's cursor to the end. + * Set the command-line input value. The caret is reset to the + * end of the line. * * @param {string} cmd */ @@ -596,12 +640,12 @@ function CommandLine() //{{{ } /** - * @private - display a message styled with highlightGroup - * and, if forceSingle is true, ensure it takes only one line. + * Display a message in the command-line area. * * @param {string} str * @param {string} highlightGroup - * @param {boolean} forceSingle + * @param {boolean} forceSingle If provided, don't let over-long + * messages move to the MOW. */ function echoLine(str, highlightGroup, forceSingle) { @@ -619,13 +663,12 @@ function CommandLine() //{{{ } /** - * Display a multiline message, possible through a "more" like interface - * - * TODO: resize upon a window resize + * Display a multiline message. * * @param {string} str * @param {string} highlightGroup */ + // TODO: resize upon a window resize function echoMultiline(str, highlightGroup) { let doc = multilineOutputWidget.contentDocument; @@ -642,7 +685,6 @@ function CommandLine() //{{{ let output = util.xmlToDom(lastMowOutput, doc); XML.ignoreWhitespace = true; - // FIXME: need to make sure an open MOW is closed when commands // that don't generate output are executed if (outputContainer.collapsed) @@ -671,8 +713,7 @@ function CommandLine() //{{{ } /** - * @private - ensure that the Multiline input widget is the - * correct size. + * Ensure that the multiline input widget is the correct size. */ function autosizeMultilineInputWidget() { @@ -682,14 +723,12 @@ function CommandLine() //{{{ } /** - * @private - eval()s a javascript expression - * and returns a string suitable to be echo'd. - * - * If useColor is true, util.objectToString will - * colorize object output. + * eval() a JavaScript expression and return a string suitable + * to be echoed. * * @param {string} arg - * @param {boolean} useColor + * @param {boolean} useColor When true, the result is a + * highlighted XML object. */ function echoArgumentToString(arg, useColor) { @@ -757,7 +796,7 @@ function CommandLine() //{{{ "Items which are completed at the :[tab]open prompt", "charlist", "sfl", { - completer: function completer(filter) [k for each (k in completion.urlCompleters)], + completer: function (context) [k for each (k in completion.urlCompleters)], validator: Option.validateCompleter }); @@ -796,18 +835,15 @@ function CommandLine() //{{{ "Define how command line completion works", "stringlist", "list:full", { - completer: function completer(filter) - { - return [ - // Why do we need ""? - ["", "Complete only the first match"], - ["full", "Complete the next full match"], - ["longest", "Complete to longest common string"], - ["list", "If more than one match, list all matches"], - ["list:full", "List all and complete first match"], - ["list:longest", "List all and complete common string"] - ]; - }, + completer: function (context) [ + // Why do we need ""? + ["", "Complete only the first match"], + ["full", "Complete the next full match"], + ["longest", "Complete to longest common string"], + ["list", "If more than one match, list all matches"], + ["list:full", "List all and complete first match"], + ["list:longest", "List all and complete common string"] + ], validator: Option.validateCompleter, checkHas: function (value, val) { @@ -837,7 +873,7 @@ function CommandLine() //{{{ var myModes = [modes.COMMAND_LINE]; - // TODO: move "", "", "" here from mappings mappings.add(myModes, [""], "Focus content", function () { events.onEscape(); }); @@ -941,14 +977,14 @@ function CommandLine() //{{{ return { - HL_NORMAL : "Normal", - HL_ERRORMSG : "ErrorMsg", - HL_MODEMSG : "ModeMsg", - HL_MOREMSG : "MoreMsg", - HL_QUESTION : "Question", - HL_INFOMSG : "InfoMsg", - HL_WARNINGMSG : "WarningMsg", - HL_LINENR : "LineNr", + HL_NORMAL: "Normal", + HL_ERRORMSG: "ErrorMsg", + HL_MODEMSG: "ModeMsg", + HL_MOREMSG: "MoreMsg", + HL_QUESTION: "Question", + HL_INFOMSG: "InfoMsg", + HL_WARNINGMSG: "WarningMsg", + HL_LINENR: "LineNr", FORCE_MULTILINE : 1 << 0, FORCE_SINGLELINE : 1 << 1, @@ -962,7 +998,8 @@ function CommandLine() //{{{ get mode() (modes.extended == modes.EX) ? "cmd" : "search", get silent() silent, - set silent(val) { + set silent(val) + { silent = val; if (silent) storage.styles.addSheet(true, "silent-mode", "chrome://*", "#liberator-commandline > * { opacity: 0 }"); @@ -970,9 +1007,6 @@ function CommandLine() //{{{ storage.styles.removeSheet(true, "silent-mode"); }, - /** - * XXX: This function is not used! - */ runSilently: function (fn, self) { let wasSilent = this.silent; @@ -991,6 +1025,8 @@ function CommandLine() //{{{ { try { + // The long path is because of complications with the + // completion preview. return commandWidget.inputField.editor.rootElement.firstChild.textContent; } catch (e) {} @@ -1001,14 +1037,14 @@ function CommandLine() //{{{ get message() messageBox.value, /** - * Changes the command line to display the following prompt (usually ":") - * followed by the command, in the given mode. Valid modes are - * attributes of the "modes" variable, and modes.EX is probably - * a good choice. + * Open the command-line. The main mode is set to + * COMMAND_LINE, the extended mode to extendedMode. + * Further, callbacks defined for extendedMode are + * triggered as appropriate (see {@link Liberator#registerCallback}). * * @param {string} prompt * @param {string} cmd - * @param {number} mode + * @param {number} extendedMode */ open: function open(prompt, cmd, extendedMode) { @@ -1031,13 +1067,14 @@ function CommandLine() //{{{ completions = Completions(commandWidget.inputField); // open the completion list automatically if wanted - liberator.triggerCallback("change", currentExtendedMode, cmd); + if (cmd.length) + liberator.triggerCallback("change", currentExtendedMode, cmd); }, /** - * Removes any input from the command line, without executing its - * contents. Removes any "More" windows or other such output. - * Pressing in EX mode normally has this effect. + * Closes the command-line. This is ordinarily triggered automatically + * by a mode change. Will not hide the command-line immediately if + * called directly after a successful command, otherwise it will. */ close: function close() { @@ -1048,7 +1085,7 @@ function CommandLine() //{{{ if (history) history.save(); - this.resetCompletions(); // cancels any asynchronous completion still going on, must be before completions = null + this.resetCompletions(); // cancels any asynchronous completion still going on, must be before we set completions = null completions = null; history = null; @@ -1072,9 +1109,9 @@ function CommandLine() //{{{ keepCommand = false; }, - /** - * Hide any auto-completion/More-ing that is happening. + * Hides the command-line, and shows any status messages that + * are under it. */ hide: function hide() { @@ -1082,23 +1119,28 @@ function CommandLine() //{{{ }, /** - * Output the given string onto the command line coloured - * using the rules according to highlightGroup. If not - * given higlightGroup defaults to commandline.HL_NORMAL - * and other possibe values are at commandline.HL_[A-Z]*. - * - * Flags can be any of: - * commandline.APPEND_TO_MESSAGES (causes message to be added to the messagesHistory) - * commandline.FORCE_SINGLELINE | commandline.DISALLOW_MULTILINE - * commandline.FORCE_MULTILINE + * Output the given string onto the command-line. With no flags, the + * message will be shown in the status line if it's short enough to + * fit, and contains no new lines, and isn't XML. Otherwise, it will be + * shown in the MOW. * * @param {string} str - * @param {string} highlightGroup - * @param {number} flags + * @param {string} highlightGroup The Highlight group for the + * message. @default "Normal" + * @param {number} flags Changes the behavior as follows: + * commandline.APPEND_TO_MESSAGES - Causes message to be added to the + * messages history, and shown by :messages. + * commandline.FORCE_SINGLELINE - Forbids the command from being + * pushed to the MOW if it's too long or of there are already + * status messages being shown. + * commandline.DISALLOW_MULTILINE - Cancels the operation if the MOW + * is already visible. + * commandline.FORCE_MULTILINE - Forces the message to appear in + * the MOW. */ echo: function echo(str, highlightGroup, flags) { - // liberator.echo uses different order of flags as it omits the highlight group, change v.commandline.echo argument order? --mst + // liberator.echo uses different order of flags as it omits the highlight group, change commandline.echo argument order? --mst if (silent) return false; @@ -1144,29 +1186,28 @@ function CommandLine() //{{{ }, /** - * Prompt the user for a string and execute the given - * callback with that as the only argument on - * extra can have any of the following attributes: + * Prompt the user. Sets modes.main to COMMAND_LINE, which the user may + * pop at any time to close the prompt. * - * onChange: A function to be called with the current input every time it changes - * completer: A function called with a ?context? when the user tries to tabcomplete - * promptHighlight: The HighlightGroup to use (default commandline.HL_QUESTION, others - * can be found at commandline.HL_[A-Z]*) - * - * This function sets the mode to modes.COMMAND_LINE, and thus popping the mode will - * stop further input from being waited for (useful for stopping onChange) - * - * @param {string} prompt + * @param {string} prompt The input prompt to use. * @param {function(string)} callback - * @param {Object} extra + * @param {object} extra + * @... {function} onChange - A function to be called with the current + * input every time it changes. + * @... {function(CompletionContext)} completer - A completion function + * for the user's input. + * @... {string} promptHighlight - The HighlightGroup used for the + * prompt. @default "Question" */ - input: function input(prompt, callback, extra) + input: function _input(prompt, callback, extra) { extra = extra || {}; - promptSubmitCallback = callback; - promptChangeCallback = extra.onChange; - promptCompleter = extra.completer; + input = { + submit: callback, + change: extra.onChange, + complete: extra.completer, + }; modes.push(modes.COMMAND_LINE, modes.PROMPT); currentExtendedMode = modes.PROMPT; @@ -1180,13 +1221,14 @@ function CommandLine() //{{{ }, /** - * Get a multiline input from a user, up to but not including - * the line which matches the given regular expression. Then - * execute the callback with that string as a parameter. + * Get a multiline input from a user, up to but not including the line + * which matches the given regular expression. Then execute the + * callback with that string as a parameter. * * @param {RegExp} untilRegexp * @param {function(string)} callbackFunc */ + // FIXME: Buggy, especially when pasting. Shouldn't use a RegExp. inputMultiline: function inputMultiline(untilRegexp, callbackFunc) { // Kludge. @@ -1207,11 +1249,12 @@ function CommandLine() //{{{ }, /** - * Handle events, the come from liberator when liberator.mode = modes.COMMAND_LINE - * but also takes blur/focus/input events raw from #liberator-commandline-command - * in the XUL + * Handles all command-line events. All key events are passed here when + * COMMAND_LINE mode is active, as well as all input, keyup, focus, and + * blur events sent to the command-line XUL element. * * @param {Event} event + * @private */ onEvent: function onEvent(event) { @@ -1347,15 +1390,14 @@ function CommandLine() //{{{ }, /** - * Handle events when we are in multiline output mode, - * these come from liberator when modes.extended & modes.MULTILINE_OUTPUT - * and also from #liberator-multiline-output in the XUL - * - * FIXME: if 'more' is set and the MOW is not scrollable we should still - * allow a down motion after an up rather than closing + * Handle events when we are in multiline output mode, these come from + * liberator when modes.extended & modes.MULTILINE_OUTPUT and also from + * #liberator-multiline-output in the XUL. * * @param {Event} event */ + // FIXME: if 'more' is set and the MOW is not scrollable we should still + // allow a down motion after an up rather than closing onMultilineOutputEvent: function onMultilineOutputEvent(event) { let win = multilineOutputWidget.contentWindow; @@ -1374,8 +1416,8 @@ function CommandLine() //{{{ { event.preventDefault(); let target = event.button == 0 ? liberator.CURRENT_TAB : liberator.NEW_TAB; - if (event.target.href == "#") - liberator.open(String(event.target), target); + if (event.target.getAttribute("href") == "#") + liberator.open(event.target.textContent, target); else liberator.open(event.target.href, target); } @@ -1565,14 +1607,20 @@ function CommandLine() //{{{ } }, + getSpaceNeeded: function getSpaceNeeded() + { + let rect = commandlineWidget.getBoundingClientRect(); + let offset = rect.bottom - window.innerHeight; + return Math.max(0, offset); + }, + /** - * Refresh or remove the prompt that displays when in multiline mode. - * showHelp will cause the possible key-options to be displayed, - * force will cause a display of the default message even if it - * could be at the end of the output. + * Update or remove the multiline output widget's "MORE" prompt. * - * @param {boolean} force - * @param {boolean} showHelp + * @param {boolean} force If true, "-- More --" is shown even if we're + * at the end of the output. + * @param {boolean} showHelp When true, show the valid key sequences + * and what they do. */ updateMorePrompt: function updateMorePrompt(force, showHelp) { @@ -1592,12 +1640,11 @@ function CommandLine() //{{{ }, /** - * Changes the height of the multilineOutputWidget to fit - * its contents, if open is true, it will cause the - * widget to uncollapse, if not it will leave the widget - * closed. + * Changes the height of the multilineOutputWidget to fit in the + * available space. * - * @param {boolean} open + * @param {boolean} open If true, the widget will be opened if it's not + * already so. */ updateOutputHeight: function updateOutputHeight(open) { @@ -1606,13 +1653,13 @@ function CommandLine() //{{{ let doc = multilineOutputWidget.contentDocument; - // The container needs to be collapsed for this calculation to work. - outputContainer.collapsed = true; let availableHeight = 250; try { availableHeight = getBrowser().mPanelContainer ? getBrowser().mPanelContainer.boxObject.height : getBrowser().boxObject.height; + if (!outputContainer.collapsed) + availableHeight += parseFloat(outputContainer.height); } catch (e) {} doc.body.style.minWidth = commandlineWidget.scrollWidth + "px"; @@ -1621,15 +1668,8 @@ function CommandLine() //{{{ outputContainer.collapsed = false; }, - /** - * Disable any active completion functions by calling their cancelFunc's - * Will also remove the completions preview window. - */ resetCompletions: function resetCompletions() { - autocompleteTimer.reset(); - - // liberator.dump("Resetting completions..."); if (completions) { completions.context.cancelAll(); @@ -1644,11 +1684,12 @@ function CommandLine() //{{{ }; //}}} /** - * The list which is used for the completion box (and QuickFix window in future) + * The list which is used for the completion box (and QuickFix window in + * future). * - * @param {string} id The id of the XUL