├── .gitignore ├── CHANGELOG.md ├── keymaps └── foldername-tabs.cson ├── package.json ├── LICENSE.md ├── spec └── foldername-tabs-spec.coffee ├── styles └── foldername-tabs.less ├── lib ├── main.coffee └── foldername-tabs.coffee └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.1.5 2 | * Options to limit folder and path length #3 3 | ## 0.1.4 4 | * Added ability to use a short name instead of a number for 5 | multi folder projects - thx @averrin 6 | ## 0.1.3 7 | * Removed coloring of tabs 8 | ## 0.1.2 9 | * Added trim on long nesting folders - thx @averrin 10 | ## 0.1.1 11 | * Bugfix for folder names for multi folder projects 12 | ## 0.1.0 13 | * Refined appearance 14 | -------------------------------------------------------------------------------- /keymaps/foldername-tabs.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/behind-atom-keymaps-in-depth 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "foldername-tabs", 3 | "main": "./lib/main", 4 | "version": "0.2.4", 5 | "description": "Adds foldernames to tabs..", 6 | "keywords": [ 7 | "tabs", 8 | "foldernames" 9 | ], 10 | "repository": "https://github.com/paulpflug/foldername-tabs", 11 | "license": "MIT", 12 | "engines": { 13 | "atom": ">=1.8.0" 14 | }, 15 | "dependencies": { 16 | "abbreviate": "~0.0.3" 17 | }, 18 | "consumedServices": { 19 | "autoreload": { 20 | "versions": { 21 | "^0.0.1": "consumeAutoreload" 22 | } 23 | }, 24 | "debug": { 25 | "versions": { 26 | "^0.0.1": "consumeDebug" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Paul Pflugradt 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 | -------------------------------------------------------------------------------- /spec/foldername-tabs-spec.coffee: -------------------------------------------------------------------------------- 1 | pkg = "foldername-tabs" 2 | describe "FoldernameTabs", -> 3 | [workspaceElement, activationPromise] = [] 4 | 5 | beforeEach -> 6 | atom.devMode = true 7 | atom.config.set("#{pkg}.debug",2) 8 | workspaceElement = atom.views.getView(atom.workspace) 9 | waitsForPromise -> 10 | atom.packages.activatePackage("tabs") 11 | .then -> 12 | atom.workspace.open('sample.js') 13 | .then -> 14 | atom.packages.activatePackage(pkg) 15 | 16 | describe "when the foldername-tabs:toggle event is triggered", -> 17 | it "removes and adds foldernames in tabs", -> 18 | runs -> 19 | expect(workspaceElement.querySelector('.tab-bar')).toExist() 20 | fntElement = workspaceElement.querySelector('div.foldername-tabs') 21 | expect(fntElement).toExist() 22 | expect(fntElement.querySelector("span.folder").innerHTML) 23 | .toEqual "/" 24 | expect(fntElement.querySelector("span.file").innerHTML) 25 | .toEqual "sample.js" 26 | atom.commands.dispatch workspaceElement, 'foldername-tabs:toggle' 27 | expect(workspaceElement.querySelector('div.foldername-tabs')).not 28 | .toExist() 29 | expect(workspaceElement.querySelector('.tab-bar div.title').innerHTML) 30 | .toEqual("sample.js") 31 | -------------------------------------------------------------------------------- /styles/foldername-tabs.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-workspace.theme-seti-ui .tab-bar .tab div.title { 8 | line-height: 2.5em; 9 | margin-top: 0; 10 | &:before { 11 | margin-top: 4px; 12 | } 13 | } 14 | atom-workspace.theme-one-dark-ui div.foldername-tabs>span.file-only, 15 | atom-workspace.theme-one-light-ui div.foldername-tabs>span.file-only { 16 | top: 0.5em; 17 | } 18 | 19 | div.foldername-tabs { 20 | margin-left: 4px; 21 | display: inline-flex; 22 | flex-direction: column; 23 | position: relative; 24 | top: -0.6em; 25 | justify-content: center; 26 | height: 3em; 27 | >span { 28 | line-height: 1.2em; 29 | &.file { 30 | font-size: 1.025em; 31 | &.file-only { 32 | top: 0.2em; 33 | position:relative; 34 | } 35 | } 36 | &.folder { 37 | font-size: 0.9em; 38 | } 39 | } 40 | } 41 | 42 | atom-workspace.theme-isotope-ui ul.tab-bar>li.tab[is="tabs-tab"] { 43 | background: @tab-background-color; 44 | overflow: hidden; 45 | } 46 | 47 | .file-icons-tab-pane-icon atom-pane .tab-bar { 48 | .tab .title[data-path]:before { 49 | position: relative; 50 | float: left; 51 | margin-right: 5px; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/main.coffee: -------------------------------------------------------------------------------- 1 | FoldernameTabs = null 2 | 3 | pkgName = "foldername-tabs" 4 | 5 | module.exports = new class Main 6 | subscriptions: null 7 | foldernameTabs: null 8 | config: 9 | maxLength: 10 | title: "Maximum path length" 11 | type: "integer" 12 | default: "20" 13 | description: "Allowed length of a path, if set to 0, will not shorten the path" 14 | folderLength: 15 | title: "Maximum folder length" 16 | type: "integer" 17 | default: "0" 18 | description: "Allowed length of a single folder, if set to 0, will not shorten the folder" 19 | mfpIdent: 20 | title: "Multi-folder project identifier" 21 | type: "integer" 22 | default: "0" 23 | description: "length of the project identifier, if set to 0 will use numbers instead" 24 | filenameFirst: 25 | title: "Filename first" 26 | type: "boolean" 27 | default: false 28 | description: "Puts the filename above the foldername" 29 | debug: 30 | type: "integer" 31 | default: 0 32 | minimum: 0 33 | debugger: -> -> 34 | debug: -> 35 | consumeDebug: (debugSetup) => 36 | @debugger = debugSetup(pkg: pkgName) 37 | @debug = @debugger "main" 38 | @debug "debug service consumed", 2 39 | consumeAutoreload: (reloader) => 40 | reloader(pkg:pkgName,folders:["lib/","styles/"]) 41 | @debug "autoreload service consumed", 2 42 | activate: -> 43 | unless @foldernameTabs? 44 | @debug "loading core" 45 | load = => 46 | FoldernameTabs ?= require "./foldername-tabs" 47 | @foldernameTabs = new FoldernameTabs(@debugger) 48 | # make sure it activates only after the tabs package 49 | if atom.packages.isPackageActive("tabs") 50 | load() 51 | else 52 | @onceActivated = atom.packages.onDidActivatePackage (p) => 53 | if p.name == "tabs" 54 | load() 55 | @onceActivated.dispose() 56 | 57 | 58 | deactivate: -> 59 | @debug "deactivating" 60 | @onceActivated?.dispose?() 61 | @foldernameTabs?.destroy?() 62 | @foldernameTabs = null 63 | FoldernameTabs = null 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # foldername-tabs package 2 | 3 | Adds foldernames to tabs.. 4 | 5 | | Theme | Appearance | 6 | | -----:| :----------| 7 | | atom | ![fnt-atom](https://cloud.githubusercontent.com/assets/1881921/8182308/7b0d9572-142e-11e5-91fa-6a5ed02eac32.png) | 8 | | graphite | ![fnt-graphite](https://cloud.githubusercontent.com/assets/1881921/8182309/7b287c70-142e-11e5-822f-a714b1bfb945.png) | 9 | | isotope | ![fnt-isotope](https://cloud.githubusercontent.com/assets/1881921/8182310/7b37b5c8-142e-11e5-8446-bae30d303235.png) | 10 | | one | ![fnt-one](https://cloud.githubusercontent.com/assets/1881921/8182311/7b398b00-142e-11e5-8a27-9e179e285a5c.png) | 11 | | polymorph | ![fnt-polymorph](https://cloud.githubusercontent.com/assets/1881921/8182312/7b41b83e-142e-11e5-93e7-27c21c9a2bf0.png) | 12 | | seti | ![fnt-seti](https://cloud.githubusercontent.com/assets/1881921/8182313/7b42d0fc-142e-11e5-9aa0-f8a62711305c.png) | 13 | 14 | Will be automatically enabled once installed. Can be toggled but will re-enable on restart. 15 | 16 | ## Current folder naming schema: 17 | 18 | ``` 19 | ## outside of your project 20 | folder: /.../lastFolder 21 | 22 | ## within your project 23 | folder: empty # In root folder 24 | folder: lastFolder # directly below root 25 | folder: ../../lastFolder # nested below root 26 | 27 | ## within your multi-folder project 28 | # like in a single-folder project but each folder gets the number of the root 29 | # folder prepended. Can be set to use a short name instead of a number. 30 | ``` 31 | ### Settings 32 | 33 | ![foldername-settings](https://cloud.githubusercontent.com/assets/1881921/8568995/600b0c7c-2573-11e5-8b6a-02afec61cc9c.png) 34 | 35 | Available settings: 36 | ```coffee 37 | "Maximum path length": 38 | default: 20 39 | description: "Allowed length of a path, if set to 0, will not shorten the path" 40 | "Maximum folder length": 41 | default: 0 42 | description: "Allowed length of a single folder, if set to 0, will not shorten the folder" 43 | "Multi-folder project identifier": 44 | default: 0 45 | description: "length of the project identifier, if set to 0 will use numbers instead" 46 | "Filename first" 47 | default: false 48 | description: "Puts the filename above the foldername" 49 | ``` 50 | ## Developing 51 | 52 | Run `npm install` in the package directory. 53 | 54 | Open it in atom in dev mode. 55 | 56 | For debugging set the debug field in package settings to the needed debug level. 57 | 58 | Should autoreload the package on changes in `lib` and `styles` folders 59 | 60 | 61 | ## License 62 | Copyright (c) 2015 Paul Pflugradt 63 | Licensed under the MIT license. 64 | -------------------------------------------------------------------------------- /lib/foldername-tabs.coffee: -------------------------------------------------------------------------------- 1 | sep = require("path").sep 2 | abbreviate = require "abbreviate" 3 | log = () -> 4 | 5 | {CompositeDisposable} = require 'atom' 6 | paths = {} 7 | 8 | # Parses a string path into an object containing the shortened foldername 9 | # and filename 10 | parsePath = (path) -> 11 | result = {} 12 | relativePath = atom.project.relativizePath path 13 | if relativePath?[0]? # within a project folder 14 | splitted = relativePath[1].split(sep) 15 | else 16 | splitted = path.split(sep) 17 | result.filename = splitted.pop() 18 | folderLength = atom.config.get("foldername-tabs.folderLength") 19 | splitted = splitted.map (string) -> 20 | if folderLength > 0 21 | return abbreviate string, { 22 | length: folderLength 23 | keepSeparators: true 24 | strict: false 25 | } 26 | else 27 | return string 28 | if splitted.length > 0 29 | lastFolder = splitted.pop() 30 | else 31 | lastFolder = "" 32 | if relativePath?[0]? # within a project folder 33 | projectPaths = atom.project.getPaths() 34 | pathIdentifier = "" 35 | if projectPaths.length > 1 # multi-folder project 36 | mfpIdent = atom.config.get("foldername-tabs.mfpIdent") 37 | if mfpIdent <= 0 38 | pathIdentifier += "#{projectPaths.indexOf(relativePath[0])+1}" 39 | else 40 | pathIdentifier += abbreviate relativePath[0].split(sep).pop(), { 41 | length: mfpIdent 42 | keepSeparators: true 43 | strict: false 44 | } 45 | pathIdentifier += sep if lastFolder 46 | result.foldername = pathIdentifier 47 | else # outside of project 48 | splitted.shift() # remove empty entry 49 | result.foldername = sep 50 | if splitted.length > 0 # there are some folders 51 | maxLength = atom.config.get("foldername-tabs.maxLength") 52 | if maxLength > 0 # there is a space limitation 53 | maxLength -= lastFolder.length+4 54 | maxLength -= mfpIdent+1 if relativePath?[0]? and mfpIdent 55 | if maxLength > 0 # there is room for more information 56 | if relativePath?[0]? and splitted[0].length < maxLength # add first folder within project 57 | maxLength -= splitted[0].length 58 | result.foldername += splitted.shift()+ sep 59 | remaining = "" 60 | while splitted.length > 0 61 | current = splitted.pop() 62 | if maxLength > current.length+1 63 | maxLength -= current.length+1 64 | remaining = current + sep + remaining 65 | else 66 | break 67 | remaining += sep if remaining.length > 0 68 | if splitted.length > 0 69 | result.foldername += "..."+sep+remaining 70 | else 71 | result.foldername += remaining 72 | else # no space left 73 | result.foldername += "..." + sep 74 | else # no space limitation 75 | result.foldername += splitted.join(sep) + sep 76 | result.foldername += lastFolder 77 | return result 78 | 79 | processAllTabs = (revert=false)-> 80 | log "processing all tabs, reverting:#{revert}" 81 | paths = [] 82 | paneItems = atom.workspace.getPaneItems() 83 | for paneItem in paneItems # get the unique paths of all opened files 84 | if paneItem.getPath? 85 | path = paneItem.getPath() 86 | if path? and paths.indexOf(path) == -1 87 | paths.push path 88 | log "found #{paths.length} different paths of 89 | total #{paneItems.length} paneItems",2 90 | for path in paths # process all opened paths 91 | tabs = atom.views.getView(atom.workspace).querySelectorAll "ul.tab-bar> 92 | li.tab[data-type='TextEditor']> 93 | div.title[data-path=\"#{path.replace(/\\/g,"\\\\").replace(/\"/g,"\\\"")}\"]" 94 | log "found #{tabs.length} tabs for #{path}",2 95 | for tab in tabs # if there are several tabs per path 96 | container = tab.querySelector "div.foldername-tabs" 97 | if container? and revert # removing all made changes 98 | log "reverting #{path}",2 99 | tab.removeChild container 100 | tab.innerHTML = path.split(sep).pop() 101 | else if not container? and not revert 102 | log "applying #{path}",2 103 | paths[path] ?= parsePath path 104 | tab.innerHTML = "" 105 | container = document.createElement("div") 106 | container.classList.add "foldername-tabs" 107 | filenameElement = document.createElement("span") 108 | filenameElement.classList.add "file" 109 | if paths[path].foldername == "" 110 | filenameElement.classList.add "file-only" 111 | filenameElement.innerHTML = paths[path].filename 112 | container.appendChild filenameElement 113 | if paths[path].foldername != "" 114 | foldernameElement = document.createElement("span") 115 | foldernameElement.classList.add "folder" 116 | foldernameElement.innerHTML = paths[path].foldername 117 | filenameFirst = atom.config.get("foldername-tabs.filenameFirst") 118 | if filenameFirst 119 | container.appendChild foldernameElement 120 | else 121 | container.insertBefore foldernameElement, filenameElement 122 | tab.appendChild container 123 | return !revert 124 | 125 | 126 | module.exports = 127 | class FoldernameTabs 128 | disposables: null 129 | processed: false 130 | constructor: (logger) -> 131 | log = logger "core" 132 | @processed = processAllTabs() 133 | unless @disposables? 134 | @disposables = new CompositeDisposable 135 | @disposables.add atom.workspace.onDidAddPaneItem -> setTimeout processAllTabs, 10 136 | @disposables.add atom.workspace.observePanes (pane) => 137 | processAllTabs() 138 | disposables = new CompositeDisposable 139 | #disposable1 = pane.onDidAddItem -> setTimeout processAllTabs, 10 140 | disposable3 = pane.onDidRemoveItem -> setTimeout processAllTabs, 10 141 | disposable4 = pane.onDidMoveItem -> setTimeout processAllTabs, 10 142 | disposable2 = pane.onDidDestroy -> 143 | disposables.dispose() if disposables.disposables? 144 | disposables.add disposable2,disposable3,disposable4 145 | @disposables.add disposable2,disposable3,disposable4 146 | #@disposables.add atom.workspace.onDidAddPaneItem -> 147 | # setTimeout processAllTabs, 10 148 | #@disposables.add atom.workspace.onDidDestroyPaneItem -> 149 | # setTimeout processAllTabs, 10 150 | @disposables.add atom.commands.add 'atom-workspace', 151 | 'foldername-tabs:toggle': @toggle 152 | @disposables.add atom.config.observe("foldername-tabs.mfpIdent", @repaint) 153 | @disposables.add atom.config.observe("foldername-tabs.folderLength", @repaint) 154 | @disposables.add atom.config.observe("foldername-tabs.maxLength", @repaint) 155 | @disposables.add atom.config.observe("foldername-tabs.filenameFirst", @repaint) 156 | log "loaded" 157 | repaint: => 158 | if @processed 159 | processAllTabs(true) 160 | processAllTabs() 161 | toggle: => 162 | @processed = processAllTabs(@processed) 163 | destroy: => 164 | @processed = processAllTabs(true) 165 | @disposables?.dispose() 166 | @disposables = null 167 | --------------------------------------------------------------------------------