Skip to content

Commit

Permalink
Now support closing tag upon typing of </
Browse files Browse the repository at this point in the history
+ various bug fixes on tag discovery.
  • Loading branch information
uzyn committed Mar 31, 2014
1 parent 9b0517b commit cf7605e
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 57 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,12 @@ Or search for `tag` under `Atom Preferences > Install Packages`.
Features
---
- Close Tag (⌥⌘.)
- Close tag upon typing of `</`
- Toggle-able at Atom Preference
- When turned on, this feature is only triggered when grammar's name contains `HTML` or `XML`.

To do
---
- Close tag upon typing of `</`
- Expand Selection to Tag (⇧⌘A)
- Customization options for single tags
- Tests
134 changes: 97 additions & 37 deletions lib/tag.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
module.exports = {
voidElements: ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'],

configDefaults: {
'closeTagOn </': true
},

activate: function(state) {
var _this = this;

atom.workspaceView.eachEditorView(function(editorView) {
editorView.editor.buffer.on('changed', function(e) {
_this.bufferChanged(e);
});
});

return atom.workspaceView.command("tag:close-tag", (function(_this) {
return function() {
return _this.closeTag();
Expand All @@ -8,55 +22,101 @@ module.exports = {
},

closeTag: function() {
var editor = atom.workspace.activePaneItem;
var editor = atom.workspace.getActiveEditor();
if (editor === undefined) {
return;
}

var cursorPos = editor.getCursor().getBufferPosition();
var buffer = editor.getTextInBufferRange([[0, 0], cursorPos]);
var voidElements = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr'];
editor.insertText(this.getClosingTag(buffer));
},

var getClosingTag = function (str) {
var tokens = str.split('<');
if (tokens.length == 0) {
return '';
}
tokens.reverse();
bufferChanged: function(e) {
var editor = atom.workspace.getActiveEditor();

var getTag = function (token) {
return token.substring(0, token.search(' |>'));
}
if (editor === undefined || !atom.config.get('tag.closeTagOn </') || e.newText !== '/') {
return;
}

var isVoidElement = function(tag) {
tag = tag.trim();
if (tag.substr(tag.length - 1) == '/') {
return true;
}
var grammar = editor.getGrammar().name;
if (
grammar.length == 0 ||
(grammar.indexOf('HTML') < 0 && grammar.indexOf('XML') < 0)
) {
return;
}

tag = tag.toLowerCase();
if (voidElements.indexOf(tag) >= 0) {
return true;
}
var cursorPos = editor.getCursor().getBufferPosition();
var translatedPos = cursorPos.translate([0, -2]);
var lastTwo = editor.getTextInBufferRange([translatedPos, cursorPos]);

if (lastTwo !== '</') {
return;
}

return false;
var buffer = editor.getTextInBufferRange([[0, 0], translatedPos]);
var closingTag = this.getClosingTag(buffer);
if (closingTag.length > 0) {
// Temporary workaround for a funny cursor bug
setTimeout(function() {
editor.insertText(closingTag.substring(2));
}, 1);
}
return;
},

getClosingTag: function (str) {
var _this = this;
var tokens = str.split('<');

if (tokens.length == 0) {
return '';
}
tokens.reverse();

var getTag = function (token) {
var tag = token.substring(0, token.search('>')).trim();

if (tag.substr(tag.length - 1) == '/') {
return tag;
} else if (tag.indexOf(' ') < 0) {
return tag;
} else {
return tag.substring(0, token.search(' '));
}
}

var skip = [];
for (var i in tokens) {
var token = tokens[i];
var tag = getTag(token);
if (tag !== undefined && tag.length > 0 && !isVoidElement(tag)) {
if (tag.substring(0, 1) != '/') {
if (skip.length === 0) {
return '</' + tag + '>';
} else if (skip[skip.length - 1] == tag) {
skip.pop();
}
} else {
skip.push(tag.substring(1));
var isVoidElement = function(tag) {
tag = tag.trim();
if (tag.substr(tag.length - 1) == '/') {
return true;
}

tag = tag.toLowerCase();
if (_this.voidElements.indexOf(tag) >= 0) {
return true;
}

return false;
}

var skip = [];
for (var i in tokens) {
var token = tokens[i];
var tag = getTag(token);
if (tag !== undefined && tag.length > 0 && !isVoidElement(tag)) {
if (tag.substring(0, 1) != '/') {
if (skip.length === 0) {
return '</' + tag + '>';
} else if (skip[skip.length - 1] == tag) {
skip.pop();
}
} else {
skip.push(tag.substring(1));
}
}
return '';
}

editor.insertText(getClosingTag(buffer));
return '';
}
};
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
"sublime"
],
"repository": "https://github.com/uzyn/tag",
"activationEvents": [
"tag:close-tag"
],
"author": "U-Zyn Chua <chua@uzyn.com>",
"license": "MIT",
"engines": {
Expand Down
16 changes: 0 additions & 16 deletions spec/tag-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,3 @@ describe "Tag", ->
beforeEach ->
atom.workspaceView = new WorkspaceView
activationPromise = atom.packages.activatePackage('tag')

describe "when the tag:toggle event is triggered", ->
it "attaches and then detaches the view", ->
expect(atom.workspaceView.find('.tag')).not.toExist()

# This is an activation event, triggering it will cause the package to be
# activated.
atom.workspaceView.trigger 'tag:toggle'

waitsForPromise ->
activationPromise

runs ->
expect(atom.workspaceView.find('.tag')).toExist()
atom.workspaceView.trigger 'tag:toggle'
expect(atom.workspaceView.find('.tag')).not.toExist()

0 comments on commit cf7605e

Please sign in to comment.