Skip to content
This repository has been archived by the owner on Dec 15, 2022. It is now read-only.

introduce numberOfRecentlyConfirmedCommandsShowsAtTop #103

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions lib/command-palette-package.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class CommandPalettePackage {
this.disposables.add(atom.config.observe('command-palette.preserveLastSearch', (newValue) => {
this.commandPaletteView.update({preserveLastSearch: newValue})
}))
this.disposables.add(atom.config.observe('command-palette.numberOfRecentlyConfirmedCommandsShowsAtTop', (newValue) => {
this.commandPaletteView.update({numberOfRecentlyConfirmedCommandsShowsAtTop: newValue})
}))
return this.commandPaletteView.show()
}

Expand Down
39 changes: 38 additions & 1 deletion lib/command-palette-view.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,16 @@ import {humanizeKeystroke} from 'underscore-plus'
import fuzzaldrin from 'fuzzaldrin'
import fuzzaldrinPlus from 'fuzzaldrin-plus'

function removeItemBy (list, fn) {
const index = list.findIndex(fn)
if (index !== -1) list.splice(index, 1)
}

export default class CommandPaletteView {
constructor (initiallyVisibleItemCount = 10) {
this.keyBindingsForActiveElement = []
this.recentlyConfirmedCommands = []
this.numberOfRecentlyConfirmedCommandsShowsAtTop = 0
this.elementCache = new WeakMap()
this.selectListView = new SelectListView({
initiallyVisibleItemCount: initiallyVisibleItemCount, // just for being able to disable visible-on-render in spec
Expand Down Expand Up @@ -85,6 +92,9 @@ export default class CommandPaletteView {
return li
},
didConfirmSelection: (keyBinding) => {
if (this.numberOfRecentlyConfirmedCommandsShowsAtTop > 0) {
this.updateRecentlyConfirmedCommands(keyBinding)
}
this.hide()
const event = new CustomEvent(keyBinding.name, {bubbles: true, cancelable: true})
this.activeElement.dispatchEvent(event)
Expand Down Expand Up @@ -126,7 +136,21 @@ export default class CommandPaletteView {
.findCommands({target: this.activeElement})
.filter(command => showHiddenCommands === !!command.hiddenInCommandPalette)
commandsForActiveElement.sort((a, b) => a.displayName.localeCompare(b.displayName))
await this.selectListView.update({items: commandsForActiveElement})

if (this.numberOfRecentlyConfirmedCommandsShowsAtTop > 0) {
for (const command of this.recentlyConfirmedCommands) {
// NOTE: items lifted to top should not be returned from cache.
// Since cached element which already attached to DOM need to be stable.
this.elementCache.delete(command)
// When package have activationCommands, `command` object is replaced after initial invocation.
// So we can't simply use `commandsForActiveElement.indexOf(command)`.
removeItemBy(commandsForActiveElement, item => item.name === command.name)
}
await this.selectListView.update({items: [...this.recentlyConfirmedCommands, ...commandsForActiveElement]})
} else {
await this.selectListView.update({items: commandsForActiveElement})
}


this.previouslyFocusedElement = document.activeElement
this.panel.show()
Expand All @@ -149,6 +173,11 @@ export default class CommandPaletteView {
if (props.hasOwnProperty('useAlternateScoring')) {
this.useAlternateScoring = props.useAlternateScoring
}

if (props.hasOwnProperty('numberOfRecentlyConfirmedCommandsShowsAtTop')) {
this.numberOfRecentlyConfirmedCommandsShowsAtTop = props.numberOfRecentlyConfirmedCommandsShowsAtTop
this.updateRecentlyConfirmedCommands()
}
}

get fuzz () {
Expand Down Expand Up @@ -267,4 +296,12 @@ export default class CommandPaletteView {
})
return tagsEl
}

updateRecentlyConfirmedCommands (command) {
if (command) {
removeItemBy(this.recentlyConfirmedCommands, item => item.name === command.name)
this.recentlyConfirmedCommands = [command, ...this.recentlyConfirmedCommands]
}
this.recentlyConfirmedCommands.splice(this.numberOfRecentlyConfirmedCommandsShowsAtTop)
}
}
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,12 @@
"type": "boolean",
"default": false,
"description": "Preserve the last search when reopening the command palette."
},
"numberOfRecentlyConfirmedCommandsShowsAtTop": {
"type": "integer",
"default": 0,
"minimum": 0,
"description": "Specified number of recently confirmed commands appears at top of command list."
}
}
}
121 changes: 121 additions & 0 deletions test/command-palette-view.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,125 @@ describe('CommandPaletteView', () => {
})
})
})

