├── .github ├── no-response.yml └── workflows │ └── ci.yml ├── .gitignore ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── lib ├── cache-panel-view.js ├── main.js ├── package-panel-view.js ├── timecop-view.js └── window-panel-view.js ├── menus └── timecop.cson ├── package.json ├── spec ├── async-spec-helpers.js └── timecop-spec.js └── styles └── timecop.less /.github/no-response.yml: -------------------------------------------------------------------------------- 1 | # Configuration for probot-no-response - https://github.com/probot/no-response 2 | 3 | # Number of days of inactivity before an issue is closed for lack of response 4 | daysUntilClose: 28 5 | 6 | # Label requiring a response 7 | responseRequiredLabel: more-information-needed 8 | 9 | # Comment to post when closing an issue for lack of response. Set to `false` to disable. 10 | closeComment: > 11 | This issue has been automatically closed because there has been no response 12 | to our request for more information from the original author. With only the 13 | information that is currently in the issue, we don't have enough information 14 | to take action. Please reach out if you have or find the answers we need so 15 | that we can investigate further. 16 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push] 4 | 5 | env: 6 | CI: true 7 | 8 | jobs: 9 | Test: 10 | strategy: 11 | matrix: 12 | os: [ubuntu-latest, macos-latest, windows-latest] 13 | channel: [stable, beta] 14 | runs-on: ${{ matrix.os }} 15 | steps: 16 | - uses: actions/checkout@v1 17 | - uses: UziTech/action-setup-atom@v2 18 | with: 19 | version: ${{ matrix.channel }} 20 | - name: Install dependencies 21 | run: apm install 22 | - name: Run tests 23 | run: atom --test spec 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | See the [Atom contributing guide](https://github.com/atom/atom/blob/master/CONTRIBUTING.md) 2 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ### Prerequisites 10 | 11 | * [ ] Put an X between the brackets on this line if you have done all of the following: 12 | * Reproduced the problem in Safe Mode: http://flight-manual.atom.io/hacking-atom/sections/debugging/#using-safe-mode 13 | * Followed all applicable steps in the debugging guide: http://flight-manual.atom.io/hacking-atom/sections/debugging/ 14 | * Checked the FAQs on the message board for common solutions: https://discuss.atom.io/c/faq 15 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3Aatom 16 | * Checked that there is not already an Atom package that provides the described functionality: https://atom.io/packages 17 | 18 | ### Description 19 | 20 | [Description of the issue] 21 | 22 | ### Steps to Reproduce 23 | 24 | 1. [First Step] 25 | 2. [Second Step] 26 | 3. [and so on...] 27 | 28 | **Expected behavior:** [What you expect to happen] 29 | 30 | **Actual behavior:** [What actually happens] 31 | 32 | **Reproduces how often:** [What percentage of the time does it reproduce?] 33 | 34 | ### Versions 35 | 36 | You can get this information from copy and pasting the output of `atom --version` and `apm --version` from the command line. Also, please include the OS and what version of the OS you're running. 37 | 38 | ### Additional Information 39 | 40 | Any additional information, configuration or data that might be necessary to reproduce the issue. 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 GitHub Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Benefits 19 | 20 | 21 | 22 | ### Possible Drawbacks 23 | 24 | 25 | 26 | ### Applicable Issues 27 | 28 | 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) 2 | # Timecop package 3 | [![OS X Build Status](https://travis-ci.org/atom/timecop.svg?branch=master)](https://travis-ci.org/atom/timecop) [![Windows Build Status](https://ci.appveyor.com/api/projects/status/37fhichmvx90sd97/branch/master?svg=true)](https://ci.appveyor.com/project/Atom/timecop/branch/master) [![Dependency Status](https://david-dm.org/atom/timecop.svg)](https://david-dm.org/atom/timecop) 4 | 5 | Displays information about where time is spent while Atom loads. 6 | 7 | * Startup time 8 | * Compile cache 9 | * Package loading time 10 | * Package activation time 11 | * Theme loading time 12 | * Theme activation time 13 | 14 | ![](https://cloud.githubusercontent.com/assets/378023/20422582/9e5907f8-adae-11e6-8267-faa3514de896.png) 15 | 16 | Inspired by [Timecop](http://www.imdb.com/title/tt0111438/) the movie. :watch: :rotating_light: 17 | -------------------------------------------------------------------------------- /lib/cache-panel-view.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /** @jsx etch.dom */ 3 | 4 | import path from 'path' 5 | import etch from 'etch' 6 | 7 | export default class CachePanelView { 8 | constructor () { 9 | etch.initialize(this) 10 | } 11 | 12 | update () {} 13 | 14 | destroy () { 15 | return etch.destroy(this) 16 | } 17 | 18 | render () { 19 | return ( 20 |
21 |
22 |
Compile Cache
23 |
24 |
25 | CoffeeScript files compiled 26 | Loading… 27 |
28 | 29 |
30 | Babel files compiled 31 | Loading… 32 |
33 | 34 |
35 | Typescript files compiled 36 | Loading… 37 |
38 | 39 |
40 | CSON files compiled 41 | Loading… 42 |
43 | 44 |
45 | Less files compiled 46 | Loading… 47 |
48 |
49 |
50 |
51 | ) 52 | } 53 | 54 | populate () { 55 | const compileCacheStats = this.getCompileCacheStats() 56 | if (compileCacheStats) { 57 | this.refs.coffeeCompileCount.classList.add('highlight-info') 58 | this.refs.coffeeCompileCount.textContent = compileCacheStats['.coffee'].misses 59 | this.refs.babelCompileCount.classList.add('highlight-info') 60 | this.refs.babelCompileCount.textContent = compileCacheStats['.js'].misses 61 | this.refs.typescriptCompileCount.classList.add('highlight-info') 62 | this.refs.typescriptCompileCount.textContent = compileCacheStats['.ts'].misses 63 | } 64 | 65 | this.refs.csonCompileCount.classList.add('highlight-info') 66 | this.refs.csonCompileCount.textContent = this.getCsonCompiles() 67 | this.refs.lessCompileCount.classList.add('highlight-info') 68 | this.refs.lessCompileCount.textContent = this.getLessCompiles() 69 | } 70 | 71 | getCompileCacheStats () { 72 | try { 73 | return require(path.join(atom.getLoadSettings().resourcePath, 'src', 'compile-cache')).getCacheStats() 74 | } catch (error) { 75 | return null 76 | } 77 | } 78 | 79 | getCsonCompiles () { 80 | try { 81 | const CSON = require(path.join(atom.getLoadSettings().resourcePath, 'node_modules', 'season')) 82 | if (CSON.getCacheMisses) { 83 | return CSON.getCacheMisses() || 0 84 | } else { 85 | return 0 86 | } 87 | } catch (error) { 88 | return 0 89 | } 90 | } 91 | 92 | getLessCompiles () { 93 | const lessCache = atom.themes.lessCache 94 | if (lessCache && lessCache.cache && lessCache.cache.stats && lessCache.cache.stats.misses) { 95 | return lessCache.cache.stats.misses || 0 96 | } else { 97 | return 0 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | const {CompositeDisposable} = require('atom') 2 | 3 | let TimecopView = null 4 | const ViewURI = 'atom://timecop' 5 | 6 | module.exports = { 7 | activate () { 8 | this.subscriptions = new CompositeDisposable() 9 | this.subscriptions.add(atom.workspace.addOpener(filePath => { 10 | if (filePath === ViewURI) return this.createTimecopView({uri: ViewURI}) 11 | })) 12 | 13 | this.subscriptions.add(atom.commands.add('atom-workspace', 'timecop:view', () => atom.workspace.open(ViewURI))) 14 | }, 15 | 16 | deactivate () { 17 | this.subscriptions.dispose() 18 | }, 19 | 20 | createTimecopView (state) { 21 | if (TimecopView == null) TimecopView = require('./timecop-view') 22 | return new TimecopView(state) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /lib/package-panel-view.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /** @jsx etch.dom */ 3 | 4 | import {Disposable} from 'atom' 5 | import etch from 'etch' 6 | 7 | export default class PackagePanelView { 8 | constructor ({title}) { 9 | this.title = title 10 | etch.initialize(this) 11 | 12 | const clickHandler = (event) => { 13 | const target = event.target.closest('a.package') 14 | if (target) { 15 | atom.workspace.open(`atom://config/packages/${target.dataset.package}`) 16 | } 17 | } 18 | this.element.addEventListener('click', clickHandler) 19 | this.disposable = new Disposable(() => { this.element.removeEventListener('click', clickHandler) }) 20 | } 21 | 22 | update () {} 23 | 24 | destroy () { 25 | this.disposable.dispose() 26 | return etch.destroy(this) 27 | } 28 | 29 | render () { 30 | return ( 31 |
32 |
33 |
{this.title}
34 |
35 |
Loading…
36 |
    37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | addPackages (packages, timeKey) { 44 | for (const pack of packages) { 45 | this.addPackage(pack, timeKey) 46 | } 47 | } 48 | 49 | addPackage (pack, timeKey) { 50 | const li = document.createElement('div') 51 | li.classList.add('list-item') 52 | 53 | const a = document.createElement('a') 54 | a.classList.add('inline-block', 'package') 55 | a.dataset.package = pack.name 56 | a.textContent = pack.name 57 | li.appendChild(a) 58 | 59 | const line = document.createElement('span') 60 | line.classList.add('timecop-line') 61 | li.appendChild(line) 62 | 63 | const timeSpan = document.createElement('span') 64 | timeSpan.classList.add('inline-block', pack[timeKey] > 25 ? 'highlight-error' : 'highlight-warning') 65 | timeSpan.textContent = `${pack[timeKey]}ms` 66 | li.appendChild(timeSpan) 67 | 68 | this.refs.list.appendChild(li) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/timecop-view.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /** @jsx etch.dom */ 3 | 4 | import _ from 'underscore-plus' 5 | import dedent from 'dedent' 6 | import etch from 'etch' 7 | import CachePanelView from './cache-panel-view' 8 | import PackagePanelView from './package-panel-view' 9 | import WindowPanelView from './window-panel-view' 10 | 11 | export default class TimecopView { 12 | constructor ({uri}) { 13 | this.uri = uri 14 | etch.initialize(this) 15 | this.refs.cacheLoadingPanel.populate() 16 | if (atom.packages.hasLoadedInitialPackages()) { 17 | this.populateLoadingViews() 18 | } else { 19 | atom.packages.onDidLoadInitialPackages(() => this.populateLoadingViews()) 20 | } 21 | 22 | if (atom.packages.hasActivatedInitialPackages()) { 23 | this.populateActivationViews() 24 | } else { 25 | atom.packages.onDidActivateInitialPackages(() => this.populateActivationViews()) 26 | } 27 | } 28 | 29 | update () {} 30 | 31 | destroy () { 32 | return etch.destroy(this) 33 | } 34 | 35 | render () { 36 | return ( 37 |
38 |
39 |
40 | 41 | 42 |
43 |
44 | 45 | 46 | 47 | 48 |
49 |
50 |
51 | ) 52 | } 53 | 54 | populateLoadingViews () { 55 | this.showLoadedPackages() 56 | this.showLoadedThemes() 57 | } 58 | 59 | populateActivationViews () { 60 | this.refs.windowLoadingPanel.populate() 61 | this.showActivePackages() 62 | this.showActiveThemes() 63 | } 64 | 65 | showLoadedPackages () { 66 | const {time, count, packages} = this.getSlowPackages( 67 | atom.packages.getLoadedPackages().filter(pack => pack.getType() !== 'theme'), 68 | 'loadTime' 69 | ) 70 | this.refs.packageLoadingPanel.addPackages(packages, 'loadTime') 71 | this.refs.packageLoadingPanel.refs.summary.textContent = dedent` 72 | Loaded ${count} packages in ${time}ms. 73 | ${_.pluralize(packages.length, 'package')} took longer than 5ms to load. 74 | ` 75 | } 76 | 77 | showActivePackages () { 78 | const {time, count, packages} = this.getSlowPackages( 79 | atom.packages.getActivePackages().filter(pack => pack.getType() !== 'theme'), 80 | 'activateTime' 81 | ) 82 | this.refs.packageActivationPanel.addPackages(packages, 'activateTime') 83 | this.refs.packageActivationPanel.refs.summary.textContent = dedent` 84 | Activated ${count} packages in ${time}ms. 85 | ${_.pluralize(packages.length, 'package')} took longer than 5ms to activate.\ 86 | ` 87 | } 88 | 89 | showLoadedThemes () { 90 | const {time, count, packages} = this.getSlowPackages(atom.themes.getLoadedThemes(), 'loadTime') 91 | this.refs.themeLoadingPanel.addPackages(packages, 'loadTime') 92 | this.refs.themeLoadingPanel.refs.summary.textContent = dedent` 93 | Loaded ${count} themes in ${time}ms. 94 | ${_.pluralize(packages.length, 'theme')} took longer than 5ms to load.\ 95 | ` 96 | } 97 | 98 | showActiveThemes () { 99 | const {time, count, packages} = this.getSlowPackages(atom.themes.getActiveThemes(), 'activateTime') 100 | this.refs.themeActivationPanel.addPackages(packages, 'activateTime') 101 | this.refs.themeActivationPanel.refs.summary.textContent = dedent` 102 | Activated ${count} themes in ${time}ms. 103 | ${_.pluralize(packages.length, 'theme')} took longer than 5ms to activate.\ 104 | ` 105 | } 106 | 107 | getSlowPackages (packages, timeKey) { 108 | let time = 0 109 | let count = 0 110 | packages = packages.filter(function (pack) { 111 | time += pack[timeKey] 112 | count++ 113 | return pack[timeKey] > 5 114 | }) 115 | packages.sort((pack1, pack2) => pack2[timeKey] - pack1[timeKey]) 116 | return {time, count, packages} 117 | } 118 | 119 | serialize () { 120 | return { 121 | deserializer: this.constructor.name, 122 | uri: this.getURI() 123 | } 124 | } 125 | 126 | getURI () { 127 | return this.uri 128 | } 129 | 130 | getTitle () { 131 | return 'Timecop' 132 | } 133 | 134 | getIconName () { 135 | return 'dashboard' 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /lib/window-panel-view.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | /** @jsx etch.dom */ 3 | 4 | import {CompositeDisposable} from 'atom' 5 | import etch from 'etch' 6 | 7 | export default class WindowPanelView { 8 | constructor () { 9 | etch.initialize(this) 10 | 11 | this.disposables = new CompositeDisposable() 12 | this.disposables.add(atom.tooltips.add(this.refs.shellTiming, {title: 'The time taken to launch the app'})) 13 | this.disposables.add(atom.tooltips.add(this.refs.windowTiming, {title: 'The time taken to load this window'})) 14 | this.disposables.add(atom.tooltips.add(this.refs.projectTiming, {title: 'The time taken to rebuild the previously opened buffers'})) 15 | this.disposables.add(atom.tooltips.add(this.refs.workspaceTiming, {title: 'The time taken to rebuild the previously opened editors'})) 16 | } 17 | 18 | update () {} 19 | 20 | destroy () { 21 | this.disposables.dispose() 22 | return etch.destroy(this) 23 | } 24 | 25 | render () { 26 | return ( 27 |
28 |
29 |
Startup Time
30 |
31 |
32 | Shell load time 33 | Loading… 34 |
35 | 36 |
37 | Window load time 38 | Loading… 39 |
40 | 41 |
42 |
43 | Project load time 44 | Loading… 45 |
46 | 47 |
48 | Workspace load time 49 | Loading… 50 |
51 |
52 |
53 |
54 |
55 | ) 56 | } 57 | 58 | populate () { 59 | const time = atom.getWindowLoadTime() 60 | this.refs.windowLoadTime.classList.add(this.getHighlightClass(time)) 61 | this.refs.windowLoadTime.textContent = `${time}ms` 62 | 63 | const {shellLoadTime} = atom.getLoadSettings() 64 | if (shellLoadTime != null) { 65 | this.refs.shellLoadTime.classList.add(this.getHighlightClass(shellLoadTime)) 66 | this.refs.shellLoadTime.textContent = `${shellLoadTime}ms` 67 | } else { 68 | this.refs.shellTiming.style.display = 'none' 69 | } 70 | 71 | if (atom.deserializeTimings.project != null) { 72 | // Project and workspace timings only exist if the current project was previously opened 73 | this.refs.projectLoadTime.classList.add(this.getHighlightClass(atom.deserializeTimings.project)) 74 | this.refs.projectLoadTime.textContent = `${atom.deserializeTimings.project}ms` 75 | this.refs.workspaceLoadTime.classList.add(this.getHighlightClass(atom.deserializeTimings.workspace)) 76 | this.refs.workspaceLoadTime.textContent = `${atom.deserializeTimings.workspace}ms` 77 | } else { 78 | this.refs.deserializeTimings.style.display = 'none' 79 | } 80 | } 81 | 82 | getHighlightClass (time) { 83 | if (time > 1000) { 84 | return 'highlight-error' 85 | } else if (time > 800) { 86 | return 'highlight-warning' 87 | } else { 88 | return 'highlight-info' 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /menus/timecop.cson: -------------------------------------------------------------------------------- 1 | 'menu': [ 2 | 'label': 'Packages' 3 | 'submenu': [ 4 | 'label': 'Timecop' 5 | 'submenu': [ 6 | 'label': 'Show' 7 | 'command': 'timecop:view' 8 | ] 9 | ] 10 | ] 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "timecop", 3 | "version": "0.36.2", 4 | "description": "Displays information about where time is spent while Atom loads.", 5 | "main": "./lib/main", 6 | "repository": "https://github.com/atom/timecop", 7 | "license": "MIT", 8 | "engines": { 9 | "atom": "*" 10 | }, 11 | "deserializers": { 12 | "TimecopView": "createTimecopView" 13 | }, 14 | "dependencies": { 15 | "dedent": "^0.7.0", 16 | "etch": "^0.12.6", 17 | "underscore-plus": "^1.0.0" 18 | }, 19 | "devDependencies": { 20 | "standard": "^10.0.3" 21 | }, 22 | "standard": { 23 | "env": { 24 | "atomtest": true, 25 | "browser": true, 26 | "jasmine": true, 27 | "node": true 28 | }, 29 | "globals": [ 30 | "atom" 31 | ] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /spec/async-spec-helpers.js: -------------------------------------------------------------------------------- 1 | /** @babel */ 2 | 3 | export function beforeEach (fn) { 4 | global.beforeEach(function () { 5 | const result = fn() 6 | if (result instanceof Promise) { 7 | waitsForPromise(() => result) 8 | } 9 | }) 10 | } 11 | 12 | export function afterEach (fn) { 13 | global.afterEach(function () { 14 | const result = fn() 15 | if (result instanceof Promise) { 16 | waitsForPromise(() => result) 17 | } 18 | }) 19 | } 20 | 21 | ['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { 22 | module.exports[name] = function (description, fn) { 23 | if (fn === undefined) { 24 | global[name](description) 25 | return 26 | } 27 | 28 | global[name](description, function () { 29 | const result = fn() 30 | if (result instanceof Promise) { 31 | waitsForPromise(() => result) 32 | } 33 | }) 34 | } 35 | }) 36 | 37 | export async function conditionPromise (condition, description = 'anonymous condition') { 38 | const startTime = Date.now() 39 | 40 | while (true) { 41 | await timeoutPromise(100) 42 | 43 | if (await condition()) { 44 | return 45 | } 46 | 47 | if (Date.now() - startTime > 5000) { 48 | throw new Error('Timed out waiting on ' + description) 49 | } 50 | } 51 | } 52 | 53 | export function timeoutPromise (timeout) { 54 | return new Promise(function (resolve) { 55 | global.setTimeout(resolve, timeout) 56 | }) 57 | } 58 | 59 | function waitsForPromise (fn) { 60 | const promise = fn() 61 | global.waitsFor('spec promise to resolve', function (done) { 62 | promise.then(done, function (error) { 63 | jasmine.getEnv().currentSpec.fail(error) 64 | done() 65 | }) 66 | }) 67 | } 68 | 69 | export function emitterEventPromise (emitter, event, timeout = 15000) { 70 | return new Promise((resolve, reject) => { 71 | const timeoutHandle = setTimeout(() => { 72 | reject(new Error(`Timed out waiting for '${event}' event`)) 73 | }, timeout) 74 | emitter.once(event, () => { 75 | clearTimeout(timeoutHandle) 76 | resolve() 77 | }) 78 | }) 79 | } 80 | 81 | export function promisify (original) { 82 | return function (...args) { 83 | return new Promise((resolve, reject) => { 84 | args.push((err, ...results) => { 85 | if (err) { 86 | reject(err) 87 | } else { 88 | resolve(...results) 89 | } 90 | }) 91 | 92 | return original(...args) 93 | }) 94 | } 95 | } 96 | 97 | export function promisifySome (obj, fnNames) { 98 | const result = {} 99 | for (const fnName of fnNames) { 100 | result[fnName] = promisify(obj[fnName]) 101 | } 102 | return result 103 | } 104 | -------------------------------------------------------------------------------- /spec/timecop-spec.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const CompileCache = require(path.join(atom.getLoadSettings().resourcePath, 'src', 'compile-cache')) 3 | const CSON = require(path.join(atom.getLoadSettings().resourcePath, 'node_modules', 'season')) 4 | 5 | const {it, fit, ffit, beforeEach, afterEach} = require('./async-spec-helpers') // eslint-disable-line no-unused-vars 6 | 7 | describe('Timecop', () => { 8 | beforeEach(async () => { 9 | spyOn(CompileCache, 'getCacheStats').andReturn({ 10 | '.js': {hits: 3, misses: 4}, 11 | '.ts': {hits: 5, misses: 6}, 12 | '.coffee': {hits: 7, misses: 8} 13 | }) 14 | 15 | spyOn(CSON, 'getCacheMisses').andReturn(10) 16 | 17 | atom.themes.lessCache.cache.stats.misses = 12 18 | 19 | await atom.packages.activatePackage('timecop') 20 | }) 21 | 22 | describe('the Timecop view', () => { 23 | let timecopView = null 24 | 25 | beforeEach(async () => { 26 | const packages = [ 27 | new FakePackage({ 28 | name: 'slow-activating-package-1', 29 | activateTime: 500, 30 | loadTime: 5 31 | }), 32 | new FakePackage({ 33 | name: 'slow-activating-package-2', 34 | activateTime: 500, 35 | loadTime: 5 36 | }), 37 | new FakePackage({ 38 | name: 'slow-loading-package', 39 | activateTime: 5, 40 | loadTime: 500 41 | }), 42 | new FakePackage({ 43 | name: 'fast-package', 44 | activateTime: 2, 45 | loadTime: 3 46 | }) 47 | ] 48 | 49 | spyOn(atom.packages, 'getLoadedPackages').andReturn(packages) 50 | spyOn(atom.packages, 'getActivePackages').andReturn(packages) 51 | spyOn(atom.packages, 'hasLoadedInitialPackages').andReturn(true) 52 | spyOn(atom.packages, 'hasActivatedInitialPackages').andReturn(true) 53 | 54 | timecopView = await atom.workspace.open('atom://timecop') 55 | }) 56 | 57 | afterEach(() => jasmine.unspy(atom.packages, 'getLoadedPackages')) 58 | 59 | it('shows the packages that loaded slowly', () => { 60 | const loadingPanel = timecopView.refs.packageLoadingPanel 61 | expect(loadingPanel.element.textContent).toMatch(/1 package took longer than 5ms to load/) 62 | expect(loadingPanel.element.textContent).toMatch(/slow-loading-package/) 63 | 64 | expect(loadingPanel.element.textContent).not.toMatch(/slow-activating-package/) 65 | expect(loadingPanel.element.textContent).not.toMatch(/fast-package/) 66 | }) 67 | 68 | it('shows the packages that activated slowly', () => { 69 | const activationPanel = timecopView.refs.packageActivationPanel 70 | expect(activationPanel.element.textContent).toMatch(/2 packages took longer than 5ms to activate/) 71 | expect(activationPanel.element.textContent).toMatch(/slow-activating-package-1/) 72 | expect(activationPanel.element.textContent).toMatch(/slow-activating-package-2/) 73 | 74 | expect(activationPanel.element.textContent).not.toMatch(/slow-loading-package/) 75 | expect(activationPanel.element.textContent).not.toMatch(/fast-package/) 76 | }) 77 | 78 | it('shows how many files were transpiled from each language', () => { 79 | const cachePanel = timecopView.refs.cacheLoadingPanel 80 | 81 | expect(cachePanel.element.textContent).toMatch(/CoffeeScript files compiled\s*8/) 82 | expect(cachePanel.element.textContent).toMatch(/Babel files compiled\s*4/) 83 | expect(cachePanel.element.textContent).toMatch(/Typescript files compiled\s*6/) 84 | expect(cachePanel.element.textContent).toMatch(/CSON files compiled\s*10/) 85 | expect(cachePanel.element.textContent).toMatch(/Less files compiled\s*12/) 86 | }) 87 | }) 88 | }) 89 | 90 | class FakePackage { 91 | constructor ({name, activateTime, loadTime}) { 92 | this.name = name 93 | this.activateTime = activateTime 94 | this.loadTime = loadTime 95 | } 96 | getType () { return 'package' } 97 | isTheme () { return false } 98 | } 99 | -------------------------------------------------------------------------------- /styles/timecop.less: -------------------------------------------------------------------------------- 1 | @import "ui-variables"; 2 | 3 | .timecop { 4 | .timecop-panel { 5 | background-color: @tool-panel-background-color; 6 | } 7 | 8 | overflow: auto; 9 | 10 | .panels { 11 | display: flex; 12 | flex-direction: row; 13 | } 14 | 15 | .package-panel { 16 | display: flex; 17 | flex-direction: row; 18 | flex: 1; 19 | 20 | .list-group { 21 | overflow: hidden; 22 | } 23 | 24 | .inset-panel { 25 | width: 100%; 26 | } 27 | } 28 | 29 | .text-info { 30 | margin-bottom: @component-padding; 31 | } 32 | 33 | .timing, 34 | .list-item { 35 | display: flex; 36 | justify-content: space-between; 37 | .inline-block:last-child { 38 | margin-right: 0; 39 | } 40 | } 41 | 42 | .timing { 43 | margin-bottom: @component-padding; 44 | } 45 | 46 | .list-item { 47 | margin-top: @component-padding / 2;; 48 | margin-bottom: @component-padding / 2; 49 | } 50 | 51 | .package { 52 | color: @text-color; 53 | &:hover { 54 | text-decoration: none; 55 | color: @text-color-selected; 56 | } 57 | } 58 | .timecop-line { 59 | flex: 1 1 0; 60 | margin-right: @component-padding; 61 | margin-top: .80em; 62 | border-top: 1px dashed fade(@text-color, 20%); 63 | } 64 | 65 | .package:hover + .timecop-line { 66 | border-color: fade(@text-color, 60%); 67 | } 68 | 69 | } 70 | --------------------------------------------------------------------------------