├── .gitignore ├── .vscode └── launch.json ├── .vscodeignore ├── README.md ├── images ├── example.png └── icon.png ├── package.json ├── src ├── completion.js ├── highlight.js ├── hover.js ├── htmlCompletion.js ├── htmlHighlight.js ├── htmlHover.js ├── htmlScanner.js ├── htmlSpec.js └── main.js └── syntaxes ├── source.ng.css.json ├── source.ng.ts.json ├── styles.ng.json └── template.ng.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | // A launch configuration that compiles the extension and then opens it inside a new window 2 | { 3 | "version": "0.1.0", 4 | "configurations": [ 5 | { 6 | "name": "Launch Extension", 7 | "type": "extensionHost", 8 | "request": "launch", 9 | "runtimeExecutable": "${execPath}", 10 | "args": ["--extensionDevelopmentPath=${workspaceRoot}" ] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | .gitignore -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## angular2-inline 2 | 3 | **This extension is no longer supported.** 4 | All functionality that was provided by this extension can now be found in the Angular language service extension: https://marketplace.visualstudio.com/items?itemName=Angular.ng-template 5 | 6 | *This extension replaces the language-vscode-javascript-angular2 extension.* 7 | 8 | This package is a language extension for Microsoft Visual Studio Code. It extends the javascript and 9 | typescript languages to add Angular2 specific features for inline templates and stylesheets. 10 | When you define an inline template or inline style sheet using the backtick character(`` ` ``) the 11 | content will be processed by this extension. 12 | 13 | The features provided by this extension: 14 | * Syntax highlighting of inline html and css. 15 | * Code completion, highlighting, and hover information for inline html. 16 | 17 | Below is an example of what the colorizer looks like in action. 18 | 19 | ![Image of Example](images/example.png) 20 | 21 | This extension uses modified versions of grammar files and code that is part of 22 | the [vscode](https://github.com/Microsoft/vscode) project. 23 | -------------------------------------------------------------------------------- /images/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natewallace/angular2-inline/8de56558e0bfe614f17340c94a03ce6be7b49bbd/images/example.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natewallace/angular2-inline/8de56558e0bfe614f17340c94a03ce6be7b49bbd/images/icon.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular2-inline", 3 | "description": "Visual Studio Code language extension for javascript/typescript files that use Angular2.", 4 | "version": "0.0.17", 5 | "publisher": "natewallace", 6 | "license": "UNLICENSED", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/natewallace/angular2-inline.git" 10 | }, 11 | "engines": { 12 | "vscode": "^1.7.1" 13 | }, 14 | "icon": "images/icon.png", 15 | "galleryBanner": { 16 | "color": "#99C3D7" 17 | }, 18 | "categories": [ 19 | "Languages" 20 | ], 21 | "activationEvents": [ 22 | "onLanguage:typescript", 23 | "onLanguage:javascript" 24 | ], 25 | "main": "./src/main", 26 | "contributes": { 27 | "grammars": [ 28 | { 29 | "scopeName": "template.ng", 30 | "path": "./syntaxes/template.ng.json", 31 | "injectTo": [ 32 | "source.js", 33 | "source.ts" 34 | ], 35 | "embeddedLanguages": { 36 | "meta.embedded.block.html": "html" 37 | } 38 | }, 39 | { 40 | "scopeName": "styles.ng", 41 | "path": "./syntaxes/styles.ng.json", 42 | "injectTo": [ 43 | "source.js", 44 | "source.ts" 45 | ], 46 | "embeddedLanguages": { 47 | "meta.embedded.block.css": "css" 48 | } 49 | }, 50 | { 51 | "scopeName": "source.ng.ts", 52 | "path": "./syntaxes/source.ng.ts.json" 53 | }, 54 | { 55 | "scopeName": "source.ng.css", 56 | "path": "./syntaxes/source.ng.css.json" 57 | } 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/completion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode = require('vscode'); 4 | const html = require('./htmlCompletion'); 5 | 6 | /** 7 | * Class used for code completion that implements CompletionItemProvider. 8 | */ 9 | module.exports = (function () { 10 | /** 11 | * Constructor. 12 | */ 13 | function Completion() { 14 | } 15 | 16 | /** 17 | * Provide completion items for the given position within the document. 18 | * 19 | * @param {TextDocument} document - The document in which the command was invoked. 20 | * @param {Position} position - The position at which the command was invoked. 21 | * @param {CancellationToken} - A cancelation token. 22 | * @return {Promise} - A promise that resolves to an array of CompletionItem objects. 23 | */ 24 | Completion.prototype.provideCompletionItems = function provideCompletionItems(document, position, token) { 25 | return new Promise(function (resolve, reject) { 26 | 27 | // find containing template string if one exists 28 | const templateRegExp = /(template\s*:\s*`)([^`]*)`/g; 29 | const text = document.getText(); 30 | const pos = document.offsetAt(position); 31 | let match = null; 32 | let resolved = false; 33 | 34 | while ((match = templateRegExp.exec(text)) !== null) { 35 | if (pos > match.index + match[1].length && pos < match.index + match[0].length) { 36 | resolve(html.createCompletions(match[2], pos - match[1].length - match.index)); 37 | resolved = true; 38 | break; 39 | } 40 | } 41 | 42 | if (!resolved) { 43 | resolve([]); 44 | } 45 | }); 46 | }; 47 | 48 | /** 49 | * Given a completion item fill in more data, like doc-comment or details. 50 | * The editor will only resolve a completion item once. 51 | * 52 | * @param {CompletionItem} item - The document in which the command was invoked. 53 | * @param {CancellationToken} token - A cancelation token. 54 | * @return {CompletionItem} - The resolved completion item 55 | */ 56 | Completion.prototype.resolveCompletionItem = function provideCompletionItems(item, token) { 57 | return item; 58 | }; 59 | 60 | return Completion; 61 | }()); -------------------------------------------------------------------------------- /src/highlight.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode = require('vscode'); 4 | const html = require('./htmlHighlight'); 5 | 6 | /** 7 | * Class used for highlights that implements DocumentHighlightProvider. 8 | */ 9 | module.exports = (function () { 10 | /** 11 | * Constructor. 12 | */ 13 | function Highlight() { 14 | } 15 | 16 | /** 17 | * Provide highlights for the given position within the document. 18 | * 19 | * @param {TextDocument} document - The document in which the command was invoked. 20 | * @param {Position} position - The position at which the command was invoked. 21 | * @param {CancellationToken} - A cancelation token. 22 | * @return {Promise} - A promise that resolves to an array of DocumentHighlight objects. 23 | */ 24 | Highlight.prototype.provideDocumentHighlights = function provideDocumentHighlights(document, position, token) { 25 | return new Promise(function (resolve, reject) { 26 | 27 | // find containing template string if one exists 28 | const templateRegExp = /(template\s*:\s*`)([^`]*)`/g; 29 | const text = document.getText(); 30 | const pos = document.offsetAt(position); 31 | let match = null; 32 | let resolved = false; 33 | 34 | while ((match = templateRegExp.exec(text)) !== null) { 35 | if (pos > match.index + match[1].length && pos < match.index + match[0].length) { 36 | resolve(html.createHighlights( 37 | match[2], 38 | pos - match[1].length - match.index, 39 | match.index + match[1].length, 40 | document)); 41 | resolved = true; 42 | break; 43 | } 44 | } 45 | 46 | if (!resolved) { 47 | resolve([]); 48 | } 49 | }); 50 | }; 51 | 52 | return Highlight; 53 | }()); -------------------------------------------------------------------------------- /src/hover.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode = require('vscode'); 4 | const html = require('./htmlHover'); 5 | 6 | /** 7 | * Class used for hover info that implements HoverProvider. 8 | */ 9 | module.exports = (function () { 10 | /** 11 | * Constructor. 12 | */ 13 | function Hover() { 14 | } 15 | 16 | /** 17 | * Provide hover info for the given position within the document. 18 | * 19 | * @param {TextDocument} document - The document in which the command was invoked. 20 | * @param {Position} position - The position at which the command was invoked. 21 | * @param {CancellationToken} - A cancelation token. 22 | * @return {Promise} - A promise that resolves to a Hover object. 23 | */ 24 | Hover.prototype.provideHover = function provideHover(document, position, token) { 25 | return new Promise(function (resolve, reject) { 26 | 27 | // find containing template string if one exists 28 | const templateRegExp = /(template\s*:\s*`)([^`]*)`/g; 29 | const text = document.getText(); 30 | const pos = document.offsetAt(position); 31 | let match = null; 32 | let resolved = false; 33 | 34 | while ((match = templateRegExp.exec(text)) !== null) { 35 | if (pos > match.index + match[1].length && pos < match.index + match[0].length) { 36 | resolve(html.createHover( 37 | match[2], 38 | pos - match[1].length - match.index, 39 | match.index + match[1].length, 40 | document)); 41 | resolved = true; 42 | break; 43 | } 44 | } 45 | 46 | if (!resolved) { 47 | resolve([]); 48 | } 49 | }); 50 | }; 51 | 52 | return Hover; 53 | }()); -------------------------------------------------------------------------------- /src/htmlCompletion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const spec = require('./htmlSpec'); 4 | const Html = require('./htmlScanner'); 5 | 6 | /** 7 | * Parse the given text to get the open element that contains or preceeds the cursor position. 8 | * 9 | * @param {string} text - The html text to parse. 10 | * @param {number} pos - The current zero based cursor position relative to the text parameter. 11 | * @return {object} - An object that describes the parsed element. 12 | */ 13 | function parseForElement(text, pos) { 14 | const scanner = Html.createScanner(text); 15 | const elementStack = []; 16 | let element = null; 17 | let attributeName = null; 18 | let done = false; 19 | let pop = false; 20 | 21 | while (!done && scanner.scan() !== Html.TokenType.EOS) { 22 | switch (scanner.getTokenType()) { 23 | 24 | // start tag open 25 | case Html.TokenType.StartTagOpen: 26 | done = pos <= scanner.getTokenOffset(); 27 | break; 28 | 29 | // start tag 30 | case Html.TokenType.StartTag: 31 | element = { 32 | name: scanner.getTokenText(), 33 | offset: scanner.getTokenOffset(), 34 | endOffset: scanner.getTokenOffset() + scanner.getTokenLength(), 35 | attributeValues: [] 36 | }; 37 | elementStack.push(element); 38 | break; 39 | 40 | // attribute name 41 | case Html.TokenType.AttributeName: 42 | attributeName = scanner.getTokenText(); 43 | break; 44 | 45 | // attribute value 46 | case Html.TokenType.AttributeValue: 47 | if (element) { 48 | element.attributeValues.push({ 49 | attributeName: attributeName, 50 | offset: scanner.getTokenOffset(), 51 | endOffset: scanner.getTokenOffset() + scanner.getTokenLength() 52 | }) 53 | } 54 | break; 55 | 56 | // start tag self closed 57 | case Html.TokenType.StartTagSelfClose: 58 | if (element) { 59 | element.endTagOffset = scanner.getTokenOffset(); 60 | done = pos <= scanner.getTokenOffset(); 61 | pop = !done; 62 | } 63 | break; 64 | 65 | // start tag closed 66 | case Html.TokenType.StartTagClose: 67 | done = pos <= scanner.getTokenOffset(); 68 | if (!done && element) { 69 | element.endTagOffset = scanner.getTokenOffset(); 70 | pop = spec.isEmptyTag(element.name); 71 | } 72 | break; 73 | 74 | // end tag 75 | case Html.TokenType.EndTagOpen: 76 | pop = true; 77 | break; 78 | 79 | default: 80 | done = pos <= scanner.getTokenOffset(); 81 | break; 82 | } 83 | 84 | // remove the last added element and set top element to latest 85 | if (pop) { 86 | pop = false; 87 | if (elementStack.length > 0) { 88 | elementStack.splice(elementStack.length - 1); 89 | if (elementStack.length > 0) { 90 | element = elementStack[elementStack.length - 1]; 91 | } else { 92 | element = null; 93 | } 94 | } 95 | } 96 | } 97 | 98 | // determine if we are inside the element, an attribute, or an attribute value 99 | if (element) { 100 | if (typeof element.endTagOffset === 'undefined') { 101 | element.endTagOffset = text.length; 102 | } 103 | element.isInsideElement = pos <= element.endTagOffset; 104 | element.isInsideElementName = (pos >= element.offset && pos <= element.endOffset); 105 | element.isInsideAttributeValue = false; 106 | element.attributeName = null; 107 | for (let i = 0; i < element.attributeValues.length; i++) { 108 | if (pos >= element.attributeValues[i].offset && pos <= element.attributeValues[i].endOffset) { 109 | element.isInsideAttributeValue = true; 110 | element.attributeName = element.attributeValues[i].attributeName; 111 | } 112 | } 113 | } 114 | 115 | return element; 116 | } 117 | 118 | /** 119 | * Get the first occurance of an invalid element name character that proceeds the current position. 120 | * 121 | * @param {string} text - The full text that the cursor appears in. 122 | * @param {number} pos - The current zero based cursor position within the text parameter to start the search from. 123 | * @return {string} - The first occurance of an invalid element name character that proceeds the current position. If there isn't an invalid character 124 | * found before reaching the beginning of the text, null will be returned. 125 | */ 126 | function firstNonNameCharacter(text, pos) { 127 | const nameRegExp = /[_$@a-zA-Z*#()\[\]]/; 128 | for (let i = pos - 1; i >= 0; i--) { 129 | if (!nameRegExp.test(text[i])) { 130 | return text[i]; 131 | } 132 | } 133 | 134 | return null; 135 | } 136 | 137 | /** 138 | * Get the opening characters that appear before an attribute name if there are any. 139 | * 140 | * @param {string} text - The full text that the cursor appears in. 141 | * @param {number} pos - The current zero based cursor position within the text parameter to start the search from. 142 | * @return {string} - The opening characters found or an empty string if non are present. The possible return values are: '[', '[(', '(', '([', '' 143 | */ 144 | function getOpenAttributeCharacters(text, pos) { 145 | const termRegExp = /[\s"']/; 146 | const openRegExp = /\[|\(/; 147 | for (let i = pos - 1; i >= 0; i--) { 148 | if (termRegExp.test(text[i])) { 149 | const part0 = (i + 1 < text.length) ? text[i + 1] : ''; 150 | const part1 = (i + 2 < text.length) ? text[i + 2] : ''; 151 | return (openRegExp.test(part0) ? part0 : '') + 152 | (openRegExp.test(part1) ? part1 : '') 153 | } 154 | } 155 | 156 | return ''; 157 | } 158 | 159 | /** 160 | * Get the closing characters that appear after an attribute name if there are any. 161 | * 162 | * @param {string} text - The full text that the cursor appears in. 163 | * @param {number} pos - The current zero based cursor position within the text parameter to start the search from. 164 | * @return {string} - The closing characters found or an empty string if non are present. The possible return values are: 165 | * ']', ')]', ')', '])', ']=', ')]=', ')=', '])=', '' 166 | */ 167 | function getCloseAttributeCharacters(text, pos) { 168 | const nameRegExp = /[_$@a-zA-Z*#]/; 169 | const whitespaceRegExp = /[\s]/; 170 | let closeRegExp = /\]|\)/; 171 | let result = ''; 172 | for (let i = pos; i < text.length; i++) { 173 | if (!nameRegExp.test(text[i])) { 174 | for (let j = i; j < text.length; j++) { 175 | if (closeRegExp && closeRegExp.test(text[j])) { 176 | result += text[j]; 177 | } else if (whitespaceRegExp.test(text[j])) { 178 | closeRegExp = null; 179 | result += text[j]; 180 | } else if (text[j] === '=') { 181 | result += '='; 182 | break; 183 | } else { 184 | break; 185 | } 186 | } 187 | 188 | return result.trim(); 189 | } 190 | } 191 | 192 | return ''; 193 | } 194 | 195 | /** 196 | * Create list of completions based on position in the html text. 197 | * 198 | * @param {string} text - The html text to create completions for. 199 | * @param {number} pos - The position within the html text to create completions for. 200 | * @return {CompletionItem[]} - The completion items to display. 201 | */ 202 | module.exports.createCompletions = function createCompletions(text, pos) { 203 | const element = parseForElement(text, pos); 204 | 205 | // return all tags if we are not following an element 206 | if (!element || element.isInsideElementName) { 207 | if (firstNonNameCharacter(text, pos) === '<') { 208 | return spec.getTags(); 209 | } 210 | 211 | return []; 212 | } 213 | 214 | // return all tags plus closing tag if following an element but not inside of it 215 | if (!element.isInsideElement) { 216 | if (firstNonNameCharacter(text, pos) === '<') { 217 | return [spec.newCloseTag(element.name)].concat(spec.getTags()); 218 | } 219 | 220 | return []; 221 | } 222 | 223 | // const elementAttribute = parseForAttribute(element, text, pos); 224 | const openCharacters = getOpenAttributeCharacters(text, pos); 225 | const closeCharacters = getCloseAttributeCharacters(text, pos); 226 | 227 | // return all attributes for element if there is no current attribute or we are not inside the value portion of an attribute 228 | if (!element.isInsideAttributeValue) { 229 | return spec.getAttributes(element.name, openCharacters, closeCharacters); 230 | } 231 | 232 | // return valid values for attribute we are inside 233 | return spec.getAttributeValues(element.name, element.attributeName); 234 | } -------------------------------------------------------------------------------- /src/htmlHighlight.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const spec = require('./htmlSpec'); 4 | const Html = require('./htmlScanner'); 5 | const vscode = require('vscode'); 6 | 7 | /** 8 | * Parse the given text to get the ranges for open and close elements. 9 | * 10 | * @param {string} text - The html text to parse. 11 | * @param {number} pos - The current zero based cursor position relative to the text parameter. 12 | * @return {object} - An object that contains the ranges for open and closing elements. 13 | */ 14 | function parseForElement(text, pos) { 15 | const scanner = Html.createScanner(text); 16 | const elementStack = []; 17 | let found = false; 18 | let pop = false; 19 | 20 | while (scanner.scan() !== Html.TokenType.EOS) { 21 | switch (scanner.getTokenType()) { 22 | 23 | // start tag 24 | case Html.TokenType.StartTag: 25 | const element = { 26 | name: scanner.getTokenText(), 27 | offset: scanner.getTokenOffset(), 28 | endOffset: scanner.getTokenOffset() + scanner.getTokenLength() 29 | }; 30 | element.isHighlight = pos >= element.offset && pos <= element.endOffset; 31 | if (element.isHighlight) { 32 | found = true; 33 | } 34 | elementStack.push(element); 35 | break; 36 | 37 | // start tag closed 38 | case Html.TokenType.StartTagClose: 39 | if (elementStack.length > 0) { 40 | pop = spec.isEmptyTag(elementStack[elementStack.length - 1].name); 41 | } 42 | break; 43 | 44 | // end tag 45 | case Html.TokenType.EndTag: 46 | pop = true; 47 | break; 48 | 49 | // check for bail out 50 | default: 51 | if (!found && pos <= scanner.getTokenOffset()) { 52 | return null; 53 | } 54 | break; 55 | } 56 | 57 | // remove the last added element and set top element to latest 58 | if (pop) { 59 | pop = false; 60 | if (elementStack.length > 0) { 61 | 62 | // check for matching tag 63 | const element = elementStack[elementStack.length - 1]; 64 | const tokenText = scanner.getTokenText(); 65 | const tokenOffset = scanner.getTokenOffset(); 66 | const isHighlight = element.isHighlight || (pos >= tokenOffset && pos <= tokenOffset + tokenText.length); 67 | if (isHighlight && (element.name === tokenText || tokenText === '>')) { 68 | return { 69 | openStart: element.offset, 70 | openEnd: element.endOffset, 71 | closeStart: scanner.getTokenOffset(), 72 | closeEnd: scanner.getTokenOffset() + scanner.getTokenText().length 73 | } 74 | } 75 | 76 | elementStack.splice(elementStack.length - 1); 77 | } 78 | } 79 | } 80 | 81 | return null; 82 | } 83 | 84 | /** 85 | * Create list of highlights based on position in the html text. 86 | * 87 | * @param {string} text - The html text to create highlights for. 88 | * @param {number} pos - The position within the html text to create highlights for. 89 | * @param {number} textOffset - The offset of the text within the entire document. 90 | * @param {TextDocument} document - The document to create highlights for. 91 | * @return {DocumentHighlight[]} - The highlights to display. 92 | */ 93 | module.exports.createHighlights = function createCompletions(text, pos, textOffset, document) { 94 | const range = parseForElement(text, pos); 95 | if (!range) { 96 | return null; 97 | } 98 | 99 | return [ 100 | new vscode.DocumentHighlight( 101 | new vscode.Range(document.positionAt(textOffset + range.openStart), document.positionAt(textOffset + range.openEnd)), 102 | vscode.DocumentHighlightKind.Read 103 | ), 104 | new vscode.DocumentHighlight( 105 | new vscode.Range(document.positionAt(textOffset + range.closeStart), document.positionAt(textOffset + range.closeEnd)), 106 | vscode.DocumentHighlightKind.Read 107 | ) 108 | ]; 109 | } -------------------------------------------------------------------------------- /src/htmlHover.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const spec = require('./htmlSpec'); 4 | const Html = require('./htmlScanner'); 5 | const vscode = require('vscode'); 6 | 7 | /** 8 | * Parse the given text to get the element at the given position. 9 | * 10 | * @param {string} text - The html text to parse. 11 | * @param {number} pos - The current zero based cursor position relative to the text parameter. 12 | * @return {object} - The element at the given position or null if there isn't one. 13 | */ 14 | function parseForElement(text, pos) { 15 | const scanner = Html.createScanner(text); 16 | 17 | while (scanner.scan() !== Html.TokenType.EOS) { 18 | switch (scanner.getTokenType()) { 19 | 20 | // start tag 21 | case Html.TokenType.StartTag: 22 | if (pos >= scanner.getTokenOffset() && pos <= scanner.getTokenOffset() + scanner.getTokenText().length) { 23 | return { 24 | name: scanner.getTokenText(), 25 | label: '<' + scanner.getTokenText() + '>', 26 | offset: scanner.getTokenOffset(), 27 | endOffset: scanner.getTokenOffset() + scanner.getTokenLength() 28 | }; 29 | } 30 | break; 31 | 32 | // end tag 33 | case Html.TokenType.EndTag: 34 | if (pos >= scanner.getTokenOffset() && pos <= scanner.getTokenOffset() + scanner.getTokenText().length) { 35 | return { 36 | name: scanner.getTokenText(), 37 | label: '', 38 | offset: scanner.getTokenOffset(), 39 | endOffset: scanner.getTokenOffset() + scanner.getTokenLength() 40 | }; 41 | } 42 | break; 43 | 44 | // check for bail out 45 | default: 46 | if (pos <= scanner.getTokenOffset()) { 47 | return null; 48 | } 49 | break; 50 | } 51 | } 52 | 53 | return null; 54 | } 55 | 56 | /** 57 | * Escape any markdown syntax. This function is taken from the VSCode project. 58 | * 59 | * @param {string} text - The text to escape for markdown. 60 | * @return {string} - The markdown safe text. 61 | */ 62 | function textToMarkedString(text) { 63 | if (!text) { 64 | return ''; 65 | } 66 | return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash 67 | } 68 | 69 | /** 70 | * Create hover info based on position in the html text. 71 | * 72 | * @param {string} text - The html text to create hover info for. 73 | * @param {number} pos - The position within the html text to create hover info for. 74 | * @param {number} textOffset - The offset of the text within the entire document. 75 | * @param {TextDocument} document - The document to create hover info for. 76 | * @return {Hover} - The hover info to display. 77 | */ 78 | module.exports.createHover = function createCompletions(text, pos, textOffset, document) { 79 | const element = parseForElement(text, pos); 80 | if (!element) { 81 | return null; 82 | } 83 | 84 | const tag = spec.getTag(element.name); 85 | if (tag) { 86 | return new vscode.Hover( 87 | [{ language: 'html', value: element.label }, textToMarkedString(tag.documentation)], 88 | new vscode.Range(document.positionAt(textOffset + element.offset), document.positionAt(textOffset + element.endOffset))); 89 | } 90 | 91 | return null; 92 | } -------------------------------------------------------------------------------- /src/htmlScanner.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * This code has been taken from the https://github.com/Microsoft/vscode-html-languageservice/ project 3 | * and modified to work within this project. The original license header is included below. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /*--------------------------------------------------------------------------------------------- 7 | * Copyright (c) Microsoft Corporation. All rights reserved. 8 | * Licensed under the MIT License. See License.txt in the project root for license information. 9 | *--------------------------------------------------------------------------------------------*/ 10 | 11 | 'use strict'; 12 | 13 | const define = function (require, exports) { 14 | (function (TokenType) { 15 | TokenType[TokenType["StartCommentTag"] = 0] = "StartCommentTag"; 16 | TokenType[TokenType["Comment"] = 1] = "Comment"; 17 | TokenType[TokenType["EndCommentTag"] = 2] = "EndCommentTag"; 18 | TokenType[TokenType["StartTagOpen"] = 3] = "StartTagOpen"; 19 | TokenType[TokenType["StartTagClose"] = 4] = "StartTagClose"; 20 | TokenType[TokenType["StartTagSelfClose"] = 5] = "StartTagSelfClose"; 21 | TokenType[TokenType["StartTag"] = 6] = "StartTag"; 22 | TokenType[TokenType["EndTagOpen"] = 7] = "EndTagOpen"; 23 | TokenType[TokenType["EndTagClose"] = 8] = "EndTagClose"; 24 | TokenType[TokenType["EndTag"] = 9] = "EndTag"; 25 | TokenType[TokenType["DelimiterAssign"] = 10] = "DelimiterAssign"; 26 | TokenType[TokenType["AttributeName"] = 11] = "AttributeName"; 27 | TokenType[TokenType["AttributeValue"] = 12] = "AttributeValue"; 28 | TokenType[TokenType["StartDoctypeTag"] = 13] = "StartDoctypeTag"; 29 | TokenType[TokenType["Doctype"] = 14] = "Doctype"; 30 | TokenType[TokenType["EndDoctypeTag"] = 15] = "EndDoctypeTag"; 31 | TokenType[TokenType["Content"] = 16] = "Content"; 32 | TokenType[TokenType["Whitespace"] = 17] = "Whitespace"; 33 | TokenType[TokenType["Unknown"] = 18] = "Unknown"; 34 | TokenType[TokenType["Script"] = 19] = "Script"; 35 | TokenType[TokenType["Styles"] = 20] = "Styles"; 36 | TokenType[TokenType["EOS"] = 21] = "EOS"; 37 | })(exports.TokenType || (exports.TokenType = {})); 38 | var TokenType = exports.TokenType; 39 | var MultiLineStream = (function () { 40 | function MultiLineStream(source, position) { 41 | this.source = source; 42 | this.len = source.length; 43 | this.position = position; 44 | } 45 | MultiLineStream.prototype.eos = function () { 46 | return this.len <= this.position; 47 | }; 48 | MultiLineStream.prototype.getSource = function () { 49 | return this.source; 50 | }; 51 | MultiLineStream.prototype.pos = function () { 52 | return this.position; 53 | }; 54 | MultiLineStream.prototype.goBackTo = function (pos) { 55 | this.position = pos; 56 | }; 57 | MultiLineStream.prototype.goBack = function (n) { 58 | this.position -= n; 59 | }; 60 | MultiLineStream.prototype.advance = function (n) { 61 | this.position += n; 62 | }; 63 | MultiLineStream.prototype.goToEnd = function () { 64 | this.position = this.source.length; 65 | }; 66 | MultiLineStream.prototype.nextChar = function () { 67 | return this.source.charCodeAt(this.position++) || 0; 68 | }; 69 | MultiLineStream.prototype.peekChar = function (n) { 70 | if (n === void 0) { n = 0; } 71 | return this.source.charCodeAt(this.position + n) || 0; 72 | }; 73 | MultiLineStream.prototype.advanceIfChar = function (ch) { 74 | if (ch === this.source.charCodeAt(this.position)) { 75 | this.position++; 76 | return true; 77 | } 78 | return false; 79 | }; 80 | MultiLineStream.prototype.advanceIfChars = function (ch) { 81 | var i; 82 | if (this.position + ch.length > this.source.length) { 83 | return false; 84 | } 85 | for (i = 0; i < ch.length; i++) { 86 | if (this.source.charCodeAt(this.position + i) !== ch[i]) { 87 | return false; 88 | } 89 | } 90 | this.advance(i); 91 | return true; 92 | }; 93 | MultiLineStream.prototype.advanceIfRegExp = function (regex) { 94 | var str = this.source.substr(this.position); 95 | var match = str.match(regex); 96 | if (match) { 97 | this.position = this.position + match.index + match[0].length; 98 | return match[0]; 99 | } 100 | return ''; 101 | }; 102 | MultiLineStream.prototype.advanceUntilRegExp = function (regex) { 103 | var str = this.source.substr(this.position); 104 | var match = str.match(regex); 105 | if (match) { 106 | this.position = this.position + match.index; 107 | return match[0]; 108 | } 109 | return ''; 110 | }; 111 | MultiLineStream.prototype.advanceUntilChar = function (ch) { 112 | while (this.position < this.source.length) { 113 | if (this.source.charCodeAt(this.position) === ch) { 114 | return true; 115 | } 116 | this.advance(1); 117 | } 118 | return false; 119 | }; 120 | MultiLineStream.prototype.advanceUntilChars = function (ch) { 121 | while (this.position + ch.length <= this.source.length) { 122 | var i = 0; 123 | for (; i < ch.length && this.source.charCodeAt(this.position + i) === ch[i]; i++) { 124 | } 125 | if (i === ch.length) { 126 | return true; 127 | } 128 | this.advance(1); 129 | } 130 | this.goToEnd(); 131 | return false; 132 | }; 133 | MultiLineStream.prototype.skipWhitespace = function () { 134 | var n = this.advanceWhileChar(function (ch) { 135 | return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR; 136 | }); 137 | return n > 0; 138 | }; 139 | MultiLineStream.prototype.advanceWhileChar = function (condition) { 140 | var posNow = this.position; 141 | while (this.position < this.len && condition(this.source.charCodeAt(this.position))) { 142 | this.position++; 143 | } 144 | return this.position - posNow; 145 | }; 146 | return MultiLineStream; 147 | }()); 148 | var _BNG = '!'.charCodeAt(0); 149 | var _MIN = '-'.charCodeAt(0); 150 | var _LAN = '<'.charCodeAt(0); 151 | var _RAN = '>'.charCodeAt(0); 152 | var _FSL = '/'.charCodeAt(0); 153 | var _EQS = '='.charCodeAt(0); 154 | var _DQO = '"'.charCodeAt(0); 155 | var _SQO = '\''.charCodeAt(0); 156 | var _NWL = '\n'.charCodeAt(0); 157 | var _CAR = '\r'.charCodeAt(0); 158 | var _LFD = '\f'.charCodeAt(0); 159 | var _WSP = ' '.charCodeAt(0); 160 | var _TAB = '\t'.charCodeAt(0); 161 | (function (ScannerState) { 162 | ScannerState[ScannerState["WithinContent"] = 0] = "WithinContent"; 163 | ScannerState[ScannerState["AfterOpeningStartTag"] = 1] = "AfterOpeningStartTag"; 164 | ScannerState[ScannerState["AfterOpeningEndTag"] = 2] = "AfterOpeningEndTag"; 165 | ScannerState[ScannerState["WithinDoctype"] = 3] = "WithinDoctype"; 166 | ScannerState[ScannerState["WithinTag"] = 4] = "WithinTag"; 167 | ScannerState[ScannerState["WithinEndTag"] = 5] = "WithinEndTag"; 168 | ScannerState[ScannerState["WithinComment"] = 6] = "WithinComment"; 169 | ScannerState[ScannerState["WithinScriptContent"] = 7] = "WithinScriptContent"; 170 | ScannerState[ScannerState["WithinStyleContent"] = 8] = "WithinStyleContent"; 171 | ScannerState[ScannerState["AfterAttributeName"] = 9] = "AfterAttributeName"; 172 | ScannerState[ScannerState["BeforeAttributeValue"] = 10] = "BeforeAttributeValue"; 173 | })(exports.ScannerState || (exports.ScannerState = {})); 174 | var ScannerState = exports.ScannerState; 175 | var htmlScriptContents = { 176 | 'text/x-handlebars-template': true 177 | }; 178 | function createScanner(input, initialOffset, initialState) { 179 | if (initialOffset === void 0) { initialOffset = 0; } 180 | if (initialState === void 0) { initialState = ScannerState.WithinContent; } 181 | var stream = new MultiLineStream(input, initialOffset); 182 | var state = initialState; 183 | var tokenOffset = 0; 184 | var tokenType = void 0; 185 | var tokenError; 186 | var hasSpaceAfterTag; 187 | var lastTag; 188 | var lastAttributeName; 189 | var lastTypeValue; 190 | function nextElementName() { 191 | return stream.advanceIfRegExp(/^[_:\w][_:\w-.\d]*/).toLowerCase(); 192 | } 193 | function nextAttributeName() { 194 | return stream.advanceIfRegExp(/^[^\s"'>/=\x00-\x0F\x7F\x80-\x9F]*/).toLowerCase(); 195 | } 196 | function finishToken(offset, type, errorMessage) { 197 | tokenType = type; 198 | tokenOffset = offset; 199 | tokenError = errorMessage; 200 | return type; 201 | } 202 | function scan() { 203 | var offset = stream.pos(); 204 | var oldState = state; 205 | var token = internalScan(); 206 | if (token !== TokenType.EOS && offset === stream.pos()) { 207 | console.log('Scanner.scan has not advanced at offset ' + offset + ', state before: ' + oldState + ' after: ' + state); 208 | stream.advance(1); 209 | return finishToken(offset, TokenType.Unknown); 210 | } 211 | return token; 212 | } 213 | function internalScan() { 214 | var offset = stream.pos(); 215 | if (stream.eos()) { 216 | return finishToken(offset, TokenType.EOS); 217 | } 218 | var errorMessage; 219 | switch (state) { 220 | case ScannerState.WithinComment: 221 | if (stream.advanceIfChars([_MIN, _MIN, _RAN])) { 222 | state = ScannerState.WithinContent; 223 | return finishToken(offset, TokenType.EndCommentTag); 224 | } 225 | stream.advanceUntilChars([_MIN, _MIN, _RAN]); // --> 226 | return finishToken(offset, TokenType.Comment); 227 | case ScannerState.WithinDoctype: 228 | if (stream.advanceIfChar(_RAN)) { 229 | state = ScannerState.WithinContent; 230 | return finishToken(offset, TokenType.EndDoctypeTag); 231 | } 232 | stream.advanceUntilChar(_RAN); // > 233 | return finishToken(offset, TokenType.Doctype); 234 | case ScannerState.WithinContent: 235 | if (stream.advanceIfChar(_LAN)) { 236 | if (!stream.eos() && stream.peekChar() === _BNG) { 237 | if (stream.advanceIfChars([_BNG, _MIN, _MIN])) { 238 | state = ScannerState.WithinComment; 239 | return finishToken(offset, TokenType.StartCommentTag); 240 | } 241 | if (stream.advanceIfRegExp(/^!doctype/i)) { 242 | state = ScannerState.WithinDoctype; 243 | return finishToken(offset, TokenType.StartDoctypeTag); 244 | } 245 | } 246 | if (stream.advanceIfChar(_FSL)) { 247 | state = ScannerState.AfterOpeningEndTag; 248 | return finishToken(offset, TokenType.EndTagOpen); 249 | } 250 | state = ScannerState.AfterOpeningStartTag; 251 | return finishToken(offset, TokenType.StartTagOpen); 252 | } 253 | stream.advanceUntilChar(_LAN); 254 | return finishToken(offset, TokenType.Content); 255 | case ScannerState.AfterOpeningEndTag: 256 | var tagName = nextElementName(); 257 | if (tagName.length > 0) { 258 | state = ScannerState.WithinEndTag; 259 | return finishToken(offset, TokenType.EndTag); 260 | } 261 | if (stream.skipWhitespace()) { 262 | return finishToken(offset, TokenType.Whitespace, 'Tag name must directly follow the open bracket.'); 263 | } 264 | state = ScannerState.WithinEndTag; 265 | stream.advanceUntilChar(_RAN); 266 | if (offset < stream.pos()) { 267 | return finishToken(offset, TokenType.Unknown, 'End tag name expected.'); 268 | } 269 | return internalScan(); 270 | case ScannerState.WithinEndTag: 271 | if (stream.skipWhitespace()) { 272 | return finishToken(offset, TokenType.Whitespace); 273 | } 274 | if (stream.advanceIfChar(_RAN)) { 275 | state = ScannerState.WithinContent; 276 | return finishToken(offset, TokenType.EndTagClose); 277 | } 278 | errorMessage = 'Closing bracket expected.'; 279 | break; 280 | case ScannerState.AfterOpeningStartTag: 281 | lastTag = nextElementName(); 282 | lastTypeValue = null; 283 | lastAttributeName = null; 284 | if (lastTag.length > 0) { 285 | hasSpaceAfterTag = false; 286 | state = ScannerState.WithinTag; 287 | return finishToken(offset, TokenType.StartTag); 288 | } 289 | if (stream.skipWhitespace()) { 290 | return finishToken(offset, TokenType.Whitespace, 'Tag name must directly follow the open bracket.'); 291 | } 292 | state = ScannerState.WithinTag; 293 | stream.advanceUntilChar(_RAN); 294 | if (offset < stream.pos()) { 295 | return finishToken(offset, TokenType.Unknown, 'Start tag name expected.'); 296 | } 297 | return internalScan(); 298 | case ScannerState.WithinTag: 299 | if (stream.skipWhitespace()) { 300 | hasSpaceAfterTag = true; // remember that we have seen a whitespace 301 | return finishToken(offset, TokenType.Whitespace); 302 | } 303 | if (hasSpaceAfterTag) { 304 | lastAttributeName = nextAttributeName(); 305 | if (lastAttributeName.length > 0) { 306 | state = ScannerState.AfterAttributeName; 307 | hasSpaceAfterTag = false; 308 | return finishToken(offset, TokenType.AttributeName); 309 | } 310 | } 311 | if (stream.advanceIfChars([_FSL, _RAN])) { 312 | state = ScannerState.WithinContent; 313 | return finishToken(offset, TokenType.StartTagSelfClose); 314 | } 315 | if (stream.advanceIfChar(_RAN)) { 316 | if (lastTag === 'script') { 317 | if (lastTypeValue && htmlScriptContents[lastTypeValue]) { 318 | // stay in html 319 | state = ScannerState.WithinContent; 320 | } 321 | else { 322 | state = ScannerState.WithinScriptContent; 323 | } 324 | } 325 | else if (lastTag === 'style') { 326 | state = ScannerState.WithinStyleContent; 327 | } 328 | else { 329 | state = ScannerState.WithinContent; 330 | } 331 | return finishToken(offset, TokenType.StartTagClose); 332 | } 333 | stream.advance(1); 334 | return finishToken(offset, TokenType.Unknown, 'Unexpected character in tag.'); 335 | case ScannerState.AfterAttributeName: 336 | if (stream.skipWhitespace()) { 337 | hasSpaceAfterTag = true; 338 | return finishToken(offset, TokenType.Whitespace); 339 | } 340 | if (stream.advanceIfChar(_EQS)) { 341 | state = ScannerState.BeforeAttributeValue; 342 | return finishToken(offset, TokenType.DelimiterAssign); 343 | } 344 | state = ScannerState.WithinTag; 345 | return internalScan(); // no advance yet - jump to WithinTag 346 | case ScannerState.BeforeAttributeValue: 347 | if (stream.skipWhitespace()) { 348 | return finishToken(offset, TokenType.Whitespace); 349 | } 350 | var attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/); 351 | if (attributeValue.length > 0) { 352 | if (lastAttributeName === 'type') { 353 | lastTypeValue = attributeValue; 354 | } 355 | state = ScannerState.WithinTag; 356 | hasSpaceAfterTag = false; 357 | return finishToken(offset, TokenType.AttributeValue); 358 | } 359 | var ch = stream.peekChar(); 360 | if (ch === _SQO || ch === _DQO) { 361 | stream.advance(1); // consume quote 362 | if (stream.advanceUntilChar(ch)) { 363 | stream.advance(1); // consume quote 364 | } 365 | if (lastAttributeName === 'type') { 366 | lastTypeValue = stream.getSource().substring(offset + 1, stream.pos() - 1); 367 | } 368 | state = ScannerState.WithinTag; 369 | hasSpaceAfterTag = false; 370 | return finishToken(offset, TokenType.AttributeValue); 371 | } 372 | state = ScannerState.WithinTag; 373 | hasSpaceAfterTag = false; 374 | return internalScan(); // no advance yet - jump to WithinTag 375 | case ScannerState.WithinScriptContent: 376 | // see http://stackoverflow.com/questions/14574471/how-do-browsers-parse-a-script-tag-exactly 377 | var sciptState = 1; 378 | while (!stream.eos()) { 379 | var match = stream.advanceIfRegExp(/|<\/?script\s*\/?>?/i); 380 | if (match.length === 0) { 381 | stream.goToEnd(); 382 | return finishToken(offset, TokenType.Script); 383 | } 384 | else if (match === '') { 390 | sciptState = 1; 391 | } 392 | else if (match[1] !== '/') { 393 | if (sciptState === 2) { 394 | sciptState = 3; 395 | } 396 | } 397 | else { 398 | if (sciptState === 3) { 399 | sciptState = 2; 400 | } 401 | else { 402 | stream.goBack(match.length); // to the beginning of the closing tag 403 | break; 404 | } 405 | } 406 | } 407 | state = ScannerState.WithinContent; 408 | if (offset < stream.pos()) { 409 | return finishToken(offset, TokenType.Script); 410 | } 411 | return internalScan(); // no advance yet - jump to content 412 | case ScannerState.WithinStyleContent: 413 | stream.advanceUntilRegExp(/<\/style/i); 414 | state = ScannerState.WithinContent; 415 | if (offset < stream.pos()) { 416 | return finishToken(offset, TokenType.Styles); 417 | } 418 | return internalScan(); // no advance yet - jump to content 419 | } 420 | stream.advance(1); 421 | state = ScannerState.WithinContent; 422 | return finishToken(offset, TokenType.Unknown, errorMessage); 423 | } 424 | return { 425 | scan: scan, 426 | getTokenType: function () { return tokenType; }, 427 | getTokenOffset: function () { return tokenOffset; }, 428 | getTokenLength: function () { return stream.pos() - tokenOffset; }, 429 | getTokenEnd: function () { return stream.pos(); }, 430 | getTokenText: function () { return stream.getSource().substring(tokenOffset, stream.pos()); }, 431 | getScannerState: function () { return state; }, 432 | getTokenError: function () { return tokenError; } 433 | }; 434 | } 435 | exports.createScanner = createScanner; 436 | }; 437 | 438 | define(require, module.exports); -------------------------------------------------------------------------------- /src/htmlSpec.js: -------------------------------------------------------------------------------- 1 | /*--------------------------------------------------------------------------------------------- 2 | * Some of this code has been taken from the https://github.com/Microsoft/vscode-html-languageservice/ project 3 | * and modified to work within this project. The original license header is included below. 4 | *--------------------------------------------------------------------------------------------*/ 5 | 6 | /*--------------------------------------------------------------------------------------------- 7 | * Copyright (c) Microsoft Corporation. All rights reserved. 8 | * Licensed under the MIT License. See License.txt in the project root for license information. 9 | *--------------------------------------------------------------------------------------------*/ 10 | 11 | 'use strict'; 12 | 13 | const vscode = require('vscode'); 14 | 15 | /** 16 | * Holds all tags that get defined. 17 | */ 18 | const allTags = []; 19 | 20 | /** 21 | * Create a new value completion. 22 | * 23 | * @param {string} label - The label for the completion. 24 | * @return {CompletionItem} - A new CompletionItem for a value. 25 | */ 26 | function newValue(label) { 27 | return new vscode.CompletionItem(label, vscode.CompletionItemKind.Unit); 28 | } 29 | 30 | /** 31 | * Create a new attribute completion. 32 | * 33 | * @param {string} label - The label for the completion. 34 | * @return {CompletionItem} - A new CompletionItem for an attribute. 35 | */ 36 | function newAttribute(label) { 37 | const parts = label.split(':'); 38 | const attributeItem = new vscode.CompletionItem(parts[0], vscode.CompletionItemKind.Enum); 39 | attributeItem.insertText = parts[0] + '=""'; 40 | attributeItem.command = { 41 | command: 'cursorMove', 42 | arguments: [{ 43 | to: 'left', 44 | by: 'character', 45 | value: 1, 46 | select: false 47 | }] 48 | }; 49 | 50 | if (parts.length > 0) { 51 | attributeItem.valueType = parts[1]; 52 | } 53 | 54 | return attributeItem; 55 | } 56 | 57 | /** 58 | * Create a new tag. This function will return the new tag as well as add it to a global list of tags. 59 | * 60 | * @param {string} label - A label for the tag. To specify a mapping from tag to value set use the form 'name:key'' where key is a lookup property used with 61 | * the valueSets object and name is the value to display as the label. 62 | * @param {string} documentation - A detailed description for the tag. 63 | * @param {string[]} attributes - Collection of attribute names specific to the tag. 64 | * @return {CompletionItem} - A new CompletionItem for a tag. 65 | */ 66 | function newTag(label, documentation, attributes) { 67 | const item = new vscode.CompletionItem(label, vscode.CompletionItemKind.Property); 68 | item.documentation = documentation; 69 | 70 | item.attributes = []; 71 | if (attributes) { 72 | for (let i = 0; i < attributes.length; i++) { 73 | item.attributes.push(newAttribute(attributes[i])); 74 | } 75 | } 76 | 77 | allTags.push(item); 78 | return item; 79 | } 80 | 81 | /** 82 | * Definition of html tags. The descriptions here are taken from the VSCode project. 83 | */ 84 | const tags = { 85 | // The root element 86 | html: newTag('html', 'The html element represents the root of an HTML document.', ['manifest']), 87 | // Document metadata 88 | head: newTag('head', 'The head element represents a collection of metadata for the Document.'), 89 | title: newTag('title', 'The title element represents the document\'s title or name. Authors should use titles that identify their documents even when they are used out of context, for example in a user\'s history or bookmarks, or in search results. The document\'s title is often different from its first heading, since the first heading does not have to stand alone when taken out of context.'), 90 | base: newTag('base', 'The base element allows authors to specify the document base URL for the purposes of resolving relative URLs, and the name of the default browsing context for the purposes of following hyperlinks. The element does not represent any content beyond this information.', ['href', 'target']), 91 | link: newTag('link', 'The link element allows authors to link their document to other resources.', ['href', 'crossorigin:xo', 'rel', 'media', 'hreflang', 'type', 'sizes']), 92 | meta: newTag('meta', 'The meta element represents various kinds of metadata that cannot be expressed using the title, base, link, style, and script elements.', ['name', 'http-equiv', 'content', 'charset']), 93 | style: newTag('style', 'The style element allows authors to embed style information in their documents. The style element is one of several inputs to the styling processing model. The element does not represent content for the user.', ['media', 'nonce', 'type', 'scoped:v']), 94 | // Sections 95 | body: newTag('body', 'The body element represents the content of the document.', ['onafterprint', 'onbeforeprint', 'onbeforeunload', 'onhashchange', 'onlanguagechange', 'onmessage', 'onoffline', 'ononline', 'onpagehide', 'onpageshow', 'onpopstate', 'onstorage', 'onunload']), 96 | article: newTag('article', 'The article element represents a complete, or self-contained, composition in a document, page, application, or site and that is, in principle, independently distributable or reusable, e.g. in syndication. This could be a forum post, a magazine or newspaper article, a blog entry, a user-submitted comment, an interactive widget or gadget, or any other independent item of content. Each article should be identified, typically by including a heading (h1–h6 element) as a child of the article element.'), 97 | section: newTag('section', 'The section element represents a generic section of a document or application. A section, in this context, is a thematic grouping of content. Each section should be identified, typically by including a heading ( h1- h6 element) as a child of the section element.'), 98 | nav: newTag('nav', 'The nav element represents a section of a page that links to other pages or to parts within the page: a section with navigation links.'), 99 | aside: newTag('aside', 'The aside element represents a section of a page that consists of content that is tangentially related to the content around the aside element, and which could be considered separate from that content. Such sections are often represented as sidebars in printed typography.'), 100 | h1: newTag('h1', 'The h1 element represents a section heading.'), 101 | h2: newTag('h2', 'The h2 element represents a section heading.'), 102 | h3: newTag('h3', 'The h3 element represents a section heading.'), 103 | h4: newTag('h4', 'The h4 element represents a section heading.'), 104 | h5: newTag('h5', 'The h5 element represents a section heading.'), 105 | h6: newTag('h6', 'The h6 element represents a section heading.'), 106 | header: newTag('header', 'The header element represents introductory content for its nearest ancestor sectioning content or sectioning root element. A header typically contains a group of introductory or navigational aids. When the nearest ancestor sectioning content or sectioning root element is the body element, then it applies to the whole page.'), 107 | footer: newTag('footer', 'The footer element represents a footer for its nearest ancestor sectioning content or sectioning root element. A footer typically contains information about its section such as who wrote it, links to related documents, copyright data, and the like.'), 108 | address: newTag('address', 'The address element represents the contact information for its nearest article or body element ancestor. If that is the body element, then the contact information applies to the document as a whole.'), 109 | // Grouping content 110 | p: newTag('p', 'The p element represents a paragraph.'), 111 | hr: newTag('hr', 'The hr element represents a paragraph-level thematic break, e.g. a scene change in a story, or a transition to another topic within a section of a reference book.'), 112 | pre: newTag('pre', 'The pre element represents a block of preformatted text, in which structure is represented by typographic conventions rather than by elements.'), 113 | blockquote: newTag('blockquote', 'The blockquote element represents content that is quoted from another source, optionally with a citation which must be within a footer or cite element, and optionally with in-line changes such as annotations and abbreviations.', ['cite']), 114 | ol: newTag('ol', 'The ol element represents a list of items, where the items have been intentionally ordered, such that changing the order would change the meaning of the document.', ['reversed:v', 'start', 'type:lt']), 115 | ul: newTag('ul', 'The ul element represents a list of items, where the order of the items is not important — that is, where changing the order would not materially change the meaning of the document.'), 116 | li: newTag('li', 'The li element represents a list item. If its parent element is an ol, ul, or menu element, then the element is an item of the parent element\'s list, as defined for those elements. Otherwise, the list item has no defined list-related relationship to any other li element.', ['value']), 117 | dl: newTag('dl', 'The dl element represents an association list consisting of zero or more name-value groups (a description list). A name-value group consists of one or more names (dt elements) followed by one or more values (dd elements), ignoring any nodes other than dt and dd elements. Within a single dl element, there should not be more than one dt element for each name.'), 118 | dt: newTag('dt', 'The dt element represents the term, or name, part of a term-description group in a description list (dl element).'), 119 | dd: newTag('dd', 'The dd element represents the description, definition, or value, part of a term-description group in a description list (dl element).'), 120 | figure: newTag('figure', 'The figure element represents some flow content, optionally with a caption, that is self-contained (like a complete sentence) and is typically referenced as a single unit from the main flow of the document.'), 121 | figcaption: newTag('figcaption', 'The figcaption element represents a caption or legend for the rest of the contents of the figcaption element\'s parent figure element, if any.'), 122 | main: newTag('main', 'The main element represents the main content of the body of a document or application. The main content area consists of content that is directly related to or expands upon the central topic of a document or central functionality of an application.'), 123 | div: newTag('div', 'The div element has no special meaning at all. It represents its children. It can be used with the class, lang, and title attributes to mark up semantics common to a group of consecutive elements.'), 124 | // Text-level semantics 125 | a: newTag('a', 'If the a element has an href attribute, then it represents a hyperlink (a hypertext anchor) labeled by its contents.', ['href', 'target', 'download', 'ping', 'rel', 'hreflang', 'type']), 126 | em: newTag('em', 'The em element represents stress emphasis of its contents.'), 127 | strong: newTag('strong', 'The strong element represents strong importance, seriousness, or urgency for its contents.'), 128 | small: newTag('small', 'The small element represents side comments such as small print.'), 129 | s: newTag('s', 'The s element represents contents that are no longer accurate or no longer relevant.'), 130 | cite: newTag('cite', 'The cite element represents a reference to a creative work. It must include the title of the work or the name of the author(person, people or organization) or an URL reference, or a reference in abbreviated form as per the conventions used for the addition of citation metadata.'), 131 | q: newTag('q', 'The q element represents some phrasing content quoted from another source.', ['cite']), 132 | dfn: newTag('dfn', 'The dfn element represents the defining instance of a term. The paragraph, description list group, or section that is the nearest ancestor of the dfn element must also contain the definition(s) for the term given by the dfn element.'), 133 | abbr: newTag('abbr', 'The abbr element represents an abbreviation or acronym, optionally with its expansion. The title attribute may be used to provide an expansion of the abbreviation. The attribute, if specified, must contain an expansion of the abbreviation, and nothing else.'), 134 | ruby: newTag('ruby', 'The ruby element allows one or more spans of phrasing content to be marked with ruby annotations. Ruby annotations are short runs of text presented alongside base text, primarily used in East Asian typography as a guide for pronunciation or to include other annotations. In Japanese, this form of typography is also known as furigana. Ruby text can appear on either side, and sometimes both sides, of the base text, and it is possible to control its position using CSS. A more complete introduction to ruby can be found in the Use Cases & Exploratory Approaches for Ruby Markup document as well as in CSS Ruby Module Level 1. [RUBY-UC] [CSSRUBY]'), 135 | rb: newTag('rb', 'The rb element marks the base text component of a ruby annotation. When it is the child of a ruby element, it doesn\'t represent anything itself, but its parent ruby element uses it as part of determining what it represents.'), 136 | rt: newTag('rt', 'The rt element marks the ruby text component of a ruby annotation. When it is the child of a ruby element or of an rtc element that is itself the child of a ruby element, it doesn\'t represent anything itself, but its ancestor ruby element uses it as part of determining what it represents.'), 137 | rp: newTag('rp', 'The rp element is used to provide fallback text to be shown by user agents that don\'t support ruby annotations. One widespread convention is to provide parentheses around the ruby text component of a ruby annotation.'), 138 | time: newTag('time', 'The time element represents its contents, along with a machine-readable form of those contents in the datetime attribute. The kind of content is limited to various kinds of dates, times, time-zone offsets, and durations, as described below.', ['datetime']), 139 | code: newTag('code', 'The code element represents a fragment of computer code. This could be an XML element name, a file name, a computer program, or any other string that a computer would recognize.'), 140 | var: newTag('var', 'The var element represents a variable. This could be an actual variable in a mathematical expression or programming context, an identifier representing a constant, a symbol identifying a physical quantity, a function parameter, or just be a term used as a placeholder in prose.'), 141 | samp: newTag('samp', 'The samp element represents sample or quoted output from another program or computing system.'), 142 | kbd: newTag('kbd', 'The kbd element represents user input (typically keyboard input, although it may also be used to represent other input, such as voice commands).'), 143 | sub: newTag('sub', 'The sub element represents a subscript.'), 144 | sup: newTag('sup', 'The sup element represents a superscript.'), 145 | i: newTag('i', 'The i element represents a span of text in an alternate voice or mood, or otherwise offset from the normal prose in a manner indicating a different quality of text, such as a taxonomic designation, a technical term, an idiomatic phrase from another language, transliteration, a thought, or a ship name in Western texts.'), 146 | b: newTag('b', 'The b element represents a span of text to which attention is being drawn for utilitarian purposes without conveying any extra importance and with no implication of an alternate voice or mood, such as key words in a document abstract, product names in a review, actionable words in interactive text-driven software, or an article lede.'), 147 | u: newTag('u', 'The u element represents a span of text with an unarticulated, though explicitly rendered, non-textual annotation, such as labeling the text as being a proper name in Chinese text (a Chinese proper name mark), or labeling the text as being misspelt.'), 148 | mark: newTag('mark', 'The mark element represents a run of text in one document marked or highlighted for reference purposes, due to its relevance in another context. When used in a quotation or other block of text referred to from the prose, it indicates a highlight that was not originally present but which has been added to bring the reader\'s attention to a part of the text that might not have been considered important by the original author when the block was originally written, but which is now under previously unexpected scrutiny. When used in the main prose of a document, it indicates a part of the document that has been highlighted due to its likely relevance to the user\'s current activity.'), 149 | bdi: newTag('bdi', 'The bdi element represents a span of text that is to be isolated from its surroundings for the purposes of bidirectional text formatting. [BIDI]'), 150 | bdo: newTag('dbo', 'The bdo element represents explicit text directionality formatting control for its children. It allows authors to override the Unicode bidirectional algorithm by explicitly specifying a direction override. [BIDI]'), 151 | span: newTag('span', 'The span element doesn\'t mean anything on its own, but can be useful when used together with the global attributes, e.g. class, lang, or dir. It represents its children.'), 152 | br: newTag('br', 'The br element represents a line break.'), 153 | wbr: newTag('wbr', 'The wbr element represents a line break opportunity.'), 154 | // Edits 155 | ins: newTag('ins', 'The ins element represents an addition to the document.'), 156 | del: newTag('del', 'The del element represents a removal from the document.', ['cite', 'datetime']), 157 | // Embedded content 158 | picture: newTag('picture', 'The picture element is a container which provides multiple sources to its contained img element to allow authors to declaratively control or give hints to the user agent about which image resource to use, based on the screen pixel density, viewport size, image format, and other factors. It represents its children.'), 159 | img: newTag('img', 'An img element represents an image.', ['alt', 'src', 'srcset', 'crossorigin:xo', 'usemap', 'ismap:v', 'width', 'height']), 160 | iframe: newTag('iframe', 'The iframe element represents a nested browsing context.', ['src', 'srcdoc', 'name', 'sandbox:sb', 'seamless:v', 'allowfullscreen:v', 'width', 'height']), 161 | embed: newTag('embed', 'The embed element provides an integration point for an external (typically non-HTML) application or interactive content.', ['src', 'type', 'width', 'height']), 162 | object: newTag('object', 'The object element can represent an external resource, which, depending on the type of the resource, will either be treated as an image, as a nested browsing context, or as an external resource to be processed by a plugin.', ['data', 'type', 'typemustmatch:v', 'name', 'usemap', 'form', 'width', 'height']), 163 | param: newTag('param', 'The param element defines parameters for plugins invoked by object elements. It does not represent anything on its own.', ['name', 'value']), 164 | video: newTag('video', 'A video element is used for playing videos or movies, and audio files with captions.', ['src', 'crossorigin:xo', 'poster', 'preload:pl', 'autoplay:v', 'mediagroup', 'loop:v', 'muted:v', 'controls:v', 'width', 'height']), 165 | audio: newTag('audio', 'An audio element represents a sound or audio stream.', ['src', 'crossorigin:xo', 'preload:pl', 'autoplay:v', 'mediagroup', 'loop:v', 'muted:v', 'controls:v']), 166 | source: newTag('source', 'The source element allows authors to specify multiple alternative media resources for media elements. It does not represent anything on its own.', ['src', 'type']), 167 | track: newTag('track', 'The track element allows authors to specify explicit external timed text tracks for media elements. It does not represent anything on its own.', ['default:v', 'kind:tk', 'label', 'src', 'srclang']), 168 | map: newTag('map', 'The map element, in conjunction with an img element and any area element descendants, defines an image map. The element represents its children.', ['name']), 169 | area: newTag('area', 'The area element represents either a hyperlink with some text and a corresponding area on an image map, or a dead area on an image map.', ['alt', 'coords', 'shape:sh', 'href', 'target', 'download', 'ping', 'rel', 'hreflang', 'type']), 170 | // Tabular data 171 | table: newTag('table', 'The table element represents data with more than one dimension, in the form of a table.', ['sortable:v', 'border']), 172 | caption: newTag('caption', 'The caption element represents the title of the table that is its parent, if it has a parent and that is a table element.'), 173 | colgroup: newTag('colgroup', 'The colgroup element represents a group of one or more columns in the table that is its parent, if it has a parent and that is a table element.', ['span']), 174 | col: newTag('col', 'If a col element has a parent and that is a colgroup element that itself has a parent that is a table element, then the col element represents one or more columns in the column group represented by that colgroup.', ['span']), 175 | tbody: newTag('tbody', 'The tbody element represents a block of rows that consist of a body of data for the parent table element, if the tbody element has a parent and it is a table.'), 176 | thead: newTag('thead', 'The thead element represents the block of rows that consist of the column labels (headers) for the parent table element, if the thead element has a parent and it is a table.'), 177 | tfoot: newTag('tfoot', 'The tfoot element represents the block of rows that consist of the column summaries (footers) for the parent table element, if the tfoot element has a parent and it is a table.'), 178 | tr: newTag('tr', 'The tr element represents a row of cells in a table.'), 179 | td: newTag('td', 'The td element represents a data cell in a table.', ['colspan', 'rowspan', 'headers']), 180 | th: newTag('th', 'The th element represents a header cell in a table.', ['colspan', 'rowspan', 'headers', 'scope:s', 'sorted', 'abbr']), 181 | // Forms 182 | form: newTag('form', 'The form element represents a collection of form-associated elements, some of which can represent editable values that can be submitted to a server for processing.', ['accept-charset', 'action', 'autocomplete:o', 'enctype:et', 'method:m', 'name', 'novalidate:v', 'target']), 183 | label: newTag('label', 'The label element represents a caption in a user interface. The caption can be associated with a specific form control, known as the label element\'s labeled control, either using the for attribute, or by putting the form control inside the label element itself.', ['form', 'for']), 184 | input: newTag('input', 'The input element represents a typed data field, usually with a form control to allow the user to edit the data.', ['accept', 'alt', 'autocomplete:inputautocomplete', 'autofocus:v', 'checked:v', 'dirname', 'disabled:v', 'form', 'formaction', 'formenctype:et', 'formmethod:fm', 'formnovalidate:v', 'formtarget', 'height', 'inputmode:im', 'list', 'max', 'maxlength', 'min', 'minlength', 'multiple:v', 'name', 'pattern', 'placeholder', 'readonly:v', 'required:v', 'size', 'src', 'step', 'type:t', 'value', 'width']), 185 | button: newTag('button', 'The button element represents a button labeled by its contents.', ['autofocus:v', 'disabled:v', 'form', 'formaction', 'formenctype:et', 'formmethod:fm', 'formnovalidate:v', 'formtarget', 'name', 'type:bt', 'value']), 186 | select: newTag('select', 'The select element represents a control for selecting amongst a set of options.', ['autocomplete:inputautocomplete', 'autofocus:v', 'disabled:v', 'form', 'multiple:v', 'name', 'required:v', 'size']), 187 | datalist: newTag('datalist', 'The datalist element represents a set of option elements that represent predefined options for other controls. In the rendering, the datalist element represents nothing and it, along with its children, should be hidden.'), 188 | optgroup: newTag('optgroup', 'The optgroup element represents a group of option elements with a common label.', ['disabled:v', 'label']), 189 | option: newTag('option', 'The option element represents an option in a select element or as part of a list of suggestions in a datalist element.', ['disabled:v', 'label', 'selected:v', 'value']), 190 | textarea: newTag('textarea', 'The textarea element represents a multiline plain text edit control for the element\'s raw value. The contents of the control represent the control\'s default value.', ['autocomplete:inputautocomplete', 'autofocus:v', 'cols', 'dirname', 'disabled:v', 'form', 'inputmode:im', 'maxlength', 'minlength', 'name', 'placeholder', 'readonly:v', 'required:v', 'rows', 'wrap:w']), 191 | output: newTag('output', 'The output element represents the result of a calculation performed by the application, or the result of a user action.', ['for', 'form', 'name']), 192 | progress: newTag('progress', 'The progress element represents the completion progress of a task. The progress is either indeterminate, indicating that progress is being made but that it is not clear how much more work remains to be done before the task is complete (e.g. because the task is waiting for a remote host to respond), or the progress is a number in the range zero to a maximum, giving the fraction of work that has so far been completed.', ['value', 'max']), 193 | meter: newTag('meter', 'The meter element represents a scalar measurement within a known range, or a fractional value; for example disk usage, the relevance of a query result, or the fraction of a voting population to have selected a particular candidate.', ['value', 'min', 'max', 'low', 'high', 'optimum']), 194 | fieldset: newTag('fieldset', 'The fieldset element represents a set of form controls optionally grouped under a common name.', ['disabled:v', 'form', 'name']), 195 | legend: newTag('legend', 'The legend element represents a caption for the rest of the contents of the legend element\'s parent fieldset element, if any.'), 196 | details: newTag('details', 'The details element represents a disclosure widget from which the user can obtain additional information or controls.', ['open:v']), 197 | summary: newTag('summary', 'The summary element represents a summary, caption, or legend for the rest of the contents of the summary element\'s parent details element, if any.'), 198 | dialog: newTag('dialog', 'The dialog element represents a part of an application that a user interacts with to perform a task, for example a dialog box, inspector, or window.'), 199 | script: newTag('script', 'The script element allows authors to include dynamic script and data blocks in their documents. The element does not represent content for the user.', ['src', 'type', 'charset', 'async:v', 'defer:v', 'crossorigin:xo', 'nonce']), 200 | noscript: newTag('noscript', 'The noscript element represents nothing if scripting is enabled, and represents its children if scripting is disabled. It is used to present different markup to user agents that support scripting and those that don\'t support scripting, by affecting how the document is parsed.'), 201 | template: newTag('template', 'The template element is used to declare fragments of HTML that can be cloned and inserted in the document by script.'), 202 | canvas: newTag('canvas', 'The canvas element provides scripts with a resolution-dependent bitmap canvas, which can be used for rendering graphs, game graphics, art, or other visual images on the fly.', ['width', 'height']) 203 | }; 204 | 205 | /** 206 | * Definition of common attributes. 207 | */ 208 | const commonAttributes = [ 209 | newAttribute('aria-activedescendant'), 210 | newAttribute('aria-atomic:b'), 211 | newAttribute('aria-autocomplete:autocomplete'), 212 | newAttribute('aria-busy:b'), 213 | newAttribute('aria-checked:tristate'), 214 | newAttribute('aria-colcount'), 215 | newAttribute('aria-colindex'), 216 | newAttribute('aria-colspan'), 217 | newAttribute('aria-controls'), 218 | newAttribute('aria-current:current'), 219 | newAttribute('aria-describedat'), 220 | newAttribute('aria-describedby'), 221 | newAttribute('aria-disabled:b'), 222 | newAttribute('aria-dropeffect:dropeffect'), 223 | newAttribute('aria-errormessage'), 224 | newAttribute('aria-expanded:u'), 225 | newAttribute('aria-flowto'), 226 | newAttribute('aria-grabbed:u'), 227 | newAttribute('aria-haspopup:b'), 228 | newAttribute('aria-hidden:b'), 229 | newAttribute('aria-invalid:invalid'), 230 | newAttribute('aria-kbdshortcuts'), 231 | newAttribute('aria-label'), 232 | newAttribute('aria-labelledby'), 233 | newAttribute('aria-level'), 234 | newAttribute('aria-live:live'), 235 | newAttribute('aria-modal:b'), 236 | newAttribute('aria-multiline:b'), 237 | newAttribute('aria-multiselectable:b'), 238 | newAttribute('aria-orientation:orientation'), 239 | newAttribute('aria-owns'), 240 | newAttribute('aria-placeholder'), 241 | newAttribute('aria-posinset'), 242 | newAttribute('aria-pressed:tristate'), 243 | newAttribute('aria-readonly:b'), 244 | newAttribute('aria-relevant:relevant'), 245 | newAttribute('aria-required:b'), 246 | newAttribute('aria-roledescription'), 247 | newAttribute('aria-rowcount'), 248 | newAttribute('aria-rowindex'), 249 | newAttribute('aria-rowspan'), 250 | newAttribute('aria-selected:u'), 251 | newAttribute('aria-setsize'), 252 | newAttribute('aria-sort:sort'), 253 | newAttribute('aria-valuemax'), 254 | newAttribute('aria-valuemin'), 255 | newAttribute('aria-valuenow'), 256 | newAttribute('aria-valuetext'), 257 | newAttribute('accesskey'), 258 | newAttribute('class'), 259 | newAttribute('contenteditable:b'), 260 | newAttribute('contextmenu'), 261 | newAttribute('dir:d'), 262 | newAttribute('draggable:b'), 263 | newAttribute('dropzone'), 264 | newAttribute('hidden:v'), 265 | newAttribute('id'), 266 | newAttribute('itemid'), 267 | newAttribute('itemprop'), 268 | newAttribute('itemref'), 269 | newAttribute('itemscope:v'), 270 | newAttribute('itemtype'), 271 | newAttribute('lang'), 272 | newAttribute('role:roles'), 273 | newAttribute('spellcheck:b'), 274 | newAttribute('style'), 275 | newAttribute('tabindex'), 276 | newAttribute('title'), 277 | newAttribute('translate:y') 278 | ]; 279 | 280 | /** 281 | * Definition of common attributes. 282 | */ 283 | const eventHandlers = [ 284 | newAttribute('onabort'), 285 | newAttribute('onblur'), 286 | newAttribute('oncanplay'), 287 | newAttribute('oncanplaythrough'), 288 | newAttribute('onchange'), 289 | newAttribute('onclick'), 290 | newAttribute('oncontextmenu'), 291 | newAttribute('ondblclick'), 292 | newAttribute('ondrag'), 293 | newAttribute('ondragend'), 294 | newAttribute('ondragenter'), 295 | newAttribute('ondragleave'), 296 | newAttribute('ondragover'), 297 | newAttribute('ondragstart'), 298 | newAttribute('ondrop'), 299 | newAttribute('ondurationchange'), 300 | newAttribute('onemptied'), 301 | newAttribute('onended'), 302 | newAttribute('onerror'), 303 | newAttribute('onfocus'), 304 | newAttribute('onformchange'), 305 | newAttribute('onforminput'), 306 | newAttribute('oninput'), 307 | newAttribute('oninvalid'), 308 | newAttribute('onkeydown'), 309 | newAttribute('onkeypress'), 310 | newAttribute('onkeyup'), 311 | newAttribute('onload'), 312 | newAttribute('onloadeddata'), 313 | newAttribute('onloadedmetadata'), 314 | newAttribute('onloadstart'), 315 | newAttribute('onmousedown'), 316 | newAttribute('onmousemove'), 317 | newAttribute('onmouseout'), 318 | newAttribute('onmouseover'), 319 | newAttribute('onmouseup'), 320 | newAttribute('onmousewheel'), 321 | newAttribute('onpause'), 322 | newAttribute('onplay'), 323 | newAttribute('onplaying'), 324 | newAttribute('onprogress'), 325 | newAttribute('onratechange'), 326 | newAttribute('onreset'), 327 | newAttribute('onresize'), 328 | newAttribute('onreadystatechange'), 329 | newAttribute('onscroll'), 330 | newAttribute('onseeked'), 331 | newAttribute('onseeking'), 332 | newAttribute('onselect'), 333 | newAttribute('onshow'), 334 | newAttribute('onstalled'), 335 | newAttribute('onsubmit'), 336 | newAttribute('onsuspend'), 337 | newAttribute('ontimeupdate'), 338 | newAttribute('onvolumechange'), 339 | newAttribute('onwaiting') 340 | ]; 341 | 342 | /** 343 | * Definition of all known attributes. 344 | */ 345 | const globalAttributes = [].concat(commonAttributes, eventHandlers); 346 | 347 | /** 348 | * Map of value sets for specific tags. 349 | */ 350 | const valueSets = { 351 | b: [newValue('true'), newValue('false')], 352 | u: [newValue('true'), newValue('false'), newValue('undefined')], 353 | o: [newValue('on'), newValue('off')], 354 | y: [newValue('yes'), newValue('no')], 355 | w: [newValue('soft'), newValue('hard')], 356 | d: [newValue('ltr'), newValue('rtl'), newValue('auto')], 357 | m: [newValue('GET'), newValue('POST'), newValue('dialog')], 358 | fm: [newValue('GET'), newValue('POST')], 359 | s: [newValue('row'), newValue('col'), newValue('rowgroup'), newValue('colgroup')], 360 | t: [newValue('hidden'), newValue('text'), newValue('search'), newValue('tel'), newValue('url'), newValue('email'), newValue('password'), newValue('datetime'), newValue('date'), newValue('month'), 361 | newValue('week'), newValue('time'), newValue('datetime-local'), newValue('number'), newValue('range'), newValue('color'), newValue('checkbox'), newValue('radio'), newValue('file'), newValue('submit'), newValue('image'), newValue('reset'), newValue('button')], 362 | im: [newValue('verbatim'), newValue('latin'), newValue('latin-name'), newValue('latin-prose'), newValue('full-width-latin'), newValue('kana'), newValue('kana-name'), newValue('katakana'), newValue('numeric'), newValue('tel'), newValue('email'), newValue('url')], 363 | bt: [newValue('button'), newValue('submit'), newValue('reset'), newValue('menu')], 364 | lt: [newValue('1'), newValue('a'), newValue('A'), newValue('i'), newValue('I')], 365 | mt: [newValue('context'), newValue('toolbar')], 366 | mit: [newValue('command'), newValue('checkbox'), newValue('radio')], 367 | et: [newValue('application/x-www-form-urlencoded'), newValue('multipart/form-data'), newValue('text/plain')], 368 | tk: [newValue('subtitles'), newValue('captions'), newValue('descriptions'), newValue('chapters'), newValue('metadata')], 369 | pl: [newValue('none'), newValue('metadata'), newValue('auto')], 370 | sh: [newValue('circle'), newValue('default'), newValue('poly'), newValue('rect')], 371 | xo: [newValue('anonymous'), newValue('use-credentials')], 372 | sb: [newValue('allow-forms'), newValue('allow-modals'), newValue('allow-pointer-lock'), newValue('allow-popups'), newValue('allow-popups-to-escape-sandbox'), newValue('allow-same-origin'), newValue('allow-scripts'), newValue('allow-top-navigation')], 373 | tristate: [newValue('true'), newValue('false'), newValue('mixed'), newValue('undefined')], 374 | inputautocomplete: [newValue('additional-name'), newValue('address-level1'), newValue('address-level2'), newValue('address-level3'), newValue('address-level4'), newValue('address-line1'), 375 | newValue('address-line2'), newValue('address-line3'), newValue('bday'), newValue('bday-year'), newValue('bday-day'), newValue('bday-month'), newValue('billing'), 376 | newValue('cc-additional-name'), newValue('cc-csc'), newValue('cc-exp'), newValue('cc-exp-month'), newValue('cc-exp-year'), newValue('cc-family-name'), newValue('cc-given-name'), 377 | newValue('cc-name'), newValue('cc-number'), newValue('cc-type'), newValue('country'), newValue('country-name'), newValue('current-password'), newValue('email'), 378 | newValue('family-name'), newValue('fax'), newValue('given-name'), newValue('home'), newValue('honorific-prefix'), newValue('honorific-suffix'), newValue('impp'), 379 | newValue('language'), newValue('mobile'), newValue('name'), newValue('new-password'), newValue('nickname'), newValue('organization'), newValue('organization-title'), 380 | newValue('pager'), newValue('photo'), newValue('postal-code'), newValue('sex'), newValue('shipping'), newValue('street-address'), newValue('tel-area-code'), newValue('tel'), 381 | newValue('tel-country-code'), newValue('tel-extension'), newValue('tel-local'), newValue('tel-local-prefix'), newValue('tel-local-suffix'), newValue('tel-national'), 382 | newValue('transaction-amount'), newValue('transaction-currency'), newValue('url'), newValue('username'), newValue('work')], 383 | autocomplete: [newValue('inline'), newValue('list'), newValue('both'), newValue('none')], 384 | current: [newValue('page'), newValue('step'), newValue('location'), newValue('date'), newValue('time'), newValue('true'), newValue('false')], 385 | dropeffect: [newValue('copy'), newValue('move'), newValue('link'), newValue('execute'), newValue('popup'), newValue('none')], 386 | invalid: [newValue('grammar'), newValue('false'), newValue('spelling'), newValue('true')], 387 | live: [newValue('off'), newValue('polite'), newValue('assertive')], 388 | orientation: [newValue('vertical'), newValue('horizontal'), newValue('undefined')], 389 | relevant: [newValue('additions'), newValue('removals'), newValue('text'), newValue('all'), newValue('additions text')], 390 | sort: [newValue('ascending'), newValue('descending'), newValue('none'), newValue('other')], 391 | roles: [newValue('alert'), newValue('alertdialog'), newValue('button'), newValue('checkbox'), newValue('dialog'), newValue('gridcell'), newValue('link'), newValue('log'), newValue('marquee'), 392 | newValue('menuitem'), newValue('menuitemcheckbox'), newValue('menuitemradio'), newValue('option'), newValue('progressbar'), newValue('radio'), newValue('scrollbar'), newValue('searchbox'), 393 | newValue('slider'), newValue('spinbutton'), newValue('status'), newValue('switch'), newValue('tab'), newValue('tabpanel'), newValue('textbox'), newValue('timer'), newValue('tooltip'), 394 | newValue('treeitem'), newValue('combobox'), newValue('grid'), newValue('listbox'), newValue('menu'), newValue('menubar'), newValue('radiogroup'), newValue('tablist'), newValue('tree'), 395 | newValue('treegrid'), newValue('application'), newValue('article'), newValue('cell'), newValue('columnheader'), newValue('definition'), newValue('directory'), newValue('document'), 396 | newValue('feed'), newValue('figure'), newValue('group'), newValue('heading'), newValue('img'), newValue('list'), newValue('listitem'), newValue('math'), newValue('none'), newValue('note'), 397 | newValue('presentation'), newValue('region'), newValue('row'), newValue('rowgroup'), newValue('rowheader'), newValue('separator'), newValue('table'), newValue('term'), newValue('text'), 398 | newValue('toolbar'), newValue('banner'), newValue('complementary'), newValue('contentinfo'), newValue('form'), newValue('main'), newValue('navigation'), newValue('region'), newValue('search')] 399 | }; 400 | 401 | /** 402 | * Get all defined tags. 403 | * 404 | * @return {CompletionItem[]} - All of the tags defined by this module. 405 | */ 406 | module.exports.getTags = function getTags() { 407 | return allTags; 408 | } 409 | 410 | /** 411 | * Get the tag with the given name. 412 | * 413 | * @param {string} name - The name of the tag to get. The lookup is case-insensitive. 414 | * @return {CompletionItem|undefined} - The tag with the given name or undefined if not found. 415 | */ 416 | module.exports.getTag = function getTag(name) { 417 | const nameLower = name ? name.toLowerCase() : ''; 418 | return tags[nameLower]; 419 | } 420 | 421 | /** 422 | * Get all of the attributes for the given tag. 423 | * 424 | * @param {string} tagName - The name of the tag to get attributes for. 425 | * @param {string} openCharacters - Characters that appear at the beginning of the attribute name. 426 | * @param {string} closeCharacters - Characters that appear at the end of the attribute name. 427 | * @return {CompletionItem[]} - The attributes for the given tag. Even if the tag specified isn't found the global attributes will be returned. 428 | */ 429 | module.exports.getAttributes = function getAttributes(tagName, openCharacters, closeCharacters) { 430 | let result = null; 431 | const tag = module.exports.getTag(tagName); 432 | if (!tag) { 433 | result = globalAttributes; 434 | } else { 435 | result = tag.attributes.concat(globalAttributes); 436 | } 437 | 438 | let nameClose = ''; 439 | let moveLeft = 0; 440 | const oc = openCharacters ? openCharacters : ''; 441 | const cc = closeCharacters ? closeCharacters : ''; 442 | 443 | const hasAssign = cc.length && cc[cc.length - 1] === '='; 444 | if (hasAssign) { 445 | nameClose = ''; 446 | moveLeft = 0; 447 | } else { 448 | const bindOpenRegExp = /\[|\(/; 449 | const bindCloseRegExp = /\]|\)/; 450 | 451 | // get open bind characters 452 | let bindoc = ''; 453 | const bindoc0 = oc.length > 0 ? oc[0] : ''; 454 | const bindoc1 = oc.length > 1 ? oc[1] : ''; 455 | bindoc += bindOpenRegExp.test(bindoc0) ? bindoc0 : ''; 456 | bindoc += bindOpenRegExp.test(bindoc1) ? bindoc1 : ''; 457 | 458 | // get closing bind characters 459 | let bindcc = ''; 460 | const bindcc0 = cc.length > 0 ? cc[0] : ''; 461 | const bindcc1 = cc.length > 1 ? cc[1] : ''; 462 | bindcc += bindCloseRegExp.test(bindcc0) ? bindcc0 : ''; 463 | bindcc += bindCloseRegExp.test(bindcc1) ? bindcc1 : ''; 464 | 465 | // complete binding and assignment 466 | if (bindcc === '') { 467 | switch (bindoc) { 468 | case '[': 469 | nameClose = ']=""'; 470 | break; 471 | case '[(': 472 | nameClose = ')]=""'; 473 | break; 474 | case '(': 475 | nameClose = ')=""'; 476 | break; 477 | case '([': 478 | nameClose = '])=""'; 479 | break; 480 | default: 481 | nameClose = '=""'; 482 | break; 483 | } 484 | moveLeft = 1; 485 | } else { 486 | nameClose = ''; 487 | moveLeft = -1 * bindcc.length; 488 | } 489 | } 490 | 491 | for (let i = 0; i < result.length; i++) { 492 | result[i].insertText = result[i].label + nameClose; 493 | result[i].command.arguments[0].value = moveLeft; 494 | } 495 | 496 | return result; 497 | } 498 | 499 | /** 500 | * Get the given attribute for the given tag. 501 | * 502 | * @param {string} tagName - The name of the tag to get the attribute from. 503 | * @param {string} attributeName - The name of the attribute to get. 504 | * @return {CompletionItem} - The requested attribute from the tag or undefined if the tag or attribute isn't defined. 505 | */ 506 | module.exports.getAttribute = function getAttribute(tagName, attributeName) { 507 | const attributes = module.exports.getAttributes(tagName); 508 | const nameLower = attributeName ? attributeName.toLowerCase() : ''; 509 | for (let i = 0; i < attributes.length; i++) { 510 | if (nameLower === attributes[i].label) { 511 | return attributes[i]; 512 | } 513 | } 514 | 515 | return undefined; 516 | } 517 | 518 | /** 519 | * Get the valid values for the given attribute. 520 | * 521 | * @param {string} tagName - The name of the tag to get the attribute from. 522 | * @param {string} attributeName - The name of the attribute to get the valid values for. 523 | * @return {CompletionItem} - The valid values for the specified attribute or an empty array if the tag or attribute isn't defined or all values are valid. 524 | */ 525 | module.exports.getAttributeValues = function getAttributeValues(tagName, attributeName) { 526 | const attribute = module.exports.getAttribute(tagName, attributeName); 527 | if (!attribute || !attribute.valueType || !valueSets[attribute.valueType]) { 528 | return []; 529 | } 530 | 531 | return valueSets[attribute.valueType]; 532 | } 533 | 534 | /** 535 | * Create a new closing tag. 536 | * 537 | * @param {string} name - The name of the tag. 538 | * @return {CompletionItem} - A new CompletionItem for the close tag. i.e. '/div>'. 539 | */ 540 | module.exports.newCloseTag = function newCloseTag(name) { 541 | const closeTag = new vscode.CompletionItem('/' + name, vscode.CompletionItemKind.Property); 542 | closeTag.insertText = '/' + name + '>'; 543 | return closeTag; 544 | } 545 | 546 | /** 547 | * Check if the given tag is an empty tag that has no content. 548 | * 549 | * @param {string} name - The name of the tag. 550 | * @return {bool} - true if the givne tag is an empty tag, false if it isn't. 551 | */ 552 | module.exports.isEmptyTag = function isEmptyTag(name) { 553 | switch (name.toLowerCase()) { 554 | case 'area': 555 | case 'base': 556 | case 'br': 557 | case 'col': 558 | case 'hr': 559 | case 'img': 560 | case 'input': 561 | case 'link': 562 | case 'meta': 563 | case 'param': 564 | case 'command': 565 | case 'keygen': 566 | case 'source': 567 | return true; 568 | default: 569 | false; 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vscode = require('vscode'); 4 | const Completion = require('./completion'); 5 | const Highlight = require('./highlight'); 6 | const Hover = require('./hover'); 7 | 8 | /** 9 | * Entry point for extension. 10 | * 11 | * @param {ExtensionContext} context - The extension context object. 12 | */ 13 | module.exports.activate = function activate(context) { 14 | vscode.languages.registerCompletionItemProvider(['typescript', 'javascript'], new Completion(), '<'); 15 | vscode.languages.registerDocumentHighlightProvider(['typescript', 'javascript'], new Highlight()); 16 | vscode.languages.registerHoverProvider(['typescript', 'javascript'], new Hover()); 17 | } -------------------------------------------------------------------------------- /syntaxes/source.ng.css.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [ "css", "css.erb" ], 3 | "name": "Angular2 CSS", 4 | "patterns": [ 5 | { "include": "#comment-block" }, 6 | { "include": "#selector" }, 7 | { 8 | "begin": "\\s*((@)charset\\b)\\s*", 9 | "captures": { 10 | "1": { "name": "keyword.control.at-rule.charset.css" }, 11 | "2": { "name": "punctuation.definition.keyword.css" } 12 | }, 13 | "end": "\\s*((?=;|$))", 14 | "name": "meta.at-rule.charset.css", 15 | "patterns": [ 16 | { "include": "#string-double" }, 17 | { "include": "#string-single" } 18 | ] 19 | }, 20 | { 21 | "begin": "\\s*((@)import\\b)\\s*", 22 | "captures": { 23 | "1": { "name": "keyword.control.at-rule.import.css" }, 24 | "2": { "name": "punctuation.definition.keyword.css" } 25 | }, 26 | "end": "\\s*((?=;|\\}))", 27 | "name": "meta.at-rule.import.css", 28 | "patterns": [ 29 | { "include": "#string-double" }, 30 | { "include": "#string-single" }, 31 | { 32 | "begin": "\\s*(url)\\s*(\\()\\s*", 33 | "beginCaptures": { 34 | "1": { "name": "support.function.url.css" }, 35 | "2": { "name": "punctuation.section.function.css" } 36 | }, 37 | "end": "\\s*(\\))\\s*", 38 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 39 | "patterns": [ 40 | { 41 | "match": "[^'\") \\t]+", 42 | "name": "variable.parameter.url.css" 43 | }, 44 | { "include": "#string-single" }, 45 | { "include": "#string-double" } 46 | ] 47 | }, 48 | { "include": "#media-query-list" } 49 | ] 50 | }, 51 | { 52 | "begin": "^\\s*((@)font-face)\\s*(?=\\{)", 53 | "beginCaptures": { 54 | "1": { "name": "keyword.control.at-rule.font-face.css" }, 55 | "2": { "name": "punctuation.definition.keyword.css" } 56 | }, 57 | "end": "(?!\\G)", 58 | "name": "meta.at-rule.font-face.css", 59 | "patterns": [ { "include": "#rule-list" } ] 60 | }, 61 | { 62 | "begin": "(?=^\\s*@media\\s*.*?\\{)", 63 | "end": "\\s*(\\})", 64 | "endCaptures": { "1": { "name": "punctuation.section.property-list.end.css" } }, 65 | "patterns": [ 66 | { 67 | "begin": "^\\s*((@)media)(?=.*?\\{)", 68 | "beginCaptures": { 69 | "1": { "name": "keyword.control.at-rule.media.css" }, 70 | "2": { "name": "punctuation.definition.keyword.css" }, 71 | "3": { "name": "support.constant.media.css" } 72 | }, 73 | "end": "\\s*(?=\\{)", 74 | "name": "meta.at-rule.media.css", 75 | "patterns": [ { "include": "#media-query-list" } ] 76 | }, 77 | { 78 | "begin": "\\s*(\\{)", 79 | "beginCaptures": { "1": { "name": "punctuation.section.property-list.begin.css" } }, 80 | "end": "(?=\\})", 81 | "patterns": [ { "include": "$self" } ] 82 | } 83 | ] 84 | }, 85 | { 86 | "begin": "^\\s*((@)(-(o|ms)-)?viewport)\\s*(?=\\{)", 87 | "beginCaptures": { 88 | "1": { "name": "keyword.control.at-rule.viewport.css" }, 89 | "2": { "name": "punctuation.definition.keyword.css" } 90 | }, 91 | "end": "(?!\\G)", 92 | "name": "meta.at-rule.viewport.css", 93 | "patterns": [ { "include": "#rule-list" } ] 94 | }, 95 | { 96 | "begin": "(?=\\{)", 97 | "end": "(?!\\G)", 98 | "patterns": [ { "include": "#rule-list" } ] 99 | } 100 | ], 101 | "repository": { 102 | "color-values": { 103 | "patterns": [ 104 | { 105 | "comment": "Basic color keywords: http:\/\/www.w3.org\/TR\/css3-color\/#html4", 106 | "match": "\\b(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)\\b", 107 | "name": "support.constant.color.w3c-standard-color-name.css" 108 | }, 109 | { 110 | "comment": "Extended color keywords: http:\/\/www.w3.org\/TR\/css3-color\/#svg-color", 111 | "match": "\\b(aliceblue|antiquewhite|aquamarine|azure|beige|bisque|blanchedalmond|blueviolet|brown|burlywood|cadetblue|chartreuse|chocolate|coral|cornflowerblue|cornsilk|crimson|cyan|darkblue|darkcyan|darkgoldenrod|darkgray|darkgreen|darkgrey|darkkhaki|darkmagenta|darkolivegreen|darkorange|darkorchid|darkred|darksalmon|darkseagreen|darkslateblue|darkslategray|darkslategrey|darkturquoise|darkviolet|deeppink|deepskyblue|dimgray|dimgrey|dodgerblue|firebrick|floralwhite|forestgreen|gainsboro|ghostwhite|gold|goldenrod|greenyellow|grey|honeydew|hotpink|indianred|indigo|ivory|khaki|lavender|lavenderblush|lawngreen|lemonchiffon|lightblue|lightcoral|lightcyan|lightgoldenrodyellow|lightgray|lightgreen|lightgrey|lightpink|lightsalmon|lightseagreen|lightskyblue|lightslategray|lightslategrey|lightsteelblue|lightyellow|limegreen|linen|magenta|mediumaquamarine|mediumblue|mediumorchid|mediumpurple|mediumseagreen|mediumslateblue|mediumspringgreen|mediumturquoise|mediumvioletred|midnightblue|mintcream|mistyrose|moccasin|navajowhite|oldlace|olivedrab|orangered|orchid|palegoldenrod|palegreen|paleturquoise|palevioletred|papayawhip|peachpuff|peru|pink|plum|powderblue|rebeccapurple|rosybrown|royalblue|saddlebrown|salmon|sandybrown|seagreen|seashell|sienna|skyblue|slateblue|slategray|slategrey|snow|springgreen|steelblue|tan|thistle|tomato|turquoise|violet|wheat|whitesmoke|yellowgreen)\\b", 112 | "name": "support.constant.color.w3c-extended-color-name.css" 113 | }, 114 | { 115 | "begin": "(hsla?|rgba?)\\s*(\\()", 116 | "beginCaptures": { 117 | "1": { "name": "support.function.misc.css" }, 118 | "2": { "name": "punctuation.section.function.css" } 119 | }, 120 | "end": "(\\))", 121 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 122 | "patterns": [ 123 | { 124 | "match": "(?x)\\b\n\t\t\t\t\t\t\t (0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\\s*,\\s*){2}\n\t\t\t\t\t\t\t (0*((1?[0-9]{1,2})|(2([0-4][0-9]|5[0-5])))\\b)\n\t\t\t\t\t\t\t (\\s*,\\s*((0?\\.[0-9]+)|[0-1]))?\n\t\t\t\t\t\t\t", 125 | "name": "constant.other.color.rgb-value.css" 126 | }, 127 | { 128 | "match": "\\b([0-9]{1,2}|100)\\s*%,\\s*([0-9]{1,2}|100)\\s*%,\\s*([0-9]{1,2}|100)\\s*%", 129 | "name": "constant.other.color.rgb-percentage.css" 130 | }, 131 | { "include": "#numeric-values" } 132 | ] 133 | } 134 | ] 135 | }, 136 | "comment-block": { 137 | "begin": "\/\\*", 138 | "captures": [ { "name": "punctuation.definition.comment.css" } ], 139 | "end": "\\*\/", 140 | "name": "comment.block.css" 141 | }, 142 | "media-query": { 143 | "begin": "(?i)\\s*(only|not)?\\s*(all|aural|braille|embossed|handheld|print|projection|screen|speech|tty|tv)?", 144 | "beginCaptures": { 145 | "1": { "name": "keyword.operator.logic.media.css" }, 146 | "2": { "name": "support.constant.media.css" } 147 | }, 148 | "end": "\\s*(?:(,)|(?=[{;]))", 149 | "endCaptures": { "1": { "name": "punctuation.definition.arbitrary-repitition.css" } }, 150 | "patterns": [ 151 | { 152 | "begin": "\\s*(and)?\\s*(\\()\\s*", 153 | "beginCaptures": { "1": { "name": "keyword.operator.logic.media.css" } }, 154 | "end": "\\)", 155 | "patterns": [ 156 | { 157 | "begin": "(?x)\n\t (\n\t ((min|max)-)?\n\t (\n\t ((device-)?(height|width|aspect-ratio))|\n\t (color(-index)?)|monochrome|resolution\n\t )\n\t )|grid|scan|orientation|((any-)?(pointer|hover))\n\t \\s*(?=[:)])", 158 | "beginCaptures": [ { "name": "support.type.property-name.media.css" } ], 159 | "end": "(:)|(?=\\))", 160 | "endCaptures": { "1": { "name": "punctuation.separator.key-value.css" } } 161 | }, 162 | { 163 | "match": "\\b(portrait|landscape|progressive|interlace|none|coarse|fine|on-demand|hover)", 164 | "name": "support.constant.property-value.css" 165 | }, 166 | { 167 | "captures": { 168 | "1": { "name": "constant.numeric.css" }, 169 | "2": { "name": "keyword.operator.arithmetic.css" }, 170 | "3": { "name": "constant.numeric.css" } 171 | }, 172 | "match": "\\s*(\\d+)(\/)(\\d+)" 173 | }, 174 | { "include": "#numeric-values" } 175 | ] 176 | } 177 | ] 178 | }, 179 | "media-query-list": { 180 | "begin": "\\s*(?=[^{;])", 181 | "end": "\\s*(?=[{;])", 182 | "patterns": [ { "include": "#media-query" } ] 183 | }, 184 | "numeric-values": { 185 | "patterns": [ 186 | { 187 | "captures": { "1": { "name": "punctuation.definition.constant.css" } }, 188 | "match": "(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\\b", 189 | "name": "constant.other.color.rgb-value.css" 190 | }, 191 | { 192 | "captures": { "1": { "name": "keyword.other.unit.css" } }, 193 | "match": "(?x)\n\t\t\t\t\t (?:-|\\+)?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))\n\t\t\t\t\t ((?:px|pt|ch|cm|mm|in|r?em|ex|pc|vw|vh|vmin|vmax|deg|g?rad|turn|dpi|dpcm|dppx|fr|s|ms|Hz|kHz)\\b|%)?\n\t\t\t\t\t", 194 | "name": "constant.numeric.css" 195 | } 196 | ] 197 | }, 198 | "property-values": { 199 | "patterns": [ 200 | { 201 | "include": "#template-substitution-element" 202 | }, 203 | { 204 | "match": "\\b(absolute|add|additive|all(-scroll)?|allow-end|alpha|alphabetic|alternate(-reverse)?|always|any|armenian|auto|avoid(-(column|flex|line|page|region))?|backwards|balance(-all)?|bar|baseline|below|bengali|bevel|bidi-override|block(-(end|start))?|bold|bolder|border-box|both|bottom|box-decoration|break-(all|word)|bullets|cambodian|capitalize|center|central|char|circle|cjk-(decimal|earthly-branch|heavenly-stem|ideographic)|clear|clip|clone|coarse|col-resize|collapse|color(-(burn|dodge))?|column(-reverse)?|contain|content(-box)?|contents|cover|create|crisp-edges|currentcolor|cyclic|darken|densedashed|decimal-leading-zero|decimal|default|dense|devanagari|difference|digits|disabled|disc|disclosure-(closed|open)|display|distribute-all-lines|distribute(-(letter|space))?|dot|dotted|double(-circle)?|e-resize|each-line|ease(-(in(-out)?|out))?|economy|edges|ellipsis|embed|end|ethiopic-numeric|evenodd|exact|exclude|exclusion|extends|fade|fill(-(available|box))?|filled|first(-baseline)?|fit-content|fixed|flex(-(end|start))?|flow(-root)?|force-end|forwards|fragments|from-image|full-width|geometricPrecision|georgian|grid|groove|gujarati|gurmukhi|hand|hanging|hard-light|hebrew|help|hidden|hiragana(-iroha)?|horizontal(-tb)?|hue|ideograph-(alpha|numeric|parenthesis|space)|ideographic|inactive|infinite|inherit|initial|ink|inline(-(block|end|flex|grid|list-item|start|table))?|inset|inside|inter-(character|ideograph|word)|intersect|invalid|invert|isolate(-override)?|italic|japanese-(formal|informal)|justify(-all)?|katakana(-iroha)?|keep-all|khmer|korean-(hangul-formal|hanja-(formal|informal))|landscape|lao|last(-baseline)?|leading-spaces|left|legacy|lighter|line(-(edge|through))?|list-(container|item)|local|logical|loose|lower-(alpha|armenian|greek|latin|roman)|lowercase|lr-tb|ltr|luminance|luminosity|malayalam|mandatory|manipulation|manual|margin-box|marker|match-parent|mathematical|max-content|maximum|medium|middle|minimum|mixed|mongolian|move|multiply|myanmar|n-resize|ne-resize|newspaper|no-(clip|close-quote|composite|compress|drop|open-quote|repeat)|non-blocking|none|nonzero|normal|notch|not-allowed|nowrap|numbers|numeric|nw-resize|objects|oblique|open(-quote)?|oriya|optimize(Legibility|Quality|Speed)|outset|outside(-shape)?|overlay|overline|padding-box|page|paginate|paint|pan-(x|y)|paused|persian|physical|pixelated|plaintext|pointer|portrait|pre(-(line|wrap(-auto)?))?|preserve(-(auto|breaks|spaces|trim))?|progress|proximity|punctuation|region|relative|repeat(-(x|y))?|reverse|revert|ridge|right|rotate|row(-reverse)?|row-resize|rtl|ruby(-((base|text)(-container)?))?|run-in|running|s-resize|saturation|scale-down|scroll(-position)?|se-resize|self-(end|start)|separate|sesame|show|sideways(-(left|lr|right|rl))?|simp-chinese-(formal|informal)|slice|small-caps|smooth|snap(-(block|inline))?|soft-light|solid|space(-(adjacent|around|between|end|evenly|start))?|spaces|spell-out|spread|square|start|static|step-(end|start)|sticky|stretch|strict|stroke-box|sub|subgrid|subtract|super|sw-resize|symbolic|table(-(caption|cell|(column|row)(-group)?|footer-group|header-group))?|tamil|tb-rl|telugu|text(-(bottom|top))?|thai|thick|thin|tibetan|top|transparent|triangle|trim-(adjacent|end|inner|start)|true|under|underline|underscore|unsafe|unset|upper-(alpha|latin|roman)|uppercase|upright|use-glyph-orientation|vertical(-(ideographic|lr|rl|text))?|view-box|visible(Painted|Fill|Stroke)?|w-resize|wait|whitespace|words|wrap|zero|smaller|larger|((xx?-)?(small|large))|painted|fill|stroke)\\b", 205 | "name": "support.constant.property-value.css" 206 | }, 207 | { 208 | "match": "(\\b(?i:arial|century|comic|courier|garamond|georgia|helvetica|impact|lucida|symbol|system|tahoma|times|trebuchet|utopia|verdana|webdings|sans-serif|serif|monospace)\\b)", 209 | "name": "support.constant.font-name.css" 210 | }, 211 | { "include": "#numeric-values" }, 212 | { "include": "#color-values" }, 213 | { "include": "#string-double" }, 214 | { "include": "#string-single" }, 215 | { 216 | "begin": "(blur|drop-shadow|perspective|rect|translate(3d|X|Y|Z)?)\\s*(\\()", 217 | "beginCaptures": { 218 | "1": { "name": "support.function.misc.css" }, 219 | "2": { "name": "punctuation.section.function.css" } 220 | }, 221 | "end": "(\\))", 222 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 223 | "patterns": [ { "include": "#numeric-values" } ] 224 | }, 225 | { 226 | "begin": "(cubic-bezier|matrix(3d)?|scale(3d|X|Y|Z)?)\\s*(\\()", 227 | "beginCaptures": { 228 | "1": { "name": "support.function.misc.css" }, 229 | "2": { "name": "punctuation.section.function.css" } 230 | }, 231 | "end": "(\\))", 232 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 233 | "patterns": [ 234 | { 235 | "match": "(?:-|\\+)?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))", 236 | "name": "constant.numeric.css" 237 | } 238 | ] 239 | }, 240 | { 241 | "begin": "((hue-)?rotate|skew(X|Y|Z)?)\\s*(\\()", 242 | "beginCaptures": { 243 | "1": { "name": "support.function.misc.css" }, 244 | "2": { "name": "punctuation.section.function.css" } 245 | }, 246 | "end": "(\\))", 247 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 248 | "patterns": [ 249 | { 250 | "match": "(?x)\n\t\t\t\t\t \t\t(?:-|\\+)?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))\n\t\t\t\t\t\t\t\t((?:deg|g?rad|turn)\\b)\n\t\t\t\t\t\t\t", 251 | "name": "constant.numeric.css" 252 | } 253 | ] 254 | }, 255 | { 256 | "begin": "(brightness|contrast|grayscale|invert|opacity|saturate|sepia)\\s*(\\()", 257 | "beginCaptures": { 258 | "1": { "name": "support.function.misc.css" }, 259 | "2": { "name": "punctuation.section.function.css" } 260 | }, 261 | "end": "(\\))", 262 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 263 | "patterns": [ 264 | { 265 | "match": "(?x)\n\t\t\t\t\t \t\t(?:-|\\+)?(?:(?:[0-9]+(?:\\.[0-9]+)?)|(?:\\.[0-9]+))\n\t\t\t\t\t\t\t\t(%)?\n\t\t\t\t\t\t\t", 266 | "name": "constant.numeric.css" 267 | } 268 | ] 269 | }, 270 | { 271 | "begin": "(format|local|url|attr|counter|counters)\\s*(\\()", 272 | "beginCaptures": { 273 | "1": { "name": "support.function.misc.css" }, 274 | "2": { "name": "punctuation.section.function.css" } 275 | }, 276 | "end": "(\\))", 277 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 278 | "patterns": [ 279 | { "include": "#string-single" }, 280 | { "include": "#string-double" }, 281 | { 282 | "match": "[^'\") \\t]+", 283 | "name": "variable.parameter.misc.css" 284 | } 285 | ] 286 | }, 287 | { 288 | "begin": "(var)\\s*(\\()", 289 | "beginCaptures": { 290 | "1": { "name": "support.function.misc.css" }, 291 | "2": { "name": "punctuation.section.function.css" } 292 | }, 293 | "end": "(\\))", 294 | "endCaptures": { "1": { "name": "punctuation.section.function.css" } }, 295 | "patterns": [ { "include": "#variable-name" } ] 296 | }, 297 | { 298 | "match": "\\!\\s*important", 299 | "name": "keyword.other.important.css" 300 | } 301 | ] 302 | }, 303 | "rule-list": { 304 | "begin": "\\{", 305 | "beginCaptures": [ { "name": "punctuation.section.property-list.begin.css" } ], 306 | "end": "\\}", 307 | "endCaptures": [ { "name": "punctuation.section.property-list.end.css" } ], 308 | "name": "meta.property-list.css", 309 | "patterns": [ 310 | { "include": "#comment-block" }, 311 | { 312 | "begin": "(?(['\"])(?:[^\\\\]|\\\\.)*?(\\6)))))?\\s*(\\])", 408 | "name": "meta.attribute-selector.css" 409 | } 410 | ] 411 | }, 412 | "string-double": { 413 | "begin": "\"", 414 | "beginCaptures": [ { "name": "punctuation.definition.string.begin.css" } ], 415 | "end": "\"", 416 | "endCaptures": [ { "name": "punctuation.definition.string.end.css" } ], 417 | "name": "string.quoted.double.css", 418 | "patterns": [ 419 | { 420 | "match": "\\\\.", 421 | "name": "constant.character.escape.css" 422 | } 423 | ] 424 | }, 425 | "string-single": { 426 | "begin": "'", 427 | "beginCaptures": [ { "name": "punctuation.definition.string.begin.css" } ], 428 | "end": "'", 429 | "endCaptures": [ { "name": "punctuation.definition.string.end.css" } ], 430 | "name": "string.quoted.single.css", 431 | "patterns": [ 432 | { 433 | "match": "\\\\.", 434 | "name": "constant.character.escape.css" 435 | } 436 | ] 437 | }, 438 | "variable-name": { 439 | "match": "\\-\\-[^:\\),]+", 440 | "name": "support.type.property-name.variable.css" 441 | }, 442 | "template-substitution-element": { 443 | "name": "meta.template.expression.ts", 444 | "begin": "\\$\\{", 445 | "beginCaptures": { 446 | "0": { 447 | "name": "punctuation.definition.template-expression.begin.ts" 448 | } 449 | }, 450 | "end": "\\}", 451 | "endCaptures": { 452 | "0": { 453 | "name": "punctuation.definition.template-expression.end.ts" 454 | } 455 | } 456 | } 457 | }, 458 | "scopeName": "source.ng.css", 459 | "uuid": "4e1059f4-4900-45c8-b4a7-4ad04cb635c8" 460 | } 461 | -------------------------------------------------------------------------------- /syntaxes/styles.ng.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:meta.objectliteral", 4 | "name": "object-member-ng-styles", 5 | "patterns": [ 6 | { 7 | "include": "#object-member-ng-styles" 8 | } 9 | ], 10 | "repository": { 11 | 12 | "object-member-ng-styles": { 13 | "begin": "styles\\s*:", 14 | "end": "(?=,|\\})", 15 | "name": "meta.object.member", 16 | "patterns": [ 17 | { 18 | "include": "#styles-ng" 19 | } 20 | ] 21 | }, 22 | 23 | "styles-ng": { 24 | "begin": "\\[", 25 | "beginCaptures": { 26 | "0": { 27 | "name": "meta.brace.square" 28 | } 29 | }, 30 | "end": "\\]", 31 | "endCaptures": { 32 | "0": { 33 | "name": "meta.brace.square" 34 | } 35 | }, 36 | "name": "meta.array.literal", 37 | "patterns": [ 38 | { 39 | "include": "#style-ng" 40 | }, 41 | { 42 | "include": "source.ts" 43 | } 44 | ] 45 | }, 46 | 47 | "style-ng": { 48 | "begin": "`|\\(`", 49 | "beginCaptures": { 50 | "0": { 51 | "name": "string.template" 52 | } 53 | }, 54 | "end": "`\\)|`", 55 | "endCaptures": { 56 | "0": { 57 | "name": "string.template" 58 | } 59 | }, 60 | "name": "meta.template", 61 | "contentName": "meta.embedded.block.css", 62 | "patterns": [ 63 | { 64 | "include": "source.ng.css" 65 | } 66 | ] 67 | } 68 | }, 69 | "scopeName": "styles.ng" 70 | } 71 | -------------------------------------------------------------------------------- /syntaxes/template.ng.json: -------------------------------------------------------------------------------- 1 | { 2 | "fileTypes": [], 3 | "injectionSelector": "L:meta.objectliteral", 4 | "patterns": [ 5 | { 6 | "include": "#object-member-ng-template" 7 | } 8 | ], 9 | "repository": { 10 | 11 | "object-member-ng-template": { 12 | "begin": "(template)\\s*(:)", 13 | "beginCaptures": { 14 | "1": { 15 | "name": "meta.object.member.object-literal.key" 16 | }, 17 | "2": { 18 | "name": "punctuation.object-literal.key.separator.key-value" 19 | } 20 | }, 21 | "end": "(?=,|\\})", 22 | "name": "meta.object.member", 23 | "patterns": [ 24 | { 25 | "include": "#template-ng" 26 | }, 27 | { 28 | "include": "source.ng.ts" 29 | } 30 | ] 31 | }, 32 | 33 | "template-ng": { 34 | "begin": "`|\\(`", 35 | "beginCaptures": { 36 | "0": { 37 | "name": "string.template" 38 | } 39 | }, 40 | "end": "`\\)|`", 41 | "endCaptures": { 42 | "0": { 43 | "name": "string.template" 44 | } 45 | }, 46 | "name": "meta.template", 47 | "contentName": "meta.embedded.block.html", 48 | "patterns": [ 49 | { 50 | "include": "#template-substitution-element" 51 | }, 52 | { 53 | "include": "#comment-block" 54 | }, 55 | { 56 | "include": "#ng-style" 57 | }, 58 | { 59 | "include": "#ng-children" 60 | } 61 | ] 62 | }, 63 | 64 | "template-substitution-element": { 65 | "name": "meta.template.expression.ts", 66 | "begin": "\\$\\{", 67 | "beginCaptures": { 68 | "0": { 69 | "name": "punctuation.definition.template-expression.begin.ts" 70 | } 71 | }, 72 | "end": "\\}", 73 | "endCaptures": { 74 | "0": { 75 | "name": "punctuation.definition.template-expression.end.ts" 76 | } 77 | } 78 | }, 79 | 80 | "ng-style": { 81 | "begin": "(<)([Ss][Tt][Yy][Ll][Ee])(>)", 82 | "beginCaptures": { 83 | "1": { 84 | "name": "punctuation.definition.tag.begin" 85 | }, 86 | "2": { 87 | "name": "entity.name.tag" 88 | }, 89 | "3": { 90 | "name": "punctuation.definition.tag.end" 91 | } 92 | }, 93 | "end": "()", 94 | "endCaptures": { 95 | "1": { 96 | "name": "punctuation.definition.tag.begin" 97 | }, 98 | "2": { 99 | "name": "entity.name.tag" 100 | }, 101 | "3": { 102 | "name": "punctuation.definition.tag.end" 103 | } 104 | }, 105 | "name": "ng-style", 106 | "patterns": [ 107 | { 108 | "include": "source.ng.css" 109 | } 110 | ] 111 | }, 112 | 113 | "ng-children": { 114 | "patterns": [ 115 | { 116 | "include": "#template-substitution-element" 117 | }, 118 | { 119 | "include": "#ng-tag-comment" 120 | }, 121 | { 122 | "include": "#ng-tag-open" 123 | }, 124 | { 125 | "include": "#ng-tag-close" 126 | }, 127 | { 128 | "include": "#ng-tag-invalid" 129 | }, 130 | { 131 | "include": "#ng-evaluated-code" 132 | }, 133 | { 134 | "include": "#ng-entities" 135 | } 136 | ] 137 | }, 138 | 139 | "ng-tag-comment": { 140 | "begin": "", 142 | "name": "comment.block" 143 | }, 144 | 145 | "ng-entities": { 146 | "patterns": [ 147 | { 148 | "captures": { 149 | "1": { 150 | "name": "punctuation.definition.entity" 151 | }, 152 | "3": { 153 | "name": "punctuation.definition.entity" 154 | } 155 | }, 156 | "match": "(&)([a-zA-Z0-9]+|#[0-9]+|#x[0-9a-fA-F]+)(;)", 157 | "name": "constant.character.entity" 158 | }, 159 | { 160 | "match": "&", 161 | "name": "invalid.illegal.bad-ampersand" 162 | } 163 | ] 164 | }, 165 | 166 | "ng-evaluated-code": { 167 | "begin": "{{", 168 | "beginCaptures": { 169 | "0": { 170 | "name": "punctuation.definition.brace.curly.start" 171 | } 172 | }, 173 | "end": "}}", 174 | "endCaptures": { 175 | "0": { 176 | "name": "punctuation.definition.brace.curly.end" 177 | } 178 | }, 179 | "name": "meta.brace.curly", 180 | "patterns": [ 181 | { 182 | "include": "source.ng.ts" 183 | } 184 | ] 185 | }, 186 | 187 | "ng-evaluated-code-attribute-double": { 188 | "begin": "(?<=\\]=|\\)=)\"", 189 | "beginCaptures": { 190 | "0": { 191 | "name": "punctuation.definition.string.begin" 192 | } 193 | }, 194 | "end": "\"", 195 | "endCaptures": { 196 | "0": { 197 | "name": "punctuation.definition.string.end" 198 | } 199 | }, 200 | "name": "meta.brace.curly", 201 | "patterns": [ 202 | { 203 | "include": "source.ng.ts" 204 | } 205 | ] 206 | }, 207 | 208 | "ng-evaluated-code-attribute-single": { 209 | "begin": "(?<=\\]=|\\)=)'", 210 | "beginCaptures": { 211 | "0": { 212 | "name": "punctuation.definition.string.begin" 213 | } 214 | }, 215 | "end": "'", 216 | "endCaptures": { 217 | "0": { 218 | "name": "punctuation.definition.string.end" 219 | } 220 | }, 221 | "name": "meta.brace.curly", 222 | "patterns": [ 223 | { 224 | "include": "source.ng.ts" 225 | } 226 | ] 227 | }, 228 | 229 | "ng-string-double-quoted": { 230 | "begin": "\"", 231 | "beginCaptures": { 232 | "0": { 233 | "name": "punctuation.definition.string.begin" 234 | } 235 | }, 236 | "end": "\"", 237 | "endCaptures": { 238 | "0": { 239 | "name": "punctuation.definition.string.end" 240 | } 241 | }, 242 | "name": "string.quoted.double", 243 | "patterns": [ 244 | { 245 | "include": "#template-substitution-element" 246 | }, 247 | { 248 | "include": "#ng-entities" 249 | } 250 | ] 251 | }, 252 | 253 | "ng-string-single-quoted": { 254 | "begin": "'", 255 | "beginCaptures": { 256 | "0": { 257 | "name": "punctuation.definition.string.begin" 258 | } 259 | }, 260 | "end": "'", 261 | "endCaptures": { 262 | "0": { 263 | "name": "punctuation.definition.string.end" 264 | } 265 | }, 266 | "name": "string.quoted.single", 267 | "patterns": [ 268 | { 269 | "include": "#template-substitution-element" 270 | }, 271 | { 272 | "include": "#ng-entities" 273 | } 274 | ] 275 | }, 276 | 277 | "ng-tag-attribute-assignment": { 278 | "match": "=(?=\\s*(?:'|\"|{|/\\*|//|\\n))", 279 | "name": "keyword.operator.assignment" 280 | }, 281 | 282 | "ng-tag-attribute-name": { 283 | "captures": { 284 | "1": { 285 | "name": "entity.other.attribute-name" 286 | } 287 | }, 288 | "match": "(?x)\\s*([_$@a-zA-Z*#(\\[][-$@.:\\w()\\[\\]]*)(?=\\s|=|/?>|/\\*|//)", 289 | "name": "meta.tag.attribute-name" 290 | }, 291 | 292 | "ng-tag-attributes": { 293 | "patterns": [ 294 | { 295 | "include": "#ng-tag-attribute-name" 296 | }, 297 | { 298 | "include": "#ng-tag-attribute-assignment" 299 | }, 300 | { 301 | "include": "#ng-evaluated-code-attribute-single" 302 | }, 303 | { 304 | "include": "#ng-evaluated-code-attribute-double" 305 | }, 306 | { 307 | "include": "#ng-string-double-quoted" 308 | }, 309 | { 310 | "include": "#ng-string-single-quoted" 311 | } 312 | ] 313 | }, 314 | 315 | "ng-tag-attributes-illegal": { 316 | "match": "\\S+", 317 | "name": "invalid.illegal.attribute" 318 | }, 319 | 320 | "ng-tag-close": { 321 | "begin": "()", 331 | "endCaptures": { 332 | "1": { 333 | "name": "punctuation.definition.tag.end" 334 | } 335 | }, 336 | "name": "tag.close" 337 | }, 338 | 339 | "ng-tag-invalid": { 340 | "match": "<\\s*>", 341 | "name": "invalid.illegal.tag.incomplete" 342 | }, 343 | 344 | "ng-tag-open": { 345 | "begin": "(?x)(<)([_$a-zA-Z][-$\\w.:]*(?)", 346 | "beginCaptures": { 347 | "1": { 348 | "name": "punctuation.definition.tag.begin" 349 | }, 350 | "2": { 351 | "name": "entity.name.tag" 352 | } 353 | }, 354 | "end": "(/?>)", 355 | "endCaptures": { 356 | "1": { 357 | "name": "punctuation.definition.tag.end" 358 | } 359 | }, 360 | "name": "tag.open", 361 | "patterns": [ 362 | { 363 | "include": "#ng-tag-attributes" 364 | }, 365 | { 366 | "include": "#ng-tag-attributes-illegal" 367 | } 368 | ] 369 | } 370 | }, 371 | "scopeName": "template.ng" 372 | } 373 | --------------------------------------------------------------------------------