# Source: control/Bindings.js

    // ---------------------------------------------------------------------------
    // Key values are found here:
    // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values
    //
    // Hardwired binding: event contextmenu bound to command contextualMenu
    //
    // Bindings not supported:
    // - Triple-click = "selectText", null. (Cannot make it work reliably.)
    //
    // * On the Mac, almost all keyboard shortcuts seem to be reserved
    // either by the browser or by macOS. For example, all ALT|MOD, that is,
    // Command+Option, are.
    //
    // Hence, on the Mac, same *CTRL* bindings as on XXE Desktop.
    //
    // * On Windows and Linux, the following keyboard shortcuts
    // are reserved by browsers:
    //
    // Firefox: Alt-Left=Back, Alt-Right=Forward
    //          Ctrl-R=Reload
    //          Ctrl-P=Print, Ctrl+Shift-P=New private window
    //          Ctrl-T=New tab
    //          Ctrl-W=Close Tab or Window
    //          Ctrl-Tab=Next tab, Ctrl+Shift-Tab=Previous tab
    //          Ctrl-U=Show page source
    //          Ctrl-I=Show page info (but not blocked)
    //          Ctrl+Shift-I=Show element inspector (but not blocked)
    //          Ctrl-B=Show bookmarks pane (but not blocked)
    //          Ctrl+Shift-B=Show/hide bookmark bar (but not blocked)
    //          Ctrl-J=Focus search bar (but not blocked)
    //          Ctrl+Shift-A=Show/hide addon manager (but not blocked)
    //
    // Chrome: Alt-Left=Back, Alt-Right=Forward
    //         Ctrl-R=Reload
    //         Ctrl-P=Print, Ctrl+Shift-P=Print settings
    //         Ctrl-T=New tab, Ctrl+Shift-T=Reopen closed tab
    //         Ctrl-W=Close Tab or Window
    //         Ctrl-Tab=New tab, Ctrl+Shift-Tab=Previous tab
    //         Ctrl-U=Show page source
    //         Ctrl+Shift-I=Show element inspector (but not blocked)
    //         Ctrl+Shift-B=Show/hide bookmark bar (but not blocked)
    //         Ctrl-J=Show downloads (but not blocked)
    //         Ctrl-E=Focus search bar (but not blocked)
    //
    // Edge: Ctrl-K=Open a search query in the address bar.
    //
    // Windows: Ctrl+Alt-E=Input Euro symbol.
    //
    // Linux (or KDE?): Ctrl+Shift-E=Emoji input method.
    //        Example: Ctrl+Shift-E e ==> e underlined,
    //                 then type "joy" ENTER ==> ejoy emoji.
    //        Ctrl+Shift-T=Open Terminal
    //        Ctrl+Shift-R=Manually Invoke Action
    //                     on Current Clipboard (??? ==> disabled)
    //
    // Hence, on Windows and Linux, binding changes compared to XXE Desktop:
    //
    // "insertControlChar" "\t": Ctrl-Tab --> Esc Tab
    //
    // "replace": Ctrl-R --> Ctrl+Alt-R
    //
    // "insert", "into": Ctrl-I --> Ctrl+Alt-I
    // "insert", "before[implicitElement]": Ctrl-H --> Ctrl+Alt-H
    // "insert", "after[implicitElement]": Ctrl-J --> Ctrl+Alt-J
    //
    // "convert", "[implicitElement]": Ctrl-T --> Ctrl+Alt-C
    // "wrap", "[implicitElement]": Ctrl+Shift-T --> Ctrl+Alt-W
    //
    // "paste", "before[implicitElement]": Ctrl-U --> Ctrl+Shift-V
    // "paste", "after[implicitElement]": Ctrl-W --> Ctrl+Alt-V
    //
    // "editAttributes", "[implicitElement]": Ctrl-E --> Ctrl+Alt-M
    // ---------------------------------------------------------------------------

    const COMMON_DEFAULT_BINDINGS = [
        new Binding(new MouseInput(USER_INPUT_MOUSE_DOWN, USER_INPUT_MOUSE_BUTTON1),
                    "selectAt", "begin"),

        new Binding(new MouseInput(USER_INPUT_MOUSE_DRAG, USER_INPUT_MOUSE_BUTTON1),
                    "selectAt", "extend"),

        new Binding(new MouseInput(USER_INPUT_MOUSE_UP, USER_INPUT_MOUSE_BUTTON1),
                    "selectAt", "end"),

        new Binding(new MouseInput(USER_INPUT_MOUSE_CLICK, USER_INPUT_MOUSE_BUTTON1,
                                   /*modifier*/ 0, /*click*/ 2),
                    "selectText", "word"),

        /* Cannot be tested using Esc x because modal dialog boxes causes
           caretPositionFromPoint to fail.
        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('L')],
                    "selectText", "line"),
        */

        // Tab ---

        new Binding(new KeyDownInput("Tab"),
                    "moveDotTo", "nextTextNode"),

        new Binding(new KeyDownInput("Tab", USER_INPUT_MODIFIER_SHIFT),
                    "moveDotTo", "previousTextNode"),

        // Left, Right ---

        new Binding(new KeyDownInput("ArrowLeft"),
                    "moveDotTo", "previousChar"),

        new Binding(new KeyDownInput("ArrowLeft", USER_INPUT_MODIFIER_SHIFT),
                    "selectTo", "previousChar"),

        new Binding(new KeyDownInput("ArrowRight"),
                    "moveDotTo", "nextChar"),

        new Binding(new KeyDownInput("ArrowRight", USER_INPUT_MODIFIER_SHIFT),
                    "selectTo", "nextChar"),

        // Up, Down ---

        new Binding(new KeyDownInput("ArrowUp"),
                    "moveDotTo", "previousLine"),

        new Binding(new KeyDownInput("ArrowUp", USER_INPUT_MODIFIER_SHIFT),
                    "selectTo", "previousLine"),

        new Binding(new KeyDownInput("ArrowDown"),
                    "moveDotTo", "nextLine"),

        new Binding(new KeyDownInput("ArrowDown", USER_INPUT_MODIFIER_SHIFT),
                    "selectTo", "nextLine"),

        // Home, End ---

        new Binding(new KeyDownInput("Home"),
                    "moveDotTo", "lineBegin"),

        new Binding(new KeyDownInput("Home", USER_INPUT_MODIFIER_SHIFT),
                    "selectTo", "lineBegin"),

        new Binding(new KeyDownInput("End"),
                    "moveDotTo", "lineEnd"),

        new Binding(new KeyDownInput("End", USER_INPUT_MODIFIER_SHIFT),
                    "selectTo", "lineEnd"),

        // Ctrl-Left, Ctrl-Right ---

        new Binding(new KeyDownInput("ArrowLeft", USER_INPUT_MODIFIER_MOD),
                    "moveDotTo", "previousWord"),

        new Binding(new KeyDownInput("ArrowLeft",
                                     (USER_INPUT_MODIFIER_SHIFT |
                                      USER_INPUT_MODIFIER_MOD)),
                    "selectTo", "previousWord"),

        new Binding(new KeyDownInput("ArrowRight", USER_INPUT_MODIFIER_MOD),
                    "moveDotTo", "nextWord"),

        new Binding(new KeyDownInput("ArrowRight",
                                     (USER_INPUT_MODIFIER_SHIFT |
                                      USER_INPUT_MODIFIER_MOD)),
                    "selectTo", "nextWord"),

        // ---

        new Binding(new MouseInput(USER_INPUT_MOUSE_CLICK, USER_INPUT_MOUSE_BUTTON1,
                                   USER_INPUT_MODIFIER_MOD),
                    "selectNodeAt", "orParent"),

        new Binding(new MouseInput(USER_INPUT_MOUSE_CLICK, USER_INPUT_MOUSE_BUTTON1,
                                   USER_INPUT_MODIFIER_SHIFT),
                    "extendSelectionAt", null),

        // Ctrl-Up, Ctrl-Down ---

        new Binding(new KeyDownInput("ArrowUp", USER_INPUT_MODIFIER_MOD),
                    "selectNode", "parentOrNode"),

        new Binding(new KeyDownInput("ArrowDown", USER_INPUT_MODIFIER_MOD),
                    "selectNode", "childOrNone"),

        // Shift-Ctrl-Up, Shift-Ctrl-Down ---

        new Binding(new KeyDownInput("ArrowUp",
                                     (USER_INPUT_MODIFIER_SHIFT |
                                      USER_INPUT_MODIFIER_MOD)),
                    "selectNode", "previousSibling[implicitElement]"),

        new Binding(new KeyDownInput("ArrowDown",
                                     (USER_INPUT_MODIFIER_SHIFT |
                                      USER_INPUT_MODIFIER_MOD)),
                    "selectNode", "nextSibling[implicitElement]"),

        // Esc Left, Esc Right, Esc Down ---

        new Binding([new KeyDownInput("Escape"),
                     new KeyDownInput("ArrowLeft")],
                    "selectNode", "extendToPreviousSiblingOrElement"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyDownInput("ArrowRight")],
                    "selectNode", "extendToNextSiblingOrElement"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyDownInput("ArrowDown")],
                    "selectNode", "children[implicitElement]"),

        // Esc Esc ---

        new Binding([new KeyDownInput("Escape"),
                     new KeyDownInput("Escape")],
                    "cancelSelection", null),

        // Ctrl-L ---

        new Binding(new KeyDownInput("L", USER_INPUT_MODIFIER_MOD),
                    "center", null),

        // ---

        // "followLinkAt" client-side command invoking
        // server-side "${c}followLinkAt".
        new Binding(new MouseInput(USER_INPUT_MOUSE_CLICK, USER_INPUT_MOUSE_BUTTON1,
                                   (USER_INPUT_MODIFIER_MOD|
                                    USER_INPUT_MODIFIER_ALT)),
                    "followLinkAt", null),

        // Copy ---

        new Binding(new KeyDownInput("C", USER_INPUT_MODIFIER_MOD),
                    "copy", "[implicitElement]"),

        new Binding(new KeyDownInput("C",
                                     (USER_INPUT_MODIFIER_SHIFT |
                                      USER_INPUT_MODIFIER_MOD)),
                    "copyChars", "[separateParagraphs]"),

        // ---

        new Binding(new KeyDownInput("Delete"),
                    "deleteSelectionOrDeleteChar", null),

        new Binding(new KeyDownInput("Backspace"),
                    "deleteSelectionOrDeleteChar", "backwards"),

        new Binding(new KeyDownInput("Delete",
                                     USER_INPUT_MODIFIER_MOD),
                    "deleteWord", null),

        new Binding(new KeyDownInput("Backspace",
                                     USER_INPUT_MODIFIER_MOD),
                    "deleteWord", "backwards"),

        // CTRL even on the Mac.
        new Binding(new KeyDownInput(" ", USER_INPUT_MODIFIER_CTRL),
                    "insertString", "\u00A0"),

        new Binding(new KeyDownInput("Enter"),
                    "insertControlChar", "\n"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyDownInput("Tab")],
                    "insertControlChar", "\t"),

        new Binding(new KeyDownInput("K", USER_INPUT_MODIFIER_MOD),
                    "delete", "[implicitElement]"),

        new Binding(new KeyDownInput("X", USER_INPUT_MODIFIER_MOD),
                    "cut", "[implicitElement]"),

        new Binding(new KeyDownInput("V", USER_INPUT_MODIFIER_MOD),
                    "paste", "toOrInto"),

        new Binding(new KeyDownInput("Z", USER_INPUT_MODIFIER_MOD),
                    "undo", null),

        new Binding(new KeyDownInput("Y", USER_INPUT_MODIFIER_MOD),
                    "redo", null),

        new Binding(new KeyDownInput("A", USER_INPUT_MODIFIER_MOD),
                    "repeat", null),

        new Binding(new KeyDownInput("A", (USER_INPUT_MODIFIER_SHIFT |
                                           USER_INPUT_MODIFIER_MOD)),
                    "listRepeatable", null),

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('x')],
                    "execute", null),

        // Character case ---

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('l')],
                    "convertCase", "lower"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('u')],
                    "convertCase", "upper"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('c')],
                    "convertCase", "capital"),

        // Insert char by name ---

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('n')],
                    "insertCharByName", "[DocBookIfNone]"),

        // ---

        new Binding(new MouseInput(USER_INPUT_MOUSE_CLICK,USER_INPUT_MOUSE_BUTTON2),
                    "pastePrimarySelection", null),

        new Binding(new MouseInput(USER_INPUT_CONTEXT_MENU, /*button*/ 0),
                    "contextualMenu", null),
    ];

    const OTHER_DEFAULT_BINDINGS = [
        // Ins key ---

        new Binding(new KeyDownInput("Insert"),
                    "insertTextOrMoveDot", "after"),

        new Binding(new KeyDownInput("Insert", USER_INPUT_MODIFIER_SHIFT),
                    "insertTextOrMoveDot", "before"),

        new Binding(new KeyDownInput("Insert", USER_INPUT_MODIFIER_MOD),
                    "insertNode", "sameElementAfter[implicitElement]"),

        new Binding(new KeyDownInput("Insert",
                                     (USER_INPUT_MODIFIER_SHIFT |
                                      USER_INPUT_MODIFIER_MOD)),
                    "insertNode", "sameElementBefore[implicitElement]"),

        // Navigation ---

        new Binding(new KeyDownInput("ArrowLeft",
                                     (USER_INPUT_MODIFIER_ALT |
                                      USER_INPUT_MODIFIER_CTRL)),
                    "go", "back"),

        new Binding(new KeyDownInput("ArrowRight",
                                     (USER_INPUT_MODIFIER_ALT |
                                      USER_INPUT_MODIFIER_CTRL)),
                    "go", "forward"),

        // Basic editing. See above comment. ---

        new Binding(new KeyDownInput("R", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "replace", "[implicitElement]"),

        new Binding(new KeyDownInput("I", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "insert", "into"),

        new Binding(new KeyDownInput("H", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "insert", "before[implicitElement]"),

        new Binding(new KeyDownInput("J", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "insert", "after[implicitElement]"),

        new Binding(new KeyDownInput("C", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "convert", "[implicitElement]"),

        new Binding(new KeyDownInput("W", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "wrap", "[implicitElement]"),

        new Binding(new KeyDownInput("V", (USER_INPUT_MODIFIER_SHIFT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "paste", "before[implicitElement]"),

        new Binding(new KeyDownInput("V", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "paste", "after[implicitElement]"),

        new Binding(new KeyDownInput("M", (USER_INPUT_MODIFIER_ALT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "editAttributes", "[implicitElement]"),
    ];

    const MAC_DEFAULT_BINDINGS = [
        // The Mac keyboard has no Del key and no Ins key.
        // Just having Backspace to delete is OK (and Ctrl-D seems to be
        // equivalent to Del).
        // Use F1 instead of Ins on the Mac. ---

        new Binding(new KeyDownInput("F1"),
                    "insertTextOrMoveDot", "after"),

        new Binding(new KeyDownInput("F1", USER_INPUT_MODIFIER_SHIFT),
                    "insertTextOrMoveDot", "before"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('s')],
                    "insertNode", "sameElementAfter[implicitElement]"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('S')],
                    "insertNode", "sameElementBefore[implicitElement]"),

        // Navigation ---

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('<')],
                    "go", "back"),

        new Binding([new KeyDownInput("Escape"),
                     new KeyPressInput('>')],
                    "go", "forward"),

        // Basic editing. Use the same *CTRL* bindings as on XXE Desktop. ---

        new Binding(new KeyDownInput("R", USER_INPUT_MODIFIER_CTRL),
                    "replace", "[implicitElement]"),

        new Binding(new KeyDownInput("I", USER_INPUT_MODIFIER_CTRL),
                    "insert", "into"),

        new Binding(new KeyDownInput("H", USER_INPUT_MODIFIER_CTRL),
                    "insert", "before[implicitElement]"),

        new Binding(new KeyDownInput("J", USER_INPUT_MODIFIER_CTRL),
                    "insert", "after[implicitElement]"),

        new Binding(new KeyDownInput("T", USER_INPUT_MODIFIER_CTRL),
                    "convert", "[implicitElement]"),

        new Binding(new KeyDownInput("T", (USER_INPUT_MODIFIER_SHIFT |
                                           USER_INPUT_MODIFIER_CTRL)),
                    "wrap", "[implicitElement]"),

        new Binding(new KeyDownInput("U", USER_INPUT_MODIFIER_CTRL),
                    "paste", "before[implicitElement]"),

        new Binding(new KeyDownInput("W", USER_INPUT_MODIFIER_CTRL),
                    "paste", "after[implicitElement]"),

        new Binding(new KeyDownInput("E", USER_INPUT_MODIFIER_CTRL),
                    "editAttributes", "[implicitElement]"),
    ];

    /**
     * The registry of all local commands.
     * <p>Local commands auto-register themselves here as follows:
     * <pre>ALL_LOCAL_COMMANDS[<i>command_name</i>] = new <i>class_name</i>()</pre>
     */
    export const ALL_LOCAL_COMMANDS = {};

    class Bindings {
        static compile(bindings=null) {
            let builtInBindings;
            if (PLATFORM_IS_MAC_OS) {
                builtInBindings =
                    COMMON_DEFAULT_BINDINGS.concat(MAC_DEFAULT_BINDINGS);
            } else {
                builtInBindings =
                    COMMON_DEFAULT_BINDINGS.concat(OTHER_DEFAULT_BINDINGS);
            }

            // ---

            let allBindings = [];
            if (bindings === null) {
                // No configuration specific bindings.
                for (let b of builtInBindings) {
                    allBindings.push(Bindings.bindCommand(b));
                }
            } else {
                let userInputToBinding = {};
                for (let b of builtInBindings) {
                  userInputToBinding[b.getUserInputId()] = Bindings.bindCommand(b);
                }
                for (let b of bindings) {
                  userInputToBinding[b.getUserInputId()] = Bindings.bindCommand(b);
                }
                allBindings = Object.values(userInputToBinding);
            }

            return allBindings;
        }

        static bindCommand(binding) {
            let cmd = ALL_LOCAL_COMMANDS[binding.commandName];
            if (!cmd) {
                cmd = new RemoteCommand(binding.commandName);
            }
            return new Binding(binding.userInput,
                               binding.commandName, binding.commandParams, cmd);
        }
    }