Merge pull request #424 from pulsar-edit/bundle-time

Additional Bundling of Core Packages
This commit is contained in:
confused_techie 2023-04-07 12:33:33 -07:00 committed by GitHub
commit 80f3fc6b05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2861 additions and 93 deletions

View File

@ -39,7 +39,7 @@
"autocomplete-plus": "file:./packages/autocomplete-plus",
"autocomplete-snippets": "file:packages/autocomplete-snippets",
"autoflow": "file:packages/autoflow",
"autosave": "https://codeload.github.com/atom/autosave/legacy.tar.gz/refs/tags/v0.24.6",
"autosave": "file:./packages/autosave",
"babel-preset-atomic": "^5.0.0",
"background-tips": "file:packages/background-tips",
"base16-tomorrow-dark-theme": "file:packages/base16-tomorrow-dark-theme",
@ -81,7 +81,7 @@
"jasmine-reporters": "1.1.0",
"jasmine-tagged": "^1.1.4",
"key-path-helpers": "^0.4.0",
"keybinding-resolver": "https://codeload.github.com/atom/keybinding-resolver/legacy.tar.gz/refs/tags/v0.39.1",
"keybinding-resolver": "file:./packages/keybinding-resolver",
"language-c": "file:packages/language-c",
"language-clojure": "file:packages/language-clojure",
"language-coffee-script": "file:packages/language-coffee-script",
@ -160,7 +160,7 @@
"tabs": "file:packages/tabs",
"temp": "0.9.4",
"text-buffer": "^13.18.6",
"timecop": "https://codeload.github.com/atom/timecop/legacy.tar.gz/refs/tags/v0.36.2",
"timecop": "file:./packages/timecop",
"tree-sitter": "0.20.0",
"tree-view": "https://codeload.github.com/atom/tree-view/legacy.tar.gz/refs/tags/v0.229.1",
"typescript-simple": "8.0.6",
@ -168,7 +168,7 @@
"update-package-dependencies": "file:./packages/update-package-dependencies",
"vscode-ripgrep": "1.9.0",
"welcome": "file:packages/welcome",
"whitespace": "https://codeload.github.com/atom/whitespace/legacy.tar.gz/refs/tags/v0.37.8",
"whitespace": "file:./packages/whitespace",
"winreg": "^1.2.1",
"wrap-guide": "file:./packages/wrap-guide",
"yargs": "17.6.2"
@ -194,10 +194,10 @@
"autocomplete-plus": "file:./packages/autocomplete-plus",
"autocomplete-snippets": "file:./packages/autocomplete-snippets",
"autoflow": "file:./packages/autoflow",
"autosave": "0.24.6",
"autosave": "file:./packages/autosave",
"background-tips": "file:./packages/background-tips",
"bookmarks": "file:./packages/bookmarks",
"bracket-matcher": "0.92.0",
"bracket-matcher": "file:./packages/bracket-matcher",
"command-palette": "file:./packages/command-palette",
"dalek": "file:./packages/dalek",
"deprecation-cop": "file:./packages/deprecation-cop",
@ -212,7 +212,7 @@
"grammar-selector": "file:./packages/grammar-selector",
"image-view": "file:./packages/image-view",
"incompatible-packages": "file:./packages/incompatible-packages",
"keybinding-resolver": "0.39.1",
"keybinding-resolver": "file:./packages/keybinding-resolver",
"line-ending-selector": "file:./packages/line-ending-selector",
"link": "file:./packages/link",
"markdown-preview": "file:./packages/markdown-preview",
@ -226,11 +226,11 @@
"styleguide": "file:./packages/styleguide",
"symbols-view": "0.118.4",
"tabs": "file:./packages/tabs",
"timecop": "0.36.2",
"timecop": "file:./packages/timecop",
"tree-view": "0.229.1",
"update-package-dependencies": "file:./packages/update-package-dependencies",
"welcome": "file:./packages/welcome",
"whitespace": "0.37.8",
"whitespace": "file:./packages/whitespace",
"wrap-guide": "file:./packages/wrap-guide",
"language-c": "file:./packages/language-c",
"language-clojure": "file:./packages/language-clojure",

View File

