const _ = require('underscore-plus') const ItemSpecificities = new WeakMap() // Add an item to a menu, ensuring separators are not duplicated. function addItemToMenu (item, menu) { const lastMenuItem = _.last(menu) const lastMenuItemIsSpearator = lastMenuItem && lastMenuItem.type === 'separator' if (!(item.type === 'separator' && lastMenuItemIsSpearator)) { menu.push(item) } } function merge (menu, item, itemSpecificity = Infinity) { item = cloneMenuItem(item) ItemSpecificities.set(item, itemSpecificity) const matchingItemIndex = findMatchingItemIndex(menu, item) if (matchingItemIndex === -1) { addItemToMenu(item, menu) return } const matchingItem = menu[matchingItemIndex] if (item.submenu != null) { for (let submenuItem of item.submenu) { merge(matchingItem.submenu, submenuItem, itemSpecificity) } } else if (itemSpecificity && itemSpecificity >= ItemSpecificities.get(matchingItem)) { menu[matchingItemIndex] = item } } function unmerge (menu, item) { const matchingItemIndex = findMatchingItemIndex(menu, item) if (matchingItemIndex === -1) { return } const matchingItem = menu[matchingItemIndex] if (item.submenu != null) { for (let submenuItem of item.submenu) { unmerge(matchingItem.submenu, submenuItem) } } if (matchingItem.submenu == null || matchingItem.submenu.length === 0) { menu.splice(matchingItemIndex, 1) } } function findMatchingItemIndex (menu, { type, label, submenu }) { if (type === 'separator') { return -1 } for (let index = 0; index < menu.length; index++) { const item = menu[index] if ( normalizeLabel(item.label) === normalizeLabel(label) && (item.submenu != null) === (submenu != null) ) { return index } } return -1 } function normalizeLabel (label) { if (label == null) { return } return process.platform === 'darwin' ? label : label.replace(/&/g, '') } function cloneMenuItem (item) { item = _.pick( item, 'type', 'label', 'enabled', 'visible', 'command', 'submenu', 'commandDetail', 'role', 'accelerator', 'before', 'after', 'beforeGroupContaining', 'afterGroupContaining' ) if (item.submenu != null) { item.submenu = item.submenu.map(submenuItem => cloneMenuItem(submenuItem)) } return item } // Determine the Electron accelerator for a given Atom keystroke. // // keystroke - The keystroke. // // Returns a String containing the keystroke in a format that can be interpreted // by Electron to provide nice icons where available. function acceleratorForKeystroke (keystroke) { if (!keystroke) { return null } let modifiers = keystroke.split(/-(?=.)/) const key = modifiers .pop() .toUpperCase() .replace('+', 'Plus') modifiers = modifiers.map(modifier => modifier .replace(/shift/gi, 'Shift') .replace(/cmd/gi, 'Command') .replace(/ctrl/gi, 'Ctrl') .replace(/alt/gi, 'Alt') ) const keys = [...modifiers, key] return keys.join('+') } module.exports = { merge, unmerge, normalizeLabel, cloneMenuItem, acceleratorForKeystroke }