aboutsummaryrefslogtreecommitdiff
path: root/modules/firefox.home/chrome/globalKeybindings.uc.js
blob: 95d8a10f5a20e2ab192091f8efadeaa13e9ea702 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// ==UserScript==
// @name                 Global Keybindings
// @version              1.0
// @description          Setup global keybindings on all windows
// @startup              UC.globalKeybindings.init(win);
// @shutdown             UC.globalKeybindings.destroy();
// ==/UserScript==


(() => {
  // Configure global keybindings
  const KEYBINDINGS = () => ({
    RELEASE: [
      [ctrl(key('j')), nextTab()],
      [ctrl(key('k')), prevTab()],
      [alt(key('j')), moveSelectedTabBy(+1)],
      [alt(key('k')), moveSelectedTabBy(-1)],
      [ctrl(shift(key('J'))), moveSelectedTabBy(+1)],
      [ctrl(shift(key('K'))), moveSelectedTabBy(-1)],

      ...(Array.from({ length: 10 }, (_, idx) =>
        [ctrl(key(idx === 9 ? 0 : idx + 1)), tabIndex(idx)],
      )),
    ],
    PRESS: [
      // Prevent the default browser's action
      [ctrl(shift(key('J'))), preventDefault()],
      [ctrl(shift(key('K'))), preventDefault()],
    ],
  });

  const nextTab = () => () => updateTabIndex((n, len) => (n + 1) % len);
  const prevTab = () => () => updateTabIndex((n, len) => n === 0 ? len - 1 : n - 1);
  const tabIndex = idx => () => updateTabIndex((_n, _len) => idx)
  const preventDefault = f => e => { e.preventDefault(); f?.(e); };
  const moveSelectedTabBy = incr => () => updateSelectedTabIndex(incr);

  const ctrl = b => e => b(e) && e.ctrlKey;
  const alt = b => e => b(e) && e.altKey;
  const shift = b => e => b(e) && e.shiftKey;
  const key = key => e => `${e.key}` === `${key}`;

  const updateTabIndex = (f) => {
    const newIndex = f(gBrowser.tabContainer.selectedIndex, gBrowser.tabs.length)
    if (typeof newIndex !== 'number') return;
    if (newIndex >= gBrowser.tabContainer.allTabs.length) return;
    if (newIndex < 0) return;
    gBrowser.selectTabAtIndex(newIndex);
  };

  const updateSelectedTabIndex = (incr) => {
    if (typeof incr !== 'number') return;
    if (incr === +1) return gBrowser.moveTabForward();
    if (incr === -1) return gBrowser.moveTabBackward();
    const newIndex = gBrowser.tabContainer.selectedIndex + incr;
    if (newIndex >= gBrowser.tabContainer.allTabs.length) return;
    if (newIndex < 0) return;
    gBrowser.moveTabTo(gBrowser.selectedTab, newIndex)
  };

  const module = {
    enabled: true,

    evaluateKeybindings: (bindings, e) => {
      if (!module.enabled) return;

      for (const [isKey, action] of bindings) {
        if (isKey(e)) {
          const shouldContinue = action(e);
          if (!shouldContinue) break;
        }
      }
    },

    handleKeyUpEvent: e => module.evaluateKeybindings(KEYBINDINGS().RELEASE, e),
    handleKeyDownEvent: e => module.evaluateKeybindings(KEYBINDINGS().PRESS, e),

    init(win) {
      let observe = () => {
        Services.obs.removeObserver(observe, 'browser-window-before-show');
        win.addEventListener('keyup', module.handleKeyUpEvent, true);
        win.addEventListener('keydown', module.handleKeyDownEvent, true);
      }

      if (win.__SSi) {
        win.addEventListener('keyup', module.handleKeyUpEvent, true);
        win.addEventListener('keydown', module.handleKeyDownEvent, true);
      } else {
        Services.obs.addObserver(observe, 'browser-window-before-show');
      }
    },
    destroy() {
      _uc.windows((_doc, win) => {
        win.removeEventListener('keyup', module.handleKeyUpEvent, true);
        win.removeEventListener('keydown', module.handleKeyDownEvent, true);
      });
      delete UC.globalKeybindings;
    },
  };

  UC.globalKeybindings = module;
})();