@ -19,12 +19,12 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **autocomplete-plus** | [`./autocomplete-plus`](./autocomplete-plus) | |
| **autocomplete-snippets** | [`./autocomplete-snippets`](./autocomplete-snippets) | |
| **autoflow** | [`./autoflow`](./autoflow) | |
| **autosave** | [`pulsar-edit/autosave`][autosave] | [#17834](https://github.com/atom/atom/issues/17834) |
| **autosave** | [`./autosave`](./autosave) | |
| **background-tips** | [`./background-tips`](./background-tips) | |
| **base16-tomorrow-dark-theme** | [`./base16-tomorrow-dark-theme`](./base16-tomorrow-dark-theme) | |
| **base16-tomorrow-light-theme** | [`./base16-tomorrow-light-theme`](./base16-tomorrow-light-theme) | |
| **bookmarks** | [`./bookmarks`](./bookmarks) | |
| **bracket-matcher** | [`atom/bracket-matcher`][bracket-matcher] | |
| **bracket-matcher** | [`./bracket-matcher`](./bracket-matcher) | |
| **command-palette** | [`./command-palette`](./command-palette) | |
| **dalek** | [`./dalek`](./dalek) | |
| **deprecation-cop** | [`./deprecation-cop`](./deprecation-cop) | |
@ -39,7 +39,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **grammar-selector** | [`./grammar-selector`](./grammar-selector) | |
| **image-view** | [`./image-view`](./image-view) | |
| **incompatible-packages** | [`./incompatible-packages`](./incompatible-packages) | |
| **keybinding-resolver** | [`atom/keybinding-resolver`][keybinding-resolver] | [#18275](https://github.com/atom/atom/issues/18275) |
| **keybinding-resolver** | [`./keybinding-resolver`](./keybinding-resolver) | |
| **language-c** | [`./language-c`](./language-c) | |
| **language-clojure** | [`./language-clojure`](./language-clojure) | |
| **language-coffee-script** | [`./language-coffee-script`](./language-coffee-script) | |
@ -93,7 +93,7 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **styleguide** | [`./styleguide`](./styleguide) | |
| **symbols-view** | [`pulsar-edit/symbols-view`][symbols-view] | |
| **tabs** | [`./tabs`](./tabs) | |
| **timecop** | [`pulsar-edit/timecop`][timecop] | [#18272](https://github.com/atom/atom/issues/18272) |
| **timecop** | [`./timecop`](./timecop) | |
| **tree-view** | [`pulsar-edit/tree-view`][tree-view] | |
| **update-package-dependencies** | [`./update-package-dependencies`](./update-package-dependencies) | |
| **welcome** | [`./welcome`](./welcome) | |
@ -101,8 +101,6 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
| **wrap-guide** | [`./wrap-guide`](./wrap-guide) | |
[autocomplete-atom-api]: https://github.com/pulsar-edit/autocomplete-atom-api
[autosave]: https://github.com/pulsar-edit/autosave
[bracket-matcher]: https://github.com/pulsar-edit/bracket-matcher
[find-and-replace]: https://github.com/pulsar-edit/find-and-replace
[fuzzy-finder]: https://github.com/pulsar-edit/fuzzy-finder
[github]: https://github.com/pulsar-edit/github
@ -111,5 +109,4 @@ See [RFC 003](https://github.com/atom/atom/blob/master/docs/rfcs/003-consolidate
[snippets]: https://github.com/pulsar-edit/snippets
[spell-check]: https://github.com/pulsar-edit/spell-check
[symbols-view]: https://github.com/pulsar-edit/symbols-view
[timecop]: https://github.com/pulsar-edit/timecop
[tree-view]: https://github.com/pulsar-edit/tree-view

1
packages/autosave/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

View File

@ -0,0 +1,20 @@
Copyright (c) 2014 GitHub Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,32 @@
# Autosave package
Autosaves editor when they lose focus, are destroyed, or when the window is closed.
This package is disabled by default and can be enabled via the `autosave.enabled` config
setting or by checking *Enabled* in the settings for the *autosave* package in the
Settings view.
## Service API
The service exposes an object with a function `dontSaveIf`, which accepts a callback.
Callbacks will be invoked with each pane item eligible for an autosave and if the callback
returns true, the item will be skipped.
### Usage
#### package.json
``` json
"consumedServices": {
"autosave": {
"versions": {
"1.0.0": "consumeAutosave"
}
}
}
```
#### package initialize
``` javascript
consumeAutosave({dontSaveIf}) {
dontSaveIf(paneItem -> paneItem.getPath() === '/dont/autosave/me.coffee')
}
```

View File

@ -0,0 +1,66 @@
const fs = require('fs')
const {CompositeDisposable, Disposable} = require('atom')
const {dontSaveIf, shouldSave} = require('./controls')
module.exports = {
subscriptions: null,
provideService () {
return {dontSaveIf}
},
activate () {
this.subscriptions = new CompositeDisposable()
const handleBlur = event => {
if (event.target === window) {
this.autosaveAllPaneItems()
} else if (event.target.matches('atom-text-editor:not(mini)')) {
return this.autosavePaneItem(event.target.getModel())
}
}
window.addEventListener('blur', handleBlur, true)
this.subscriptions.add(new Disposable(() => window.removeEventListener('blur', handleBlur, true)))
this.subscriptions.add(atom.workspace.onDidAddPaneItem(({item}) => this.autosavePaneItem(item, true)))
this.subscriptions.add(atom.workspace.onWillDestroyPaneItem(({item}) => this.autosavePaneItem(item)))
},
deactivate () {
this.subscriptions.dispose()
return this.autosaveAllPaneItems()
},
autosavePaneItem (paneItem, create = false) {
if (!atom.config.get('autosave.enabled')) return
if (!paneItem) return
if (typeof paneItem.getURI !== 'function' || !paneItem.getURI()) return
if (typeof paneItem.isModified !== 'function' || !paneItem.isModified()) return
if (typeof paneItem.getPath !== 'function' || !paneItem.getPath()) return
if (!shouldSave(paneItem)) return
try {
const stats = fs.statSync(paneItem.getPath())
if (!stats.isFile()) return
} catch (e) {
if (e.code !== 'ENOENT') return
if (!create) return
}
const pane = atom.workspace.paneForItem(paneItem)
let promise = Promise.resolve()
if (pane) {
promise = pane.saveItem(paneItem)
} else if (typeof paneItem.save === 'function') {
promise = paneItem.save()
}
return promise
},
autosaveAllPaneItems () {
return Promise.all(
atom.workspace.getPaneItems().map((paneItem) => this.autosavePaneItem(paneItem))
)
}
}

21
packages/autosave/lib/controls.js vendored Normal file
View File

@ -0,0 +1,21 @@
const tests = []
module.exports = {
// Public: Add a predicate to set of tests
//
// * `predicate` A {Function} determining if a {PaneItem} should autosave.
//
// Returns `undefined`.
dontSaveIf (predicate) {
tests.push(predicate)
},
// Public: Test whether a paneItem should be autosaved.
//
// * `paneItem` A pane item {Object}.
//
// Returns `Boolean`.
shouldSave (paneItem) {
return !tests.some(test => test(paneItem))
}
}

166
packages/autosave/package-lock.json generated Normal file
View File

@ -0,0 +1,166 @@
{
"name": "autosave",
"version": "0.24.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "autosave",
"version": "0.24.6",
"dependencies": {
"fs-plus": "^3.0.0"
},
"engines": {
"atom": ">0.27.0"
}
},
"node_modules/async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w=="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"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/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/fs-plus": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz",
"integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==",
"dependencies": {
"async": "^1.5.2",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2",
"underscore-plus": "1.x"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz",
"integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mkdirp": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
"dependencies": {
"minimist": "^1.2.6"
},
"bin": {
"mkdirp": "bin/cmd.js"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"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": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
}
},
"node_modules/underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"node_modules/underscore-plus": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz",
"integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==",
"dependencies": {
"underscore": "^1.9.1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
}
}
}

View File

@ -0,0 +1,25 @@
{
"name": "autosave",
"main": "./lib/autosave",
"version": "0.24.6",
"private": true,
"description": "Save editors when they lose focus or are closed",
"repository": "https://github.com/pulsar-edit/pulsar",
"engines": {
"atom": ">0.27.0"
},
"providedServices": {
"autosave": {
"description": "A configuration object to control what is autosaved",
"versions": {
"1.0.0": "provideService"
}
}
},
"configSchema": {
"enabled": {
"type": "boolean",
"default": false
}
}
}

View File

@ -0,0 +1,43 @@
exports.beforeEach = function beforeEach (fn) {
global.beforeEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
exports.afterEach = function afterEach (fn) {
global.afterEach(function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
;['it', 'fit', 'ffit', 'fffit'].forEach(function (name) {
exports[name] = function (description, fn) {
if (fn === undefined) {
global[name](description)
return
}
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,267 @@
const fs = require('fs')
const {it, fit, ffit, beforeEach} = require('./async-spec-helpers') // eslint-disable-line
describe('Autosave', () => {
let workspaceElement, initialActiveItem, otherItem1, otherItem2
beforeEach(async () => {
workspaceElement = atom.views.getView(atom.workspace)
jasmine.attachToDOM(workspaceElement)
await atom.packages.activatePackage('autosave')
await atom.workspace.open('sample.js')
initialActiveItem = atom.workspace.getActiveTextEditor()
if (atom.workspace.createItemForURI != null) {
otherItem1 = await atom.workspace.createItemForURI('sample.coffee')
} else {
otherItem1 = await atom.workspace.open('sample.coffee', {activateItem: false})
}
otherItem2 = otherItem1.copy()
spyOn(initialActiveItem, 'save').andCallFake(() => Promise.resolve())
spyOn(otherItem1, 'save').andCallFake(() => Promise.resolve())
spyOn(otherItem2, 'save').andCallFake(() => Promise.resolve())
})
describe('when the item is not modified', () => {
it('does not autosave the item', () => {
atom.config.set('autosave.enabled', true)
atom.workspace.getActivePane().splitRight({items: [otherItem1]})
expect(initialActiveItem.save).not.toHaveBeenCalled()
})
})
describe('when the buffer is modified', () => {
beforeEach(() => initialActiveItem.setText('i am modified'))
it('autosaves newly added items', async () => {
const newItem = await atom.workspace.createItemForURI('notyet.js')
spyOn(newItem, 'isModified').andReturn(true)
atom.config.set('autosave.enabled', true)
spyOn(atom.workspace.getActivePane(), 'saveItem').andCallFake(() => Promise.resolve())
atom.workspace.getActivePane().addItem(newItem)
expect(atom.workspace.getActivePane().saveItem).toHaveBeenCalledWith(newItem)
})
describe('when a pane loses focus', () => {
it('saves the item if autosave is enabled and the item has a uri', () => {
document.body.focus()
expect(initialActiveItem.save).not.toHaveBeenCalled()
workspaceElement.focus()
atom.config.set('autosave.enabled', true)
document.body.focus()
expect(initialActiveItem.save).toHaveBeenCalled()
})
it('suppresses autosave if the file does not exist', () => {
document.body.focus()
expect(initialActiveItem.save).not.toHaveBeenCalled()
workspaceElement.focus()
atom.config.set('autosave.enabled', true)
const originalPath = atom.workspace.getActiveTextEditor().getPath()
const tmpPath = `${originalPath}~`
fs.renameSync(originalPath, tmpPath)
document.body.focus()
expect(initialActiveItem.save).not.toHaveBeenCalled()
fs.renameSync(tmpPath, originalPath)
})
it('suppresses autosave if the focused element is contained by the editor, such as occurs when opening the autocomplete menu', () => {
atom.config.set('autosave.enabled', true)
const focusStealer = document.createElement('div')
focusStealer.setAttribute('tabindex', -1)
const textEditorElement = atom.views.getView(atom.workspace.getActiveTextEditor())
textEditorElement.appendChild(focusStealer)
focusStealer.focus()
expect(initialActiveItem.save).not.toHaveBeenCalled()
})
})
describe('when a new pane is created', () => {
it('saves the item if autosave is enabled and the item has a uri', () => {
const leftPane = atom.workspace.getActivePane()
const rightPane = leftPane.splitRight()
expect(initialActiveItem.save).not.toHaveBeenCalled()
rightPane.destroy()
leftPane.activate()
atom.config.set('autosave.enabled', true)
leftPane.splitRight()
expect(initialActiveItem.save).toHaveBeenCalled()
})
})
describe('when an item is destroyed', () => {
describe('when the item is the active item', () => {
it('does not save the item if autosave is enabled and the item has a uri', async () => {
let leftPane = atom.workspace.getActivePane()
const rightPane = leftPane.splitRight({items: [otherItem1]})
leftPane.activate()
expect(initialActiveItem).toBe(atom.workspace.getActivePaneItem())
leftPane.destroyItem(initialActiveItem)
expect(initialActiveItem.save).not.toHaveBeenCalled()
otherItem2.setText('I am also modified')
atom.config.set('autosave.enabled', true)
leftPane = rightPane.splitLeft({items: [otherItem2]})
expect(otherItem2).toBe(atom.workspace.getActivePaneItem())
await leftPane.destroyItem(otherItem2)
expect(otherItem2.save).toHaveBeenCalled()
})
})
describe('when the item is NOT the active item', () => {
it('does not save the item if autosave is enabled and the item has a uri', () => {
let leftPane = atom.workspace.getActivePane()
const rightPane = leftPane.splitRight({items: [otherItem1]})
expect(initialActiveItem).not.toBe(atom.workspace.getActivePaneItem())
leftPane.destroyItem(initialActiveItem)
expect(initialActiveItem.save).not.toHaveBeenCalled()
otherItem2.setText('I am also modified')
atom.config.set('autosave.enabled', true)
leftPane = rightPane.splitLeft({items: [otherItem2]})
rightPane.focus()
expect(otherItem2).not.toBe(atom.workspace.getActivePaneItem())
leftPane.destroyItem(otherItem2)
expect(otherItem2.save).toHaveBeenCalled()
})
})
})
describe('when the item does not have a URI', () => {
it('does not save the item', async () => {
await atom.workspace.open()
const pathLessItem = atom.workspace.getActiveTextEditor()
spyOn(pathLessItem, 'save').andCallThrough()
pathLessItem.setText('text!')
expect(pathLessItem.getURI()).toBeFalsy()
atom.config.set('autosave.enabled', true)
atom.workspace.getActivePane().destroyItem(pathLessItem)
expect(pathLessItem.save).not.toHaveBeenCalled()
})
})
})
describe('when the window is blurred', () => {
it('saves all items', () => {
atom.config.set('autosave.enabled', true)
const leftPane = atom.workspace.getActivePane()
leftPane.splitRight({items: [otherItem1]})
initialActiveItem.insertText('a')
otherItem1.insertText('b')
window.dispatchEvent(new FocusEvent('blur'))
expect(initialActiveItem.save).toHaveBeenCalled()
expect(otherItem1.save).toHaveBeenCalled()
})
})
describe('when the package is deactivated', () => {
it('saves all items and waits for saves to complete', () => {
atom.config.set('autosave.enabled', true)
const leftPane = atom.workspace.getActivePane()
leftPane.splitRight({items: [otherItem1]})
initialActiveItem.insertText('a')
otherItem1.insertText('b')
let deactivated = false
let asyncDeactivateSupported = true
let resolveInitial = () => {}
let resolveOther = () => {}
initialActiveItem.save.andCallFake(() => {
return new Promise(resolve => {
resolveInitial = resolve
})
})
otherItem1.save.andCallFake(() => {
return new Promise(resolve => {
resolveOther = resolve
})
})
let deactivatePromise = atom.packages.deactivatePackage('autosave')
if (!deactivatePromise || !deactivatePromise.then || typeof deactivatePromise.then !== 'function') {
// Atom does not support asynchronous package deactivation.
// This keeps us from failing on 1.20
asyncDeactivateSupported = false
deactivatePromise = Promise.resolve()
}
deactivatePromise.then((result) => {
if (result === undefined) {
// This keeps us from failing on 1.21-beta1
asyncDeactivateSupported = false
}
deactivated = true
})
waitsForPromise(() => Promise.resolve())
runs(() => {
if (asyncDeactivateSupported) {
expect(deactivated).toBe(false)
}
resolveInitial()
resolveOther()
})
waitsFor(() => !asyncDeactivateSupported || deactivated)
})
})
it("saves via the item's Pane so that write errors are handled via notifications", async () => {
const saveError = new Error('Save failed')
saveError.code = 'EACCES'
saveError.path = initialActiveItem.getPath()
initialActiveItem.save.andThrow(saveError)
const errorCallback = jasmine.createSpy('errorCallback').andCallFake(({preventDefault}) => preventDefault())
atom.onWillThrowError(errorCallback)
spyOn(atom.notifications, 'addWarning')
initialActiveItem.insertText('a')
atom.config.set('autosave.enabled', true)
await atom.workspace.destroyActivePaneItem()
expect(initialActiveItem.save).toHaveBeenCalled()
expect(atom.notifications.addWarning.callCount > 0 || errorCallback.callCount > 0).toBe(true)
})
describe('dontSaveIf service', () => {
it("doesn't save a paneItem if a predicate function registered via the dontSaveIf service returns true", async () => {
atom.workspace.getActivePane().addItem(otherItem1)
atom.config.set('autosave.enabled', true)
const service = atom.packages.getActivePackage('autosave').mainModule.provideService()
service.dontSaveIf(paneItem => paneItem === initialActiveItem)
initialActiveItem.setText('foo')
otherItem1.setText('bar')
window.dispatchEvent(new FocusEvent('blur'))
expect(initialActiveItem.save).not.toHaveBeenCalled()
expect(otherItem1.save).toHaveBeenCalled()
})
})
})

View File

@ -0,0 +1,23 @@
class Quicksort
sort: (items) ->
return items if items.length <= 1
pivot = items.shift()
left = []
right = []
# Comment in the middle (and add the word 'items' again)
while items.length > 0
current = items.shift()
if current < pivot
left.push(current)
else
right.push(current)
sort(left).concat(pivot).concat(sort(right))
noop: ->
# just a noop
exports.modules = quicksort

View File

@ -0,0 +1,13 @@
var quicksort = function () {
var sort = function(items) {
if (items.length <= 1) return items;
var pivot = items.shift(), current, left = [], right = [];
while(items.length > 0) {
current = items.shift();
current < pivot ? left.push(current) : right.push(current);
}
return sort(left).concat(pivot).concat(sort(right));
};
return sort(Array.apply(this, arguments));
};

1
packages/bracket-matcher/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

441
packages/bracket-matcher/package-lock.json generated Normal file
View File

@ -0,0 +1,441 @@
{
"name": "bracket-matcher",
"version": "0.92.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"camelcase": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz",
"integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8="
},
"cliui": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz",
"integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=",
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1",
"wrap-ansi": "^2.0.0"
}
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"coffee-script": {
"version": "1.12.7",
"resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz",
"integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
},
"cson-parser": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/cson-parser/-/cson-parser-1.3.5.tgz",
"integrity": "sha1-fsZ14DkUVTO/KmqFYHPxWZ2cLSQ=",
"requires": {
"coffee-script": "^1.10.0"
}
},
"d": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz",
"integrity": "sha1-2hhMU10Y2O57oqoim5FACfrhEwk=",
"requires": {
"es5-ext": "~0.10.2"
}
},
"decamelize": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
},
"emissary": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/emissary/-/emissary-1.3.3.tgz",
"integrity": "sha1-phjZLWgrIy0xER3DYlpd9mF5lgY=",
"requires": {
"es6-weak-map": "^0.1.2",
"mixto": "1.x",
"property-accessors": "^1.1",
"underscore-plus": "1.x"
}
},
"es5-ext": {
"version": "0.10.52",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.52.tgz",
"integrity": "sha512-bWCbE9fbpYQY4CU6hJbJ1vSz70EClMlDgJ7BmwI+zEJhxrwjesZRPglGJlsZhu0334U3hI+gaspwksH9IGD6ag==",
"requires": {
"es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.2",
"next-tick": "~1.0.0"
},
"dependencies": {
"d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"requires": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=",
"requires": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"requires": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
}
}
},
"es6-iterator": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-0.1.3.tgz",
"integrity": "sha1-1vWLjE/EE8JJtLqhl2j45NfIlE4=",
"requires": {
"d": "~0.1.1",
"es5-ext": "~0.10.5",
"es6-symbol": "~2.0.1"
}
},
"es6-symbol": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-2.0.1.tgz",
"integrity": "sha1-dhtcZ8/U8dGK+yNPaR1nhoLLO/M=",
"requires": {
"d": "~0.1.1",
"es5-ext": "~0.10.5"
}
},
"es6-weak-map": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-0.1.4.tgz",
"integrity": "sha1-cGzvnpmqI2undmwjnIueKG6n0ig=",
"requires": {
"d": "~0.1.1",
"es5-ext": "~0.10.6",
"es6-iterator": "~0.1.3",
"es6-symbol": "~2.0.1"
}
},
"event-kit": {
"version": "2.5.3",
"resolved": "https://registry.npmjs.org/event-kit/-/event-kit-2.5.3.tgz",
"integrity": "sha512-b7Qi1JNzY4BfAYfnIRanLk0DOD1gdkWHT4GISIn8Q2tAf3LpU8SP2CMwWaq40imYoKWbtN4ZhbSRxvsnikooZQ=="
},
"ext": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.2.0.tgz",
"integrity": "sha512-0ccUQK/9e3NreLFg6K6np8aPyRgwycx+oFGtfx1dSp7Wj00Ozw9r05FgBRlzjf2XBM7LAzwgLyDscRrtSU91hA==",
"requires": {
"type": "^2.0.0"
},
"dependencies": {
"type": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz",
"integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow=="
}
}
},
"first-mate": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/first-mate/-/first-mate-7.4.1.tgz",
"integrity": "sha512-SEG5W0aajCvK/Ngoo3he3Ib4DsT+CRPhBAgSju5hksBLvvUfRWP7Jf3+HQ+CNTD4GZZqbDNOEJNOxbf35EblrQ==",
"requires": {
"emissary": "^1",
"event-kit": "^2.2.0",
"fs-plus": "^3.0.0",
"grim": "^2.0.1",
"oniguruma": "7.2.1",
"season": "^6.0.2",
"underscore-plus": "^1"
}
},
"fs-plus": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz",
"integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==",
"requires": {
"async": "^1.5.2",
"mkdirp": "^0.5.1",
"rimraf": "^2.5.2",
"underscore-plus": "1.x"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
"integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
"requires": {
"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"
}
},
"grim": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/grim/-/grim-2.0.2.tgz",
"integrity": "sha512-Qj7hTJRfd87E/gUgfvM0YIH/g2UA2SV6niv6BYXk1o6w4mhgv+QyYM1EjOJQljvzgEj4SqSsRWldXIeKHz3e3Q==",
"requires": {
"event-kit": "^2.0.0"
}
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"invert-kv": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
"integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"requires": {
"number-is-nan": "^1.0.0"
}
},
"lcid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
"integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
"requires": {
"invert-kv": "^1.0.0"
}
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0="
},
"mixto": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/mixto/-/mixto-1.0.0.tgz",
"integrity": "sha1-wyDvYbUvKJj1IuF9i7xtUG2EJbY="
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"requires": {
"minimist": "0.0.8"
}
},
"nan": {
"version": "2.14.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
"integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg=="
},
"next-tick": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz",
"integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw="
},
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
"requires": {
"wrappy": "1"
}
},
"oniguruma": {
"version": "7.2.1",
"resolved": "https://registry.npmjs.org/oniguruma/-/oniguruma-7.2.1.tgz",
"integrity": "sha512-WPS/e1uzhswPtJSe+Zls/kAj27+lEqZjCmRSjnYk/Z4L2Mu+lJC2JWtkZhPJe4kZeTQfz7ClcLyXlI4J68MG2w==",
"requires": {
"nan": "^2.14.0"
}
},
"os-locale": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz",
"integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=",
"requires": {
"lcid": "^1.0.0"
}
},
"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="
},
"property-accessors": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/property-accessors/-/property-accessors-1.1.3.tgz",
"integrity": "sha1-Hd6EAkYxhlkJ7zBwM2VoDF+SixU=",
"requires": {
"es6-weak-map": "^0.1.2",
"mixto": "1.x"
}
},
"rimraf": {
"version": "2.7.1",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
"integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
"requires": {
"glob": "^7.1.3"
}
},
"season": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/season/-/season-6.0.2.tgz",
"integrity": "sha1-naWPsd3SSCTXYhstxjpxI7UCF7Y=",
"requires": {
"cson-parser": "^1.3.0",
"fs-plus": "^3.0.0",
"yargs": "^3.23.0"
}
},
"string-width": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
"strip-ansi": "^3.0.0"
}
},
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"requires": {
"ansi-regex": "^2.0.0"
}
},
"type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"underscore": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.8.3.tgz",
"integrity": "sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI="
},
"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==",
"requires": {
"underscore": "~1.8.3"
}
},
"window-size": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz",
"integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY="
},
"wrap-ansi": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
"requires": {
"string-width": "^1.0.1",
"strip-ansi": "^3.0.1"
}
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"y18n": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz",
"integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ=="
},
"yargs": {
"version": "3.32.0",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz",
"integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=",
"requires": {
"camelcase": "^2.0.1",
"cliui": "^3.0.3",
"decamelize": "^1.1.1",
"os-locale": "^1.4.0",
"string-width": "^1.0.1",
"window-size": "^0.1.4",
"y18n": "^3.2.0"
}
}
}
}