describe('numberOfRecentlyConfirmedCommandsShowsAtTop', () => {
let commandPalette, disposable, testCommands

const selectAndConfirmItem = async (item) => {
await commandPalette.selectListView.selectItem(item)
const originalDidConfirmSelection = commandPalette.selectListView.props.didConfirmSelection
await new Promise(resolve => {
const stub = sinon.stub(commandPalette.selectListView.props, "didConfirmSelection").callsFake((...args) => {
originalDidConfirmSelection(...args)
resolve()
stub.restore()
})
commandPalette.selectListView.confirmSelection()
})
}

beforeEach(async () => {
commandPalette = new CommandPaletteView()
disposable = atom.commands.add('*', {
'xxxxx:0': () => {},
'xxxxx:1': () => {},
'xxxxx:2': () => {},
'xxxxx:3': () => {},
'xxxxx:4': () => {},
})
await commandPalette.show()
testCommands = commandPalette.selectListView.items.filter(item => item.name.startsWith('xxxxx:'))

assert.equal(testCommands[0].name, "xxxxx:0")
assert.equal(testCommands[1].name, "xxxxx:1")
assert.equal(testCommands[2].name, "xxxxx:2")
assert.equal(testCommands[3].name, "xxxxx:3")
assert.equal(testCommands[4].name, "xxxxx:4")
assert.equal(testCommands.length, 5)

await commandPalette.hide()
})

afterEach(() => {
disposable.dispose()
})

it('keep specified nubmer of recentlyConfirmedCommands and show at top', async () => {
const withItemElements = fn => fn(Array.from(selectListView.element.querySelectorAll('li')))

const {selectListView} = commandPalette
await commandPalette.update({numberOfRecentlyConfirmedCommandsShowsAtTop: 3})

await commandPalette.show()
await selectAndConfirmItem(testCommands[0])
await commandPalette.show()
await withItemElements(elements => {
assert.equal(elements[0].textContent, testCommands[0].displayName)
})

await selectAndConfirmItem(testCommands[1])
await commandPalette.show()
await withItemElements(elements => {
assert.equal(elements[0].textContent, testCommands[1].displayName)
assert.equal(elements[1].textContent, testCommands[0].displayName)
})

await selectAndConfirmItem(testCommands[2])
await commandPalette.show()
await withItemElements(elements => {
assert.equal(elements[0].textContent, testCommands[2].displayName)
assert.equal(elements[1].textContent, testCommands[1].displayName)
assert.equal(elements[2].textContent, testCommands[0].displayName)
})

await selectAndConfirmItem(testCommands[3])
await commandPalette.show()
await withItemElements(elements => {
assert.equal(elements[0].textContent, testCommands[3].displayName)
assert.equal(elements[1].textContent, testCommands[2].displayName)
assert.equal(elements[2].textContent, testCommands[1].displayName)
assert.notEqual(elements[3].textContent, testCommands[0].displayName)
})

await selectAndConfirmItem(testCommands[2])
await commandPalette.show()
await withItemElements(elements => {
assert.equal(elements[0].textContent, testCommands[2].displayName)
assert.equal(elements[1].textContent, testCommands[3].displayName)
assert.equal(elements[2].textContent, testCommands[1].displayName)
assert.notEqual(elements[3].textContent, testCommands[0].displayName)
})

commandPalette.selectListView.refs.queryEditor.setText('xxxxx')
await commandPalette.selectListView.update()
withItemElements(elements => {
elements = Array.from(selectListView.element.querySelectorAll('li'))
const restElements = elements.slice(3)
assert.equal(elements[0].textContent, testCommands[2].displayName)
assert.equal(elements[1].textContent, testCommands[3].displayName)
assert.equal(elements[2].textContent, testCommands[1].displayName)
assert(!restElements.find(element => element.textContent === testCommands[2].displayName))
assert(!restElements.find(element => element.textContent === testCommands[3].displayName))
assert(!restElements.find(element => element.textContent === testCommands[1].displayName))
assert(restElements.find(element => element.textContent === testCommands[0].displayName))
})
})

it('immediately update size of recentlyConfirmedCommands', async () => {
await commandPalette.update({numberOfRecentlyConfirmedCommandsShowsAtTop: 3})

await commandPalette.show()
await selectAndConfirmItem(testCommands[0])
await commandPalette.show()
await selectAndConfirmItem(testCommands[1])
await commandPalette.show()
await selectAndConfirmItem(testCommands[2])

assert.deepEqual(commandPalette.recentlyConfirmedCommands, [testCommands[2], testCommands[1], testCommands[0]])
await commandPalette.update({numberOfRecentlyConfirmedCommandsShowsAtTop: 2})
assert.deepEqual(commandPalette.recentlyConfirmedCommands, [testCommands[2], testCommands[1]])
await commandPalette.update({numberOfRecentlyConfirmedCommandsShowsAtTop: 0})
assert.deepEqual(commandPalette.recentlyConfirmedCommands, [])
})
})
})