Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
TextMate HTML language tweaked to support unqoted attributes
{ scopeName = 'text.html.basic';
firstLineMatch = '<!DOCTYPE|<(?i:html)|<\?(?i:php)';
fileTypes = ( 'html', 'htm', 'shtml', 'xhtml', 'phtml', 'php', 'inc', 'tmpl', 'tpl', 'ctp' );
foldingStartMarker = '(?x)
(<(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl)\b.*?>
|<!--(?!.*--\s*>)
|^<!--\ \#tminclude\ (?>.*?-->)$
|<\?(?:php)?.*\b(if|for(each)?|while)\b.+:
|\{\{?(if|foreach|capture|literal|foreach|php|section|strip)
|\{\s*($|\?>\s*$|//|/\*(.*\*/\s*$|(?!.*?\*/)))
)';
foldingStopMarker = '(?x)
(</(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl)>
|^(?!.*?<!--).*?--\s*>
|^<!--\ end\ tminclude\ -->$
|<\?(?:php)?.*\bend(if|for(each)?|while)\b
|\{\{?/(if|foreach|capture|literal|foreach|php|section|strip)
|^[^{]*\}
)';
patterns = (
{ name = 'meta.tag.any.html';
begin = '(<)([a-zA-Z0-9:]++)(?=[^>]*></\2>)';
end = '(>(<)/)(\2)(>)';
beginCaptures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'entity.name.tag.html'; };
};
endCaptures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'meta.scope.between-tag-pair.html'; };
3 = { name = 'entity.name.tag.html'; };
4 = { name = 'punctuation.definition.tag.html'; };
};
patterns = ( { include = '#tag-stuff'; } );
},
{ name = 'meta.tag.preprocessor.xml.html';
begin = '(<\?)(xml)';
end = '(\?>)';
captures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'entity.name.tag.xml.html'; };
};
patterns = (
{ include = '#tag-generic-attribute'; },
{ include = '#string-double-quoted'; },
{ include = '#string-single-quoted'; },
);
},
{ name = 'comment.block.html';
begin = '<!--';
end = '--\s*>';
captures = { 0 = { name = 'punctuation.definition.comment.html'; }; };
patterns = (
{ name = 'invalid.illegal.bad-comments-or-CDATA.html';
match = '--';
},
{ include = '#embedded-code'; },
);
},
{ name = 'meta.tag.sgml.html';
begin = '<!';
end = '>';
captures = { 0 = { name = 'punctuation.definition.tag.html'; }; };
patterns = (
{ name = 'meta.tag.sgml.doctype.html';
begin = '(DOCTYPE)';
end = '(?=>)';
captures = { 1 = { name = 'entity.name.tag.doctype.html'; }; };
patterns = (
{ name = 'string.quoted.double.doctype.identifiers-and-DTDs.html';
match = '"[^">]*"';
},
);
},
{ name = 'constant.other.inline-data.html';
begin = '\[CDATA\[';
end = ']](?=>)';
},
{ name = 'invalid.illegal.bad-comments-or-CDATA.html';
match = '(\s*)(?!--|>)\S(\s*)';
},
);
},
{ include = '#embedded-code'; },
{ name = 'source.css.embedded.html';
begin = '(?:^\s+)?(<)((?i:style))\b(?![^>]*/>)';
end = '(</)((?i:style))(>)(?:\s*\n)?';
captures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'entity.name.tag.style.html'; };
3 = { name = 'punctuation.definition.tag.html'; };
};
patterns = (
{ include = '#tag-stuff'; },
{ begin = '(>)';
end = '(?=</(?i:style))';
beginCaptures = { 1 = { name = 'punctuation.definition.tag.html'; }; };
patterns = (
{ include = '#embedded-code'; },
{ include = 'source.css'; },
);
},
);
},
{ name = 'source.js.embedded.html';
begin = '(?:^\s+)?(<)((?i:script))\b(?![^>]*/>)';
end = '(?<=</(script|SCRIPT))(>)(?:\s*\n)?';
beginCaptures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'entity.name.tag.script.html'; };
};
endCaptures = { 2 = { name = 'punctuation.definition.tag.html'; }; };
patterns = (
{ include = '#tag-stuff'; },
{ begin = '(?<!</(?:script|SCRIPT))(>)';
end = '(</)((?i:script))';
captures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'entity.name.tag.script.html'; };
};
patterns = (
{ name = 'comment.line.double-slash.js';
match = '(//).*?((?=</script)|$\n?)';
captures = { 1 = { name = 'punctuation.definition.comment.js'; }; };
},
{ name = 'comment.block.js';
begin = '/\*';
end = '\*/|(?=</script)';
captures = { 0 = { name = 'punctuation.definition.comment.js'; }; };
},
{ include = '#php'; },
{ include = 'source.js'; },
);
},
);
},
{ name = 'meta.tag.structure.any.html';
begin = '(</?)((?i:body|head|html)\b)';
end = '(>)';
captures = {
1 = { name = 'punctuation.definition.tag.html'; };
2 = { name = 'entity.name.tag.structure.any.html'; };
};
patterns = ( { include = '#tag-stuff'; } );
},
{ name = 'meta.tag.block.any.html';
begin = '(</?)((?i:address|blockquote|dd|div|dl|dt|fieldset|form|frame|frameset|h1|h2|h3|h4|h5|h6|iframe|noframes|object|ol|p|ul|applet|center|dir|hr|menu|pre)\b)';
end = '(>)';
beginCaptures = {
1 = { name = 'punctuation.definition.tag.begin.html'; };
2 = { name = 'entity.name.tag.block.any.html'; };
};
endCaptures = { 1 = { name = 'punctuation.definition.tag.end.html'; }; };
patterns = ( { include = '#tag-stuff'; } );
},
{ name = 'meta.tag.inline.any.html';
begin = '(</?)((?i:a|abbr|acronym|area|b|base|basefont|bdo|big|br|button|caption|cite|code|col|colgroup|del|dfn|em|font|head|html|i|img|input|ins|isindex|kbd|label|legend|li|link|map|meta|noscript|optgroup|option|param|q|s|samp|script|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|title|tr|tt|u|var)\b)';
end = '((?: ?/)?>)';
beginCaptures = {
1 = { name = 'punctuation.definition.tag.begin.html'; };
2 = { name = 'entity.name.tag.inline.any.html'; };
};
endCaptures = { 1 = { name = 'punctuation.definition.tag.end.html'; }; };
patterns = ( { include = '#tag-stuff'; } );
},
{ name = 'meta.tag.other.html';
begin = '(</?)([a-zA-Z0-9:]+)';
end = '(>)';
beginCaptures = {
1 = { name = 'punctuation.definition.tag.begin.html'; };
2 = { name = 'entity.name.tag.other.html'; };
};
endCaptures = { 1 = { name = 'punctuation.definition.tag.end.html'; }; };
patterns = ( { include = '#tag-stuff'; } );
},
{ include = '#entities'; },
{ name = 'invalid.illegal.incomplete.html';
match = '<>';
},
{ name = 'invalid.illegal.bad-angle-bracket.html';
match = '<';
},
);
repository = {
embedded-code = {
patterns = (
{ include = '#ruby'; },
{ include = '#php'; },
{ include = '#smarty'; },
{ include = '#python'; },
);
};
entities = {
patterns = (
{ name = 'constant.character.entity.html';
match = '(&)([a-zA-Z0-9]+|#[0-9]+|#x[0-9a-fA-F]+)(;)';
captures = {
1 = { name = 'punctuation.definition.entity.html'; };
3 = { name = 'punctuation.definition.entity.html'; };
};
},
{ name = 'invalid.illegal.bad-ampersand.html';
match = '&';
},
{ name = 'specialcharacter.html';
match = '­';
},
{ name = 'invisiblespace.html';
match = ' ';
},
{ name = 'unprintable.html';
match = '[^[:print:]]';
},
);
};
php = {
begin = '(?=(^\s*)?<\?)';
end = '(?!(^\s*)?<\?)';
patterns = ( { include = 'source.php'; } );
};
python = {
name = 'source.python.embedded.html';
begin = '(?:^\s*)<\?python(?!.*\?>)';
end = '\?>(?:\s*$\n)?';
patterns = ( { include = 'source.python'; } );
};
ruby = {
patterns = (
{ name = 'comment.block.erb';
begin = '<%+#';
end = '%>';
captures = { 0 = { name = 'punctuation.definition.comment.erb'; }; };
},
{ name = 'source.ruby.embedded.html';
begin = '<%+(?!>)=?';
end = '-?%>';
captures = { 0 = { name = 'punctuation.section.embedded.ruby'; }; };
patterns = (
{ name = 'comment.line.number-sign.ruby';
match = '(#).*?(?=-?%>)';
captures = { 1 = { name = 'punctuation.definition.comment.ruby'; }; };
},
{ include = 'source.ruby'; },
);
},
{ name = 'source.ruby.nitro.embedded.html';
begin = '<\?r(?!>)=?';
end = '-?\?>';
captures = { 0 = { name = 'punctuation.section.embedded.ruby.nitro'; }; };
patterns = (
{ name = 'comment.line.number-sign.ruby.nitro';
match = '(#).*?(?=-?\?>)';
captures = { 1 = { name = 'punctuation.definition.comment.ruby.nitro'; }; };
},
{ include = 'source.ruby'; },
);
},
);
};
smarty = {
patterns = (
{ begin = '(\{(literal)\})';
end = '(\{/(literal)\})';
captures = {
1 = { name = 'source.smarty.embedded.html'; };
2 = { name = 'support.function.built-in.smarty'; };
};
},
{ name = 'source.smarty.embedded.html';
begin = '{{|{';
end = '}}|}';
patterns = ( { include = 'source.smarty'; } );
disabled = 1;
},
);
};
string-double-quoted = {
name = 'string.quoted.double.html';
begin = '"';
end = '"';
beginCaptures = { 0 = { name = 'punctuation.definition.string.begin.html'; }; };
endCaptures = { 0 = { name = 'punctuation.definition.string.end.html'; }; };
patterns = (
{ include = '#embedded-code'; },
{ include = '#entities'; },
);
};
string-single-quoted = {
name = 'string.quoted.single.html';
begin = "'";
end = "'";
beginCaptures = { 0 = { name = 'punctuation.definition.string.begin.html'; }; };
endCaptures = { 0 = { name = 'punctuation.definition.string.end.html'; }; };
patterns = (
{ include = '#embedded-code'; },
{ include = '#entities'; },
);
};
tag-generic-attribute = {
name = 'entity.other.attribute-name.html';
match = '(?<=[^=])\b([a-zA-Z0-9:-]+)';
};
tag-id-attribute = {
name = 'meta.attribute-with-value.id.html';
begin = '\b(id)\b\s*(=)\s*';
end = '(?<=''|")|(?!>)';
captures = {
1 = { name = 'entity.other.attribute-name.id.html'; };
2 = { name = 'punctuation.separator.key-value.html'; };
};
patterns = (
{ name = 'string.quoted.double.html';
contentName = 'meta.toc-list.id.html';
begin = '"';
end = '"';
beginCaptures = { 0 = { name = 'punctuation.definition.string.begin.html'; }; };
endCaptures = { 0 = { name = 'punctuation.definition.string.end.html'; }; };
patterns = (
{ include = '#embedded-code'; },
{ include = '#entities'; },
);
},
{ name = 'string.quoted.single.html';
contentName = 'meta.toc-list.id.html';
begin = "'";
end = "'";
beginCaptures = { 0 = { name = 'punctuation.definition.string.begin.html'; }; };
endCaptures = { 0 = { name = 'punctuation.definition.string.end.html'; }; };
patterns = (
{ include = '#embedded-code'; },
{ include = '#entities'; },
);
},
);
};
tag-stuff = {
patterns = (
{ include = '#tag-id-attribute'; },
{ include = '#tag-generic-attribute'; },
{ include = '#unquoted-attribute'; },
{ include = '#string-double-quoted'; },
{ include = '#string-single-quoted'; },
{ include = '#embedded-code'; },
);
};
unquoted-attribute = {
name = 'string.unquoted.html';
match = '(?<==)(?:[^\s<>/''"]|/(?!>))+';
};
};
}
@mathiasbynens

