Source: xxe/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);
    }
}