├── .gitignore ├── CHANGELOG.MD ├── LICENSE.md ├── README.md ├── keymaps └── advanced-new-file.cson ├── lib └── advanced-new-file-view.coffee ├── menus └── advanced-new-file.cson ├── package.json └── spec └── advanced-new-file-view-spec.coffee /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.MD: -------------------------------------------------------------------------------- 1 | 0.4.4 (May 22 2015) 2 | =================== 3 | 4 | * Added message for switching to advanced open file. 5 | 6 | 0.4.3 (May 22 2015) 7 | =================== 8 | 9 | * Removed deprecated API for calling listening for core:confirm and core:cancel 10 | 11 | 12 | 0.4.2 (May 15 2015) 13 | =================== 14 | 15 | * Updated Context menu and use activationCommands intead of actiovationEvents(thanks to zimme) 16 | * Removed deprecated configDefaults (thanks to olmokramer) 17 | 18 | 0.4.1 (Apr 27 2015) 19 | ================== 20 | * Resolved problem with creating folder if create file instantly was set to true (thanks to phyllisstein) 21 | 22 | 0.4.0 (Apr 14 2015) 23 | ================== 24 | * Added option for creating file immediately (thanks to silentvick) 25 | * Fixed problem with overlay in Atom One theme (thanks to silentvick) 26 | 27 | 0.3.3 (Feb 15 2015) 28 | =================== 29 | 30 | * Uncaught TypeError: undefined is not a function #9 31 | 32 | 33 | 0.3.2 (Feb 14 2015) 34 | =================== 35 | 36 | * Focus editor on the start of the package 37 | 38 | 0.3.1 (Feb 14 2015) 39 | =================== 40 | 41 | * Add text from selection only if there is active text editor 42 | 43 | 0.3.0 (Feb 14 2015) 44 | =================== 45 | 46 | * Removed deprecation calls #6 47 | * New setting for inserting selection to the input field #7 48 | 49 | 0.2.2 (Feb 7 2015) 50 | =================== 51 | 52 | * Opens new window with new file #5 53 | * Can't install v0.2.0 #4 54 | 55 | 0.2.1 (Feb 1 2015) 56 | =================== 57 | 58 | * Added Remove Whole folder setting 59 | * Resolved few issues brought by last release 60 | 61 | 0.2.0 (Jan 31 2015) 62 | =================== 63 | 64 | * Simplify navigating up directory structure #2 65 | * Remove deprecated code. #1 66 | 67 | 0.1.0 (Jan 18 2015) 68 | =================== 69 | 70 | * Initial release 71 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Peter Toth 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advanced New File package 2 | 3 | ## Please switch to Advanced Open File 4 | 5 | Advanced New File is no longer being maintained. Please switch to Advanced Open File: [advanced-open-file](https://atom.io/packages/advanced-open-file) 6 | 7 | ## Advanced New File 8 | 9 | Advanced New File is fork of existing package Fancy New File. First of all let me thank rev087 - author of Fancy New File for great work. 10 | I forked this folder because old one wasn't maintained for few months and I couldn't contact the author. 11 | 12 | Advanced New File is a package for helping Atom users to create new files and folders more easily. 13 | It supports create new files and directory tree by typing a relative path with bash-like autocomplete support. 14 | 15 | If you have any ideas or even better pull requests, please let me know :) 16 | 17 | ## How to Use 18 | 19 | Simply press alt-cmd-n, or ctrl-alt-n, depending on your platform. 20 | 21 | ## Example 22 | ![advanced-new-file](https://cloud.githubusercontent.com/assets/3289225/5792505/81f41c72-9f1b-11e4-9085-38cfb832383c.gif) 23 | 24 | ## Settings: 25 | 26 | All settings can be turned on and off via the built-in settings tab (`cmd-,`) and selecting Advanced New File from the list of installed packages. 27 | 28 | - Case Sensitive Auto Completion 29 | - Show Files In Auto Completion 30 | - Suggest Current File Path 31 | - Remove whole folder 32 | - Create file instantly 33 | -------------------------------------------------------------------------------- /keymaps/advanced-new-file.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 | '.platform-darwin atom-workspace': 11 | 'alt-cmd-n': 'advanced-new-file:toggle' 12 | 13 | '.platform-win32 atom-workspace, .platform-linux atom-workspace': 14 | 'ctrl-alt-n': 'advanced-new-file:toggle' 15 | -------------------------------------------------------------------------------- /lib/advanced-new-file-view.coffee: -------------------------------------------------------------------------------- 1 | {BufferedProcess} = require 'atom' 2 | {$, $$, View, TextEditorView} = require 'atom-space-pen-views' 3 | fs = require 'fs' 4 | path = require 'path' 5 | mkdirp = require 'mkdirp' 6 | touch = require 'touch' 7 | 8 | module.exports = 9 | class AdvancedFileView extends View 10 | PATH_SEPARATOR: "," 11 | advancedFileView: null 12 | keyUpListener: null 13 | 14 | @config: 15 | removeWholeFolder: 16 | type: 'boolean' 17 | default: true 18 | suggestCurrentFilePath: 19 | type: 'boolean' 20 | default: false 21 | showFilesInAutoComplete: 22 | type: 'boolean' 23 | default: false 24 | caseSensitiveAutoCompletion: 25 | type: 'boolean' 26 | default: false 27 | addTextFromSelection: 28 | type: 'boolean' 29 | default: false 30 | createFileInstantly: 31 | type: 'boolean' 32 | default: false 33 | 34 | @activate: (state) -> 35 | @advancedFileView = new AdvancedFileView(state.advancedFileViewState) 36 | try 37 | @seenNotification = JSON.parse(state).seenNotification 38 | catch 39 | @seenNotification = false 40 | 41 | @serialize: -> 42 | return JSON.stringify({seenNotification: @seenNotification}) 43 | 44 | @deactivate: -> 45 | @advancedFileView.detach() 46 | 47 | @content: (params)-> 48 | @div class: 'advanced-new-file', => 49 | @div outlet: 'mainUI', => 50 | @p outlet:'message', class:'icon icon-file-add', "Enter the path for the new file/directory. Directories end with a '" + path.sep + "'." 51 | @subview 'miniEditor', new TextEditorView({mini:true}) 52 | @ul class: 'list-group', outlet: 'directoryList' 53 | @div outlet: 'notificationUI', => 54 | @h2 class:'icon icon-info', "Advanced New File is no longer maintained" 55 | @p => 56 | @raw """ 57 | advanced-new-file won't be getting any more updates, but the project 58 | has been forked as advanced-open-file. 59 | The fork opens files as well as creating them, and adds several new features. 60 | """ 61 | @p => 62 | @text """ 63 | Click the Update button below to replace advanced-new-file with 64 | advanced-open-file (the current window will be reloaded). Otherwise, 65 | click the No Thanks button and this message will not appear again. 66 | """ 67 | @div class: 'btn-toolbar', => 68 | @div class: 'btn-group', => 69 | @button outlet: 'closeNotification', class: 'btn', "No thanks" 70 | @div class: 'btn-group', => 71 | @button outlet: 'replacePackageBtn', class: 'btn btn-success', "Update" 72 | 73 | @detaching: false, 74 | 75 | initialize: (serializeState) -> 76 | atom.commands.add 'atom-workspace', 'advanced-new-file:toggle', => @toggle() 77 | @miniEditor.getModel().setPlaceholderText(path.join('path','to','file.txt')); 78 | atom.commands.add @element, 79 | 'core:confirm': => @confirm() 80 | 'core:cancel': => @detach() 81 | 82 | # Retrieves the reference directory for the relative paths 83 | referenceDir: () -> 84 | homeDir = process.env.HOME or process.env.HOMEPATH or process.env.USERPROFILE 85 | atom.project.getPaths()[0] or homeDir 86 | 87 | # Resolves the path being inputted in the dialog, up to the last slash 88 | inputPath: () -> 89 | input = @getLastSearchedFile() 90 | path.join @referenceDir(), input.substr(0, input.lastIndexOf(path.sep)) 91 | 92 | inputFullPath: () -> 93 | input = @getLastSearchedFile() 94 | path.join @referenceDir(), input 95 | 96 | getLastSearchedFile: () -> 97 | input = @miniEditor.getText() 98 | commanIndex = input.lastIndexOf(@PATH_SEPARATOR) + 1 99 | input.substring(commanIndex, input.length) 100 | 101 | 102 | # Returns the list of directories matching the current input (path and autocomplete fragment) 103 | getFileList: (callback) -> 104 | input = @getLastSearchedFile() 105 | fs.stat @inputPath(), (err, stat) => 106 | 107 | if err?.code is 'ENOENT' 108 | return [] 109 | 110 | fs.readdir @inputPath(), (err, files) => 111 | fileList = [] 112 | dirList = [] 113 | 114 | files.forEach (filename) => 115 | fragment = input.substr(input.lastIndexOf(path.sep) + 1, input.length) 116 | caseSensitive = atom.config.get 'advanced-new-file.caseSensitiveAutoCompletion' 117 | 118 | if not caseSensitive 119 | fragment = fragment.toLowerCase() 120 | 121 | matches = 122 | caseSensitive and filename.indexOf(fragment) is 0 or 123 | not caseSensitive and filename.toLowerCase().indexOf(fragment) is 0 124 | 125 | if matches 126 | try 127 | isDir = fs.statSync(path.join(@inputPath(), filename)).isDirectory() 128 | catch 129 | ## TODO fix error which is thrown when you hold backspace 130 | 131 | (if isDir then dirList else fileList).push name:filename, isDir:isDir 132 | 133 | if atom.config.get 'advanced-new-file.showFilesInAutoComplete' 134 | callback.apply @, [dirList.concat fileList] 135 | else 136 | callback.apply @, [dirList] 137 | 138 | 139 | # Called only when pressing Tab to trigger auto-completion 140 | autocomplete: (str) -> 141 | @getFileList (files) -> 142 | newString = str 143 | oldInputText = @miniEditor.getText() 144 | indexOfString = oldInputText.lastIndexOf(str) 145 | textWithoutSuggestion = oldInputText.substring(0, indexOfString) 146 | if files?.length is 1 147 | newPath = path.join(@inputPath(), files[0].name) 148 | 149 | suffix = if files[0].isDir then path.sep else '' 150 | @updatePath(newPath + suffix, textWithoutSuggestion) 151 | 152 | else if files?.length > 1 153 | longestPrefix = @longestCommonPrefix((file.name for file in files)) 154 | newPath = path.join(@inputPath(), longestPrefix) 155 | 156 | if (newPath.length > @inputFullPath().length) 157 | @updatePath(newPath, textWithoutSuggestion) 158 | else 159 | atom.beep() 160 | else 161 | atom.beep() 162 | 163 | updatePath: (newPath, oldPath) -> 164 | relativePath = oldPath + atom.project.relativize(newPath) 165 | @miniEditor.setText relativePath 166 | 167 | update: -> 168 | @getFileList (files) -> 169 | @renderAutocompleteList files 170 | 171 | if /\/$/.test @miniEditor.getText() 172 | @setMessage 'file-directory-create' 173 | else 174 | @setMessage 'file-add' 175 | 176 | setMessage: (icon, str) -> 177 | @message.removeClass 'icon'\ 178 | + ' icon-file-add'\ 179 | + ' icon-file-directory-create'\ 180 | + ' icon-alert' 181 | if icon? then @message.addClass 'icon icon-' + icon 182 | @message.text str or "Enter the path for the new file/directory. Directories end with a '" + path.sep + "'." 183 | 184 | # Renders the list of directories 185 | renderAutocompleteList: (files) -> 186 | @directoryList.empty() 187 | files?.forEach (file) => 188 | icon = if file.isDir then 'icon-file-directory' else 'icon-file-text' 189 | @directoryList.append $$ -> 190 | @li class: 'list-item', => 191 | @span class: "icon #{icon}", file.name 192 | 193 | confirm: -> 194 | relativePaths = @miniEditor.getText().split(@PATH_SEPARATOR) 195 | 196 | for relativePath in relativePaths 197 | pathToCreate = path.join(@referenceDir(), relativePath) 198 | createWithin = path.dirname(pathToCreate) 199 | try 200 | if /\/$/.test(pathToCreate) 201 | mkdirp pathToCreate 202 | else 203 | if atom.config.get 'advanced-new-file.createFileInstantly' 204 | mkdirp createWithin unless fs.existsSync(createWithin) and fs.statSync(createWithin) 205 | touch pathToCreate 206 | atom.workspace.open pathToCreate 207 | catch error 208 | @setMessage 'alert', error.message 209 | 210 | @detach() 211 | 212 | detach: -> 213 | return unless @hasParent() 214 | @detaching = true 215 | @miniEditor.setText '' 216 | @setMessage() 217 | @directoryList.empty() 218 | miniEditorFocused = @miniEditor.isFocused 219 | @keyUpListener.off() 220 | super 221 | @panel?.hide() 222 | @restoreFocus() if miniEditorFocused 223 | @detaching = false 224 | 225 | attach: -> 226 | @suggestPath() 227 | @previouslyFocusedElement = $(':focus') 228 | @panel = atom.workspace.addModalPanel(item: this) 229 | 230 | if @seenNotification 231 | @mainUI.removeClass 'hidden' 232 | @notificationUI.addClass 'hidden' 233 | else 234 | @notificationUI.removeClass 'hidden' 235 | @mainUI.addClass 'hidden' 236 | 237 | @closeNotification.on 'click', (ev) => 238 | @seenNotification = true 239 | @mainUI.removeClass 'hidden' 240 | @notificationUI.addClass 'hidden' 241 | 242 | @replacePackageBtn.on 'click', (ev) => 243 | @seenNotification = true 244 | @detach() 245 | @replacePackage() 246 | 247 | @miniEditor.on 'focusout', => @detach() unless @detaching 248 | @miniEditor.focus() 249 | 250 | consumeKeypress = (ev) => ev.preventDefault(); ev.stopPropagation() 251 | 252 | # Populate the directory listing live 253 | @miniEditor.getModel().onDidChange => @update() 254 | 255 | # Consume the keydown event from holding down the Tab key 256 | @miniEditor.on 'keydown', (ev) => if ev.keyCode is 9 then consumeKeypress ev 257 | 258 | # Handle the Tab completion 259 | @keyUpListener = @miniEditor.on 'keyup', (ev) => 260 | if ev.keyCode is 9 261 | consumeKeypress ev 262 | pathToComplete = @getLastSearchedFile() 263 | @autocomplete pathToComplete 264 | else if ev.keyCode is 8 265 | ## Remove whole folder in path instead of one letter 266 | if atom.config.get 'advanced-new-file.removeWholeFolder' 267 | absolutePathToFile = @inputFullPath() 268 | if fs.existsSync(absolutePathToFile) and fs.statSync(absolutePathToFile) 269 | editorText = @miniEditor.getText() 270 | pathSepIndex = editorText.lastIndexOf(path.sep) + 1 271 | fileSep = editorText.lastIndexOf(@PATH_SEPARATOR) 272 | substr = Math.max(pathSepIndex, fileSep) 273 | @miniEditor.setText(editorText.substring(0, substr)) 274 | ## Add selected text 275 | if atom.config.get('advanced-new-file.addTextFromSelection') and atom.workspace.getActiveTextEditor() 276 | selection = atom.workspace.getActiveTextEditor().getSelection(); 277 | if !selection.empty? 278 | text = @miniEditor.getText() + selection.getText() 279 | @miniEditor.setText(text); 280 | @miniEditor.focus() 281 | @getFileList (files) -> @renderAutocompleteList files 282 | 283 | suggestPath: -> 284 | if atom.config.get 'advanced-new-file.suggestCurrentFilePath' 285 | activePath = atom.workspace.getActiveTextEditor()?.getPath() 286 | if activePath 287 | activeDir = path.dirname(activePath) + path.sep 288 | suggestedPath = path.relative @referenceDir(), activeDir 289 | @miniEditor.setText suggestedPath + path.sep 290 | 291 | toggle: -> 292 | if @hasParent() 293 | @detach() 294 | else 295 | @attach() 296 | 297 | restoreFocus: -> 298 | if @previouslyFocusedElement?.isOnDom() 299 | @previouslyFocusedElement.focus() 300 | 301 | longestCommonPrefix: (fileNames) -> 302 | if (fileNames?.length == 0) 303 | return "" 304 | 305 | longestCommonPrefix = "" 306 | for prefixIndex in [0..fileNames[0].length - 1] 307 | nextCharacter = fileNames[0][prefixIndex] 308 | for fileIndex in [0..fileNames.length - 1] 309 | fileName = fileNames[fileIndex] 310 | if (fileName.length < prefixIndex || fileName[prefixIndex] != nextCharacter) 311 | # The first thing that doesn't share the common prefix! 312 | return longestCommonPrefix 313 | longestCommonPrefix += nextCharacter 314 | 315 | return longestCommonPrefix 316 | 317 | executeApm: (args, exit) -> 318 | new BufferedProcess 319 | command: atom.packages.getApmPath() 320 | args: args 321 | stdout: (output) -> 322 | stderr: (output) -> 323 | exit: exit 324 | 325 | replacePackage: -> 326 | info = atom.notifications.addInfo 'Installing advanced-open-file...' 327 | @executeApm ['install', 'advanced-open-file'], (exitCode) => 328 | if exitCode is 1 329 | info.dismiss() 330 | atom.notifications.addError 'Failed to install advanced-open-file, please install it manually.' 331 | else 332 | @executeApm ['uninstall', 'advanced-new-file'], (exitCode) -> 333 | info.dismiss() 334 | if exitCode is 0 335 | atom.notifications.addSuccess( 336 | 'advanced-open-file installed successfully!', 337 | detail: 'Atom will reload in a few seconds...' 338 | ) 339 | setTimeout (-> atom.reload()), 3000 340 | else 341 | atom.notifications.addError 'Failed to uninstall advanced-new-file, please uninstall it manually.' 342 | -------------------------------------------------------------------------------- /menus/advanced-new-file.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'context-menu': 3 | 'atom-text-editor': [ 4 | 'label': 'Advanced New File' 5 | 'command': 'advanced-new-file:toggle' 6 | ] 7 | 8 | 'menu': [ 9 | { 10 | 'label': 'Packages' 11 | 'submenu': [ 12 | 'label': 'Advanced New File' 13 | 'command': 'advanced-new-file:toggle' 14 | ] 15 | } 16 | ] 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "advanced-new-file", 3 | "main": "./lib/advanced-new-file-view", 4 | "version": "0.5.0", 5 | "description": "Updated version of Fancy New File. Create multiple files and directories by typing a relative path.", 6 | "activationCommands": { 7 | "atom-workspace": [ 8 | "advanced-new-file:toggle" 9 | ] 10 | }, 11 | "repository": "https://github.com/Trudko/advanced-new-file", 12 | "license": "MIT", 13 | "engines": { 14 | "atom": ">=0.175.0" 15 | }, 16 | "dependencies": { 17 | "mkdirp": "^0.3.5", 18 | "atom-space-pen-views": "^2.0.3", 19 | "touch": "0.0.3" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /spec/advanced-new-file-view-spec.coffee: -------------------------------------------------------------------------------- 1 | AdvancedFileView = require '../lib/advanced-new-file-view' 2 | 3 | describe "AdvancedFileView", -> 4 | workSpaceElement = null 5 | beforeEach -> 6 | workSpaceElement = atom.views.getView(atom.workspace) 7 | 8 | waitsForPromise -> 9 | atom.packages.activatePackage('keybinding-resolver') 10 | 11 | describe "when the advanced-new-file:toggle event is triggered", -> 12 | it "attaches and detaches the view", -> 13 | expect(workSpaceElement.querySelector('.advanced-new-file')).not.toExist(); 14 | 15 | atom.commands.dispatch workSpaceElement, 'advanced-new-file:toggle' 16 | expect(workSpaceElement.querySelector('.advanced-new-file')).toExist(); 17 | 18 | atom.commands.dispatch workSpaceElement, 'advanced-new-file:toggle' 19 | expect(workSpaceElement.querySelector('.advanced-new-file')).not.toExist(); 20 | 21 | atom.commands.dispatch workSpaceElement, 'key-binding-resolver:toggle' 22 | expect(workSpaceElement.querySelector('.key-binding-resolver')).toExist() 23 | --------------------------------------------------------------------------------