View File

@ -1,67 +1,67 @@
{
"name": "bracket-matcher",
"version": "0.92.0",
"main": "./lib/main",
"description": "Highlight the matching bracket for the `(){}[]` character under the cursor. Move the cursor to the matching bracket with `ctrl-m`.",
"repository": "https://github.com/pulsar-edit/bracket-matcher",
"license": "MIT",
"engines": {
"atom": "*"
},
"dependencies": {
"underscore-plus": "1.x"
},
"configSchema": {
"autocompleteCharacters": {
"description": "Autocompleted characters treated as matching pairs, such as `()`, and `{}`.",
"type": "array",
"default": [
"()",
"[]",
"{}",
"\"\"",
"''",
"``",
"“”",
"",
"«»",
""
],
"items": {
"type": "string"
}
},
"pairsWithExtraNewline": {
"description": "Automatically add a newline between the pair when enter is pressed.",
"type": "array",
"default": [
"()",
"[]",
"{}"
],
"items": {
"type": "string"
}
},
"autocompleteBrackets": {
"type": "boolean",
"default": true,
"description": "Autocomplete bracket and quote characters, such as `(` and `)`, and `\"`."
},
"wrapSelectionsInBrackets": {
"type": "boolean",
"default": true,
"description": "Wrap selected text in brackets or quotes when the editor contains selections and the opening bracket or quote is typed."
},
"highlightMatchingLineNumber": {
"type": "boolean",
"default": false,
"description": "Highlight the line number of the matching bracket."
},
"alwaysSkipClosingPairs": {
"type": "boolean",
"default": false,
"description": "Always skip closing pairs in front of the cursor."
}
}
}
{
"name": "bracket-matcher",
"version": "0.92.0",
"main": "./lib/main",
"description": "Highlight the matching bracket for the `(){}[]` character under the cursor. Move the cursor to the matching bracket with `ctrl-m`.",
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",
"engines": {
"atom": "*"
},
"dependencies": {
"underscore-plus": "1.x"
},
"configSchema": {
"autocompleteCharacters": {
"description": "Autocompleted characters treated as matching pairs, such as `()`, and `{}`.",
"type": "array",
"default": [
"()",
"[]",
"{}",
"\"\"",
"''",
"``",
"“”",
"",
"«»",
""
],
"items": {
"type": "string"
}
},
"pairsWithExtraNewline": {
"description": "Automatically add a newline between the pair when enter is pressed.",
"type": "array",
"default": [
"()",
"[]",
"{}"
],
"items": {
"type": "string"
}
},
"autocompleteBrackets": {
"type": "boolean",
"default": true,
"description": "Autocomplete bracket and quote characters, such as `(` and `)`, and `\"`."
},
"wrapSelectionsInBrackets": {
"type": "boolean",
"default": true,
"description": "Wrap selected text in brackets or quotes when the editor contains selections and the opening bracket or quote is typed."
},
"highlightMatchingLineNumber": {
"type": "boolean",
"default": false,
"description": "Highlight the line number of the matching bracket."
},
"alwaysSkipClosingPairs": {
"type": "boolean",
"default": false,
"description": "Always skip closing pairs in front of the cursor."
}
}
}

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/pulsar",
"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;
}
}
}

