Bundle keybinding-resolver

This commit is contained in:
confused-Techie 2023-03-12 15:06:42 -07:00
parent 05a2dc7b77
commit 57b196ceb9
11 changed files with 878 additions and 0 deletions

15
packages/keybinding-resolver/.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
npm-debug.log
node_modules

View File

@ -0,0 +1,10 @@
# Keybinding Resolver package
Shows what commands a keybinding resolves to.
You can open and close the resolver using <kbd>Cmd+.</kbd> (macOS) or <kbd>Ctrl+.</kbd> (Linux and Windows).
Please note the clipboard icon which can be selected to copy the given keybinding
directive so that you can easily paste it into your keymap files.
![](https://user-images.githubusercontent.com/4137660/44482876-8de73a80-a617-11e8-8bd5-24023c96b39e.png)

View File

@ -0,0 +1,8 @@
'.platform-darwin':
'cmd-.': 'key-binding-resolver:toggle'
'.platform-win32':
'ctrl-.': 'key-binding-resolver:toggle'
'.platform-linux':
'ctrl-.': 'key-binding-resolver:toggle'

View File

@ -0,0 +1,266 @@
/** @babel */
/** @jsx etch.dom */
import fs from 'fs-plus'
import etch from 'etch'
import {CompositeDisposable} from 'atom'
import path from 'path'
export default class KeyBindingResolverView {
constructor () {
this.keystrokes = null
this.usedKeyBinding = null
this.unusedKeyBindings = []
this.unmatchedKeyBindings = []
this.partiallyMatchedBindings = []
this.attached = false
this.disposables = new CompositeDisposable()
this.keybindingDisposables = new CompositeDisposable()
this.disposables.add(atom.workspace.getBottomDock().observeActivePaneItem(item => {
if (item === this) {
this.attach()
} else {
this.detach()
}
}))
this.disposables.add(atom.workspace.getBottomDock().observeVisible(visible => {
if (visible) {
if (atom.workspace.getBottomDock().getActivePaneItem() === this) this.attach()
} else {
this.detach()
}
}))
etch.initialize(this)
}
getTitle () {
return 'Key Binding Resolver'
}
getIconName () {
return 'keyboard'
}
getDefaultLocation () {
return 'bottom'
}
getAllowedLocations () {
// TODO: Support left and right possibly
return ['bottom']
}
getURI () {
return 'atom://keybinding-resolver'
}
serialize () {
return {
deserializer: 'keybinding-resolver/KeyBindingResolverView'
}
}
destroy () {
this.disposables.dispose()
this.detach()
return etch.destroy(this)
}
attach () {
if (this.attached) return
this.attached = true
this.keybindingDisposables = new CompositeDisposable()
this.keybindingDisposables.add(atom.keymaps.onDidMatchBinding(({keystrokes, binding, keyboardEventTarget, eventType}) => {
if (eventType === 'keyup' && binding == null) {
return
}
const unusedKeyBindings = atom.keymaps
.findKeyBindings({keystrokes, target: keyboardEventTarget})
.filter((b) => b !== binding)
const unmatchedKeyBindings = atom.keymaps
.findKeyBindings({keystrokes})
.filter((b) => b !== binding && !unusedKeyBindings.includes(b))
this.update({usedKeyBinding: binding, unusedKeyBindings, unmatchedKeyBindings, keystrokes})
}))
this.keybindingDisposables.add(atom.keymaps.onDidPartiallyMatchBindings(({keystrokes, partiallyMatchedBindings}) => {
this.update({keystrokes, partiallyMatchedBindings})
}))
this.keybindingDisposables.add(atom.keymaps.onDidFailToMatchBinding(({keystrokes, keyboardEventTarget, eventType}) => {
if (eventType === 'keyup') {
return
}
const unusedKeyBindings = atom.keymaps.findKeyBindings({keystrokes, target: keyboardEventTarget})
const unmatchedKeyBindings = atom.keymaps
.findKeyBindings({keystrokes})
.filter((b) => !unusedKeyBindings.includes(b))
this.update({unusedKeyBindings, unmatchedKeyBindings, keystrokes})
}))
}
detach () {
if (!this.attached) return
this.attached = false
this.keybindingDisposables.dispose()
this.keybindingDisposables = null
}
update (props) {
this.keystrokes = props.keystrokes
this.usedKeyBinding = props.usedKeyBinding
this.unusedKeyBindings = props.unusedKeyBindings || []
this.unmatchedKeyBindings = props.unmatchedKeyBindings || []
this.partiallyMatchedBindings = props.partiallyMatchedBindings || []
return etch.update(this)
}
render () {
return (
<div className='key-binding-resolver'>
<div className='panel-heading'>{this.renderKeystrokes()}</div>
<div className='panel-body'>{this.renderKeyBindings()}</div>
</div>
)
}
renderKeystrokes () {
if (this.keystrokes) {
if (this.partiallyMatchedBindings.length > 0) {
return <span className='keystroke highlight-info'>{this.keystrokes} (partial)</span>
} else {
return <span className='keystroke highlight-info'>{this.keystrokes}</span>
}
} else {
return <span>Press any key</span>
}
}
renderKeyBindings () {
if (this.partiallyMatchedBindings.length > 0) {
return (
<table className='table-condensed'>
<tbody>
{this.partiallyMatchedBindings.map((binding) => (
<tr className='unused'>
<td className='copy' onclick={() => this.copyKeybinding(binding)}><span className='icon icon-clippy' /></td>
<td className='command'>{binding.command}</td>
<td className='keystrokes'>{binding.keystrokes}</td>
<td className='selector'>{binding.selector}</td>
<td className='source' onclick={() => this.openKeybindingFile(binding.source)}>{binding.source}</td>
</tr>
))}
</tbody>
</table>
)
} else {
let usedKeyBinding = ''
if (this.usedKeyBinding) {
usedKeyBinding = (
<tr className='used'>
<td className='copy' onclick={() => this.copyKeybinding(this.usedKeyBinding)}><span className='icon icon-clippy' /></td>
<td className='command'>{this.usedKeyBinding.command}</td>
<td className='selector'>{this.usedKeyBinding.selector}</td>
<td className='source' onclick={() => this.openKeybindingFile(this.usedKeyBinding.source)}>{this.usedKeyBinding.source}</td>
</tr>
)
}
return (
<table className='table-condensed'>
<tbody>
{usedKeyBinding}
{this.unusedKeyBindings.map((binding) => (
<tr className='unused'>
<td className='copy' onclick={() => this.copyKeybinding(binding)}><span className='icon icon-clippy' /></td>
<td className='command'>{binding.command}</td>
<td className='selector'>{binding.selector}</td>
<td className='source' onclick={() => this.openKeybindingFile(binding.source)}>{binding.source}</td>
</tr>
))}
{this.unmatchedKeyBindings.map((binding) => (
<tr className='unmatched'>
<td className='copy' onclick={() => this.copyKeybinding(binding)}><span className='icon icon-clippy' /></td>
<td className='command'>{binding.command}</td>
<td className='selector'>{binding.selector}</td>
<td className='source' onclick={() => this.openKeybindingFile(binding.source)}>{binding.source}</td>
</tr>
))}
</tbody>
</table>
)
}
}
isInAsarArchive (pathToCheck) {
const {resourcePath} = atom.getLoadSettings()
return pathToCheck.startsWith(`${resourcePath}${path.sep}`) && path.extname(resourcePath) === '.asar'
}
extractBundledKeymap (bundledKeymapPath) {
const metadata = require(path.join(atom.getLoadSettings().resourcePath, 'package.json'))
const bundledKeymaps = metadata ? metadata._atomKeymaps : {}
const keymapName = path.basename(bundledKeymapPath)
const extractedKeymapPath = path.join(require('temp').mkdirSync('atom-bundled-keymap-'), keymapName)
fs.writeFileSync(
extractedKeymapPath,
JSON.stringify(bundledKeymaps[keymapName] || {}, null, 2)
)
return extractedKeymapPath
}
extractBundledPackageKeymap (keymapRelativePath) {
const packageName = keymapRelativePath.split(path.sep)[1]
const keymapName = path.basename(keymapRelativePath)
const metadata = atom.packages.packagesCache[packageName] || {}
const keymaps = metadata.keymaps || {}
const extractedKeymapPath = path.join(require('temp').mkdirSync('atom-bundled-keymap-'), keymapName)
fs.writeFileSync(
extractedKeymapPath,
JSON.stringify(keymaps[keymapRelativePath] || {}, null, 2)
)
return extractedKeymapPath
}
openKeybindingFile (keymapPath) {
if (this.isInAsarArchive(keymapPath)) {
keymapPath = this.extractBundledKeymap(keymapPath)
} else if (keymapPath.startsWith('core:node_modules')) {
keymapPath = this.extractBundledPackageKeymap(keymapPath.replace('core:', ''))
} else if (keymapPath.startsWith('core:')) {
keymapPath = this.extractBundledKeymap(keymapPath.replace('core:', ''))
}
atom.workspace.open(keymapPath)
}
copyKeybinding (binding) {
let content
const keymapExtension = path.extname(atom.keymaps.getUserKeymapPath())
let escapedKeystrokes = binding.keystrokes.replace(/\\/g, '\\\\') // Escape backslashes
if (keymapExtension === '.cson') {
content = `\
'${binding.selector}':
'${escapedKeystrokes}': '${binding.command}'
`
} else {
content = `\
"${binding.selector}": {
"${escapedKeystrokes}": "${binding.command}"
}
`
}
atom.notifications.addInfo('Keybinding Copied')
return atom.clipboard.write(content)
}
}

View File

@ -0,0 +1,33 @@
const {CompositeDisposable} = require('atom')
const KeyBindingResolverView = require('./keybinding-resolver-view')
const KEYBINDING_RESOLVER_URI = 'atom://keybinding-resolver'
module.exports = {
activate () {
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(atom.workspace.addOpener(uri => {
if (uri === KEYBINDING_RESOLVER_URI) {
return new KeyBindingResolverView()
}
}))
this.subscriptions.add(atom.commands.add('atom-workspace', {
'key-binding-resolver:toggle': () => this.toggle()
}))
},
deactivate () {
this.subscriptions.dispose()
},
toggle () {
atom.workspace.toggle(KEYBINDING_RESOLVER_URI)
},
deserializeKeyBindingResolverView (serialized) {
return new KeyBindingResolverView()
}
}

View File

@ -0,0 +1,11 @@
'menu': [
{
'label': 'Packages'
'submenu': [
'label': 'Keybinding Resolver'
'submenu': [
{ 'label': 'Toggle', 'command': 'key-binding-resolver:toggle' }
]
]
}
]

View File

@ -0,0 +1,232 @@
{
"name": "keybinding-resolver",
"version": "0.39.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "keybinding-resolver",
"version": "0.39.1",
"license": "MIT",
"dependencies": {
"etch": "0.9.0",
"fs-plus": "^3.0.0",
"temp": "^0.9.0"
},
"engines": {
"atom": ">=1.17.0"
}
},
"node_modules/async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
},
"node_modules/balanced-match": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz",
"integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg="
},
"node_modules/brace-expansion": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz",
"integrity": "sha1-cZfX6qm4fmSDkOph/GbIRCdCDfk=",
"dependencies": {
"balanced-match": "^0.4.1",
"concat-map": "0.0.1"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"node_modules/etch": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/etch/-/etch-0.9.0.tgz",
"integrity": "sha1-CSJpiPLO4GkL3yCMyyXkFNXfrV8="
},
"node_modules/fs-plus": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.2.tgz",
"integrity": "sha1-a19Sp3EolMTd6f2PgfqMYN8EHz0=",
"dependencies": {
"async": "^1.5.2",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2",
"underscore-plus": "1.x"
}
},
"node_modules/fs-plus/node_modules/rimraf": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz",
"integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==",
"dependencies": {
"glob": "^7.0.5"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"node_modules/glob": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz",
"integrity": "sha1-gFIR3wT6rxxjo2ADBs31reULLsg=",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.2",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"node_modules/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz",
"integrity": "sha1-Kk5AkLlrLbBqnX3wEFWmKnfJt3Q=",
"dependencies": {
"brace-expansion": "^1.0.0"
},
"engines": {
"node": "*"
}
},
"node_modules/mkdirp": {
"version": "0.5.1",
"resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)",
"dependencies": {
"minimist": "0.0.8"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/mkdirp/node_modules/minimist": {
"version": "0.0.8",
"resolved": "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/temp": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/temp/-/temp-0.9.0.tgz",
"integrity": "sha512-YfUhPQCJoNQE5N+FJQcdPz63O3x3sdT4Xju69Gj4iZe0lBKOtnAMi0SLj9xKhGkcGhsxThvTJ/usxtFPo438zQ==",
"engines": [
"node >=4.0.0"
],
"dependencies": {
"rimraf": "~2.6.2"
}
},
"node_modules/temp/node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"node_modules/temp/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/temp/node_modules/glob": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
}
},
"node_modules/temp/node_modules/minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/temp/node_modules/rimraf": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
"integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"node_modules/underscore-plus": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.8.tgz",
"integrity": "sha512-88PrCeMKeAAC1L4xjSiiZ3Fg6kZOYrLpLGVPPeqKq/662DfQe/KTSKdSR/Q/tucKNnfW2MNAUGSCkDf8HmXC5Q==",
"dependencies": {
"underscore": "~1.8.3"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "keybinding-resolver",
"main": "./lib/main",
"version": "0.39.1",
"description": "Show what commands a keybinding resolves to",
"license": "MIT",
"repository": "https://github.com/pulsar-edit/keybinding-resolver",
"engines": {
"atom": ">=1.17.0"
},
"deserializers": {
"keybinding-resolver/KeyBindingResolverView": "deserializeKeyBindingResolverView"
},
"dependencies": {
"etch": "0.9.0",
"fs-plus": "^3.0.0",
"temp": "^0.9.0"
}
}

