diff --git a/src/vs/languages/html/common/html.ts b/src/vs/languages/html/common/html.ts
index f1b379f8a29..9dff02bf856 100644
--- a/src/vs/languages/html/common/html.ts
+++ b/src/vs/languages/html/common/html.ts
@@ -85,10 +85,14 @@ export class State extends AbstractState {
return false;
}
- private nextName(stream:Modes.IStream):string {
+ private nextElementName(stream:Modes.IStream):string {
return stream.advanceIfRegExp(/^[_:\w][_:\w-.\d]*/).toLowerCase();
}
+ private nextAttributeName(stream:Modes.IStream):string {
+ return stream.advanceIfRegExp(/^[^\s"'>/=\x00-\x0F\x7F\x80-\x9F]*/).toLowerCase();
+ }
+
public tokenize(stream:Modes.IStream) : Modes.ITokenizationResult {
switch(this.kind){
@@ -133,7 +137,7 @@ export class State extends AbstractState {
break;
case States.OpeningEndTag:
- var tagName = this.nextName(stream);
+ var tagName = this.nextElementName(stream);
if (tagName.length > 0){
return {
type: State.escapeTagName(tagName),
@@ -149,7 +153,7 @@ export class State extends AbstractState {
}
case States.OpeningStartTag:
- this.lastTagName = this.nextName(stream);
+ this.lastTagName = this.nextElementName(stream);
if (this.lastTagName.length > 0) {
this.lastAttributeName = null;
if ('script' === this.lastTagName || 'style' === this.lastTagName) {
@@ -165,18 +169,22 @@ export class State extends AbstractState {
case States.WithinTag:
if (stream.skipWhitespace2() || stream.eos()) {
+ this.lastAttributeName = ''; // remember that we have seen a whitespace
return { type: '' };
} else {
- var name:string = this.nextName(stream);
- if (name.length > 0) {
- this.lastAttributeName = name;
- this.kind = States.AttributeName;
- return { type: htmlTokenTypes.ATTRIB_NAME };
-
- } else if (stream.advanceIfString2('/>')) {
- this.kind = States.Content;
- return { type: htmlTokenTypes.DELIM_START, dontMergeWithPrev: true };
- } if (stream.advanceIfCharCode2('>'.charCodeAt(0))) {
+ if (this.lastAttributeName === '') {
+ var name = this.nextAttributeName(stream);
+ if (name.length > 0) {
+ this.lastAttributeName = name;
+ this.kind = States.AttributeName;
+ return { type: htmlTokenTypes.ATTRIB_NAME };
+ }
+ }
+ if (stream.advanceIfString2('/>')) {
+ this.kind = States.Content;
+ return { type: htmlTokenTypes.DELIM_START, dontMergeWithPrev: true };
+ }
+ if (stream.advanceIfCharCode2('>'.charCodeAt(0))) {
if (tagsEmbeddingContent.indexOf(this.lastTagName) !== -1) {
this.kind = States.WithinEmbeddedContent;
return { type: htmlTokenTypes.DELIM_START, dontMergeWithPrev: true };
@@ -200,6 +208,7 @@ export class State extends AbstractState {
return { type: htmlTokenTypes.DELIM_ASSIGN };
} else {
this.kind = States.WithinTag;
+ this.lastAttributeName = '';
return this.tokenize(stream); // no advance yet - jump to WithinTag
}
@@ -231,6 +240,7 @@ export class State extends AbstractState {
this.kind = States.WithinTag;
this.attributeValue = '';
this.attributeValueQuote = '';
+ this.lastAttributeName = null;
} else {
var part = stream.next();
this.attributeValue += part;
@@ -241,6 +251,7 @@ export class State extends AbstractState {
let attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/);
if (attributeValue.length > 0) {
this.kind = States.WithinTag;
+ this.lastAttributeName = null;
return { type: htmlTokenTypes.ATTRIB_VALUE };
}
var ch = stream.peek();
@@ -251,6 +262,7 @@ export class State extends AbstractState {
return { type: htmlTokenTypes.ATTRIB_VALUE };
} else {
this.kind = States.WithinTag;
+ this.lastAttributeName = null;
return this.tokenize(stream); // no advance yet - jump to WithinTag
}
}
diff --git a/src/vs/languages/html/test/common/html.test.ts b/src/vs/languages/html/test/common/html.test.ts
index 1fc0e908554..815d37c56d2 100644
--- a/src/vs/languages/html/test/common/html.test.ts
+++ b/src/vs/languages/html/test/common/html.test.ts
@@ -466,7 +466,7 @@ suite('Colorizing - HTML', () => {
]);
});
- test('Tag with empty atrributes', () => {
+ test('Tag with empty attributes', () => {
modesUtil.assertTokenization(tokenizationSupport, [{
line: '',
tokens: [
@@ -580,7 +580,7 @@ suite('Colorizing - HTML', () => {
]);
});
- test('Tag with Invalid Attribute Name', () => {
+ test('Tag with Interesting Attribute Name', () => {
modesUtil.assertTokenization(tokenizationSupport, [{
line: '',
tokens: [
@@ -588,14 +588,38 @@ suite('Colorizing - HTML', () => {
{ startIndex:1, type: getTag('abc') },
{ startIndex:4, type: '' },
{ startIndex:5, type: ATTRIB_NAME },
- { startIndex:8, type: '' },
- { startIndex:13, type: ATTRIB_NAME },
- { startIndex:16, type: '' },
+ { startIndex:11, type: DELIM_ASSIGN },
+ { startIndex:12, type: ATTRIB_VALUE },
{ startIndex:17, type: DELIM_START }
]}
]);
});
+ test('Tag with Angular Attribute Name', () => {
+ modesUtil.assertTokenization(tokenizationSupport, [{
+ line: '',
+ tokens: [
+ { startIndex:0, type: DELIM_START },
+ { startIndex:1, type: getTag('abc') },
+ { startIndex:4, type: '' },
+ { startIndex:5, type: ATTRIB_NAME },
+ { startIndex:13, type: '' },
+ { startIndex:14, type: ATTRIB_NAME },
+ { startIndex:21, type: DELIM_ASSIGN },
+ { startIndex:22, type: ATTRIB_VALUE },
+ { startIndex:27, type: '' },
+ { startIndex:28, type: ATTRIB_NAME },
+ { startIndex:35, type: DELIM_ASSIGN },
+ { startIndex:36, type: ATTRIB_VALUE },
+ { startIndex:50, type: '' },
+ { startIndex:51, type: ATTRIB_NAME },
+ { startIndex:56, type: DELIM_ASSIGN },
+ { startIndex:57, type: ATTRIB_VALUE },
+ { startIndex:72, type: DELIM_START }
+ ]}
+ ]);
+ });
+
test('Tag with Invalid Attribute Value', () => {
modesUtil.assertTokenization(tokenizationSupport, [{
line: '