├── .circleci └── config.yml ├── .eslintrc.js ├── .gitignore ├── LICENSE.md ├── README.md ├── examples ├── test.cpp └── test.hpp ├── keymaps └── clang-format.cson ├── lib ├── clang-format.js └── main.js ├── menus └── clang-format.cson ├── package-lock.json ├── package.json ├── release.config.js └── spec └── atom-clang-format-spec.js /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Based on https://github.com/atom/ci 2 | 3 | version: 2 4 | jobs: 5 | test: 6 | working_directory: /tmp/project 7 | environment: 8 | DISPLAY: ":99" 9 | ATOM_LINT_WITH_BUNDLED_NODE: "true" 10 | APM_TEST_PACKAGES: "" 11 | ATOM_CHANNEL: "stable" 12 | docker: 13 | - image: circleci/node:latest 14 | steps: 15 | - checkout 16 | - run: 17 | name: Update system package lists 18 | command: sudo apt-get update 19 | - run: 20 | name: Install some pre-requisite packages 21 | command: sudo apt-get --assume-yes --quiet install curl xvfb 22 | - run: 23 | name: Start display server for Atom 24 | command: /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1024x768x16 +extension RANDR 25 | background: true 26 | - run: 27 | name: Download Atom build script 28 | command: curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh 29 | - run: 30 | name: Make build script executable 31 | command: chmod u+x build-package.sh 32 | - run: 33 | name: Run package tests 34 | command: ./build-package.sh 35 | - run: 36 | name: Lint package 37 | command: npm run lint 38 | 39 | release: 40 | working_directory: /tmp/project 41 | environment: 42 | DISPLAY: ":99" 43 | ATOM_LINT_WITH_BUNDLED_NODE: "true" 44 | APM_TEST_PACKAGES: "" 45 | ATOM_CHANNEL: "stable" 46 | docker: 47 | - image: circleci/node:latest 48 | steps: 49 | - checkout 50 | - run: 51 | name: Update system package lists 52 | command: sudo apt-get update 53 | - run: 54 | name: Install some pre-requisite packages 55 | command: sudo apt-get --assume-yes --quiet install curl xvfb 56 | - run: 57 | name: Start display server for Atom 58 | command: /sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1024x768x16 +extension RANDR 59 | background: true 60 | - run: 61 | name: Download Atom build script 62 | command: curl -s -O https://raw.githubusercontent.com/atom/ci/master/build-package.sh 63 | - run: 64 | name: Make build script executable 65 | command: chmod u+x build-package.sh 66 | - run: 67 | name: Run package tests 68 | command: ./build-package.sh 69 | - run: 70 | name: Lint package 71 | command: npm run lint 72 | - run: 73 | name: Release package 74 | command: npm run release 75 | 76 | 77 | workflows: 78 | version: 2 79 | test: 80 | jobs: 81 | - test 82 | - release: 83 | requires: 84 | - test 85 | filters: 86 | branches: 87 | only: master -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "extends": "eslint-config-airbnb-base", 3 | "parser": "babel-eslint", 4 | "rules": { 5 | "strict": 0, 6 | "linebreak-style": 0, 7 | "import/no-unresolved": 0 8 | }, 9 | "globals": { 10 | "atom": true, 11 | "describe": true, 12 | "it": true, 13 | "beforeEach": true, 14 | "expect": true 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | package-lock.json 4 | yarn.lock 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 LiquidHelium 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 | # Atom Clang Format 2 | 3 | [![](https://circleci.com/gh/LiquidHelium/atom-clang-format/tree/master.svg?style=shield)](https://circleci.com/gh/LiquidHelium/atom-clang-format) [![](https://david-dm.org/LiquidHelium/atom-clang-format.svg)](https://david-dm.org/LiquidHelium/atom-clang-format) 4 | 5 | A package for the Atom text editor that lets you format your C/C++/Obj-C/Javascript files with clang-format. Clang Format binaries are included so it works out of the box (but you can bring your own binaries if you want). Includes support for automatically formatting files when you save. 6 | 7 | More info on clang-format can be found here: http://clang.llvm.org/docs/ClangFormat.html http://clang.llvm.org/docs/ClangTools.html 8 | -------------------------------------------------------------------------------- /examples/test.cpp: -------------------------------------------------------------------------------- 1 | 2 | int main() { 3 | 4 | Foo(1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 5 | 6 | 7 | 8 | 0, "hello world", 1, 2, 3, 4, 5, 6, 7); 9 | } 10 | -------------------------------------------------------------------------------- /examples/test.hpp: -------------------------------------------------------------------------------- 1 | class a { 2 | a something() { 3 | std::vector a = { 4 | 1, 5 | 2, 6 | 3, 7 | 4, 8 | 5, 9 | 6, 10 | 7, 11 | 2, 12 | 3, 13 | 4, 14 | 5, 15 | 6, 16 | 7, 17 | 2, 18 | 3, 19 | 4, 20 | 5, 21 | 6, 22 | 7, 23 | 2, 24 | 3, 25 | 4, 26 | 5, 27 | 6, 28 | 7, 29 | 2, 30 | 3, 31 | 4, 32 | 5, 33 | 6, 34 | 7, 35 | 2, 36 | 3, 37 | 4, 38 | 5, 39 | 6, 40 | 7, 41 | 2, 42 | 3, 43 | 4, 44 | 5, 45 | 6, 46 | 7, 47 | 2, 48 | 3, 49 | 4, 50 | 5, 51 | 6, 52 | 7 53 | } 54 | } 55 | }; 56 | -------------------------------------------------------------------------------- /keymaps/clang-format.cson: -------------------------------------------------------------------------------- 1 | '.platform-darwin atom-text-editor': 2 | 'cmd-shift-k': 'clang-format:format' 3 | 4 | '.platform-linux atom-text-editor, .platform-windows atom-text-editor': 5 | 'ctrl-shift-k': 'clang-format:format' 6 | -------------------------------------------------------------------------------- /lib/clang-format.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import { CompositeDisposable } from 'atom'; 4 | import { execSync } from 'child_process'; 5 | import os from 'os'; 6 | import path from 'path'; 7 | import clangFormatExecutables from 'clang-format-binaries'; 8 | 9 | export default class ClangFormat { 10 | constructor() { 11 | this.subscriptions = new CompositeDisposable(); 12 | this.subscriptions.add( 13 | atom.workspace.observeTextEditors(editor => this.handleBufferEvents(editor)), 14 | ); 15 | 16 | this.subscriptions.add(atom.commands.add('atom-workspace', 'clang-format:format', () => { 17 | const editor = atom.workspace.getActiveTextEditor(); 18 | if (editor) { 19 | this.format(editor); 20 | } 21 | })); 22 | } 23 | 24 | destroy = () => { 25 | this.subscriptions.dispose(); 26 | } 27 | 28 | handleBufferEvents = (editor) => { 29 | const buffer = editor.getBuffer(); 30 | const bufferSavedSubscription = buffer.onWillSave(() => { 31 | const scope = editor.getRootScopeDescriptor().scopes[0]; 32 | if (this.shouldFormatOnSaveForScope(scope)) { 33 | buffer.transact(() => this.format(editor)); 34 | } 35 | }); 36 | 37 | const editorDestroyedSubscription = editor.onDidDestroy(() => { 38 | bufferSavedSubscription.dispose(); 39 | editorDestroyedSubscription.dispose(); 40 | 41 | this.subscriptions.remove(bufferSavedSubscription); 42 | this.subscriptions.remove(editorDestroyedSubscription); 43 | }); 44 | 45 | this.subscriptions.add(bufferSavedSubscription); 46 | this.subscriptions.add(editorDestroyedSubscription); 47 | } 48 | 49 | format = (editor) => { 50 | const buffer = editor.getBuffer(); 51 | 52 | let exe = atom.config.get('clang-format.executable'); 53 | if (!exe) { 54 | const exePackageLocation = path.dirname(clangFormatExecutables.location); 55 | if (os.platform() === 'win32') { 56 | exe = path.join(exePackageLocation, '/bin/win32/clang-format.exe'); 57 | } else { 58 | exe = path.join(exePackageLocation, `/bin/${os.platform()}_${os.arch()}/clang-format`); 59 | } 60 | } 61 | 62 | const options = { 63 | style: atom.config.get('clang-format.style'), 64 | cursor: this.getCurrentCursorPosition(editor).toString(), 65 | 'fallback-style': atom.config.get('clang-format.fallbackStyle'), 66 | }; 67 | 68 | // Format only selection 69 | if (this.textSelected(editor)) { 70 | options.lines = this.getTargetLineNums(editor); 71 | } 72 | 73 | // Pass file path to clang-format so it can look for .clang-format files 74 | const filePath = editor.getPath(); 75 | if (filePath) { 76 | options['assume-filename'] = filePath; 77 | } 78 | 79 | // Call clang-format synchronously to ensure that save waits for us 80 | // Don't catch errors to make them visible to users via atom's UI 81 | // We need to explicitly ignore stderr since there is no parent stderr on 82 | // windows and node.js will try to write to it - whether it's there or not 83 | const args = Object.keys(options).reduce((memo, optionKey) => { 84 | const optionValue = options[optionKey]; 85 | if (optionValue) { 86 | return `${memo}-${optionKey}="${optionValue}" `; 87 | } 88 | return memo; 89 | }, ''); 90 | 91 | const execOptions = { input: editor.getText() }; 92 | 93 | if (filePath) { 94 | execOptions.cwd = path.dirname(filePath); 95 | } 96 | 97 | try { 98 | const stdout = execSync(`"${exe}" ${args}`, execOptions).toString(); 99 | // Update buffer with formatted text. setTextViaDiff minimizes re-rendering 100 | buffer.setTextViaDiff(this.getReturnedFormattedText(stdout)); 101 | // Restore cursor position 102 | const returnedCursorPos = this.getReturnedCursorPosition(stdout); 103 | const convertedCursorPos = buffer.positionForCharacterIndex(returnedCursorPos); 104 | editor.setCursorBufferPosition(convertedCursorPos); 105 | } catch (error) { 106 | if (error.message.indexOf('Command failed:') < 0) { 107 | throw error; 108 | } else { 109 | atom.notifications.addError('Clang Format Command Failed', { 110 | dismissable: true, 111 | detail: `clang-format failed with the below error. Consider reporting this by creating an issue here: https://github.com/LiquidHelium/atom-clang-format/issues.\nError message: "${error.stderr.toString()}".\nWhen running: "${exe} ${args}".\nStdout was: "${error.stdout.toString()}"`, 112 | }); 113 | } 114 | } 115 | } 116 | 117 | 118 | shouldFormatOnSaveForScope = (scope) => { 119 | if (atom.config.get('clang-format.formatCPlusPlusOnSave') && ['source.c++', 'source.cpp'].includes(scope)) { 120 | return true; 121 | } 122 | if (atom.config.get('clang-format.formatCOnSave') && ['source.c'].includes(scope)) { 123 | return true; 124 | } 125 | if (atom.config.get('clang-format.formatObjectiveCOnSave') && ['source.objc', 'source.objcpp'].includes(scope)) { 126 | return true; 127 | } 128 | if (atom.config.get('clang-format.formatJavascriptOnSave') && ['source.js'].includes(scope)) { 129 | return true; 130 | } 131 | if (atom.config.get('clang-format.formatTypescriptOnSave') && ['source.ts'].includes(scope)) { 132 | return true; 133 | } 134 | if (atom.config.get('clang-format.formatJavaOnSave') && ['source.java'].includes(scope)) { 135 | return true; 136 | } 137 | return false; 138 | } 139 | 140 | getEndJSONPosition = (text) => { 141 | for (let i = 0; i < text.length; i += 1) { 142 | if ((text[i] === '\n') || (text[i] === '\r')) { 143 | return i + 1; 144 | } 145 | } 146 | 147 | return -1; 148 | } 149 | 150 | getReturnedCursorPosition = (stdout) => { 151 | if (!stdout) { return 0; } 152 | const parsed = JSON.parse(stdout.slice(0, this.getEndJSONPosition(stdout))); 153 | return parsed.Cursor; 154 | } 155 | 156 | getReturnedFormattedText = stdout => stdout.slice(this.getEndJSONPosition(stdout)); 157 | 158 | getCurrentCursorPosition = (editor) => { 159 | const cursorPosition = editor.getCursorBufferPosition(); 160 | const text = editor.getTextInBufferRange([[0, 0], cursorPosition]); 161 | return text.length; 162 | } 163 | 164 | getCursorLineNumber = (editor) => { 165 | const cursorPosition = editor.getCursorBufferPosition(); 166 | // +1 to get 1-base line number. 167 | return cursorPosition.toArray()[0] + 1; 168 | } 169 | 170 | textSelected = (editor) => { 171 | const range = editor.getSelectedBufferRange(); 172 | return !range.isEmpty(); 173 | } 174 | 175 | getSelectedLineNums = (editor) => { 176 | const range = editor.getSelectedBufferRange(); 177 | const rows = range.getRows(); 178 | // + 1 to get 1-base line number. 179 | const startingRow = rows[0] + 1; 180 | // If 2 lines are selected, the diff between |starting_row| is 1, so -1. 181 | const endingRow = (startingRow + range.getRowCount()) - 1; 182 | return [startingRow, endingRow]; 183 | } 184 | 185 | // Returns line numbers recognizable by clang-format, i.e. ':'. 186 | getTargetLineNums = (editor) => { 187 | if (this.textSelected(editor)) { 188 | const lineNums = this.getSelectedLineNums(editor); 189 | return `${lineNums[0]}:${lineNums[1]}`; 190 | } 191 | 192 | const lineNum = this.getCursorLineNumber(editor); 193 | return `${lineNum}:${lineNum}`; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import ClangFormat from './clang-format'; 4 | 5 | export default { 6 | config: { 7 | formatCPlusPlusOnSave: { 8 | type: 'boolean', 9 | default: false, 10 | title: 'Format C++ on save', 11 | order: 1, 12 | }, 13 | formatCOnSave: { 14 | type: 'boolean', 15 | default: false, 16 | title: 'Format C on save', 17 | order: 2, 18 | }, 19 | formatObjectiveCOnSave: { 20 | type: 'boolean', 21 | default: false, 22 | title: 'Format Objective-C on save', 23 | order: 3, 24 | }, 25 | formatJavascriptOnSave: { 26 | type: 'boolean', 27 | default: false, 28 | title: 'Format JavaScript on save', 29 | order: 4, 30 | }, 31 | formatTypescriptOnSave: { 32 | type: 'boolean', 33 | default: false, 34 | title: 'Format TypeScript on save', 35 | order: 5, 36 | }, 37 | formatJavaOnSave: { 38 | type: 'boolean', 39 | default: false, 40 | title: 'Format Java on save', 41 | order: 6, 42 | }, 43 | executable: { 44 | type: 'string', 45 | default: '', 46 | order: 7, 47 | }, 48 | style: { 49 | type: 'string', 50 | default: 'file', 51 | order: 8, 52 | description: 'Default "file" uses the file ".clang-format" in one of the parent directories of the source file.', 53 | }, 54 | fallbackStyle: { 55 | type: 'string', 56 | default: 'llvm', 57 | description: 'Fallback Style. Set To "none" together with style "file" to ensure that if no ".clang-format" file exists, no reformatting takes place.', 58 | }, 59 | }, 60 | 61 | activate() { 62 | this.clangFormat = new ClangFormat(); 63 | }, 64 | 65 | deactivate() { 66 | return this.clangFormat.destroy(); 67 | }, 68 | }; 69 | -------------------------------------------------------------------------------- /menus/clang-format.cson: -------------------------------------------------------------------------------- 1 | # See https://atom.io/docs/latest/creating-a-package#menus for more details 2 | 'menu': [ 3 | { 4 | 'label': 'Packages' 5 | 'submenu': [ 6 | 'label': 'Clang Format' 7 | 'submenu': [ 8 | { 'label': 'Format', 'command': 'clang-format:format' } 9 | ] 10 | ] 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "clang-format", 3 | "main": "./lib/main", 4 | "version": "2.0.8", 5 | "private": true, 6 | "description": "Format your C/C++/Obj-C/Javascript files with clang-format from inside atom.", 7 | "repository": "https://github.com/LiquidHelium/atom-clang-format", 8 | "license": "MIT", 9 | "scripts": { 10 | "lint": "eslint .", 11 | "test": "atom --test .", 12 | "release": "semantic-release --ci --debug" 13 | }, 14 | "dependencies": { 15 | "clang-format-binaries": "^0.0.1" 16 | }, 17 | "devDependencies": { 18 | "@semantic-release/apm": "^1.0.1", 19 | "@semantic-release/git": "^7.0.7", 20 | "babel-eslint": "^10.0.1", 21 | "eslint": "^5.3.0", 22 | "eslint-config-airbnb-base": "^13.1.0", 23 | "eslint-plugin-import": "^2.14.0", 24 | "semantic-release": "^15.13.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /release.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 3 | module.exports = { 4 | plugins: [ 5 | '@semantic-release/commit-analyzer', 6 | '@semantic-release/release-notes-generator', 7 | '@semantic-release/github', 8 | '@semantic-release/apm', 9 | ['@semantic-release/git', { message: 'chore(release): ${nextRelease.version} [skip ci]' }], 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /spec/atom-clang-format-spec.js: -------------------------------------------------------------------------------- 1 | 'use babel'; 2 | 3 | import ClangFormat from '../lib/clang-format'; 4 | 5 | describe('When we format a simple c++ file', () => { 6 | let editor; 7 | 8 | beforeEach(async () => { 9 | editor = await atom.workspace.open('somefile.cpp'); 10 | await editor.setText('#include"hello.cpp";int main(){return 0;}'); 11 | await editor.setCursorBufferPosition(11); 12 | 13 | const clangFormatInstance = new ClangFormat(); 14 | await clangFormatInstance.format(editor); 15 | }); 16 | 17 | it('Should update the editor with the formatted code', async () => { 18 | expect(editor.getText()).toEqual('#include "hello.cpp"; int main(){return 0; }'); 19 | }); 20 | 21 | it('Should update the editor cursor position to stay on the same letter', async () => { 22 | expect(editor.getCursorBufferPosition()).toEqual(12); 23 | }); 24 | }); 25 | --------------------------------------------------------------------------------