1
packages/timecop/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

View File

@ -0,0 +1,14 @@
# Timecop package
Displays information about where time is spent while Pulsar loads.
* Startup time
* Compile cache
* Package loading time
* Package activation time
* Theme loading time
* Theme activation time
![](https://cloud.githubusercontent.com/assets/378023/20422582/9e5907f8-adae-11e6-8267-faa3514de896.png)
Inspired by [Timecop](http://www.imdb.com/title/tt0111438/) the movie. :watch: :rotating_light:

View File

@ -0,0 +1,100 @@
/** @babel */
/** @jsx etch.dom */
import path from 'path'
import etch from 'etch'
export default class CachePanelView {
constructor () {
etch.initialize(this)
}
update () {}
destroy () {
return etch.destroy(this)
}
render () {
return (
<div className='tool-panel padded package-panel'>
<div className='inset-panel'>
<div className='panel-heading'>Compile Cache</div>
<div className='panel-body padded'>
<div className='timing'>
<span className='inline-block'>CoffeeScript files compiled</span>
<span className='inline-block' ref='coffeeCompileCount'>Loading</span>
</div>
<div className='timing'>
<span className='inline-block'>Babel files compiled</span>
<span className='inline-block' ref='babelCompileCount'>Loading</span>
</div>
<div className='timing'>
<span className='inline-block'>Typescript files compiled</span>
<span className='inline-block' ref='typescriptCompileCount'>Loading</span>
</div>
<div className='timing'>
<span className='inline-block'>CSON files compiled</span>
<span className='inline-block' ref='csonCompileCount'>Loading</span>
</div>
<div className='timing'>
<span className='inline-block'>Less files compiled</span>
<span className='inline-block' ref='lessCompileCount'>Loading</span>
</div>
</div>
</div>
</div>
)
}
populate () {
const compileCacheStats = this.getCompileCacheStats()
if (compileCacheStats) {
this.refs.coffeeCompileCount.classList.add('highlight-info')
this.refs.coffeeCompileCount.textContent = compileCacheStats['.coffee'].misses
this.refs.babelCompileCount.classList.add('highlight-info')
this.refs.babelCompileCount.textContent = compileCacheStats['.js'].misses
this.refs.typescriptCompileCount.classList.add('highlight-info')
this.refs.typescriptCompileCount.textContent = compileCacheStats['.ts'].misses
}
this.refs.csonCompileCount.classList.add('highlight-info')
this.refs.csonCompileCount.textContent = this.getCsonCompiles()
this.refs.lessCompileCount.classList.add('highlight-info')
this.refs.lessCompileCount.textContent = this.getLessCompiles()
}
getCompileCacheStats () {
try {
return require(path.join(atom.getLoadSettings().resourcePath, 'src', 'compile-cache')).getCacheStats()
} catch (error) {
return null
}
}
getCsonCompiles () {
try {
const CSON = require(path.join(atom.getLoadSettings().resourcePath, 'node_modules', 'season'))
if (CSON.getCacheMisses) {
return CSON.getCacheMisses() || 0
} else {
return 0
}
} catch (error) {
return 0
}
}
getLessCompiles () {
const lessCache = atom.themes.lessCache
if (lessCache && lessCache.cache && lessCache.cache.stats && lessCache.cache.stats.misses) {
return lessCache.cache.stats.misses || 0
} else {
return 0
}
}
}

View File

@ -0,0 +1,24 @@
const {CompositeDisposable} = require('atom')
let TimecopView = null
const ViewURI = 'atom://timecop'
module.exports = {
activate () {
this.subscriptions = new CompositeDisposable()
this.subscriptions.add(atom.workspace.addOpener(filePath => {
if (filePath === ViewURI) return this.createTimecopView({uri: ViewURI})
}))
this.subscriptions.add(atom.commands.add('atom-workspace', 'timecop:view', () => atom.workspace.open(ViewURI)))
},
deactivate () {
this.subscriptions.dispose()
},
createTimecopView (state) {
if (TimecopView == null) TimecopView = require('./timecop-view')
return new TimecopView(state)
}
}

View File

@ -0,0 +1,70 @@
/** @babel */
/** @jsx etch.dom */
import {Disposable} from 'atom'
import etch from 'etch'
export default class PackagePanelView {
constructor ({title}) {
this.title = title
etch.initialize(this)
const clickHandler = (event) => {
const target = event.target.closest('a.package')
if (target) {
atom.workspace.open(`atom://config/packages/${target.dataset.package}`)
}
}
this.element.addEventListener('click', clickHandler)
this.disposable = new Disposable(() => { this.element.removeEventListener('click', clickHandler) })
}
update () {}
destroy () {
this.disposable.dispose()
return etch.destroy(this)
}
render () {
return (
<div className='tool-panel padded package-panel'>
<div className='inset-panel'>
<div className='panel-heading'>{this.title}</div>
<div className='panel-body padded'>
<div className='text-info' ref='summary'>Loading</div>
<ul className='list-group' ref='list' />
</div>
</div>
</div>
)
}
addPackages (packages, timeKey) {
for (const pack of packages) {
this.addPackage(pack, timeKey)
}
}
addPackage (pack, timeKey) {
const li = document.createElement('div')
li.classList.add('list-item')
const a = document.createElement('a')
a.classList.add('inline-block', 'package')
a.dataset.package = pack.name
a.textContent = pack.name
li.appendChild(a)
const line = document.createElement('span')
line.classList.add('timecop-line')
li.appendChild(line)
const timeSpan = document.createElement('span')
timeSpan.classList.add('inline-block', pack[timeKey] > 25 ? 'highlight-error' : 'highlight-warning')
timeSpan.textContent = `${pack[timeKey]}ms`
li.appendChild(timeSpan)
this.refs.list.appendChild(li)
}
}

View File

@ -0,0 +1,137 @@
/** @babel */
/** @jsx etch.dom */
import _ from 'underscore-plus'
import dedent from 'dedent'
import etch from 'etch'
import CachePanelView from './cache-panel-view'
import PackagePanelView from './package-panel-view'
import WindowPanelView from './window-panel-view'
export default class TimecopView {
constructor ({uri}) {
this.uri = uri
etch.initialize(this)
this.refs.cacheLoadingPanel.populate()
if (atom.packages.hasLoadedInitialPackages()) {
this.populateLoadingViews()
} else {
atom.packages.onDidLoadInitialPackages(() => this.populateLoadingViews())
}
if (atom.packages.hasActivatedInitialPackages()) {
this.populateActivationViews()
} else {
atom.packages.onDidActivateInitialPackages(() => this.populateActivationViews())
}
}
update () {}
destroy () {
return etch.destroy(this)
}
render () {
return (
<div className='timecop pane-item native-key-bindings' tabIndex='-1'>
<div className='timecop-panel'>
<div className='panels'>
<WindowPanelView ref='windowLoadingPanel' />
<CachePanelView ref='cacheLoadingPanel' />
</div>
<div className='panels'>
<PackagePanelView ref='packageLoadingPanel' title='Package Loading' />
<PackagePanelView ref='packageActivationPanel' title='Package Activation' />
<PackagePanelView ref='themeLoadingPanel' title='Theme Loading' />
<PackagePanelView ref='themeActivationPanel' title='Theme Activation' />
</div>
</div>
</div>
)
}
populateLoadingViews () {
this.showLoadedPackages()
this.showLoadedThemes()
}
populateActivationViews () {
this.refs.windowLoadingPanel.populate()
this.showActivePackages()
this.showActiveThemes()
}
showLoadedPackages () {
const {time, count, packages} = this.getSlowPackages(
atom.packages.getLoadedPackages().filter(pack => pack.getType() !== 'theme'),
'loadTime'
)
this.refs.packageLoadingPanel.addPackages(packages, 'loadTime')
this.refs.packageLoadingPanel.refs.summary.textContent = dedent`
Loaded ${count} packages in ${time}ms.
${_.pluralize(packages.length, 'package')} took longer than 5ms to load.
`
}
showActivePackages () {
const {time, count, packages} = this.getSlowPackages(
atom.packages.getActivePackages().filter(pack => pack.getType() !== 'theme'),
'activateTime'
)
this.refs.packageActivationPanel.addPackages(packages, 'activateTime')
this.refs.packageActivationPanel.refs.summary.textContent = dedent`
Activated ${count} packages in ${time}ms.
${_.pluralize(packages.length, 'package')} took longer than 5ms to activate.\
`
}
showLoadedThemes () {
const {time, count, packages} = this.getSlowPackages(atom.themes.getLoadedThemes(), 'loadTime')
this.refs.themeLoadingPanel.addPackages(packages, 'loadTime')
this.refs.themeLoadingPanel.refs.summary.textContent = dedent`
Loaded ${count} themes in ${time}ms.
${_.pluralize(packages.length, 'theme')} took longer than 5ms to load.\
`
}
showActiveThemes () {
const {time, count, packages} = this.getSlowPackages(atom.themes.getActiveThemes(), 'activateTime')
this.refs.themeActivationPanel.addPackages(packages, 'activateTime')
this.refs.themeActivationPanel.refs.summary.textContent = dedent`
Activated ${count} themes in ${time}ms.
${_.pluralize(packages.length, 'theme')} took longer than 5ms to activate.\
`
}
getSlowPackages (packages, timeKey) {
let time = 0
let count = 0
packages = packages.filter(function (pack) {
time += pack[timeKey]
count++
return pack[timeKey] > 5
})
packages.sort((pack1, pack2) => pack2[timeKey] - pack1[timeKey])
return {time, count, packages}
}
serialize () {
return {
deserializer: this.constructor.name,
uri: this.getURI()
}
}
getURI () {
return this.uri
}
getTitle () {
return 'Timecop'
}
getIconName () {
return 'dashboard'
}
}

View File

@ -0,0 +1,91 @@
/** @babel */
/** @jsx etch.dom */
import {CompositeDisposable} from 'atom'
import etch from 'etch'
export default class WindowPanelView {
constructor () {
etch.initialize(this)
this.disposables = new CompositeDisposable()
this.disposables.add(atom.tooltips.add(this.refs.shellTiming, {title: 'The time taken to launch the app'}))
this.disposables.add(atom.tooltips.add(this.refs.windowTiming, {title: 'The time taken to load this window'}))
this.disposables.add(atom.tooltips.add(this.refs.projectTiming, {title: 'The time taken to rebuild the previously opened buffers'}))
this.disposables.add(atom.tooltips.add(this.refs.workspaceTiming, {title: 'The time taken to rebuild the previously opened editors'}))
}
update () {}
destroy () {
this.disposables.dispose()
return etch.destroy(this)
}
render () {
return (
<div className='tool-panel padded package-panel'>
<div className='inset-panel'>
<div className='panel-heading'>Startup Time</div>
<div className='panel-body padded'>
<div className='timing' ref='shellTiming'>
<span className='inline-block'>Shell load time</span>
<span className='inline-block' ref='shellLoadTime'>Loading</span>
</div>
<div className='timing' ref='windowTiming'>
<span className='inline-block'>Window load time</span>
<span className='inline-block' ref='windowLoadTime'>Loading</span>
</div>
<div ref='deserializeTimings'>
<div className='timing' ref='projectTiming'>
<span className='inline-block'>Project load time</span>
<span className='inline-block' ref='projectLoadTime'>Loading</span>
</div>
<div className='timing' ref='workspaceTiming'>
<span className='inline-block'>Workspace load time</span>
<span className='inline-block' ref='workspaceLoadTime'>Loading</span>
</div>
</div>
</div>
</div>
</div>
)
}
populate () {
const time = atom.getWindowLoadTime()
this.refs.windowLoadTime.classList.add(this.getHighlightClass(time))
this.refs.windowLoadTime.textContent = `${time}ms`
const {shellLoadTime} = atom.getLoadSettings()
if (shellLoadTime != null) {
this.refs.shellLoadTime.classList.add(this.getHighlightClass(shellLoadTime))
this.refs.shellLoadTime.textContent = `${shellLoadTime}ms`
} else {
this.refs.shellTiming.style.display = 'none'
}
if (atom.deserializeTimings.project != null) {
// Project and workspace timings only exist if the current project was previously opened
this.refs.projectLoadTime.classList.add(this.getHighlightClass(atom.deserializeTimings.project))
this.refs.projectLoadTime.textContent = `${atom.deserializeTimings.project}ms`
this.refs.workspaceLoadTime.classList.add(this.getHighlightClass(atom.deserializeTimings.workspace))
this.refs.workspaceLoadTime.textContent = `${atom.deserializeTimings.workspace}ms`
} else {
this.refs.deserializeTimings.style.display = 'none'
}
}
getHighlightClass (time) {
if (time > 1000) {
return 'highlight-error'
} else if (time > 800) {
return 'highlight-warning'
} else {
return 'highlight-info'
}
}
}

View File

@ -0,0 +1,10 @@
'menu': [
'label': 'Packages'
'submenu': [
'label': 'Timecop'
'submenu': [
'label': 'Show'
'command': 'timecop:view'
]
]
]

44
packages/timecop/package-lock.json generated Normal file
View File

@ -0,0 +1,44 @@
{
"name": "timecop",
"version": "0.36.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "timecop",
"version": "0.36.2",
"license": "MIT",
"dependencies": {
"dedent": "^0.7.0",
"etch": "^0.12.6",
"underscore-plus": "^1.0.0"
},
"engines": {
"atom": "*"
}
},
"node_modules/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
"integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA=="
},
"node_modules/etch": {
"version": "0.12.8",
"resolved": "https://registry.npmjs.org/etch/-/etch-0.12.8.tgz",
"integrity": "sha512-dFLRe4wLroVtwzyy1vGlE3BSDZHiL0kZME5XgNGzZIULcYTvVno8vbiIleAesoKJmwWaxDTzG+4eppg2zk14JQ=="
},
"node_modules/underscore": {
"version": "1.13.6",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz",
"integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A=="
},
"node_modules/underscore-plus": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz",
"integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==",
"dependencies": {
"underscore": "^1.9.1"
}
}
}
}

