diff --git a/lib/ace/mode/html.js b/lib/ace/mode/html.js
index 066c79e7..0eaf0c1c 100644
--- a/lib/ace/mode/html.js
+++ b/lib/ace/mode/html.js
@@ -55,6 +55,226 @@ var Mode = function() {
};
oop.inherits(Mode, TextMode);
+var commonAttributes = [
+ "accesskey",
+ "class",
+ "contenteditable",
+ "contextmenu",
+ "dir",
+ "draggable",
+ "dropzone",
+ "hidden",
+ "id",
+ "lang",
+ "spellcheck",
+ "style",
+ "tabindex",
+ "title",
+ "translate"
+];
+
+var eventAttributes = [
+ "onabort",
+ "onblur",
+ "oncancel",
+ "oncanplay",
+ "oncanplaythrough",
+ "onchange",
+ "onclick",
+ "onclose",
+ "oncontextmenu",
+ "oncuechange",
+ "ondblclick",
+ "ondrag",
+ "ondragend",
+ "ondragenter",
+ "ondragleave",
+ "ondragover",
+ "ondragstart",
+ "ondrop",
+ "ondurationchange",
+ "onemptied",
+ "onended",
+ "onerror",
+ "onfocus",
+ "oninput",
+ "oninvalid",
+ "onkeydown",
+ "onkeypress",
+ "onkeyup",
+ "onload",
+ "onloadeddata",
+ "onloadedmetadata",
+ "onloadstart",
+ "onmousedown",
+ "onmousemove",
+ "onmouseout",
+ "onmouseover",
+ "onmouseup",
+ "onmousewheel",
+ "onpause",
+ "onplay",
+ "onplaying",
+ "onprogress",
+ "onratechange",
+ "onreset",
+ "onscroll",
+ "onseeked",
+ "onseeking",
+ "onselect",
+ "onshow",
+ "onstalled",
+ "onsubmit",
+ "onsuspend",
+ "ontimeupdate",
+ "onvolumechange",
+ "onwaiting"
+];
+
+var globalAttributes = commonAttributes.concat(eventAttributes);
+
+var attributeMap = {
+ "html": ["manifest"],
+ "head": [],
+ "title": [],
+ "base": ["href", "target"],
+ "link": ["href", "hreflang", "rel", "media", "type", "sizes"],
+ "meta": ["http-equiv", "name", "content", "charset"],
+ "style": ["type", "media", "scoped"],
+ "script": ["charset", "type", "src", "defer", "async"],
+ "noscript": ["href"],
+ "body": ["onafterprint", "onbeforeprint", "onbeforeunload", "onhashchange", "onmessage", "onoffline", "onpopstate", "onredo", "onresize", "onstorage", "onundo", "onunload"],
+ "section": [],
+ "nav": [],
+ "article": ["pubdate"],
+ "aside": [],
+ "h1": [],
+ "h2": [],
+ "h3": [],
+ "h4": [],
+ "h5": [],
+ "h6": [],
+ "header": [],
+ "footer": [],
+ "address": [],
+ "main": [],
+ "p": [],
+ "hr": [],
+ "pre": [],
+ "blockquote": ["cite"],
+ "ol": ["start", "reversed"],
+ "ul": [],
+ "li": ["value"],
+ "dl": [],
+ "dt": [],
+ "dd": [],
+ "figure": [],
+ "figcaption": [],
+ "div": [],
+ "a": ["href", "target", "ping", "rel", "media", "hreflang", "type"],
+ "em": [],
+ "strong": [],
+ "small": [],
+ "s": [],
+ "cite": [],
+ "q": ["cite"],
+ "dfn": [],
+ "abbr": [],
+ "data": [],
+ "time": ["datetime"],
+ "code": [],
+ "var": [],
+ "samp": [],
+ "kbd": [],
+ "sub": [],
+ "sup": [],
+ "i": [],
+ "b": [],
+ "u": [],
+ "mark": [],
+ "ruby": [],
+ "rt": [],
+ "rp": [],
+ "bdi": [],
+ "bdo": [],
+ "span": [],
+ "br": [],
+ "wbr": [],
+ "ins": ["cite", "datetime"],
+ "del": ["cite", "datetime"],
+ "img": ["alt", "src", "height", "width", "usemap", "ismap"],
+ "iframe": ["name", "src", "height", "width", "sandbox", "seamless"],
+ "embed": ["src", "height", "width", "type"],
+ "object": ["param", "data", "type", "height" , "width", "usemap", "name", "form", "classid"],
+ "param": ["name", "value"],
+ "video": ["src", "autobuffer", "autoplay", "loop", "controls", "width", "height", "poster"],
+ "audio": ["src", "autobuffer", "autoplay", "loop", "controls"],
+ "source": ["src", "type", "media"],
+ "track": ["kind", "src", "srclang", "label", "default"],
+ "canvas": ["width", "height"],
+ "map": ["name"],
+ "area": ["shape", "coords", "href", "hreflang", "alt", "target", "media", "rel", "ping", "type"],
+ "svg": [],
+ "math": [],
+ "table": ["summary"],
+ "caption": [],
+ "colgroup": ["span"],
+ "col": ["span"],
+ "tbody": [],
+ "thead": [],
+ "tfoot": [],
+ "tr": [],
+ "td": ["headers", "rowspan", "colspan"],
+ "th": ["headers", "rowspan", "colspan", "scope"],
+ "form": ["accept-charset", "action", "autocomplete", "enctype", "method", "name", "novalidate", "target"],
+ "fieldset": ["disabled", "form", "name"],
+ "legend": [],
+ "label": ["form", "for"],
+ "input": ["type", "accept", "alt", "autocomplete", "checked", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "height", "list", "max", "maxlength", "min", "multiple", "pattern", "placeholder", "readonly", "required", "size", "src", "step", "width", "files", "value"],
+ "button": ["autofocus", "disabled", "form", "formaction", "formenctype", "formmethod", "formnovalidate", "formtarget", "name", "value", "type"],
+ "select": ["autofocus", "disabled", "form", "multiple", "name", "size"],
+ "datalist": [],
+ "optgroup": ["disabled", "label"],
+ "option": ["disabled", "selected", "label", "value"],
+ "textarea": ["autofocus", "disabled", "form", "maxlength", "name", "placeholder", "readonly", "required", "rows", "cols", "wrap"],
+ "keygen": ["autofocus", "challenge", "disabled", "form", "keytype", "name"],
+ "output": ["for", "form", "name"],
+ "progress": ["value", "max"],
+ "meter": ["value", "min", "max", "low", "high", "optimum"],
+ "details": ["open"],
+ "summary": [],
+ "command": ["type", "label", "icon", "disabled", "checked", "radiogroup", "command"],
+ "menu": ["type", "label"],
+ "dialog": ["open"]
+};
+
+var allElements = Object.keys(attributeMap);
+
+
+var TokenIterator = require("../token_iterator").TokenIterator;
+
+function hasType(token, type) {
+ var tokenTypes = token.type.split('.');
+ return type.split('.').every(function(type){
+ return (tokenTypes.indexOf(type) !== -1);
+ });
+}
+
+function findTagName(iterator) {
+ var token = iterator.getCurrentToken();
+ if (!token || !hasType(token, 'tag') && !(hasType(token, 'text') && token.value.match('/'))){
+ do {
+ token = iterator.stepBackward();
+ } while (token && (hasType(token, 'string') || hasType(token, 'operator') || hasType(token, 'attribute-name') || hasType(token, 'text')));
+ }
+ if (!token || !hasType(token, 'tag-name') || iterator.stepBackward().value.match('/')) {
+ return
+ }
+ var element = token.value;
+
+ return element;
+}
+
(function() {
this.blockComment = {start: ""};
@@ -67,6 +287,54 @@ oop.inherits(Mode, TextMode);
return false;
};
+ this.getCompletions = function(state, session, pos, prefix, callback) {
+ var iterator = new TokenIterator(session, pos.row, pos.column);
+ var token = iterator.getCurrentToken();
+
+ if (!token)
+ return [];
+
+ // tag name
+ if (hasType(token, "tag-name") || (token.value == '<' && hasType(token, "text"))) {
+ var elements = allElements;
+ if (prefix) {
+ elements = elements.filter(function(element){
+ return element.indexOf(prefix) === 0;
+ });
+ }
+ return elements.map(function(element){
+ return {
+ value: element,
+ meta: "tag"
+ };
+ });
+ }
+
+ // tag attribute
+ if (hasType(token, 'text') || hasType(token, 'attribute-name')) {
+ var tagName = findTagName(iterator);
+ if (!tagName)
+ return [];
+ var attributes = globalAttributes;
+ if (tagName in attributeMap) {
+ attributes = attributes.concat(attributeMap[tagName]);
+ }
+ if (prefix) {
+ attributes = attributes.filter(function(attribute){
+ return attribute.indexOf(prefix) === 0;
+ });
+ }
+ return attributes.map(function(attribute){
+ return {
+ value: attribute,
+ meta: "attribute"
+ };
+ });
+ }
+
+ return [];
+ }
+
}).call(Mode.prototype);
exports.Mode = Mode;