├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── CHANGELOG.md ├── README.md ├── keymaps └── css-comb.cson ├── lib ├── css-comb.js └── main.js ├── menus └── css-comb.cson ├── package.json └── src ├── css-comb.js └── main.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015-loose"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "env": { 4 | "es6": true, 5 | "node": true, 6 | "mocha": false, 7 | "browser": false 8 | }, 9 | "ecmaFeatures": { 10 | "arrowFunctions": true, 11 | "blockBindings": true, 12 | "classes": true, 13 | "defaultParams": true, 14 | "destructuring": true, 15 | "forOf": true, 16 | "generators": false, 17 | "modules": true, 18 | "objectLiteralComputedProperties": true, 19 | "objectLiteralDuplicateProperties": false, 20 | "objectLiteralShorthandMethods": true, 21 | "objectLiteralShorthandProperties": true, 22 | "spread": true, 23 | "superInFunctions": true, 24 | "templateStrings": true, 25 | "jsx": true 26 | }, 27 | "rules": { 28 | // possible errors 29 | "comma-dangle": [2, "never"], 30 | "no-cond-assign": [2, "always"], 31 | "no-console": [2], 32 | "no-constant-condition": [2], 33 | "no-control-regex": [2], 34 | "no-debugger": [2], 35 | "no-dupe-args": [2], 36 | "no-dupe-keys": [2], 37 | "no-duplicate-case": [2], 38 | "no-empty": [2], 39 | "no-empty-character-class": [2], 40 | "no-ex-assign": [2], 41 | "no-extra-boolean-cast": [2], 42 | "no-extra-parens": [0], 43 | "no-extra-semi": [2], 44 | "no-func-assign": [2], 45 | "no-inner-declarations": [2], 46 | "no-invalid-regexp": [2], 47 | "no-irregular-whitespace": [2], 48 | "no-negated-in-lhs": [2], 49 | "no-obj-calls": [2], 50 | "no-regex-spaces": [2], 51 | "no-sparse-arrays": [2], 52 | "no-unexpected-multiline": [2], 53 | "no-unreachable": [2], 54 | "use-isnan": [2], 55 | "valid-jsdoc": [2, { 56 | "requireReturn": false, 57 | "requireParamDescription": false, 58 | "requireReturnDescription": false 59 | }], 60 | "valid-typeof": [2], 61 | 62 | // best practices 63 | "accessor-pairs": [2], 64 | "array-callback-return": [2], 65 | "block-scoped-var": [2], 66 | "complexity": [1, 8], 67 | "consistent-return": [0], 68 | "curly": [2, "multi-line"], 69 | "default-case": [2], 70 | "dot-location": [2, "property"], 71 | "dot-notation": [2], 72 | "eqeqeq": [2], 73 | "guard-for-in": [0], 74 | "no-alert": [2], 75 | "no-caller": [2], 76 | "no-case-declarations": [2], 77 | "no-div-regex": [2], 78 | "no-else-return": [2], 79 | "no-empty-function": [2], 80 | "no-empty-pattern": [2], 81 | "no-eq-null": [2], 82 | "no-eval": [2], 83 | "no-extend-native": [2], 84 | "no-extra-bind": [2], 85 | "no-extra-label": [2], 86 | "no-fallthrough": [2], 87 | "no-floating-decimal": [2], 88 | "no-implicit-coercion": [0], 89 | "no-implicit-globals": [2], 90 | "no-implied-eval": [2], 91 | "no-invalid-this": [2], 92 | "no-iterator": [2], 93 | "no-labels": [2], 94 | "no-lone-blocks": [2], 95 | "no-loop-func": [2], 96 | "no-magic-numbers": [1, { "ignoreArrayIndexes": true }], 97 | "no-multi-spaces": [2], 98 | "no-multi-str": [2], 99 | "no-native-reassign": [2], 100 | "no-new": [2], 101 | "no-new-func": [2], 102 | "no-new-wrappers": [2], 103 | "no-octal": [2], 104 | "no-octal-escape": [2], 105 | "no-param-reassign": [0], 106 | "no-process-env": [0], 107 | "no-proto": [2], 108 | "no-redeclare": [2], 109 | "no-return-assign": [2], 110 | "no-script-url": [2], 111 | "no-self-assign": [2], 112 | "no-self-compare": [2], 113 | "no-sequences": [2], 114 | "no-throw-literal": [2], 115 | "no-unmodified-loop-condition": [2], 116 | "no-unused-expressions": [2, { "allowShortCircuit": true }], 117 | "no-unused-labels": [2], 118 | "no-useless-call": [2], 119 | "no-useless-concat": [2], 120 | "no-void": [2], 121 | "no-warning-comments": [0], 122 | "no-with": [2], 123 | "radix": [2], 124 | "vars-on-top": [0], 125 | "wrap-iife": [2, "inside"], 126 | "yoda": [2, "never"], 127 | 128 | // strict 129 | "strict": [0], 130 | 131 | // variables 132 | "init-declarations": [0], 133 | "no-catch-shadow": [2], 134 | "no-delete-var": [2], 135 | "no-label-var": [2], 136 | "no-shadow": [2], 137 | "no-shadow-restricted-names": [2], 138 | "no-undef": [2], 139 | "no-undef-init": [2], 140 | "no-undefined": [2], 141 | "no-unused-vars": [2], 142 | "no-use-before-define": [2], 143 | 144 | // node.js 145 | "callback-return": [0], 146 | "global-require": [0], 147 | "handle-callback-err": [2], 148 | "no-mixed-requires": [2], 149 | "no-new-require": [2], 150 | "no-path-concat": [2], 151 | "no-process-exit": [2], 152 | "no-restricted-imports": [0], 153 | "no-restricted-modules": [0], 154 | "no-sync": [0], 155 | 156 | // stylistic 157 | "array-bracket-spacing": [2, "never"], 158 | "block-spacing": [2], 159 | "brace-style": [2, "1tbs", { "allowSingleLine": true }], 160 | "camelcase": [2, { "properties": "never" }], 161 | "comma-spacing": [2, { 162 | "before": false, 163 | "after": true 164 | }], 165 | "comma-style": [2, "last"], 166 | "computed-property-spacing": [2, "never"], 167 | "consistent-this": [2, "_this"], 168 | "eol-last": [2], 169 | "func-names": [0], 170 | "func-style": [0], 171 | "indent": [2, 2], 172 | "jsx-quotes": [2, "prefer-single"], 173 | "key-spacing": [2, { 174 | "beforeColon": false, 175 | "afterColon": true 176 | }], 177 | "keyword-spacing": [2], 178 | "linebreak-style": [2, "unix"], 179 | "lines-around-comment": [2, { 180 | "beforeBlockComment": true 181 | }], 182 | "max-depth": [2, 4], 183 | "max-len": [2, 120, 4], 184 | "max-nested-callbacks": [2, 4], 185 | "max-params": [0], 186 | "max-statements": [0], 187 | "new-cap": [2], 188 | "new-parens": [2], 189 | "newline-after-var": [2], 190 | "newline-per-chained-call": [0], 191 | "no-array-constructor": [2], 192 | "no-bitwise": [0], 193 | "no-continue": [0], 194 | "no-inline-comments": [0], 195 | "no-lonely-if": [2], 196 | "no-mixed-spaces-and-tabs": [2], 197 | "no-multiple-empty-lines": [2], 198 | "no-negated-condition": [0], 199 | "no-nested-ternary": [2], 200 | "no-new-object": [2], 201 | "no-plusplus": [0], 202 | "no-spaced-func": [2], 203 | "no-ternary": [0], 204 | "no-trailing-spaces": [2], 205 | "no-underscore-dangle": [0], 206 | "no-unneeded-ternary": [2], 207 | "no-whitespace-before-property": [2], 208 | "object-curly-spacing": [2, "always"], 209 | "one-var": [2, { 210 | "uninitialized": "always", 211 | "initialized": "never" 212 | }], 213 | "one-var-declaration-per-line": [0], 214 | "operator-assignment": [0], 215 | "operator-linebreak": [2, "before"], 216 | "padded-blocks": [0], 217 | "quote-props": [2, "as-needed"], 218 | "quotes": [2, "single"], 219 | "require-jsdoc": [0], 220 | "semi": [2, "always"], 221 | "semi-spacing": [2, { 222 | "before": false, 223 | "after": true 224 | }], 225 | "sort-imports": [0], 226 | "sort-vars": [0], 227 | "space-before-blocks": [2, "always"], 228 | "space-before-function-paren": [2, { 229 | "anonymous": "always", 230 | "named": "never" 231 | }], 232 | "space-in-parens": [2, "never"], 233 | "space-infix-ops": [2], 234 | "space-unary-ops": [2], 235 | "spaced-comment": [2, "always"], 236 | "wrap-regex": [2], 237 | 238 | // ecmascript6 239 | "arrow-body-style": [0], 240 | "arrow-parens": [2], 241 | "arrow-spacing": [2, { "before": true, "after": true }], 242 | "constructor-super": [2], 243 | "generator-star-spacing": [2, "after"], 244 | "no-class-assign": [2], 245 | "no-confusing-arrow": [0], 246 | "no-const-assign": [2], 247 | "no-dupe-class-members": [2], 248 | "no-new-symbol": [2], 249 | "no-this-before-super": [2], 250 | "no-useless-constructor": [2], 251 | "no-var": [2], 252 | "object-shorthand": [0], 253 | "prefer-arrow-callback": [0], 254 | "prefer-const": [2], 255 | "prefer-reflect": [0], 256 | "prefer-rest-params": [0], 257 | "prefer-spread": [0], 258 | "prefer-template": [2], 259 | "require-yield": [2], 260 | "template-curly-spacing": [2, "never"] 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | test.scss 5 | test.css 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 2.4.0 - 16.10.2015 2 | * Split package loading and initialization to two separate files that dramaticaly improves package load time from 50ms to 5ms. 3 | * Use babel loose mode for classes and modules. 4 | 5 | ## 2.3.0 - 11.10.2015 6 | * Return babel to dev dependencies. 7 | * Process file on fly by using processString instead of processFile. 8 | * Update csscomb to version 3.1.8 9 | 10 | ## 2.1.1 - 15.06.2015 11 | * Removed babel from dev dependencies. 12 | 13 | ## 2.1.0 - 15.06.2015 14 | * Update csscomb to 3.1.7. 15 | * Added highly experimental (unstable) feature for processing stylus as sass, works only when processing selection, and may break on everything. Use at your own risk. 16 | 17 | ## 2.0.4 - 14.04.2015 18 | * Correct grammar for css-comb selection processing. 19 | 20 | ## 2.0.3 - 11.03.2015 21 | * Fix css-comb plugin now again work without on save option. 22 | 23 | ## 2.0.2 - 11.03.2015 24 | * Fix loading config with ~ (home directory) in path. 25 | 26 | ## 2.0.1 - 07.03.2015 27 | * Removed babel from dev dependencies 28 | 29 | ## 2.0.0 - 07.03.2015 30 | * Use es6 with babel and precomiple step (Now I'm free from coffescript!) 31 | 32 | ## 1.2.0 — 07.03.2015 33 | * OnSave option to process file on every save 34 | * CSSComb updated to version 3.0.4 35 | 36 | ## 1.0.0 — 15.01.2015 37 | * Ability to process only selected lines 38 | 39 | Now plugin can work in both ways it means you can process whole file or only selected lines of your styles. 40 | 41 | ## 0.3.0 — 15.01.2015 42 | * Settings for disable config searching in project directory and using predefined or custom config from plugin settings 43 | 44 | ## 0.2.1 — 14.01.2015 45 | * Menu label changed to CSSComb 46 | 47 | ## 0.2.0 — 14.01.2015 48 | * Settings for defining csscomb default config 49 | 50 | ## 0.1.0 - 14.01.2015 51 | * First version 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # css-comb for Atom 2 | [![Current version](https://img.shields.io/apm/v/css-comb.svg)](https://atom.io/packages/css-comb) 3 | [![Downloads](https://img.shields.io/apm/dm/css-comb.svg)](https://atom.io/packages/css-comb) 4 | [![Github Issues](https://img.shields.io/github/issues/d4rkr00t/css-comb-atom.svg)](https://github.com/d4rkr00t/css-comb-atom/issues) 5 | ![Dependencies](https://david-dm.org/d4rkr00t/css-comb-atom.svg) 6 | [![License](https://img.shields.io/apm/l/css-comb.svg)](http://opensource.org/licenses/MIT) 7 | 8 | [CSSComb](https://github.com/csscomb/csscomb.js) 9 | 10 | ## Usage 11 | 12 | Press `ctrl+alt+c` to format you styles. 13 | 14 | Plugin searches for `.csscomb.json` until it has been found and if it hasn't plugin takes default css-comb config or your custom. 15 | 16 | ![css-comb](https://cloud.githubusercontent.com/assets/200119/5740596/e244b8f6-9c15-11e4-8263-a31909ddd47e.gif) 17 | 18 | ## Features 19 | 20 | * Search config in project directory. 21 | * Notifications, you can turn it off. 22 | * Choose 1 of 3 predefined configs or specify your own, 23 | custom config that will be used if there aren't any configs in project directory. 24 | * Disabling config searching in project directory and use predefined or custom config. 25 | * Selected lines processing. 26 | * OnSave option for processing file on every save. 27 | * No need saving file to process it. 28 | -------------------------------------------------------------------------------- /keymaps/css-comb.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-c': 'css-comb:run' 12 | -------------------------------------------------------------------------------- /lib/css-comb.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | exports.__esModule = true; 4 | 5 | var _csscomb = require('csscomb'); 6 | 7 | var _csscomb2 = _interopRequireDefault(_csscomb); 8 | 9 | var _path = require('path'); 10 | 11 | var _path2 = _interopRequireDefault(_path); 12 | 13 | var _fs = require('fs'); 14 | 15 | var _fs2 = _interopRequireDefault(_fs); 16 | 17 | var _minimatch = require('minimatch'); 18 | 19 | var _minimatch2 = _interopRequireDefault(_minimatch); 20 | 21 | var _atom = require('atom'); 22 | 23 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 24 | 25 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } /* global atom */ 26 | 27 | 28 | var allowedGrammas = ['css', 'less', 'scss', 'sass', 'styl']; 29 | 30 | function getUserHome() { 31 | return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 32 | } 33 | 34 | var CssComb = function () { 35 | function CssComb() { 36 | var _this = this; 37 | 38 | _classCallCheck(this, CssComb); 39 | 40 | this._subscriptions = new _atom.CompositeDisposable(); 41 | 42 | this._subscriptions.add(atom.commands.add('atom-workspace', { 43 | 'css-comb:run': function cssCombRun() { 44 | return _this.comb(); 45 | } 46 | })); 47 | 48 | this._editorObserver = atom.workspace.observeTextEditors(function (editor) { 49 | return _this.handleEvents(editor); 50 | }); 51 | } 52 | 53 | CssComb.prototype.deactivate = function deactivate() { 54 | this._subscriptions.dispose(); 55 | this._editorObserver.dispose(); 56 | }; 57 | 58 | /** 59 | * @private 60 | * 61 | * @param {TextEditor} editor 62 | */ 63 | 64 | 65 | CssComb.prototype.handleEvents = function handleEvents(editor) { 66 | var _this2 = this; 67 | 68 | editor.getBuffer().onWillSave(function () { 69 | if (_this2._isOnSave() && _this2._isAllowedGrama(editor)) { 70 | _this2.comb(); 71 | } 72 | }); 73 | }; 74 | 75 | /** 76 | * @private 77 | * 78 | * @returns {void} 79 | */ 80 | 81 | 82 | CssComb.prototype.comb = function comb() { 83 | var filePath = atom.workspace.getActivePaneItem().getPath(); 84 | var configPath = this._getConfigPath(filePath); 85 | var config = this._getConfig(configPath); 86 | var selectedText = this._getSelectedText(); 87 | var relativeFilePath = atom.project.relativizePath(filePath)[1]; 88 | var filePathRelativeToConfig = configPath ? _path2.default.relative(_path2.default.dirname(atom.project.relativizePath(configPath)[1]), relativeFilePath) : relativeFilePath; 89 | 90 | if (this._isIgnored(config.exclude, filePathRelativeToConfig)) { 91 | return this._showInfoNotification('File is ignored in csscomb config'); 92 | } 93 | 94 | if (selectedText) { 95 | !this._isOnSave() && this._processSelection(selectedText, config); 96 | } else { 97 | var text = this._getText(); 98 | 99 | this._processFile(text, config); 100 | } 101 | }; 102 | 103 | /** 104 | * Process whole file be csscomb 105 | * @private 106 | * 107 | * @param {String} text — content of file to process 108 | * @param {Object} config — csscomb config 109 | */ 110 | 111 | 112 | CssComb.prototype._processFile = function _processFile(text, config) { 113 | var comb = new _csscomb2.default(config); 114 | var textEditor = atom.workspace.getActiveTextEditor(); 115 | var syntax = this._getSyntax(textEditor); 116 | 117 | try { 118 | var processedString = comb.processString(text, { syntax: syntax }); 119 | 120 | textEditor.setText(processedString); 121 | 122 | this._showInfoNotification('File processed by csscomb'); 123 | } catch (err) { 124 | this._showErrorNotification(err.message); 125 | console.error(err); // eslint-disable-line 126 | } 127 | }; 128 | 129 | /** 130 | * Process only selection by csscomb 131 | * @private 132 | * 133 | * @param {String} string to process 134 | * @param {Object} config csscomb config 135 | */ 136 | 137 | 138 | CssComb.prototype._processSelection = function _processSelection(string, config) { 139 | var comb = new _csscomb2.default(config); 140 | 141 | try { 142 | var textEditor = atom.workspace.getActiveTextEditor(); 143 | var syntax = this._getSyntax(textEditor); 144 | 145 | if (syntax !== 'stylus') { 146 | var processedString = comb.processString(string, { syntax: syntax }); 147 | 148 | textEditor.setTextInBufferRange(textEditor.getSelectedBufferRange(), processedString); 149 | 150 | this._showInfoNotification('Lines processed by csscomb'); 151 | } else { 152 | this._showErrorNotification('Stylus is not supported yet!'); 153 | } 154 | } catch (err) { 155 | this._showErrorNotification(err.message); 156 | console.error(err); // eslint-disable-line 157 | } 158 | }; 159 | 160 | /** 161 | * Gets syntax from text editor 162 | * @private 163 | * 164 | * @param {Object} textEditor 165 | * 166 | * @returns {String} 167 | */ 168 | 169 | 170 | CssComb.prototype._getSyntax = function _getSyntax(textEditor) { 171 | var syntax = textEditor.getGrammar().name.toLowerCase(); 172 | 173 | if (atom.config.get('css-comb.processStylus')) { 174 | return syntax === 'stylus' ? 'sass' : syntax; 175 | } 176 | 177 | return syntax; 178 | }; 179 | 180 | /** 181 | * Show info notification 182 | * @private 183 | * 184 | * @param {String} message — notification text 185 | */ 186 | 187 | 188 | CssComb.prototype._showInfoNotification = function _showInfoNotification(message) { 189 | if (this._isShowInfoNotification()) { 190 | atom.notifications.addInfo(message); 191 | } 192 | }; 193 | 194 | /** 195 | * Show error notification 196 | * @private 197 | * 198 | * @param {String} message notification text 199 | */ 200 | 201 | 202 | CssComb.prototype._showErrorNotification = function _showErrorNotification(message) { 203 | if (this._isShowErrorNotification()) { 204 | atom.notifications.addError(message); 205 | } 206 | }; 207 | 208 | /** 209 | * Check if info notifications should be shown 210 | * @private 211 | * 212 | * @returns {Boolean} 213 | */ 214 | 215 | 216 | CssComb.prototype._isShowInfoNotification = function _isShowInfoNotification() { 217 | return atom.config.get('css-comb.showNotifications') && atom.notifications && atom.notifications.addInfo; 218 | }; 219 | 220 | /** 221 | * Check if error notifications should be shown 222 | * @private 223 | * 224 | * @returns {Boolean} 225 | */ 226 | 227 | 228 | CssComb.prototype._isShowErrorNotification = function _isShowErrorNotification() { 229 | return atom.config.get('css-comb.showNotifications') && atom.notifications && atom.notifications.addError; 230 | }; 231 | 232 | /** 233 | * Check if on save option enabled 234 | * @private 235 | * 236 | * @returns {Boolean} 237 | */ 238 | 239 | 240 | CssComb.prototype._isOnSave = function _isOnSave() { 241 | return atom.config.get('css-comb.shouldUpdateOnSave'); 242 | }; 243 | 244 | /** 245 | * Check if file is in allowed gramma list 246 | * @private 247 | * 248 | * @param {TextEditor} editor 249 | * 250 | * @returns {Boolean} 251 | */ 252 | 253 | 254 | CssComb.prototype._isAllowedGrama = function _isAllowedGrama(editor) { 255 | return allowedGrammas.indexOf(editor.getGrammar().name.toLowerCase()) !== -1; 256 | }; 257 | 258 | /** 259 | * Returns true if file is in exclude list 260 | * @private 261 | * 262 | * @param {String[]} exclude - patterns for excluding file pathes 263 | * @param {String} filePath 264 | * 265 | * @returns {Boolean} 266 | */ 267 | 268 | 269 | CssComb.prototype._isIgnored = function _isIgnored(exclude, filePath) { 270 | if (!exclude) return false; 271 | return exclude.some(function (pattern) { 272 | return (0, _minimatch2.default)(filePath, pattern); 273 | }); 274 | }; 275 | 276 | /** 277 | * Search config using builtin csscomb method 278 | * @private 279 | * 280 | * @param {String} filePath 281 | * 282 | * @returns {String} 283 | */ 284 | 285 | 286 | CssComb.prototype._getConfigPath = function _getConfigPath(filePath) { 287 | if (!atom.config.get('css-comb.shouldNotSearchConfig')) { 288 | var configPath = _path2.default.join(_path2.default.dirname(filePath), '.csscomb.json'); 289 | 290 | return _csscomb2.default.getCustomConfigPath(configPath); 291 | } 292 | }; 293 | 294 | /** 295 | * Load csscomb config 296 | * @private 297 | * 298 | * @param {String} configPath 299 | * 300 | * @returns {Object} csscomb config 301 | */ 302 | 303 | 304 | CssComb.prototype._getConfig = function _getConfig(configPath) { 305 | if (configPath) { 306 | return JSON.parse(_fs2.default.readFileSync(configPath, 'utf-8')); 307 | } 308 | 309 | configPath = atom.config.get('css-comb.customConfig'); 310 | 311 | if (configPath && configPath.match(/^\~/)) { 312 | configPath = _path2.default.join(getUserHome(), configPath.replace(/^\~\//, '')); 313 | } 314 | 315 | if (configPath && _fs2.default.existsSync(configPath)) { 316 | return JSON.parse(_fs2.default.readFileSync(configPath, 'utf-8')); 317 | } 318 | 319 | return _csscomb2.default.getConfig(atom.config.get('css-comb.predef')); 320 | }; 321 | 322 | /** 323 | * Return selected text for current opened file 324 | * @private 325 | * 326 | * @returns {String} 327 | */ 328 | 329 | 330 | CssComb.prototype._getSelectedText = function _getSelectedText() { 331 | return atom.workspace.getActiveTextEditor().getSelectedText(); 332 | }; 333 | 334 | /** 335 | * Return whole text for current active editor 336 | * @private 337 | * 338 | * @returns {String} 339 | */ 340 | 341 | 342 | CssComb.prototype._getText = function _getText() { 343 | return atom.workspace.getActiveTextEditor().getText(); 344 | }; 345 | 346 | return CssComb; 347 | }(); 348 | 349 | exports.default = CssComb; -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | 5 | /** 6 | * Config 7 | * 8 | * @type {Object} 9 | */ 10 | config: { 11 | shouldNotSearchConfig: { 12 | title: 'Disable config searching', 13 | description: 'Disable config searching in project directory and use predefined or custom config', 14 | type: 'boolean', 15 | default: false 16 | }, 17 | predef: { 18 | title: 'Predefined configs', 19 | description: 'Will be used if config is not found in project directory', 20 | type: 'string', 21 | default: 'csscomb', 22 | enum: ['csscomb', 'zen', 'yandex'] 23 | }, 24 | customConfig: { 25 | title: 'Custom config (Full path to file)', 26 | description: 'Will be used if config is not found in project directory,' + ' has more priority than predefined configs.', 27 | type: 'string', 28 | default: '' 29 | }, 30 | showNotifications: { 31 | title: 'Notifications', 32 | type: 'boolean', 33 | default: true 34 | }, 35 | shouldUpdateOnSave: { 36 | title: 'On Save', 37 | description: 'Process file on every save.', 38 | type: 'boolean', 39 | default: false 40 | }, 41 | processStylus: { 42 | title: 'Process stylus as sass', 43 | description: '!WARNING! Highly unstable feature, works only when processing selection, ' + 'and may break on everything. Use at your own risk.', 44 | type: 'boolean', 45 | default: false 46 | } 47 | }, 48 | 49 | activate: function activate(state) { 50 | var CssComb = require('./css-comb').default; 51 | 52 | this.instance = new CssComb(state); 53 | }, 54 | deactivate: function deactivate() { 55 | this.instance && this.instance.deactivate(); 56 | } 57 | }; -------------------------------------------------------------------------------- /menus/css-comb.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': 'Run CSSComb' 6 | 'command': 'css-comb:run' 7 | } 8 | ] 9 | 'menu': [ 10 | { 11 | 'label': 'Packages' 12 | 'submenu': [ 13 | 'label': 'CSSComb' 14 | 'submenu': [ 15 | { 16 | 'label': 'Run' 17 | 'command': 'css-comb:run' 18 | } 19 | ] 20 | ] 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-comb", 3 | "main": "./lib/main", 4 | "version": "2.5.0", 5 | "description": "CSS Comb for Atom", 6 | "repository": "https://github.com/d4rkr00t/css-comb-atom", 7 | "license": "MIT", 8 | "scripts": { 9 | "build": "babel -d ./lib ./src", 10 | "watch": "babel -d ./lib ./src --watch", 11 | "lint": "eslint ./src" 12 | }, 13 | "pre-commit": [ 14 | "lint" 15 | ], 16 | "engines": { 17 | "atom": ">0.50.0" 18 | }, 19 | "dependencies": { 20 | "csscomb": "^3.1.8", 21 | "minimatch": "^3.0.2" 22 | }, 23 | "devDependencies": { 24 | "babel-cli": "^6.11.4", 25 | "babel-eslint": "^6.0.4", 26 | "babel-preset-es2015-loose": "^8.0.0", 27 | "cz-conventional-changelog": "^1.1.6", 28 | "eslint": "^3.5.0", 29 | "pre-commit": "^1.1.3" 30 | }, 31 | "config": { 32 | "commitizen": { 33 | "path": "./node_modules/cz-conventional-changelog" 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/css-comb.js: -------------------------------------------------------------------------------- 1 | /* global atom */ 2 | import CSScomb from 'csscomb'; 3 | 4 | import path from 'path'; 5 | import fs from 'fs'; 6 | import minimatch from 'minimatch'; 7 | import { CompositeDisposable } from 'atom'; 8 | 9 | const allowedGrammas = ['css', 'less', 'scss', 'sass', 'styl']; 10 | 11 | function getUserHome() { 12 | return process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE; 13 | } 14 | 15 | export default class CssComb { 16 | constructor() { 17 | this._subscriptions = new CompositeDisposable(); 18 | 19 | this._subscriptions.add(atom.commands.add('atom-workspace', { 20 | 'css-comb:run': () => this.comb() 21 | })); 22 | 23 | this._editorObserver = atom.workspace.observeTextEditors((editor) => this.handleEvents(editor)); 24 | } 25 | 26 | deactivate() { 27 | this._subscriptions.dispose(); 28 | this._editorObserver.dispose(); 29 | } 30 | 31 | /** 32 | * @private 33 | * 34 | * @param {TextEditor} editor 35 | */ 36 | handleEvents(editor) { 37 | editor.getBuffer().onWillSave(() => { 38 | if (this._isOnSave() && this._isAllowedGrama(editor)) { 39 | this.comb(); 40 | } 41 | }); 42 | } 43 | 44 | /** 45 | * @private 46 | * 47 | * @returns {void} 48 | */ 49 | comb() { 50 | const filePath = atom.workspace.getActivePaneItem().getPath(); 51 | const configPath = this._getConfigPath(filePath); 52 | const config = this._getConfig(configPath); 53 | const selectedText = this._getSelectedText(); 54 | const relativeFilePath = atom.project.relativizePath(filePath)[1]; 55 | const filePathRelativeToConfig = configPath 56 | ? path.relative(path.dirname(atom.project.relativizePath(configPath)[1]), relativeFilePath) 57 | : relativeFilePath; 58 | 59 | if (this._isIgnored(config.exclude, filePathRelativeToConfig)) { 60 | return this._showInfoNotification('File is ignored in csscomb config'); 61 | } 62 | 63 | if (selectedText) { 64 | !this._isOnSave() && this._processSelection(selectedText, config); 65 | } else { 66 | const text = this._getText(); 67 | 68 | this._processFile(text, config); 69 | } 70 | } 71 | 72 | /** 73 | * Process whole file be csscomb 74 | * @private 75 | * 76 | * @param {String} text — content of file to process 77 | * @param {Object} config — csscomb config 78 | */ 79 | _processFile(text, config) { 80 | const comb = new CSScomb(config); 81 | const textEditor = atom.workspace.getActiveTextEditor(); 82 | const syntax = this._getSyntax(textEditor); 83 | 84 | try { 85 | const processedString = comb.processString(text, { syntax }); 86 | 87 | textEditor.setText(processedString); 88 | 89 | this._showInfoNotification('File processed by csscomb'); 90 | } catch (err) { 91 | this._showErrorNotification(err.message); 92 | console.error(err); // eslint-disable-line 93 | } 94 | } 95 | 96 | /** 97 | * Process only selection by csscomb 98 | * @private 99 | * 100 | * @param {String} string to process 101 | * @param {Object} config csscomb config 102 | */ 103 | _processSelection(string, config) { 104 | const comb = new CSScomb(config); 105 | 106 | try { 107 | const textEditor = atom.workspace.getActiveTextEditor(); 108 | const syntax = this._getSyntax(textEditor); 109 | 110 | if (syntax !== 'stylus') { 111 | const processedString = comb.processString(string, { syntax }); 112 | 113 | textEditor.setTextInBufferRange(textEditor.getSelectedBufferRange(), processedString); 114 | 115 | this._showInfoNotification('Lines processed by csscomb'); 116 | } else { 117 | this._showErrorNotification('Stylus is not supported yet!'); 118 | } 119 | } catch (err) { 120 | this._showErrorNotification(err.message); 121 | console.error(err); // eslint-disable-line 122 | } 123 | } 124 | 125 | /** 126 | * Gets syntax from text editor 127 | * @private 128 | * 129 | * @param {Object} textEditor 130 | * 131 | * @returns {String} 132 | */ 133 | _getSyntax(textEditor) { 134 | const syntax = textEditor.getGrammar().name.toLowerCase(); 135 | 136 | if (atom.config.get('css-comb.processStylus')) { 137 | return syntax === 'stylus' ? 'sass' : syntax; 138 | } 139 | 140 | return syntax; 141 | } 142 | 143 | /** 144 | * Show info notification 145 | * @private 146 | * 147 | * @param {String} message — notification text 148 | */ 149 | _showInfoNotification(message) { 150 | if (this._isShowInfoNotification()) { 151 | atom.notifications.addInfo(message); 152 | } 153 | } 154 | 155 | /** 156 | * Show error notification 157 | * @private 158 | * 159 | * @param {String} message notification text 160 | */ 161 | _showErrorNotification(message) { 162 | if (this._isShowErrorNotification()) { 163 | atom.notifications.addError(message); 164 | } 165 | } 166 | 167 | /** 168 | * Check if info notifications should be shown 169 | * @private 170 | * 171 | * @returns {Boolean} 172 | */ 173 | _isShowInfoNotification() { 174 | return atom.config.get('css-comb.showNotifications') && atom.notifications && atom.notifications.addInfo; 175 | } 176 | 177 | /** 178 | * Check if error notifications should be shown 179 | * @private 180 | * 181 | * @returns {Boolean} 182 | */ 183 | _isShowErrorNotification() { 184 | return atom.config.get('css-comb.showNotifications') && atom.notifications && atom.notifications.addError; 185 | } 186 | 187 | /** 188 | * Check if on save option enabled 189 | * @private 190 | * 191 | * @returns {Boolean} 192 | */ 193 | _isOnSave() { 194 | return atom.config.get('css-comb.shouldUpdateOnSave'); 195 | } 196 | 197 | /** 198 | * Check if file is in allowed gramma list 199 | * @private 200 | * 201 | * @param {TextEditor} editor 202 | * 203 | * @returns {Boolean} 204 | */ 205 | _isAllowedGrama(editor) { 206 | return allowedGrammas.indexOf(editor.getGrammar().name.toLowerCase()) !== -1; 207 | } 208 | 209 | /** 210 | * Returns true if file is in exclude list 211 | * @private 212 | * 213 | * @param {String[]} exclude - patterns for excluding file pathes 214 | * @param {String} filePath 215 | * 216 | * @returns {Boolean} 217 | */ 218 | _isIgnored(exclude, filePath) { 219 | if (!exclude) return false; 220 | return exclude.some((pattern) => minimatch(filePath, pattern)); 221 | } 222 | 223 | /** 224 | * Search config using builtin csscomb method 225 | * @private 226 | * 227 | * @param {String} filePath 228 | * 229 | * @returns {String} 230 | */ 231 | _getConfigPath(filePath) { 232 | if (!atom.config.get('css-comb.shouldNotSearchConfig')) { 233 | const configPath = path.join(path.dirname(filePath), '.csscomb.json'); 234 | 235 | return CSScomb.getCustomConfigPath(configPath); 236 | } 237 | } 238 | 239 | /** 240 | * Load csscomb config 241 | * @private 242 | * 243 | * @param {String} configPath 244 | * 245 | * @returns {Object} csscomb config 246 | */ 247 | _getConfig(configPath) { 248 | if (configPath) { 249 | return JSON.parse(fs.readFileSync(configPath, 'utf-8')); 250 | } 251 | 252 | configPath = atom.config.get('css-comb.customConfig'); 253 | 254 | if (configPath && configPath.match(/^\~/)) { 255 | configPath = path.join(getUserHome(), configPath.replace(/^\~\//, '')); 256 | } 257 | 258 | if (configPath && fs.existsSync(configPath)) { 259 | return JSON.parse(fs.readFileSync(configPath, 'utf-8')); 260 | } 261 | 262 | return CSScomb.getConfig(atom.config.get('css-comb.predef')); 263 | } 264 | 265 | /** 266 | * Return selected text for current opened file 267 | * @private 268 | * 269 | * @returns {String} 270 | */ 271 | _getSelectedText() { 272 | return atom.workspace.getActiveTextEditor().getSelectedText(); 273 | } 274 | 275 | /** 276 | * Return whole text for current active editor 277 | * @private 278 | * 279 | * @returns {String} 280 | */ 281 | _getText() { 282 | return atom.workspace.getActiveTextEditor().getText(); 283 | } 284 | } 285 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 3 | /** 4 | * Config 5 | * 6 | * @type {Object} 7 | */ 8 | config: { 9 | shouldNotSearchConfig: { 10 | title: 'Disable config searching', 11 | description: 'Disable config searching in project directory and use predefined or custom config', 12 | type: 'boolean', 13 | default: false 14 | }, 15 | predef: { 16 | title: 'Predefined configs', 17 | description: 'Will be used if config is not found in project directory', 18 | type: 'string', 19 | default: 'csscomb', 20 | enum: ['csscomb', 'zen', 'yandex'] 21 | }, 22 | customConfig: { 23 | title: 'Custom config (Full path to file)', 24 | description: 'Will be used if config is not found in project directory,' 25 | + ' has more priority than predefined configs.', 26 | type: 'string', 27 | default: '' 28 | }, 29 | showNotifications: { 30 | title: 'Notifications', 31 | type: 'boolean', 32 | default: true 33 | }, 34 | shouldUpdateOnSave: { 35 | title: 'On Save', 36 | description: 'Process file on every save.', 37 | type: 'boolean', 38 | default: false 39 | }, 40 | processStylus: { 41 | title: 'Process stylus as sass', 42 | description: '!WARNING! Highly unstable feature, works only when processing selection, ' 43 | + 'and may break on everything. Use at your own risk.', 44 | type: 'boolean', 45 | default: false 46 | } 47 | }, 48 | 49 | activate(state) { 50 | const CssComb = require('./css-comb').default; 51 | 52 | this.instance = new CssComb(state); 53 | }, 54 | 55 | deactivate() { 56 | this.instance && this.instance.deactivate(); 57 | } 58 | }; 59 | --------------------------------------------------------------------------------