├── CHANGELOG.md ├── .gitignore ├── README.md ├── styles └── atom-easy-switch.less ├── keymaps └── atom-easy-switch.cson ├── menus └── atom-easy-switch.cson ├── package.json ├── LICENSE.md ├── lib ├── path-loader.js ├── atom-easy-switch.coffee └── load-paths-handler.js └── spec └── atom-easy-switch-spec.coffee /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.0 - First Release 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # atom-easy-switch package 2 | 3 | A short description of your package. 4 | 5 | ![A screenshot of your package](https://f.cloud.github.com/assets/69169/2290250/c35d867a-a017-11e3-86be-cd7c5bf3ff9b.gif) 6 | -------------------------------------------------------------------------------- /styles/atom-easy-switch.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/styles/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .atom-easy-switch { 8 | } 9 | -------------------------------------------------------------------------------- /keymaps/atom-easy-switch.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | 'atom-workspace': 11 | 'ctrl-alt-o': 'atom-easy-switch:toggle' 12 | -------------------------------------------------------------------------------- /menus/atom-easy-switch.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'context-menu': 3 | 'atom-text-editor': [ 4 | { 5 | 'label': 'Toggle atom-easy-switch' 6 | 'command': 'atom-easy-switch:toggle' 7 | } 8 | ] 9 | 'menu': [ 10 | { 11 | 'label': 'Packages' 12 | 'submenu': [ 13 | 'label': 'atom-easy-switch' 14 | 'submenu': [ 15 | { 16 | 'label': 'Toggle' 17 | 'command': 'atom-easy-switch:toggle' 18 | } 19 | ] 20 | ] 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atom-easy-switch", 3 | "main": "./lib/atom-easy-switch", 4 | "version": "0.1.0", 5 | "description": "A short description of your package", 6 | "activationCommands": { 7 | "atom-workspace": "atom-easy-switch:toggle" 8 | }, 9 | "repository": "https://github.com/atom/atom-easy-switch", 10 | "license": "MIT", 11 | "engines": { 12 | "atom": ">=0.174.0 <2.0.0" 13 | }, 14 | "dependencies": { 15 | "fuzzaldrin": "^2.1.0", 16 | "minimatch": "^2.0.1", 17 | "underscore-plus": "^1.6.6" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Stefan Buck 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 | -------------------------------------------------------------------------------- /lib/path-loader.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Task; 3 | 4 | Task = require('atom').Task; 5 | 6 | module.exports = { 7 | startTask: function(callback) { 8 | var ignoreVcsIgnores, ignoredNames, projectPaths, task, taskPath, traverseIntoSymlinkDirectories, _ref, _ref1, _ref2, _ref3; 9 | projectPaths = []; 10 | taskPath = require.resolve('./load-paths-handler'); 11 | traverseIntoSymlinkDirectories = atom.config.get('fuzzy-finder.traverseIntoSymlinkDirectories'); 12 | ignoredNames = (_ref = atom.config.get('fuzzy-finder.ignoredNames')) != null ? _ref : []; 13 | ignoredNames = ignoredNames.concat((_ref1 = atom.config.get('core.ignoredNames')) != null ? _ref1 : []); 14 | ignoreVcsIgnores = atom.config.get('core.excludeVcsIgnoredPaths') && ((_ref2 = atom.project) != null ? (_ref3 = _ref2.getRepositories()[0]) != null ? _ref3.isProjectAtRoot() : void 0 : void 0); 15 | task = Task.once(taskPath, atom.project.getPaths()[0], traverseIntoSymlinkDirectories, ignoreVcsIgnores, ignoredNames, function() { 16 | return callback(projectPaths); 17 | }); 18 | task.on('load-paths:paths-found', function(paths) { 19 | return projectPaths.push.apply(projectPaths, paths); 20 | }); 21 | return task; 22 | } 23 | }; 24 | 25 | }).call(this); 26 | -------------------------------------------------------------------------------- /lib/atom-easy-switch.coffee: -------------------------------------------------------------------------------- 1 | {CompositeDisposable} = require 'atom' 2 | {filter} = require 'fuzzaldrin' 3 | PathLoader = require './path-loader' 4 | 5 | module.exports = AtomEasySwitch = 6 | atomEasySwitchView: null 7 | modalPanel: null 8 | subscriptions: null 9 | 10 | activate: (state) -> 11 | # Events subscribed to in atom's system can be easily cleaned up with a CompositeDisposable 12 | @subscriptions = new CompositeDisposable 13 | 14 | # Register command that toggles this view 15 | @subscriptions.add atom.commands.add 'atom-workspace', 'atom-easy-switch:toggle': => @toggle() 16 | 17 | deactivate: -> 18 | @subscriptions.dispose() 19 | 20 | getSearchTerm: -> 21 | suffix = '.test' 22 | filePath = atom.workspace.getActiveTextEditor().getPath(); 23 | ext = path.extname(filePath); 24 | fileName = path.basename(filePath); 25 | 26 | if fileName.indexOf(suffix) > -1 27 | search = suffix + ext 28 | replace = ext; 29 | else 30 | search = ext 31 | replace = suffix + ext; 32 | 33 | fileName.replace(search, replace); 34 | 35 | toggle: -> 36 | @loadPathsTask?.terminate() 37 | @loadPathsTask = PathLoader.startTask (@paths) => 38 | filePath = filter(@paths, @getSearchTerm(), {maxResults:1})[0]; 39 | if filePath 40 | atom.workspace.open(filePath) 41 | 42 | destroy: -> 43 | @loadPathsTask?.terminate() 44 | -------------------------------------------------------------------------------- /spec/atom-easy-switch-spec.coffee: -------------------------------------------------------------------------------- 1 | AtomEasySwitch = require '../lib/atom-easy-switch' 2 | 3 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs. 4 | # 5 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` 6 | # or `fdescribe`). Remove the `f` to unfocus the block. 7 | 8 | describe "AtomEasySwitch", -> 9 | [workspaceElement, activationPromise] = [] 10 | 11 | beforeEach -> 12 | workspaceElement = atom.views.getView(atom.workspace) 13 | activationPromise = atom.packages.activatePackage('atom-easy-switch') 14 | 15 | describe "when the atom-easy-switch:toggle event is triggered", -> 16 | it "hides and shows the modal panel", -> 17 | # Before the activation event the view is not on the DOM, and no panel 18 | # has been created 19 | expect(workspaceElement.querySelector('.atom-easy-switch')).not.toExist() 20 | 21 | # This is an activation event, triggering it will cause the package to be 22 | # activated. 23 | atom.commands.dispatch workspaceElement, 'atom-easy-switch:toggle' 24 | 25 | waitsForPromise -> 26 | activationPromise 27 | 28 | runs -> 29 | expect(workspaceElement.querySelector('.atom-easy-switch')).toExist() 30 | 31 | atomEasySwitchElement = workspaceElement.querySelector('.atom-easy-switch') 32 | expect(atomEasySwitchElement).toExist() 33 | 34 | atomEasySwitchPanel = atom.workspace.panelForItem(atomEasySwitchElement) 35 | expect(atomEasySwitchPanel.isVisible()).toBe true 36 | atom.commands.dispatch workspaceElement, 'atom-easy-switch:toggle' 37 | expect(atomEasySwitchPanel.isVisible()).toBe false 38 | 39 | it "hides and shows the view", -> 40 | # This test shows you an integration test testing at the view level. 41 | 42 | # Attaching the workspaceElement to the DOM is required to allow the 43 | # `toBeVisible()` matchers to work. Anything testing visibility or focus 44 | # requires that the workspaceElement is on the DOM. Tests that attach the 45 | # workspaceElement to the DOM are generally slower than those off DOM. 46 | jasmine.attachToDOM(workspaceElement) 47 | 48 | expect(workspaceElement.querySelector('.atom-easy-switch')).not.toExist() 49 | 50 | # This is an activation event, triggering it causes the package to be 51 | # activated. 52 | atom.commands.dispatch workspaceElement, 'atom-easy-switch:toggle' 53 | 54 | waitsForPromise -> 55 | activationPromise 56 | 57 | runs -> 58 | # Now we can test for view visibility 59 | atomEasySwitchElement = workspaceElement.querySelector('.atom-easy-switch') 60 | expect(atomEasySwitchElement).toBeVisible() 61 | atom.commands.dispatch workspaceElement, 'atom-easy-switch:toggle' 62 | expect(atomEasySwitchElement).not.toBeVisible() 63 | -------------------------------------------------------------------------------- /lib/load-paths-handler.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var Git, Minimatch, asyncCallDone, asyncCallStarting, asyncCallsInProgress, callback, fs, ignoredNames, isIgnored, loadFolder, loadPath, path, pathLoaded, paths, pathsChunkSize, repo, traverseSymlinkDirectories, _; 3 | 4 | fs = require('fs'); 5 | 6 | path = require('path'); 7 | 8 | _ = require('underscore-plus'); 9 | 10 | Git = require('atom').Git; 11 | 12 | Minimatch = require('minimatch').Minimatch; 13 | 14 | asyncCallsInProgress = 0; 15 | 16 | pathsChunkSize = 100; 17 | 18 | paths = []; 19 | 20 | repo = null; 21 | 22 | ignoredNames = null; 23 | 24 | traverseSymlinkDirectories = false; 25 | 26 | callback = null; 27 | 28 | isIgnored = function(loadedPath) { 29 | var ignoredName, _i, _len; 30 | if (repo != null ? repo.isPathIgnored(loadedPath) : void 0) { 31 | return true; 32 | } else { 33 | for (_i = 0, _len = ignoredNames.length; _i < _len; _i++) { 34 | ignoredName = ignoredNames[_i]; 35 | if (ignoredName.match(loadedPath)) { 36 | return true; 37 | } 38 | } 39 | } 40 | }; 41 | 42 | asyncCallStarting = function() { 43 | return asyncCallsInProgress++; 44 | }; 45 | 46 | asyncCallDone = function() { 47 | if (--asyncCallsInProgress === 0) { 48 | if (repo != null) { 49 | repo.destroy(); 50 | } 51 | emit('load-paths:paths-found', paths); 52 | return callback(); 53 | } 54 | }; 55 | 56 | pathLoaded = function(path) { 57 | if (!isIgnored(path)) { 58 | paths.push(path); 59 | } 60 | if (paths.length === pathsChunkSize) { 61 | emit('load-paths:paths-found', paths); 62 | return paths = []; 63 | } 64 | }; 65 | 66 | loadPath = function(path) { 67 | asyncCallStarting(); 68 | return fs.lstat(path, function(error, stats) { 69 | if (error == null) { 70 | if (stats.isSymbolicLink()) { 71 | asyncCallStarting(); 72 | fs.stat(path, function(error, stats) { 73 | if (error == null) { 74 | if (stats.isFile()) { 75 | pathLoaded(path); 76 | } else if (stats.isDirectory() && traverseSymlinkDirectories) { 77 | if (!isIgnored(path)) { 78 | loadFolder(path); 79 | } 80 | } 81 | } 82 | return asyncCallDone(); 83 | }); 84 | } else if (stats.isDirectory()) { 85 | if (!isIgnored(path)) { 86 | loadFolder(path); 87 | } 88 | } else if (stats.isFile()) { 89 | pathLoaded(path); 90 | } 91 | } 92 | return asyncCallDone(); 93 | }); 94 | }; 95 | 96 | loadFolder = function(folderPath) { 97 | asyncCallStarting(); 98 | return fs.readdir(folderPath, function(error, children) { 99 | var childName, _i, _len; 100 | if (children == null) { 101 | children = []; 102 | } 103 | for (_i = 0, _len = children.length; _i < _len; _i++) { 104 | childName = children[_i]; 105 | loadPath(path.join(folderPath, childName)); 106 | } 107 | return asyncCallDone(); 108 | }); 109 | }; 110 | 111 | module.exports = function(rootPath, traverseIntoSymlinkDirectories, ignoreVcsIgnores, ignores) { 112 | var error, ignore, _i, _len; 113 | if (ignores == null) { 114 | ignores = []; 115 | } 116 | traverseSymlinkDirectories = traverseIntoSymlinkDirectories; 117 | ignoredNames = []; 118 | for (_i = 0, _len = ignores.length; _i < _len; _i++) { 119 | ignore = ignores[_i]; 120 | if (ignore) { 121 | try { 122 | ignoredNames.push(new Minimatch(ignore, { 123 | matchBase: true, 124 | dot: true 125 | })); 126 | } catch (_error) { 127 | error = _error; 128 | console.warn("Error parsing ignore pattern (" + ignore + "): " + error.message); 129 | } 130 | } 131 | } 132 | callback = this.async(); 133 | if (ignoreVcsIgnores) { 134 | repo = Git.open(rootPath, { 135 | refreshOnWindowFocus: false 136 | }); 137 | } 138 | return loadFolder(rootPath); 139 | }; 140 | 141 | }).call(this); 142 | --------------------------------------------------------------------------------