├── .gitignore ├── README.md ├── lib └── main.coffee └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Open Recent Files/Folders 2 | 3 | Open recent files in the current window, and recent folders (optionally) in a new window. 4 | 5 | ## Screenshots 6 | 7 | ![](http://i.imgur.com/d9y4iAi.png) 8 | 9 | You can also open the command palette `Ctrl+Alt+P` and type `open file0`, `open dir0` or `open [filepath]`. 10 | 11 | ![](http://i.imgur.com/JUed5jx.png) 12 | 13 | ## Settings 14 | 15 | * `maxRecentFiles` and `maxRecentDirectories` limit the number of items in the menu. 16 | * `replaceNewWindowOnOpenDirectory` When true, opening a recent directory will "open" in the current window, but only if the window does not have a project path set. Eg: The window that appears when doing File > New Window. 17 | * `replaceProjectOnOpenDirectory` When true, opening a recent directory will "open" in the current window, replacing the current project. 18 | * `listDirectoriesAddedToProject` When true, the all root directories in a project will be added to the history and not just the 1st root directory. 19 | * `ignoredNames` When true, skips files and directories specified in Atom's "Ignored Names" setting. 20 | -------------------------------------------------------------------------------- /lib/main.coffee: -------------------------------------------------------------------------------- 1 | minimatch = null 2 | 3 | #--- localStorage DB 4 | class DB 5 | constructor: (@key) -> 6 | 7 | getData: -> 8 | data = localStorage[@key] 9 | data = if data? then JSON.parse(data) else {} 10 | return data 11 | 12 | setData: (data) -> 13 | localStorage[@key] = JSON.stringify(data) 14 | 15 | removeData: -> 16 | localStorage.removeItem(@key) 17 | 18 | get: (name) -> 19 | data = @getData() 20 | return data[name] 21 | 22 | set: (name, value) -> 23 | data = @getData() 24 | data[name] = value 25 | @setData(data) 26 | 27 | remove: (name) -> 28 | data = @getData() 29 | delete data[name] 30 | @setData(data) 31 | 32 | 33 | #--- OpenRecent 34 | class OpenRecent 35 | constructor: -> 36 | @eventListenerDisposables = [] 37 | @commandListenerDisposables = [] 38 | @localStorageEventListener = @onLocalStorageEvent.bind(@) 39 | @db = new DB('openRecent') 40 | 41 | #--- Event Handlers 42 | onUriOpened: -> 43 | editor = atom.workspace.getActiveTextEditor() 44 | filePath = editor?.buffer?.file?.path 45 | 46 | # Ignore anything thats not a file. 47 | return unless filePath 48 | return unless filePath.indexOf '://' is -1 49 | 50 | @insertFilePath(filePath) if filePath 51 | 52 | onProjectPathChange: (projectPaths) -> 53 | @insertCurrentPaths() 54 | 55 | onLocalStorageEvent: (e) -> 56 | if e.key is @db.key 57 | @update() 58 | 59 | encodeEventName: (s) -> 60 | s = s.replace('-', '\u2010') # HYPHEN 61 | s = s.replace(':', '\u02D0') # MO­DI­FI­ER LET­TER TRIANGULAR COLON 62 | return s 63 | 64 | commandEventName: (prefix, path) -> 65 | return "open-recent:#{prefix}-#{@encodeEventName(path)}" 66 | 67 | #--- Listeners 68 | addCommandListeners: -> 69 | #--- Commands 70 | # open-recent:File#-path 71 | for path, index in @db.get('files') 72 | do (path) => # Explicit closure 73 | disposable = atom.commands.add "atom-workspace", @commandEventName("File#{index}", path), => 74 | @openFile path 75 | @commandListenerDisposables.push disposable 76 | 77 | # open-recent:Dir#-path 78 | for path, index in @db.get('paths') 79 | do (path) => # Explicit closure 80 | disposable = atom.commands.add "atom-workspace", @commandEventName("Dir#{index}", path), => 81 | @openPath path 82 | @commandListenerDisposables.push disposable 83 | 84 | # open-recent:clear-all------... 85 | # Add tons of --- at the end to sort this item at the bottom of the command palette. 86 | # Multiple spaces are ignored inside the command palette. 87 | disposable = atom.commands.add "atom-workspace", "open-recent:clear-all" + '-'.repeat(1024), => 88 | @db.set('files', []) 89 | @db.set('paths', []) 90 | @update() 91 | @commandListenerDisposables.push disposable 92 | 93 | getProjectPath: (path) -> 94 | return atom.project.getPaths()?[0] 95 | 96 | openFile: (path) -> 97 | atom.workspace.open path 98 | 99 | openPath: (path) -> 100 | replaceCurrentProject = false 101 | options = {} 102 | 103 | if not @getProjectPath() and atom.config.get('open-recent.replaceNewWindowOnOpenDirectory') 104 | replaceCurrentProject = true 105 | else if @getProjectPath() and atom.config.get('open-recent.replaceProjectOnOpenDirectory') 106 | replaceCurrentProject = true 107 | 108 | if replaceCurrentProject 109 | atom.project.setPaths([path]) 110 | if workspaceElement = atom.views.getView(atom.workspace) 111 | atom.commands.dispatch workspaceElement, 'tree-view:toggle-focus' 112 | else 113 | atom.open { 114 | pathsToOpen: [path] 115 | newWindow: !atom.config.get('open-recent.replaceNewWindowOnOpenDirectory') 116 | } 117 | 118 | addListeners: -> 119 | #--- Commands 120 | @addCommandListeners() 121 | 122 | #--- Events 123 | disposable = atom.workspace.onDidOpen @onUriOpened.bind(@) 124 | @eventListenerDisposables.push(disposable) 125 | 126 | disposable = atom.project.onDidChangePaths @onProjectPathChange.bind(@) 127 | @eventListenerDisposables.push(disposable) 128 | 129 | # Notify other windows during a setting data in localStorage. 130 | window.addEventListener "storage", @localStorageEventListener 131 | 132 | removeCommandListeners: -> 133 | #--- Commands 134 | for disposable in @commandListenerDisposables 135 | disposable.dispose() 136 | @commandListenerDisposables = [] 137 | 138 | removeListeners: -> 139 | #--- Commands 140 | @removeCommandListeners() 141 | 142 | #--- Events 143 | for disposable in @eventListenerDisposables 144 | disposable.dispose() 145 | @eventListenerDisposables = [] 146 | 147 | window.removeEventListener 'storage', @localStorageEventListener 148 | 149 | #--- Methods 150 | init: -> 151 | # Migrate 152 | if atom.config.get('open-recent.recentDirectories') or atom.config.get('open-recent.recentFiles') 153 | @db.set('paths', atom.config.get('open-recent.recentDirectories')) 154 | @db.set('files', atom.config.get('open-recent.recentFiles')) 155 | atom.config.unset('open-recent.recentDirectories') 156 | atom.config.unset('open-recent.recentFiles') 157 | 158 | # Defaults 159 | @db.set('paths', []) unless @db.get('paths') 160 | @db.set('files', []) unless @db.get('files') 161 | 162 | @addListeners() 163 | @insertCurrentPaths() 164 | @update() 165 | 166 | # Returns true if the path should be filtered out, based on settings. 167 | filterPath: (path) -> 168 | ignoredNames = atom.config.get('core.ignoredNames') 169 | if ignoredNames 170 | minimatch ?= require('minimatch') 171 | for name in ignoredNames 172 | match = [name, "**/#{name}/**"].some (comparison) -> 173 | return minimatch(path, comparison, { matchBase: true, dot: true }) 174 | return true if match 175 | 176 | return false 177 | 178 | insertCurrentPaths: -> 179 | return unless atom.project.getDirectories().length > 0 180 | 181 | recentPaths = @db.get('paths') 182 | for projectDirectory, index in atom.project.getDirectories() 183 | # Ignore the second, third, ... folders in a project 184 | continue if index > 0 and not atom.config.get('open-recent.listDirectoriesAddedToProject') 185 | 186 | path = projectDirectory.path 187 | 188 | continue if @filterPath(path) 189 | 190 | # Remove if already listed 191 | index = recentPaths.indexOf path 192 | if index != -1 193 | recentPaths.splice index, 1 194 | 195 | recentPaths.splice 0, 0, path 196 | 197 | # Limit 198 | maxRecentDirectories = atom.config.get('open-recent.maxRecentDirectories') 199 | if recentPaths.length > maxRecentDirectories 200 | recentPaths.splice maxRecentDirectories, recentPaths.length - maxRecentDirectories 201 | 202 | @db.set('paths', recentPaths) 203 | @update() 204 | 205 | insertFilePath: (path) -> 206 | return if @filterPath(path) 207 | 208 | recentFiles = @db.get('files') 209 | 210 | # Remove if already listed 211 | index = recentFiles.indexOf path 212 | if index != -1 213 | recentFiles.splice index, 1 214 | 215 | recentFiles.splice 0, 0, path 216 | 217 | # Limit 218 | maxRecentFiles = atom.config.get('open-recent.maxRecentFiles') 219 | if recentFiles.length > maxRecentFiles 220 | recentFiles.splice maxRecentFiles, recentFiles.length - maxRecentFiles 221 | 222 | @db.set('files', recentFiles) 223 | @update() 224 | 225 | #--- Menu 226 | createSubmenu: -> 227 | submenu = [] 228 | submenu.push { command: "pane:reopen-closed-item", label: "Reopen Closed File" } 229 | submenu.push { type: "separator" } 230 | 231 | # Files 232 | recentFiles = @db.get('files') 233 | if recentFiles.length 234 | for path, index in recentFiles 235 | menuItem = { 236 | label: path 237 | command: @commandEventName("File#{index}", path) 238 | } 239 | if path.length > 100 240 | menuItem.label = path.substr(-60) 241 | menuItem.sublabel = path 242 | submenu.push menuItem 243 | submenu.push { type: "separator" } 244 | 245 | # Root Paths 246 | recentPaths = @db.get('paths') 247 | if recentPaths.length 248 | for path, index in recentPaths 249 | menuItem = { 250 | label: path 251 | command: @commandEventName("Dir#{index}", path) 252 | } 253 | if path.length > 100 254 | menuItem.label = path.substr(-60) 255 | menuItem.sublabel = path 256 | submenu.push menuItem 257 | submenu.push { type: "separator" } 258 | 259 | submenu.push { command: "open-recent:clear-all" + '-'.repeat(1024), label: "Clear List" } 260 | return submenu 261 | 262 | updateMenu: -> 263 | # Need to place our menu in top section 264 | for dropdown in atom.menu.template 265 | if dropdown.label is "File" or dropdown.label is "&File" 266 | for item in dropdown.submenu 267 | if item.command is "pane:reopen-closed-item" or item.label is "Open Recent" 268 | delete item.accelerator 269 | delete item.command 270 | delete item.click 271 | item.label = "Open Recent" 272 | item.enabled = true 273 | item.metadata ?= {} 274 | item.metadata.windowSpecific = false 275 | item.submenu = @createSubmenu() 276 | atom.menu.update() 277 | break # break for item 278 | break # break for dropdown 279 | 280 | #--- 281 | update: -> 282 | @removeCommandListeners() 283 | @updateMenu() 284 | @addCommandListeners() 285 | 286 | destroy: -> 287 | @removeListeners() 288 | 289 | 290 | #--- Module 291 | module.exports = 292 | config: 293 | maxRecentFiles: 294 | type: 'number' 295 | default: 8 296 | maxRecentDirectories: 297 | type: 'number' 298 | default: 8 299 | replaceNewWindowOnOpenDirectory: 300 | type: 'boolean' 301 | default: true 302 | description: 'When checked, opening a recent directory will "open" in the current window, but only if the window does not have a project path set. Eg: The window that appears when doing File > New Window.' 303 | replaceProjectOnOpenDirectory: 304 | type: 'boolean' 305 | default: false 306 | description: 'When checked, opening a recent directory will "open" in the current window, replacing the current project.' 307 | listDirectoriesAddedToProject: 308 | type: 'boolean' 309 | default: false 310 | description: 'When checked, the all root directories in a project will be added to the history and not just the 1st root directory.' 311 | ignoredNames: 312 | type: 'boolean' 313 | default: true 314 | description: 'When checked, skips files and directories specified in Atom\'s "Ignored Names" setting.' 315 | 316 | instance: null 317 | 318 | activate: -> 319 | @instance = new OpenRecent() 320 | @instance.init() 321 | 322 | deactivate: -> 323 | @instance.destroy() 324 | @instance = null 325 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "open-recent", 3 | "description": "Open recent files in the current window, and recent folders (optionally) in a new window.", 4 | "main": "./lib/main", 5 | "version": "5.0.0", 6 | "author": "Zren (https://github.com/Zren/)", 7 | "contributors": [], 8 | "repository": "https://github.com/Zren/atom-open-recent", 9 | "license": "MIT", 10 | "engines": { 11 | "atom": ">0.148.0" 12 | }, 13 | "dependencies": { 14 | "minimatch": "^2.0.9" 15 | } 16 | } 17 | --------------------------------------------------------------------------------