View File

@ -0,0 +1,40 @@
/** @babel */
export function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
export function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
module.exports[name] = function (description, fn) {
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
function waitsForPromise (fn) {
const promise = fn()
global.waitsFor('spec promise to resolve', function (done) {
promise.then(done, function (error) {
jasmine.getEnv().currentSpec.fail(error)
done()
})
})
}

View File

@ -0,0 +1,180 @@
const {it, fit, ffit, beforeEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
const etch = require('etch')
describe('KeyBindingResolverView', () => {
let workspaceElement, bottomDockElement
beforeEach(async () => {
workspaceElement = atom.views.getView(atom.workspace)
bottomDockElement = atom.views.getView(atom.workspace.getBottomDock())
await atom.packages.activatePackage('keybinding-resolver')
jasmine.attachToDOM(workspaceElement);
})
describe('when the key-binding-resolver:toggle event is triggered', () => {
it('toggles the view', async () => {
expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
expect(bottomDockElement.querySelector('.key-binding-resolver')).not.toExist()
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
})
it('focuses the view if it is not visible instead of destroying it', async () => {
expect(atom.workspace.getBottomDock().isVisible()).toBe(false)
expect(bottomDockElement.querySelector('.key-binding-resolver')).not.toExist()
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
atom.workspace.getBottomDock().hide()
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
expect(atom.workspace.getBottomDock().isVisible()).toBe(true)
expect(bottomDockElement.querySelector('.key-binding-resolver')).toExist()
})
})
describe('capturing keybinding events', () => {
it('captures events when the keybinding resolver is visible', async () => {
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
expect(keybindingResolverView.keybindingDisposables).not.toBe(null)
document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
})
it('does not capture events when the keybinding resolver is not the active pane item', async () => {
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
expect(keybindingResolverView.keybindingDisposables).not.toBe(null)
atom.workspace.getBottomDock().getActivePane().splitRight()
expect(keybindingResolverView.keybindingDisposables).toBe(null)
atom.workspace.getBottomDock().getActivePane().destroy()
document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
})
it('does not capture events when the dock the keybinding resolver is in is not visible', async () => {
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
const keybindingResolverView = atom.workspace.getBottomDock().getActivePaneItem()
expect(keybindingResolverView.keybindingDisposables).not.toBe(null)
atom.workspace.getBottomDock().hide()
expect(keybindingResolverView.keybindingDisposables).toBe(null)
atom.workspace.getBottomDock().show()
document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
})
})
describe('when a keydown event occurs', () => {
it('displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding', async () => {
atom.keymaps.add('name', {
'.workspace': {
'x': 'match-1'
}
})
atom.keymaps.add('name', {
'.workspace': {
'x': 'match-2'
}
})
atom.keymaps.add('name', {
'.never-again': {
'x': 'unmatch-2'
}
})
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)
// It should not render the keyup event data because there is no match
spyOn(etch.getScheduler(), 'updateDocument').andCallThrough()
document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('x', {target: bottomDockElement}))
expect(etch.getScheduler().updateDocument).not.toHaveBeenCalled()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)
})
it('displays all commands for the keydown event but does not clear for the keyup when there is no keyup binding', async () => {
atom.keymaps.add('name', {
'.workspace': {
'x': 'match-1'
}
})
atom.keymaps.add('name', {
'.workspace': {
'x ^x': 'match-2'
}
})
atom.keymaps.add('name', {
'.workspace': {
'a ^a': 'match-3'
}
})
atom.keymaps.add('name', {
'.never-again': {
'x': 'unmatch-2'
}
})
await atom.commands.dispatch(workspaceElement, 'key-binding-resolver:toggle')
// Not partial because it dispatches the command for `x` immediately due to only having keyup events in remainder of partial match
document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('x', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x')
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(1)
// It should not render the keyup event data because there is no match
document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('x', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('x ^x')
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)
document.dispatchEvent(atom.keymaps.constructor.buildKeydownEvent('a', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('a (partial)')
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(0)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)
document.dispatchEvent(atom.keymaps.constructor.buildKeyupEvent('a', {target: bottomDockElement}))
await etch.getScheduler().getNextUpdatePromise()
expect(bottomDockElement.querySelector('.key-binding-resolver .keystroke').textContent).toBe('a ^a')
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .used')).toHaveLength(1)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unused')).toHaveLength(0)
expect(bottomDockElement.querySelectorAll('.key-binding-resolver .unmatched')).toHaveLength(0)
})
})
})

View File

@ -0,0 +1,64 @@
@import "ui-variables";
@import "octicon-mixins";
.key-binding-resolver {
overflow: auto;
.panel-heading {
position: sticky;
top: 0;
z-index: 1;
}
.panel-body {
padding: 0 @component-padding;
}
table {
tr:not(:last-child) {
border-bottom: 1px solid @base-border-color;
}
.used {
color: @text-color-success;
}
.unused {
color: @text-color;
}
.unmatched {
color: @text-color-subtle;
}
// move icon so text is aligned when wrapped
.command {
padding-left: @component-icon-size;
&:before {
position: absolute;
margin-left: -@component-icon-size;
}
}
.used .command,
.unused .command {
.octicon(check);
}
.unmatched .command {
.octicon(x);
}
.command,
.selector,
.source {
min-width: 10em;
word-break: break-word; // wrap long path names
}
.source {
cursor: pointer;
}
}
}