View File

@ -0,0 +1,19 @@
{
"name": "timecop",
"version": "0.36.2",
"description": "Displays information about where time is spent while Pulsar loads.",
"main": "./lib/main",
"repository": "https://github.com/pulsar-edit/pulsar",
"license": "MIT",
"engines": {
"atom": "*"
},
"deserializers": {
"TimecopView": "createTimecopView"
},
"dependencies": {
"dedent": "^0.7.0",
"etch": "^0.12.6",
"underscore-plus": "^1.0.0"
}
}

View File

@ -0,0 +1,103 @@
/** @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) {
if (fn === undefined) {
global[name](description)
return
}
global[name](description, function () {
const result = fn()
if (result instanceof Promise) {
waitsForPromise(() => result)
}
})
}
})
export async function conditionPromise (condition, description = 'anonymous condition') {
const startTime = Date.now()
while (true) {
await timeoutPromise(100)
if (await condition()) {
return
}
if (Date.now() - startTime > 5000) {
throw new Error('Timed out waiting on ' + description)
}
}
}
export function timeoutPromise (timeout) {
return new Promise(function (resolve) {
global.setTimeout(resolve, timeout)
})
}
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()
})
})
}
export function emitterEventPromise (emitter, event, timeout = 15000) {
return new Promise((resolve, reject) => {
const timeoutHandle = setTimeout(() => {
reject(new Error(`Timed out waiting for '${event}' event`))
}, timeout)
emitter.once(event, () => {
clearTimeout(timeoutHandle)
resolve()
})
})
}
export function promisify (original) {
return function (...args) {
return new Promise((resolve, reject) => {
args.push((err, ...results) => {
if (err) {
reject(err)
} else {
resolve(...results)
}
})
return original(...args)
})
}
}
export function promisifySome (obj, fnNames) {
const result = {}
for (const fnName of fnNames) {
result[fnName] = promisify(obj[fnName])
}
return result
}

View File

@ -0,0 +1,98 @@
const path = require('path')
const CompileCache = require(path.join(atom.getLoadSettings().resourcePath, 'src', 'compile-cache'))
const CSON = require(path.join(atom.getLoadSettings().resourcePath, 'node_modules', 'season'))
const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars
describe('Timecop', () => {
beforeEach(async () => {
spyOn(CompileCache, 'getCacheStats').andReturn({
'.js': {hits: 3, misses: 4},
'.ts': {hits: 5, misses: 6},
'.coffee': {hits: 7, misses: 8}
})
spyOn(CSON, 'getCacheMisses').andReturn(10)
atom.themes.lessCache.cache.stats.misses = 12
await atom.packages.activatePackage('timecop')
})
describe('the Timecop view', () => {
let timecopView = null
beforeEach(async () => {
const packages = [
new FakePackage({
name: 'slow-activating-package-1',
activateTime: 500,
loadTime: 5
}),
new FakePackage({
name: 'slow-activating-package-2',
activateTime: 500,
loadTime: 5
}),
new FakePackage({
name: 'slow-loading-package',
activateTime: 5,
loadTime: 500
}),
new FakePackage({
name: 'fast-package',
activateTime: 2,
loadTime: 3
})
]
spyOn(atom.packages, 'getLoadedPackages').andReturn(packages)
spyOn(atom.packages, 'getActivePackages').andReturn(packages)
spyOn(atom.packages, 'hasLoadedInitialPackages').andReturn(true)
spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true)
timecopView = await atom.workspace.open('atom://timecop')
})
afterEach(() => jasmine.unspy(atom.packages, 'getLoadedPackages'))
it('shows the packages that loaded slowly', () => {
const loadingPanel = timecopView.refs.packageLoadingPanel
expect(loadingPanel.element.textContent).toMatch(/1 package took longer than 5ms to load/)
expect(loadingPanel.element.textContent).toMatch(/slow-loading-package/)
expect(loadingPanel.element.textContent).not.toMatch(/slow-activating-package/)
expect(loadingPanel.element.textContent).not.toMatch(/fast-package/)
})
it('shows the packages that activated slowly', () => {
const activationPanel = timecopView.refs.packageActivationPanel
expect(activationPanel.element.textContent).toMatch(/2 packages took longer than 5ms to activate/)
expect(activationPanel.element.textContent).toMatch(/slow-activating-package-1/)
expect(activationPanel.element.textContent).toMatch(/slow-activating-package-2/)
expect(activationPanel.element.textContent).not.toMatch(/slow-loading-package/)
expect(activationPanel.element.textContent).not.toMatch(/fast-package/)
})
it('shows how many files were transpiled from each language', () => {
const cachePanel = timecopView.refs.cacheLoadingPanel
expect(cachePanel.element.textContent).toMatch(/CoffeeScript files compiled\s*8/)
expect(cachePanel.element.textContent).toMatch(/Babel files compiled\s*4/)
expect(cachePanel.element.textContent).toMatch(/Typescript files compiled\s*6/)
expect(cachePanel.element.textContent).toMatch(/CSON files compiled\s*10/)
expect(cachePanel.element.textContent).toMatch(/Less files compiled\s*12/)
})
})
})
class FakePackage {
constructor ({name, activateTime, loadTime}) {
this.name = name
this.activateTime = activateTime
this.loadTime = loadTime
}
getType () { return 'package' }
isTheme () { return false }
}

View File

@ -0,0 +1,69 @@
@import "ui-variables";
.timecop {
.timecop-panel {
background-color: @tool-panel-background-color;
}
overflow: auto;
.panels {
display: flex;
flex-direction: row;
}
.package-panel {
display: flex;
flex-direction: row;
flex: 1;
.list-group {
overflow: hidden;
}
.inset-panel {
width: 100%;
}
}
.text-info {
margin-bottom: @component-padding;
}
.timing,
.list-item {
display: flex;
justify-content: space-between;
.inline-block:last-child {
margin-right: 0;
}
}
.timing {
margin-bottom: @component-padding;
}
.list-item {
margin-top: @component-padding / 2;;
margin-bottom: @component-padding / 2;
}
.package {
color: @text-color;
&:hover {
text-decoration: none;
color: @text-color-selected;
}
}
.timecop-line {
flex: 1 1 0;
margin-right: @component-padding;
margin-top: .80em;
border-top: 1px dashed fade(@text-color, 20%);
}
.package:hover + .timecop-line {
border-color: fade(@text-color, 60%);
}
}

View File

@ -2379,11 +2379,8 @@ atom-slick@^2, atom-slick@^2.0.0:
dependencies:
underscore-plus "^1.7.0"
"autosave@https://codeload.github.com/atom/autosave/legacy.tar.gz/refs/tags/v0.24.6":
"autosave@file:./packages/autosave":
version "0.24.6"
resolved "https://codeload.github.com/atom/autosave/legacy.tar.gz/refs/tags/v0.24.6#bbd25c3364cfa5b3c73f9d4d08da93aaac339020"
dependencies:
fs-plus "^3.0.0"
available-typed-arrays@^1.0.5:
version "1.0.5"
@ -6125,9 +6122,8 @@ key-path-helpers@^0.4.0:
resolved "https://registry.yarnpkg.com/key-path-helpers/-/key-path-helpers-0.4.0.tgz#e87f6a159144adf2692e46f2a4641ce269c09119"
integrity sha512-WGl1FG6zjyXCU6YrOVrvanyN3iiLFPLr/UmZ/jGiEOd7CXQV83/Ng5aLGK/Erehtr1nxU8dOwnRjseQDGElMQw==
"keybinding-resolver@https://codeload.github.com/atom/keybinding-resolver/legacy.tar.gz/refs/tags/v0.39.1":
"keybinding-resolver@file:./packages/keybinding-resolver":
version "0.39.1"
resolved "https://codeload.github.com/atom/keybinding-resolver/legacy.tar.gz/refs/tags/v0.39.1#a1c08beb144cfca145bf19fe0830b2e82514d4cb"
dependencies:
etch "0.9.0"
fs-plus "^3.0.0"
@ -9293,9 +9289,8 @@ through@2, through@2.3.x, through@^2.3.8, through@~2.3, through@~2.3.1:
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==
"timecop@https://codeload.github.com/atom/timecop/legacy.tar.gz/refs/tags/v0.36.2":
"timecop@file:./packages/timecop":
version "0.36.2"
resolved "https://codeload.github.com/atom/timecop/legacy.tar.gz/refs/tags/v0.36.2#8965f20444701af3b6f86dfa9f144a46e1aaa9d0"
dependencies:
dedent "^0.7.0"
etch "^0.12.6"
@ -10001,9 +9996,8 @@ which@^2.0.1, which@^2.0.2:
dependencies:
isexe "^2.0.0"
"whitespace@https://codeload.github.com/atom/whitespace/legacy.tar.gz/refs/tags/v0.37.8":
"whitespace@file:./packages/whitespace":
version "0.37.8"
resolved "https://codeload.github.com/atom/whitespace/legacy.tar.gz/refs/tags/v0.37.8#0c0510f8ead296d63a16105024700ec34b2b5d1b"
wide-align@1.1.3:
version "1.1.3"