├── .editorconfig ├── .eslintignore ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── package.json ├── src ├── default-keymap.js ├── index.js └── predefined-menu-commands.js └── test └── index.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [{package.json,*.js,*.yml}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | coverage 4 | npm-debug.log 5 | index.es5.js 6 | .nyc_output 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - v6 4 | before_script: 5 | - 'npm run lint' 6 | after_script: 7 | - 'npm i -g coveralls' 8 | - 'nyc ava' 9 | - 'coveralls < coverage/lcov.info' 10 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## 0.1.0 7 | 8 | * First semi-working version 9 | * [Submitted PR to Hyper to get it working on 100%](https://github.com/zeit/hyper/pull/925) 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Vladimir Starkov (https://iamstarkov.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyper-keymap 2 | 3 | [![NPM version][npm-image]][npm-url] 4 | [![Build Status][travis-image]][travis-url] 5 | [![Coveralls Status][coveralls-image]][coveralls-url] 6 | [![Dependency Status][depstat-image]][depstat-url] 7 | 8 | > Hotkeys management for Hyper 9 | 10 | ## Deprecated 11 | 12 | Sorry, everyone https://github.com/zeit/hyper/pull/925#issuecomment-278622008. 13 | 14 | look here instead https://github.com/zeit/hyper/pull/1509 15 | 16 | ## Install 17 | 18 | Open your Hyper preferences and add `hyper-keymap` to plugin list: 19 | 20 | ```js 21 | plugins: [ 22 | 'hyper-keymap' 23 | ], 24 | ``` 25 | 26 | Or use [hpm][hpm] 27 | 28 | npm install -g hpm-cli 29 | hpm i hyper-keymap 30 | 31 | 32 | [hpm]: https://npm.im/hpm-cli 33 | 34 | ## 🤔 Caveats 35 | 36 | Some hotkeys can still not be working, like `CmdOrCtrl+Alt+Left`, 37 | because right now Hyper (`v0.8.3`) is binding this and several others keys. 38 | Those keys are semi-working. 39 | 40 | Though, 🎉 good news, i submitted [pull-request][pr] to Hyper to fix that. 41 | Check it out, and if you want comment there what do you think about it 🗯. Any feedback is welcomed. 42 | 43 | [pr]: https://github.com/zeit/hyper/pull/925 44 | 45 | ## Usage 46 | 47 | There is a [default keymap](./src/default-keymap.js), which is used by Hyper. 48 | 49 | In order to change hotkeys you are unhappy with, add `keymap` object to your config: 50 | 51 | ```js 52 | module.exports = { 53 | config: { 54 | 55 | // other configuration 56 | 57 | keymap: { 58 | // just examples, see below for detailed explanation 59 | 'CmdOrCtrl+Alt+Left': 'prev-pane', 60 | 'CmdOrCtrl+Alt+Right': 'next-pane', 61 | }, 62 | }, 63 | 64 | plugins: [ 65 | 'hyper-keymap' 66 | // you can have another plugins as well 67 | ], 68 | }; 69 | ``` 70 | 71 | Keymap is an object of Electron's accelerator and Hyper's command. 72 | 73 | ```js 74 | keymap: { 75 | // 'accelerator' : 'Hyper command' 76 | 'CmdOrCtrl+Alt+Left': 'prev-pane', 77 | } 78 | ``` 79 | 80 | Your keymap has prio over default one. 81 | 82 | ## Electron's accelerators 83 | 84 | It is a way to define keyboard shortcuts. 85 | 86 | Accelerators can contain multiple modifiers and key codes, combined by the `+` character. 87 | 88 | Examples: 89 | 90 | * `CommandOrControl+A` 91 | * `CommandOrControl+Shift+Z` 92 | 93 | Check out Electron's [accelerators][elacc] documentation. 94 | 95 | [elacc]: http://electron.atom.io/docs/api/accelerator/ 96 | 97 | ## List of supported Hyper commands 98 | 99 | * `show-settings` 100 | * `new-window` 101 | * `new-tab` 102 | * `split-vertical` 103 | * `split-horizontal` 104 | * `close` 105 | * `close-window` 106 | * `clear` 107 | * `show-settings` 108 | * `reload` 109 | * `reload-full` 110 | * `toggle-devtools` 111 | * `zoom-reset` 112 | * `zoom-in` 113 | * `zoom-out` 114 | * `update-plugins` 115 | * `prev-tab` 116 | * `next-tab` 117 | * `prev-pane` 118 | * `next-pane` 119 | 120 | ## License 121 | 122 | MIT © [Vladimir Starkov](https://iamstarkov.com) 123 | 124 | [npm-url]: https://npmjs.org/package/hyper-keymap 125 | [npm-image]: https://img.shields.io/npm/v/hyper-keymap.svg?style=flat-square 126 | 127 | [travis-url]: https://travis-ci.org/iamstarkov/hyper-keymap 128 | [travis-image]: https://img.shields.io/travis/iamstarkov/hyper-keymap.svg?style=flat-square 129 | 130 | [coveralls-url]: https://coveralls.io/r/iamstarkov/hyper-keymap 131 | [coveralls-image]: https://img.shields.io/coveralls/iamstarkov/hyper-keymap.svg?style=flat-square 132 | 133 | [depstat-url]: https://david-dm.org/iamstarkov/hyper-keymap 134 | [depstat-image]: https://david-dm.org/iamstarkov/hyper-keymap.svg?style=flat-square 135 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hyper-keymap", 3 | "version": "0.1.0", 4 | "description": "Hotkeys management for Hyper", 5 | "main": "src", 6 | "files": [ 7 | "src" 8 | ], 9 | "scripts": { 10 | "test": "ava", 11 | "lint": "eslint .", 12 | "coverage": "nyc ava" 13 | }, 14 | "engines": { 15 | "node": ">=6.5.0" 16 | }, 17 | "eslintConfig": { 18 | "extends": [ 19 | "pedant" 20 | ], 21 | "plugins": [ 22 | "require-path-exists" 23 | ], 24 | "env": { 25 | "node": true, 26 | "es6": true 27 | }, 28 | "parserOptions": { 29 | "ecmaVersion": 2016, 30 | "sourceType": "module", 31 | "ecmaFeatures": { 32 | "impliedStrict": true, 33 | "jsx": true 34 | } 35 | } 36 | }, 37 | "nyc": { 38 | "include": [ 39 | "src/index.js" 40 | ], 41 | "reporter": [ 42 | "text", 43 | "lcov" 44 | ] 45 | }, 46 | "repository": { 47 | "type": "git", 48 | "url": "git+https://github.com/iamstarkov/hyper-keymap.git" 49 | }, 50 | "keywords": [ 51 | "hyper", 52 | "hyperterm", 53 | "hyper-plugin", 54 | "hyperterm-plugin", 55 | "keymap", 56 | "hotkey", 57 | "hotkeys", 58 | "shortcut", 59 | "shortcuts", 60 | "keybinding", 61 | "kebindings", 62 | "keymap", 63 | "keymaps" 64 | ], 65 | "author": "Vladimir Starkov (https://iamstarkov.com)", 66 | "license": "MIT", 67 | "bugs": { 68 | "url": "https://github.com/iamstarkov/hyper-keymap/issues" 69 | }, 70 | "homepage": "https://github.com/iamstarkov/hyper-keymap#readme", 71 | "dependencies": { 72 | "ramda": "^0.22.1" 73 | }, 74 | "devDependencies": { 75 | "ava": "^0.16.0", 76 | "eslint": "^3.8.1", 77 | "eslint-config-pedant": "^0.8.0", 78 | "eslint-plugin-require-path-exists": "^1.1.5", 79 | "nyc": "^8.3.2" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/default-keymap.js: -------------------------------------------------------------------------------- 1 | const isMac = process.platform === 'darwin'; 2 | 3 | module.exports = { 4 | 'CmdOrCtrl+,': 'show-settings', 5 | 6 | 'CmdOrCtrl+N': 'new-window', 7 | 'CmdOrCtrl+T': 'new-tab', 8 | 9 | [isMac 10 | ? 'CmdOrCtrl+D' 11 | : 'Ctrl+Shift+E']: 'split-vertical', 12 | [isMac 13 | ? 'CmdOrCtrl+Shift+D' 14 | : 'Ctrl+Shift+O']: 'split-horizontal', 15 | 16 | 'CmdOrCtrl+W': 'close', 17 | 'CmdOrCtrl+shift+W': 'close-window', 18 | 19 | 'CmdOrCtrl+K': 'clear', 20 | 21 | 'CmdOrCtrl+R': 'reload', 22 | 'CmdOrCtrl+Shift+R': 'reload-full', 23 | 24 | [isMac 25 | ? 'Alt+Command+I' 26 | : 'Ctrl+Shift+I']: 'toggle-devtools', 27 | 28 | 'CmdOrCtrl+0': 'zoom-reset', 29 | 'CmdOrCtrl+plus': 'zoom-in', 30 | 'CmdOrCtrl+-': 'zoom-out', 31 | 32 | 'CmdOrCtrl+Shift+U': 'update-plugins', 33 | 34 | 'CmdOrCtrl+Alt+Left': 'prev-tab', 35 | 'CmdOrCtrl+Alt+Right': 'next-tab', 36 | 37 | 'ctrl+shift+tab': 'prev-tab', 38 | 'ctrl+tab': 'next-tab', 39 | 40 | 'CmdOrCtrl+Shift+Left': 'prev-tab', 41 | 'CmdOrCtrl+Shift+Right': 'next-tab', 42 | 43 | 'CmdOrCtrl+Shift+[': 'prev-tab', 44 | 'CmdOrCtrl+Shift+]': 'next-tab', 45 | 46 | 'Ctrl+Shift+Alt+Tab': 'prev-pane', 47 | 'Ctrl+Alt+Tab': 'next-pane', 48 | 49 | /** 50 | * Wait until relevant commands will be implemented in Hyper's rpc 51 | 'CmdOrCtrl+1': 'tab-show-0', 52 | 'CmdOrCtrl+2': 'tab-show-1', 53 | 'CmdOrCtrl+3': 'tab-show-2', 54 | 'CmdOrCtrl+4': 'tab-show-3', 55 | 'CmdOrCtrl+5': 'tab-show-4', 56 | 'CmdOrCtrl+6': 'tab-show-5', 57 | 'CmdOrCtrl+7': 'tab-show-6', 58 | 'CmdOrCtrl+8': 'tab-show-7', 59 | 'CmdOrCtrl+9': 'tab-show-last', 60 | 61 | 'alt+left': 'move-word-left' 62 | 'alt+right': 'move-word-right' 63 | 'alt+backspace': 'delete-word-left' 64 | 'alt+del': 'delete-word-right' 65 | 66 | 'CmdOrCtrl+backspace': 'delete-line' 67 | 'CmdOrCtrl+left': 'move-to-line-start' 68 | 'CmdOrCtrl+right': 'move-to-line-end' 69 | 'CmdOrCtrl+a': 'select-all' 70 | */ 71 | }; 72 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const R = require('ramda'); 2 | const defaultKeymap = require('./default-keymap'); 3 | const predefinedMenuCommands = require('./predefined-menu-commands'); 4 | 5 | let storage = {}; 6 | 7 | const capture = (key, _) => config => { 8 | _[key] = config[key] || {}; 9 | }; 10 | 11 | const acceleratorsReducerBy = inputCommand => 12 | (accelerators, [accelerator, command]) => 13 | accelerators.concat((inputCommand === command) ? [accelerator] : []); 14 | 15 | const findAccelerators = R.curry((keymap, inputCommand) => 16 | R.toPairs(keymap).reduce(acceleratorsReducerBy(inputCommand) , []) 17 | ); 18 | 19 | const addCommand = predefined => { 20 | const labelsToPredefine = R.pluck('label', predefined); 21 | const findCommand = item => R.pipe( 22 | R.find(R.propEq('label', item.label)), 23 | R.prop('command') 24 | )(predefined); 25 | 26 | return item => { 27 | if (!R.contains(item.label, labelsToPredefine)) { 28 | return item; 29 | } 30 | return R.merge(item, { command: findCommand(item) }); 31 | }; 32 | } 33 | 34 | const bindUserAccelerators = ({ userKeymapFn, defaultKeymapFn }) => item => { 35 | const { command } = item; 36 | if (!command) { 37 | return item; 38 | } 39 | 40 | const userAccelerator = findAccelerators(userKeymapFn(), command)[0]; 41 | const defaultAccelerator = findAccelerators(defaultKeymapFn(), command)[0]; 42 | 43 | return R.merge(item, { 44 | accelerator: (userAccelerator || defaultAccelerator), 45 | }); 46 | }; 47 | 48 | const rebindConflictedAccelerators = ({ userKeymapFn, defaultKeymapFn }) => item => { 49 | const { accelerator, command } = item; 50 | if (!command) { 51 | return item; 52 | } 53 | 54 | const userAccelerators = R.keys(userKeymapFn()); 55 | const userCommands = R.values(userKeymapFn()); 56 | const didUserUseThisAccelerator = userAccelerators.includes(accelerator); 57 | const didUserRedefineCommand = userCommands.includes(command); 58 | const isConflicted = didUserUseThisAccelerator && !didUserRedefineCommand; 59 | 60 | if (!isConflicted) { 61 | return item; 62 | } 63 | 64 | const defaultAccelerators = findAccelerators(defaultKeymapFn(), command); 65 | const defaultAcceleratorsWithoutUserOnes = R.without(userAccelerators, defaultAccelerators); 66 | const newAccelerator = defaultAcceleratorsWithoutUserOnes[0]; 67 | 68 | return R.merge(item, { accelerator: newAccelerator }); 69 | } 70 | 71 | const decorateMenuWith = fn => R.map(R.over( 72 | R.lensProp('submenu'), 73 | R.map(fn) 74 | )); 75 | 76 | module.exports = { 77 | // helpers 78 | capture, 79 | findAccelerators, 80 | decorateMenuWith, 81 | addCommand, 82 | bindUserAccelerators, 83 | rebindConflictedAccelerators, 84 | 85 | // Hyper's stuff 86 | decorateConfig: R.tap(capture('keymap', storage)), 87 | decorateMenu: decorateMenuWith(R.pipe( 88 | addCommand(predefinedMenuCommands), 89 | bindUserAccelerators({ 90 | userKeymapFn: R.always(storage.keymap), 91 | defaultKeymapFn: R.always(defaultKeymap), 92 | }), 93 | rebindConflictedAccelerators({ 94 | userKeymapFn: R.always(storage.keymap), 95 | defaultKeymapFn: R.always(defaultKeymap), 96 | }) 97 | )) 98 | } 99 | -------------------------------------------------------------------------------- /src/predefined-menu-commands.js: -------------------------------------------------------------------------------- 1 | const isMac = process.platform === 'darwin'; 2 | 3 | module.exports = [{ 4 | label: 'Preferences...', 5 | command: 'show-settings', 6 | }, { 7 | label: 'New Window', 8 | command: 'new-window', 9 | }, { 10 | label: 'New Tab', 11 | command: 'new-tab', 12 | }, { 13 | label: 'Split Vertically', 14 | command: 'split-vertical', 15 | }, { 16 | label: 'Split Horizontally', 17 | command: 'split-horizontal', 18 | }, { 19 | label: 'Close', 20 | command: 'close', 21 | }, { 22 | label: isMac ? 'Close Terminal Window' : 'Quit', 23 | command: 'close-window', 24 | }, { 25 | label: 'Clear', 26 | command: 'clear', 27 | }, { 28 | label: 'Preferences...', 29 | command: 'show-settings', 30 | }, { 31 | label: 'Reload', 32 | command: 'reload', 33 | }, { 34 | label: 'Full Reload', 35 | command: 'reload-full', 36 | }, { 37 | label: 'Toggle Developer Tools', 38 | command: 'toggle-devtools', 39 | }, { 40 | label: 'Reset Zoom Level', 41 | command: 'zoom-reset', 42 | }, { 43 | label: 'Zoom In', 44 | command: 'zoom-in', 45 | }, { 46 | label: 'Zoom Out', 47 | command: 'zoom-out', 48 | }, { 49 | label: 'Update All Now', 50 | command: 'update-plugins', 51 | }, { 52 | label: 'Show Previous Tab', 53 | command: 'prev-tab', 54 | }, { 55 | label: 'Show Next Tab', 56 | command: 'next-tab', 57 | }, { 58 | label: 'Select Previous Pane', 59 | command: 'prev-pane', 60 | }, { 61 | label: 'Select Next Pane', 62 | command: 'next-pane', 63 | }]; 64 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import R from 'ramda'; 3 | import { 4 | capture, 5 | findAccelerators, 6 | decorateMenuWith, 7 | addCommand, 8 | bindUserAccelerators, 9 | rebindConflictedAccelerators 10 | } from '../'; 11 | 12 | 13 | test('capture', t => { 14 | let storage = {}; 15 | const config = { keymap: { a: 'b' } }; 16 | capture('keymap', storage)(config); 17 | 18 | t.deepEqual(storage.keymap, { a: 'b' }); 19 | }); 20 | 21 | test('capture undefined keymap results in empty object', t => { 22 | let storage = {}; 23 | const config = {}; 24 | capture('keymap', storage)(config); 25 | 26 | t.deepEqual(storage.keymap, {}); 27 | }); 28 | 29 | test('findAccelerators', t => { 30 | const keymap = { 31 | 'CmdOrCtrl+Alt+Left': 'prev-tab', 32 | 'CmdOrCtrl+Alt+Right': 'next-tab', 33 | 'CmdOrCtrl+Shift+Left': 'prev-tab', 34 | 'CmdOrCtrl+Shift+Right': 'next-tab', 35 | }; 36 | 37 | const actual = findAccelerators(keymap, 'next-tab'); 38 | const expected = [ 'CmdOrCtrl+Alt+Right', 'CmdOrCtrl+Shift+Right' ]; 39 | 40 | t.deepEqual(actual, expected); 41 | }); 42 | 43 | test('decorateMenuWith', t => { 44 | const menu = [ 45 | { submenu: [ 1, 2 ] }, 46 | { submenu: [ 3, 4 ] } 47 | ]; 48 | 49 | const actual = decorateMenuWith(R.multiply(10))(menu) 50 | const expected = [ 51 | { submenu: [ 10, 20 ] }, 52 | { submenu: [ 30, 40 ] } 53 | ]; 54 | 55 | t.deepEqual(actual, expected); 56 | }); 57 | 58 | test('addCommand if exists in predefined', t => { 59 | const predefined = [ 60 | { role: 'about' }, 61 | { label: 'LBL1', command: 'CMD1' }, 62 | { type: 'separator' } 63 | ]; 64 | const item = { label: 'LBL1', click() {} }; 65 | 66 | const actual = addCommand(predefined)(item); 67 | const expected = { label: 'LBL1', click() {}, command: 'CMD1' }; 68 | 69 | t.is(actual.command, expected.command); 70 | }); 71 | 72 | test('dont addCommand if not exists in predefined', t => { 73 | const item = { label: 'LBL1', click() {} }; 74 | 75 | const actual = addCommand([])(item); 76 | const expected = { label: 'LBL1', click() {}}; 77 | 78 | t.is(actual.command, expected.command); 79 | }); 80 | 81 | test('bindUserAccelerators: ignore item wo/ "command" prop', t => { 82 | const bindUserAcceleratorsFn = bindUserAccelerators({}); 83 | const items = [ 84 | { role: 'about' }, 85 | { type: 'separator' } 86 | ]; 87 | 88 | const actual = items.map(bindUserAcceleratorsFn); 89 | const expected = items; 90 | 91 | t.deepEqual(actual, expected); 92 | }); 93 | 94 | test('bindUserAccelerators: users keymap has prio over default', t => { 95 | const bindUserAcceleratorsFn = bindUserAccelerators({ 96 | userKeymapFn: R.always({ 'A': 'cmd' }), 97 | defaultKeymapFn: R.always({ 'B': 'cmd', 'C': 'cmd' }), 98 | }); 99 | const item = { command: 'cmd' }; 100 | 101 | const actual = bindUserAcceleratorsFn(item); 102 | const expected = { command: 'cmd', accelerator: 'A' }; 103 | 104 | t.deepEqual(actual, expected); 105 | }); 106 | 107 | test('bindUserAccelerators: default keymap is used when there is no suitable user keymap for relevand command', t => { 108 | const bindUserAcceleratorsFn = bindUserAccelerators({ 109 | userKeymapFn: R.always({}), 110 | defaultKeymapFn: R.always({ 'B': 'cmd', 'C': 'cmd' }), 111 | }); 112 | const item = { command: 'cmd' }; 113 | 114 | const actual = bindUserAcceleratorsFn(item); 115 | const expected = { command: 'cmd', accelerator: 'B' }; 116 | 117 | t.deepEqual(actual, expected); 118 | }); 119 | 120 | test('rebindConflictedAccelerators: ignore item wo/ "command" prop', t => { 121 | const rebindConflictedAcceleratorsFn = rebindConflictedAccelerators({}); 122 | const items = [ 123 | { role: 'about' }, 124 | { type: 'separator' } 125 | ]; 126 | 127 | const actual = items.map(rebindConflictedAcceleratorsFn); 128 | const expected = items; 129 | 130 | t.deepEqual(actual, expected); 131 | }); 132 | 133 | test('rebindConflictedAccelerators: use another default accelerator if its conflicted', t => { 134 | const rebindConflictedAcceleratorsFn = rebindConflictedAccelerators({ 135 | userKeymapFn: R.always({ 'CmdOrCtrl+Alt+Right': 'next-pane' }), 136 | defaultKeymapFn: R.always({ 137 | 'CmdOrCtrl+Alt+Right': 'next-tab', 138 | 'ctrl+tab': 'next-tab', 139 | 'CmdOrCtrl+Shift+Right': 'next-tab', 140 | 'CmdOrCtrl+Shift+]': 'next-tab', 141 | }), 142 | }); 143 | const items = [ 144 | { command: 'next-tab', accelerator: 'CmdOrCtrl+Alt+Right' }, 145 | { command: 'next-pane', accelerator: 'CmdOrCtrl+Alt+Right' }, 146 | ]; 147 | 148 | const actual = items.map(rebindConflictedAcceleratorsFn); 149 | const expected = [ 150 | { command: 'next-tab', accelerator: 'ctrl+tab' }, 151 | { command: 'next-pane', accelerator: 'CmdOrCtrl+Alt+Right' }, 152 | ]; 153 | 154 | t.deepEqual(actual, expected); 155 | }); 156 | --------------------------------------------------------------------------------