mirror of
https://github.com/pulsar-edit/pulsar.git
synced 2024-08-16 14:40:23 +03:00
Bundle keybinding-resolver
This commit is contained in:
parent
05a2dc7b77
commit
57b196ceb9
15
packages/keybinding-resolver/.gitignore
vendored
Normal file
15
packages/keybinding-resolver/.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
lib-cov
|
||||
*.seed
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
*.pid
|
||||
*.gz
|
||||
|
||||
pids
|
||||
logs
|
||||
results
|
||||
|
||||
npm-debug.log
|
||||
node_modules
|
10
packages/keybinding-resolver/README.md
Normal file
10
packages/keybinding-resolver/README.md
Normal 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)
|
@ -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'
|
266
packages/keybinding-resolver/lib/keybinding-resolver-view.js
Normal file
266
packages/keybinding-resolver/lib/keybinding-resolver-view.js
Normal 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)
|
||||
}
|
||||
}
|
33
packages/keybinding-resolver/lib/main.js
Normal file
33
packages/keybinding-resolver/lib/main.js
Normal 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()
|
||||
}
|
||||
}
|
11
packages/keybinding-resolver/menus/keybinding-resolver.cson
Normal file
11
packages/keybinding-resolver/menus/keybinding-resolver.cson
Normal file
@ -0,0 +1,11 @@
|
||||
'menu': [
|
||||
{
|
||||
'label': 'Packages'
|
||||
'submenu': [
|
||||
'label': 'Keybinding Resolver'
|
||||
'submenu': [
|
||||
{ 'label': 'Toggle', 'command': 'key-binding-resolver:toggle' }
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
232
packages/keybinding-resolver/package-lock.json
generated
Normal file
232
packages/keybinding-resolver/package-lock.json
generated
Normal 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="
|
||||
}
|
||||
}
|
||||
}
|
19
packages/keybinding-resolver/package.json
Normal file
19
packages/keybinding-resolver/package.json
Normal 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"
|
||||
}
|
||||
}
|
40
packages/keybinding-resolver/spec/async-spec-helpers.js
Normal file
40
packages/keybinding-resolver/spec/async-spec-helpers.js
Normal 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()
|
||||
})
|
||||
})
|
||||
}
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
64
packages/keybinding-resolver/styles/keybinding-resolver.less
Normal file
64
packages/keybinding-resolver/styles/keybinding-resolver.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user