mirror of
https://github.com/atom/atom.git
synced 2026-01-25 23:08:18 -05:00
187 lines
4.7 KiB
JavaScript
187 lines
4.7 KiB
JavaScript
// UTILS
|
|
|
|
function splitArray (arr, predicate) {
|
|
let lastArr = []
|
|
const multiArr = [lastArr]
|
|
arr.forEach(item => {
|
|
if (predicate(item)) {
|
|
if (lastArr.length > 0) {
|
|
lastArr = []
|
|
multiArr.push(lastArr)
|
|
}
|
|
} else {
|
|
lastArr.push(item)
|
|
}
|
|
})
|
|
return multiArr
|
|
}
|
|
|
|
function joinArrays (arrays, joiner) {
|
|
const joinedArr = []
|
|
arrays.forEach((arr, i) => {
|
|
if (i > 0 && arr.length > 0) {
|
|
joinedArr.push(joiner)
|
|
}
|
|
joinedArr.push(...arr)
|
|
})
|
|
return joinedArr
|
|
}
|
|
|
|
const pushOntoMultiMap = (map, key, value) => {
|
|
if (!map.has(key)) {
|
|
map.set(key, [])
|
|
}
|
|
map.get(key).push(value)
|
|
}
|
|
|
|
function indexOfGroupContainingCommand (groups, command, ignoreGroup) {
|
|
return groups.findIndex(
|
|
candiateGroup =>
|
|
candiateGroup !== ignoreGroup &&
|
|
candiateGroup.some(
|
|
candidateItem => candidateItem.command === command
|
|
)
|
|
)
|
|
}
|
|
|
|
// Sort nodes topologically using a depth-first approach. Encountered cycles
|
|
// are broken.
|
|
function sortTopologically (originalOrder, edgesById) {
|
|
const sorted = []
|
|
const marked = new Set()
|
|
|
|
function visit (id) {
|
|
if (marked.has(id)) {
|
|
// Either this node has already been placed, or we have encountered a
|
|
// cycle and need to exit.
|
|
return
|
|
}
|
|
marked.add(id)
|
|
const edges = edgesById.get(id)
|
|
if (edges != null) {
|
|
edges.forEach(visit)
|
|
}
|
|
sorted.push(id)
|
|
}
|
|
|
|
originalOrder.forEach(visit)
|
|
return sorted
|
|
}
|
|
|
|
function attemptToMergeAGroup (groups) {
|
|
for (let i = 0; i < groups.length; i++) {
|
|
const group = groups[i]
|
|
for (const item of group) {
|
|
const toCommands = [...(item.before || []), ...(item.after || [])]
|
|
for (const command of toCommands) {
|
|
const index = indexOfGroupContainingCommand(groups, command, group)
|
|
if (index === -1) {
|
|
// No valid edge for this command
|
|
continue
|
|
}
|
|
const mergeTarget = groups[index]
|
|
// Merge with group containing `command`
|
|
mergeTarget.push(...group)
|
|
groups.splice(i, 1)
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Merge groups based on before/after positions
|
|
// Mutates both the array of groups, and the individual group arrays.
|
|
function mergeGroups (groups) {
|
|
let mergedAGroup = true
|
|
while (mergedAGroup) {
|
|
mergedAGroup = attemptToMergeAGroup(groups)
|
|
}
|
|
return groups
|
|
}
|
|
|
|
function sortItemsInGroup (group) {
|
|
const originalOrder = group.map((node, i) => i)
|
|
const edges = new Map()
|
|
const commandToIndex = new Map(group.map((item, i) => [item.command, i]))
|
|
|
|
group.forEach((item, i) => {
|
|
if (item.before) {
|
|
item.before.forEach(toCommand => {
|
|
const to = commandToIndex.get(toCommand)
|
|
if (to != null) {
|
|
pushOntoMultiMap(edges, to, i)
|
|
}
|
|
})
|
|
}
|
|
if (item.after) {
|
|
item.after.forEach(toCommand => {
|
|
const to = commandToIndex.get(toCommand)
|
|
if (to != null) {
|
|
pushOntoMultiMap(edges, i, to)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
const sortedNodes = sortTopologically(originalOrder, edges)
|
|
|
|
return sortedNodes.map(i => group[i])
|
|
}
|
|
|
|
function findEdgesInGroup (groups, i, edges) {
|
|
const group = groups[i]
|
|
for (const item of group) {
|
|
if (item.beforeGroupContaining) {
|
|
for (const command of item.beforeGroupContaining) {
|
|
const to = indexOfGroupContainingCommand(groups, command, group)
|
|
if (to !== -1) {
|
|
pushOntoMultiMap(edges, to, i)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
if (item.afterGroupContaining) {
|
|
for (const command of item.afterGroupContaining) {
|
|
const to = indexOfGroupContainingCommand(groups, command, group)
|
|
if (to !== -1) {
|
|
pushOntoMultiMap(edges, i, to)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function sortGroups (groups) {
|
|
const originalOrder = groups.map((item, i) => i)
|
|
const edges = new Map()
|
|
|
|
for (let i = 0; i < groups.length; i++) {
|
|
findEdgesInGroup(groups, i, edges)
|
|
}
|
|
|
|
const sortedGroupIndexes = sortTopologically(originalOrder, edges)
|
|
return sortedGroupIndexes.map(i => groups[i])
|
|
}
|
|
|
|
function isSeparator (item) {
|
|
return item.type === 'separator'
|
|
}
|
|
|
|
function sortMenuItems (menuItems) {
|
|
// Split the items into their implicit groups based upon separators.
|
|
const groups = splitArray(menuItems, isSeparator)
|
|
// Merge groups that contain before/after references to eachother.
|
|
const mergedGroups = mergeGroups(groups)
|
|
// Sort each individual group internally.
|
|
const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup)
|
|
// Sort the groups based upon their beforeGroupContaining/afterGroupContaining
|
|
// references.
|
|
const sortedGroups = sortGroups(mergedGroupsWithSortedItems)
|
|
// Join the groups back
|
|
return joinArrays(sortedGroups, { type: 'separator' })
|
|
}
|
|
|
|
module.exports = {sortMenuItems}
|