diff --git a/lib/ace/mode/html.js b/lib/ace/mode/html.js index d5a4ab64..070978e2 100644 --- a/lib/ace/mode/html.js +++ b/lib/ace/mode/html.js @@ -39,11 +39,13 @@ var Tokenizer = require("../tokenizer").Tokenizer; var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules; var HtmlBehaviour = require("./behaviour/html").HtmlBehaviour; var HtmlFoldMode = require("./folding/html").FoldMode; +var HtmlCompletions = require("./html_completions").HtmlCompletions; var Mode = function() { var highlighter = new HtmlHighlightRules(); this.$tokenizer = new Tokenizer(highlighter.getRules()); this.$behaviour = new HtmlBehaviour(); + this.$completer = new HtmlCompletions(); this.$embeds = highlighter.getEmbeds(); this.createModeDelegates({ @@ -55,226 +57,6 @@ 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: ""}; @@ -287,54 +69,9 @@ function findTagName(iterator) { 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 { - caption: attribute, - snippet: attribute + '="$0"', - meta: "attribute" - }; - }); - } - - return []; - } + this.getCompletions = function(state, session, pos, prefix) { + return this.$completer.getCompletions(state, session, pos, prefix); + }; }).call(Mode.prototype); diff --git a/lib/ace/mode/html_completions.js b/lib/ace/mode/html_completions.js new file mode 100644 index 00000000..283b7137 --- /dev/null +++ b/lib/ace/mode/html_completions.js @@ -0,0 +1,313 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Distributed under the BSD license: + * + * Copyright (c) 2010, Ajax.org B.V. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of Ajax.org B.V. nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL AJAX.ORG B.V. BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * ***** END LICENSE BLOCK ***** */ + +define(function(require, exports, module) { +"use strict"; + +var TokenIterator = require("../token_iterator").TokenIterator; + +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); + +function hasType(token, type) { + var tokenTypes = token.type.split('.'); + return type.split('.').every(function(type){ + return (tokenTypes.indexOf(type) !== -1); + }); +} + +function findTagName(session, pos) { + var iterator = new TokenIterator(session, pos.row, pos.column); + 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 token.value; +} + +var HtmlCompletions = function() { + +}; + +(function() { + + this.getCompletions = function(state, session, pos, prefix) { + var token = session.getTokenAt(pos.row, pos.column); + + if (!token) + return []; + + // tag name + if (hasType(token, "tag-name") || (token.value == '<' && hasType(token, "text"))) + return this.getTagCompletions(state, session, pos, prefix); + + // tag attribute + if (hasType(token, 'text') || hasType(token, 'attribute-name')) + return this.getAttributeCompetions(state, session, pos, prefix); + + return []; + }; + + this.getTagCompletions = function(state, session, pos, prefix) { + 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" + }; + }); + }; + + this.getAttributeCompetions = function(state, session, pos, prefix) { + var tagName = findTagName(session, pos); + 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 { + caption: attribute, + snippet: attribute + '="$0"', + meta: "attribute" + }; + }); + }; + +}).call(HtmlCompletions.prototype); + +exports.HtmlCompletions = HtmlCompletions; +});