This comment has been minimized.

Copy link

commented Nov 22, 2011

To install, go to TextMate.app → Bundle Editor → Edit Languages → HTML and paste this in.

To prevent TextMate from highlighting <!doctype html> as an error, do this:

  • on line 2, replace DOCTYPE with (DOCTYPE|doctype);
  • on line 66, replace (DOCTYPE) with (DOCTYPE|doctype).

Can you get this into https://github.com/textmate/html.tmbundle? :)

@nimbupani

This comment has been minimized.

Copy link

commented Nov 22, 2011

There is an error in line 207, fixed that and mathias's changes here https://gist.github.com/gists/1386199/

@sunpig

This comment has been minimized.

Copy link

commented Nov 22, 2011

Would be nice to add the HTML5 sectioning elements to the folding start/stop markers, too:

line 5: ```
(<(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl|section|article|header|footer|nav|aside)\b.*?>


line 13: ```        (</(?i:head|body|table|thead|tbody|tfoot|tr|div|select|fieldset|style|script|ul|ol|li|form|dl|section|article|header|footer|nav|aside)>
@nimbupani

This comment has been minimized.

Copy link

commented Nov 22, 2011

Good idea! I added that in.

@paulirish

This comment has been minimized.

Copy link

commented Nov 22, 2011

Yeah can we upstream this to the github/textmate repo? Awesomeme

@charlesroper

This comment has been minimized.

Copy link

commented Nov 24, 2011

Would be great to get this upstreamed into Sublime Text too.

@yahreen

This comment has been minimized.

Copy link

commented Nov 28, 2011

I followed those directions but...I'm confused

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.