├── .gitignore ├── .npmignore ├── LICENSE.md ├── README.md ├── keymaps └── autoclose-html.cson ├── lib ├── autoclose-html.coffee └── configuration.coffee ├── package.json ├── spec └── autoclose-html-spec.coffee └── styles └── autoclose-html.less /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | test*.* 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | npm-debug.log 3 | node_modules 4 | test.* 5 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 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 | # Auto Close HTML package for Atom Text Editor 2 | 3 | Will automatically add closing tags when you complete the opening tag. 4 | 5 | # Installation 6 | 7 | Install using 8 | 9 | `apm install autoclose-html` 10 | 11 | # Usage 12 | 13 | Under normal circumstances ending tags will be inserted on the same line for inline elements and with `\n\t\n` in between for block elements. This is determined by attaching an element of the given type to the window and checking it's calculated `display` value. 14 | You can use Force Inline and Force Block preferences to override this. 15 | 16 | # Bug Reports and Contributing 17 | 18 | If you find a bug, please feel free to file an issue. Please understand however that I have very little time to work on this anymore, so most feature requests will not be implemented. 19 | 20 | Better than an issue, however, would be to try and fix it yourself and submit a PR. 21 | 22 | If you are interested in helping maintain this library, please contact me. As I mentioned, I have very little time to devote to this anymore, so if someone has interest in helping to keep it maintained, I'm open to considering it. 23 | 24 | 25 | # Options 26 | 27 | ## Force Inline 28 | 29 | Elements in this comma delimited list will render their closing tags on the same line, even if they are block by default. You can set this to "*" to force all closing tags to render inline. 30 | 31 | ## Force Block 32 | 33 | Elements in this comma delimited list will render their closing tags after a tabbed line, even if they are inline by default. A value of "*" for Force Inline overrides all values in Force Block. 34 | 35 | ## Never Close 36 | 37 | Elements in this comma delimited list should *not* render a closing tag 38 | 39 | ## Make Never Close Elements Self Closing 40 | 41 | Will convert elements in Never Close list from `
` to `
` 42 | 43 | ## Legacy/International Mode 44 | 45 | Enables the old style of completion detection using buffer events rather than keybindings. 46 | Atom doesn't work well currently with non-US/non-QUERTY keyboards and will not correctly 47 | fire events when '>' is pressed and/or fire events for entirely different keys. **Please note that 48 | this mode is buggy (ie can complete after undo) and may not be compatible with new 49 | features and bug fixes in future releases, post-0.22.0** If/when the core issues behind 50 | keybindings not reporting correctly on international keyboards is solved this option will 51 | be removed. 52 | 53 | 54 | 55 | # Changelog 56 | 57 | #### 0.20.0 58 | - HTML (Jinja Templates), Ember HTMLBars, JavaScript with JSX added to default grammars, per user requests 59 | - Dispose events on deactivate (should prevent double closing after an upgrade in the future, although I don't think it will help for this release) 60 | - Added ability to use "*" for Force Inline Options 61 | - Some Readme cleanup 62 | 63 | #### 0.21.0 64 | - Fixed double closing after changing grammar 65 | 66 | #### 0.22.0 67 | - Better way of handling events, solves rebinding problems **and** having to define grammars to apply to 68 | 69 | #### 0.23.0 70 | - Added legacy mode for users having problems with event handling introduced in 0.22.0 71 | 72 | #### 0.24.0 73 | - Stopped self closing tags from auto closing 74 | -------------------------------------------------------------------------------- /keymaps/autoclose-html.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 | #'.workspace': 11 | 12 | 'atom-text-editor[data-grammar~="html"]': 13 | '>': 'autoclose-html:close-and-complete' 14 | -------------------------------------------------------------------------------- /lib/autoclose-html.coffee: -------------------------------------------------------------------------------- 1 | isOpeningTagLikePattern = /<(?![\!\/])([a-z]{1}[^>\s=\'\"\/]*)[^>\/]*>$/i 2 | 3 | ConfigSchema = require('./configuration.coffee') 4 | {CompositeDisposable} = require 'atom' 5 | 6 | module.exports = 7 | config: ConfigSchema.config 8 | 9 | neverClose:[] 10 | forceInline: [] 11 | forceBlock: [] 12 | makeNeverCloseSelfClosing: false 13 | ignoreGrammar: false 14 | legacyMode: false 15 | 16 | activate: () -> 17 | 18 | @autocloseHTMLEvents = new CompositeDisposable 19 | 20 | @closeAndCompleteCommand = atom.commands.add 'atom-text-editor', 21 | 'autoclose-html:close-and-complete': (e) => 22 | if @legacyMode 23 | console.log(e) 24 | e.abortKeyBinding() 25 | else 26 | atom.workspace.getActiveTextEditor().insertText(">") 27 | this.execAutoclose() 28 | 29 | 30 | atom.config.observe 'autoclose-html.neverClose', (value) => 31 | @neverClose = value 32 | 33 | atom.config.observe 'autoclose-html.forceInline', (value) => 34 | @forceInline = value 35 | 36 | atom.config.observe 'autoclose-html.forceBlock', (value) => 37 | @forceBlock = value 38 | 39 | atom.config.observe 'autoclose-html.makeNeverCloseSelfClosing', (value) => 40 | @makeNeverCloseSelfClosing = value 41 | 42 | atom.config.observe 'autoclose-html.legacyMode', (value) => 43 | @legacyMode = value 44 | if @legacyMode 45 | @_events() 46 | else 47 | @_unbindEvents() 48 | 49 | 50 | deactivate: -> 51 | if @legacyMode 52 | @_unbindEvents() 53 | 54 | @closeAndCompleteCommand.dispose() 55 | 56 | isInline: (eleTag) -> 57 | if @forceInline.indexOf("*") > -1 58 | return true 59 | 60 | try 61 | ele = document.createElement eleTag 62 | catch 63 | return false 64 | 65 | if eleTag.toLowerCase() in @forceBlock 66 | return false 67 | else if eleTag.toLowerCase() in @forceInline 68 | return true 69 | 70 | document.body.appendChild ele 71 | ret = window.getComputedStyle(ele).getPropertyValue('display') in ['inline', 'inline-block', 'none'] 72 | document.body.removeChild ele 73 | 74 | ret 75 | 76 | isNeverClosed: (eleTag) -> 77 | eleTag.toLowerCase() in @neverClose 78 | 79 | execAutoclose: () -> 80 | editor = atom.workspace.getActiveTextEditor() 81 | range = editor.selections[0].getBufferRange() 82 | line = editor.buffer.getLines()[range.end.row] 83 | partial = line.substr 0, range.start.column 84 | partial = partial.substr(partial.lastIndexOf('<')) 85 | 86 | return if partial.substr(partial.length - 1, 1) is '/' 87 | 88 | singleQuotes = partial.match(/\'/g) 89 | doubleQuotes = partial.match(/\"/g) 90 | oddSingleQuotes = singleQuotes && (singleQuotes.length % 2) 91 | oddDoubleQuotes = doubleQuotes && (doubleQuotes.length % 2) 92 | 93 | return if oddSingleQuotes or oddDoubleQuotes 94 | 95 | index = -1 96 | while((index = partial.indexOf('"')) isnt -1) 97 | partial = partial.slice(0, index) + partial.slice(partial.indexOf('"', index + 1) + 1) 98 | 99 | while((index = partial.indexOf("'")) isnt -1) 100 | partial = partial.slice(0, index) + partial.slice(partial.indexOf("'", index + 1) + 1) 101 | 102 | return if not (matches = partial.match(isOpeningTagLikePattern))? 103 | 104 | eleTag = matches[matches.length - 1] 105 | 106 | if @isNeverClosed(eleTag) 107 | if @makeNeverCloseSelfClosing 108 | tag = '/>' 109 | if partial.substr partial.length - 1, 1 isnt ' ' 110 | tag = ' ' + tag 111 | editor.backspace() 112 | editor.insertText tag 113 | return 114 | 115 | isInline = @isInline eleTag 116 | 117 | if not isInline 118 | editor.insertNewline() 119 | editor.insertNewline() 120 | editor.insertText('') 121 | if isInline 122 | editor.setCursorBufferPosition range.end 123 | else 124 | editor.autoIndentBufferRow range.end.row + 1 125 | editor.setCursorBufferPosition [range.end.row + 1, atom.workspace.getActivePaneItem().getTabText().length * atom.workspace.getActivePaneItem().indentationForBufferRow(range.end.row + 1)] 126 | 127 | _events: () -> 128 | atom.workspace.observeTextEditors (textEditor) => 129 | textEditor.observeGrammar (grammar) => 130 | textEditor.autocloseHTMLbufferEvent.dispose() if textEditor.autocloseHTMLbufferEvent? 131 | if atom.views.getView(textEditor).getAttribute('data-grammar').split(' ').indexOf('html') > -1 132 | textEditor.autocloseHTMLbufferEvent = textEditor.buffer.onDidChange (e) => 133 | if e?.newText is '>' && textEditor == atom.workspace.getActiveTextEditor() 134 | setTimeout => 135 | @execAutoclose() 136 | @autocloseHTMLEvents.add(textEditor.autocloseHTMLbufferEvent) 137 | 138 | _unbindEvents: () -> 139 | @autocloseHTMLEvents.dispose() 140 | -------------------------------------------------------------------------------- /lib/configuration.coffee: -------------------------------------------------------------------------------- 1 | module.exports = 2 | config: 3 | forceInline: 4 | title: 'Force Inline' 5 | description: 'Elements in this comma delimited list will render their closing tags on the same line, even if they are block by default. Use * to force all closing tags to render inline' 6 | type: 'array' 7 | default: ['title', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'] 8 | forceBlock: 9 | title: 'Force Block' 10 | description: 'Elements in this comma delimited list will render their closing tags after a tabbed line, even if they are inline by default. Values are ignored if Force Inline is *' 11 | type: 'array' 12 | default: ['head'] 13 | neverClose: 14 | title: 'Never Close Elements' 15 | description: 'Comma delimited list of elements to never close' 16 | type: 'array' 17 | default: ['br', 'hr', 'img', 'input', 'link', 'meta', 'area', 'base', 'col', 'command', 'embed', 'keygen', 'param', 'source', 'track', 'wbr'] 18 | makeNeverCloseSelfClosing: 19 | title: 'Make Never Close Elements Self-Closing' 20 | description: 'Closes elements with " />" (ie <br> becomes <br />)' 21 | type: 'boolean' 22 | default: true 23 | legacyMode: 24 | title: "Legacy/International Mode" 25 | description: "Do not use this unless you use a non-US or non-QUERTY keyboard and/or the plugin isn't working otherwise. USING THIS OPTION WILL OPT YOU OUT OF NEW IMPROVEMENTS/FEATURES POST 0.22.0" 26 | type: 'boolean' 27 | default: false 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "autoclose-html", 3 | "author": { 4 | "name": "Matt Berkowitz" 5 | }, 6 | "main": "./lib/autoclose-html", 7 | "version": "0.24.0", 8 | "private": true, 9 | "description": "Automates closing of HTML Tags", 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/mattberkowitz/autoclose-html" 13 | }, 14 | "license": "MIT", 15 | "engines": { 16 | "atom": ">0.50.0" 17 | }, 18 | "dependencies": {}, 19 | "contributors": [ 20 | { 21 | "name": "Evan Hahn", 22 | "email": "me@evanhahn.com", 23 | "url": "http://evanhahn.com" 24 | }, 25 | { 26 | "name": "Mat Silva", 27 | "email": "askmatsilva@.com", 28 | "url": "https://github.com/matsilva" 29 | } 30 | ], 31 | "readmeFilename": "README.md", 32 | "bugs": { 33 | "url": "https://github.com/mattberkowitz/autoclose-html/issues" 34 | }, 35 | "homepage": "https://github.com/mattberkowitz/autoclose-html", 36 | "_id": "autoclose-html@0.8.0", 37 | "dist": { 38 | "shasum": "25792d1cf3395393c861bfcb919fe684f505af85" 39 | }, 40 | "_resolved": "C:\\Users\\msilva\\AppData\\Local\\Temp\\d-114711-6236-8rgida\\package.tgz", 41 | "_from": "C:\\Users\\msilva\\AppData\\Local\\Temp\\d-114711-6236-8rgida\\package.tgz" 42 | } 43 | -------------------------------------------------------------------------------- /spec/autoclose-html-spec.coffee: -------------------------------------------------------------------------------- 1 | AutocloseHtml = require '../lib/autoclose-html' 2 | 3 | # Use the command `window:run-package-specs` (cmd-alt-ctrl-p) to run specs. 4 | # 5 | # To run a specific `it` or `describe` block add an `f` to the front (e.g. `fit` 6 | # or `fdescribe`). Remove the `f` to unfocus the block. 7 | 8 | describe "AutocloseHtml", -> 9 | -------------------------------------------------------------------------------- /styles/autoclose-html.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/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .autoclose-html { 8 | } 9 | --------------------------------------------------------------------------------