]> git.donarmstrong.com Git - dactyl.git/blobdiff - common/content/modes.js
New upstream version 1.0+hg6948
[dactyl.git] / common / content / modes.js
index 55fa19fe6965ccb3e19200e68f7dfda52a9b0087..fc33fd7f24e95d88795de024fa9d690d8a166a6a 100644 (file)
@@ -1,6 +1,6 @@
 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
-// Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
+// Copyright (c) 2008-2012 Kris Maglione <maglione.k@gmail.com>
 //
 // This work is licensed for reuse under an MIT license. Details are
 // given in the LICENSE.txt file included with this file.
@@ -57,27 +57,10 @@ var Modes = Module("modes", {
             description: "Active when nothing is focused",
             bases: [this.COMMAND]
         });
-        this.addMode("VISUAL", {
-            char: "v",
-            description: "Active when text is selected",
-            display: function () "VISUAL" + (this._extended & modes.LINE ? " LINE" : ""),
-            bases: [this.COMMAND],
-            ownsFocus: true,
-            passUnknown: false
-        }, {
-            leave: function (stack, newMode) {
-                if (newMode.main == modes.CARET) {
-                    let selection = content.getSelection();
-                    if (selection && !selection.isCollapsed)
-                        selection.collapseToStart();
-                }
-                else if (stack.pop)
-                    editor.unselectText();
-            }
-        });
         this.addMode("CARET", {
+            char: "caret",
             description: "Active when the caret is visible in the web content",
-            bases: [this.COMMAND]
+            bases: [this.NORMAL]
         }, {
 
             get pref()    prefs.get("accessibility.browsewithcaret"),
@@ -88,6 +71,8 @@ var Modes = Module("modes", {
                     modes.pop();
                 else if (!stack.pop && !this.pref)
                     this.pref = true;
+                if (!stack.pop)
+                    buffer.resetCaret();
             },
 
             leave: function (stack) {
@@ -95,18 +80,6 @@ var Modes = Module("modes", {
                     this.pref = false;
             }
         });
-        this.addMode("TEXT_EDIT", {
-            char: "t",
-            description: "Vim-like editing of input elements",
-            bases: [this.COMMAND],
-            input: true,
-            ownsFocus: true,
-            passUnknown: false
-        });
-        this.addMode("OUTPUT_MULTILINE", {
-            description: "Active when the multi-line output buffer is open",
-            bases: [this.COMMAND],
-        });
 
         this.addMode("INPUT", {
             char: "I",
@@ -114,20 +87,10 @@ var Modes = Module("modes", {
             bases: [this.MAIN],
             insert: true
         });
-        this.addMode("INSERT", {
-            char: "i",
-            description: "Active when an input element is focused",
-            insert: true,
-            ownsFocus: true
-        });
-        this.addMode("AUTOCOMPLETE", {
-            description: "Active when an input autocomplete pop-up is active",
-            display: function () "AUTOCOMPLETE (insert)",
-            bases: [this.INSERT]
-        });
 
         this.addMode("EMBED", {
             description: "Active when an <embed> or <object> element is focused",
+            bases: [modes.MAIN],
             insert: true,
             ownsFocus: true,
             passthrough: true
@@ -156,7 +119,11 @@ var Modes = Module("modes", {
             onKeyPress: function (events) { if (modes.main == modes.QUOTE) modes.pop(); }
         });
         this.addMode("IGNORE", { hidden: true }, {
-            onKeyPress: function (events) Events.KILL,
+            onKeyPress: function (events_) {
+                if (events.isCancelKey(DOM.Event.stringify(events_[0])))
+                    return true;
+                return false;
+            },
             bases: [],
             passthrough: true
         });
@@ -200,22 +167,35 @@ var Modes = Module("modes", {
             }
         });
     },
+
     cleanup: function cleanup() {
         modes.reset();
     },
 
+    signals: {
+        "io.source": function ioSource(context, file, modTime) {
+            cache.flushEntry("modes.dtd", modTime);
+        }
+    },
+
     _getModeMessage: function _getModeMessage() {
         // when recording a macro
         let macromode = "";
         if (this.recording)
-            macromode = "recording";
+            macromode = "recording " + this.recording + " ";
         else if (this.replaying)
             macromode = "replaying";
 
-        let val = this._modeMap[this._main].display();
-        if (val)
-            return "-- " + val + " --" + macromode;;
-        return macromode;
+        if (!options.get("showmode").getKey(this.main.allBases, false))
+            return macromode;
+
+        let modeName = this._modeMap[this._main].display();
+        if (!modeName)
+            return macromode;
+
+        if (macromode)
+            macromode = " " + macromode;
+        return "-- " + modeName + " --" + macromode;
     },
 
     NONE: 0,
@@ -245,7 +225,19 @@ var Modes = Module("modes", {
         if (!mode.extended)
             this._mainModes.push(mode);
 
-        dactyl.triggerObserver("mode-add", mode);
+        dactyl.triggerObserver("modes.add", mode);
+    },
+
+    removeMode: function removeMode(mode) {
+        this.remove(mode);
+        if (this[mode.name] == mode)
+            delete this[mode.name];
+        if (this._modeMap[mode.name] == mode)
+            delete this._modeMap[mode.name];
+        if (this._modeMap[mode.mode] == mode)
+            delete this._modeMap[mode.mode];
+
+        this._mainModes = this._mainModes.filter(function (m) m != mode);
     },
 
     dumpStack: function dumpStack() {
@@ -270,9 +262,11 @@ var Modes = Module("modes", {
 
     // show the current mode string in the command line
     show: function show() {
-        let msg = null;
-        if (options.get("showmode").getKey(this.main.name, true))
-            msg = this._getModeMessage();
+        if (!loaded.modes)
+            return;
+
+        let msg = this._getModeMessage();
+
         if (msg || loaded.commandline)
             commandline.widgets.mode = msg || null;
     },
@@ -296,12 +290,11 @@ var Modes = Module("modes", {
         if (!(id in this.boundProperties))
             for (let elem in array.iterValues(this._modeStack))
                 elem.saved[id] = { obj: obj, prop: prop, value: obj[prop], test: test };
-        this.boundProperties[id] = { obj: Cu.getWeakReference(obj), prop: prop, test: test };
+        this.boundProperties[id] = { obj: util.weakReference(obj), prop: prop, test: test };
     },
 
     inSet: false,
 
-    // helper function to set both modes in one go
     set: function set(mainMode, extendedMode, params, stack) {
         var delayed, oldExtended, oldMain, prev, push;
 
@@ -310,7 +303,7 @@ var Modes = Module("modes", {
             return;
         }
 
-        params = params || this.getMode(mainMode || this.main).params;
+        params = params || Object.create(this.getMode(mainMode || this.main).params);
 
         if (!stack && mainMode != null && this._modeStack.length > 1)
             this.reset();
@@ -354,23 +347,23 @@ var Modes = Module("modes", {
             prev = stack && stack.pop || this.topOfStack;
             if (push)
                 this._modeStack.push(push);
-
-            if (stack && stack.pop)
-                for (let { obj, prop, value, test } in values(this.topOfStack.saved))
-                    if (!test || !test(stack, prev))
-                        dactyl.trapErrors(function () { obj[prop] = value });
-
-            this.show();
         });
 
-        delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
+        if (stack && stack.pop)
+            for (let { obj, prop, value, test } in values(this.topOfStack.saved))
+                if (!test || !test(stack, prev))
+                    dactyl.trapErrors(function () { obj[prop] = value });
+
+        this.show();
 
         if (this.topOfStack.params.enter && prev)
             dactyl.trapErrors("enter", this.topOfStack.params,
                               push ? { push: push } : stack || {},
                               prev);
 
-        dactyl.triggerObserver("modeChange", [oldMain, oldExtended], [this._main, this._extended], stack);
+        delayed.forEach(function ([fn, self]) dactyl.trapErrors(fn, self));
+
+        dactyl.triggerObserver("modes.change", [oldMain, oldExtended], [this._main, this._extended], stack);
         this.show();
     },
 
@@ -382,6 +375,9 @@ var Modes = Module("modes", {
     },
 
     push: function push(mainMode, extendedMode, params) {
+        if (this.main == this.IGNORE)
+            this.pop();
+
         this.set(mainMode, extendedMode, params, { push: this.topOfStack });
     },
 
@@ -389,19 +385,21 @@ var Modes = Module("modes", {
         while (this._modeStack.length > 1 && this.main != mode) {
             let a = this._modeStack.pop();
             this.set(this.topOfStack.main, this.topOfStack.extended, this.topOfStack.params,
-                     update({ pop: a }, args || {}));
+                     update({ pop: a }, args));
 
             if (mode == null)
                 return;
         }
     },
 
-    replace: function replace(mode, oldMode) {
+    replace: function replace(mode, oldMode, args) {
         while (oldMode && this._modeStack.length > 1 && this.main != oldMode)
             this.pop();
 
         if (this._modeStack.length > 1)
-            this.set(mode, null, null, { push: this.topOfStack, pop: this._modeStack.pop() });
+            this.set(mode, null, null,
+                     update({ push: this.topOfStack, pop: this._modeStack.pop() },
+                            args || {}));
         this.push(mode);
     },
 
@@ -428,22 +426,27 @@ var Modes = Module("modes", {
         init: function init(name, options, params) {
             if (options.bases)
                 util.assert(options.bases.every(function (m) m instanceof this, this.constructor),
-                            "Invalid bases", true);
+                           _("mode.invalidBases"), false);
 
-            update(this, {
+            this.update({
                 id: 1 << Modes.Mode._id++,
+                description: name,
                 name: name,
                 params: params || {}
             }, options);
         },
 
+        description: Messages.Localized(""),
+
+        displayName: Class.Memoize(function () this.name.split("_").map(util.capitalize).join(" ")),
+
         isinstance: function isinstance(obj)
-            this === obj || this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
+            this.allBases.indexOf(obj) >= 0 || callable(obj) && this instanceof obj,
 
-        allBases: Class.memoize(function () {
-            let seen = {}, res = [], queue = this.bases;
+        allBases: Class.Memoize(function () {
+            let seen = {}, res = [], queue = [this].concat(this.bases);
             for (let mode in array.iterValues(queue))
-                if (!set.add(seen, mode)) {
+                if (!Set.add(seen, mode)) {
                     res.push(mode);
                     queue.push.apply(queue, mode.bases);
                 }
@@ -454,9 +457,7 @@ var Modes = Module("modes", {
 
         get count() !this.insert,
 
-        get description() this._display,
-
-        _display: Class.memoize(function _display() this.name.replace("_", " ", "g")),
+        _display: Class.Memoize(function _display() this.name.replace("_", " ", "g")),
 
         display: function display() this._display,
 
@@ -464,13 +465,15 @@ var Modes = Module("modes", {
 
         hidden: false,
 
-        input: Class.memoize(function input() this.insert || this.bases.length && this.bases.some(function (b) b.input)),
+        input: Class.Memoize(function input() this.insert || this.bases.length && this.bases.some(function (b) b.input)),
 
-        insert: Class.memoize(function insert() this.bases.length && this.bases.some(function (b) b.insert)),
+        insert: Class.Memoize(function insert() this.bases.length && this.bases.some(function (b) b.insert)),
 
-        ownsFocus: Class.memoize(function ownsFocus() this.bases.length && this.bases.some(function (b) b.ownsFocus)),
+        ownsFocus: Class.Memoize(function ownsFocus() this.bases.length && this.bases.some(function (b) b.ownsFocus)),
 
-        get passUnknown() this.input,
+        passEvent: function passEvent(event) this.input && event.charCode && !(event.ctrlKey || event.altKey || event.metaKey),
+
+        passUnknown: Class.Memoize(function () options.get("passunknown").getKey(this.name)),
 
         get mask() this,
 
@@ -488,7 +491,9 @@ var Modes = Module("modes", {
         update(StackElement.prototype, {
             get toStringParams() !loaded.modes ? this.main.name : [
                 this.main.name,
-                <>({ modes.all.filter(function (m) this.extended & m, this).map(function (m) m.name).join("|") })</>
+                ["(", modes.all.filter(function (m) this.extended & m, this)
+                           .map(function (m) m.name).join("|"),
+                 ")"].join("")
             ]
         });
         return StackElement;
@@ -518,22 +523,66 @@ var Modes = Module("modes", {
         }, desc));
     }
 }, {
+    cache: function initCache() {
+        function makeTree() {
+            let list = modes.all.filter(function (m) m.name !== m.description);
+
+            let tree = {};
+
+            for (let mode in values(list))
+                tree[mode.name] = {};
+
+            for (let mode in values(list))
+                for (let base in values(mode.bases))
+                    tree[base.name][mode.name] = tree[mode.name];
+
+            let roots = iter([m.name, tree[m.name]]
+                             for (m in values(list))
+                             if (!m.bases.length)).toObject();
+
+            function rec(obj) {
+                let res = ["ul", { "dactyl:highlight": "Dense" }];
+                Object.keys(obj).sort().forEach(function (name) {
+                    let mode = modes.getMode(name);
+                    res.push(["li", {},
+                                ["em", {}, mode.displayName],
+                                ": ", mode.description,
+                                rec(obj[name])]);
+                });
+
+                if (res.length > 2)
+                    return res;
+                return [];
+            }
+
+            return rec(roots);
+        }
+
+        cache.register("modes.dtd", function ()
+            util.makeDTD(iter({ "modes.tree": makeTree() },
+                              config.dtd)));
+    },
     mappings: function initMappings() {
         mappings.add([modes.BASE, modes.NORMAL],
             ["<Esc>", "<C-[>"],
-            "Return to NORMAL mode",
+            "Return to Normal mode",
             function () { modes.reset(); });
 
-        mappings.add([modes.INPUT, modes.COMMAND, modes.PASS_THROUGH, modes.QUOTE],
+        mappings.add([modes.INPUT, modes.COMMAND, modes.OPERATOR, modes.PASS_THROUGH, modes.QUOTE],
             ["<Esc>", "<C-[>"],
             "Return to the previous mode",
+            function () { modes.pop(null, { fromEscape: true }); });
+
+        mappings.add([modes.AUTOCOMPLETE, modes.MENU], ["<C-c>"],
+            "Leave Autocomplete or Menu mode",
             function () { modes.pop(); });
 
         mappings.add([modes.MENU], ["<Esc>"],
             "Close the current popup",
             function () {
+                if (events.popups.active.length)
+                    return Events.PASS_THROUGH;
                 modes.pop();
-                return Events.PASS_THROUGH;
             });
 
         mappings.add([modes.MENU], ["<C-[>"],
@@ -541,14 +590,52 @@ var Modes = Module("modes", {
             function () { events.feedkeys("<Esc>"); });
     },
     options: function initOptions() {
+        let opts = {
+            completer: function completer(context, extra) {
+                if (extra.value && context.filter[0] == "!")
+                    context.advance(1);
+                return completer.superapply(this, arguments);
+            },
+
+            getKey: function getKey(val, default_) {
+                if (isArray(val))
+                    return (array.nth(this.value, function (v) val.some(function (m) m.name === v.mode), 0)
+                                || { result: default_ }).result;
+
+                return Set.has(this.valueMap, val) ? this.valueMap[val] : default_;
+            },
+
+            setter: function (vals) {
+                modes.all.forEach(function (m) { delete m.passUnknown; });
+
+                vals = vals.map(function (v) update(new String(v.toLowerCase()), {
+                    mode: v.replace(/^!/, "").toUpperCase(),
+                    result: v[0] !== "!"
+                }));
+
+                this.valueMap = values(vals).map(function (v) [v.mode, v.result]).toObject();
+                return vals;
+            },
+
+            validator: function validator(vals) vals.map(function (v) v.replace(/^!/, "")).every(Set.has(this.values)),
+
+            get values() array.toObject([[m.name.toLowerCase(), m.description] for (m in values(modes._modes)) if (!m.hidden)])
+        };
+
+        options.add(["passunknown", "pu"],
+            "Pass through unknown keys in these modes",
+            "stringlist", "!text_edit,!visual,base",
+            opts);
+
         options.add(["showmode", "smd"],
             "Show the current mode in the command line when it matches this expression",
-            "regexplist", "!^normal$",
-            { regexpFlags: "i" });
+            "stringlist", "caret,output_multiline,!normal,base,operator",
+            opts);
     },
     prefs: function initPrefs() {
-        prefs.watch("accessibility.browsewithcaret", function () modes.onCaretChange.apply(modes, arguments));
+        prefs.watch("accessibility.browsewithcaret",
+                    function () { modes.onCaretChange.apply(modes, arguments); });
     }
 });
 
-// vim: set fdm=marker sw=4 ts=4 et:
+// vim: set fdm=marker sw=4 sts=4 ts=8 et: