前端代码编辑器ace 语法高亮
代码编辑器codemirror和ace,都有接触过,主要是简单的api使用下。现在项目选用的ace。主要结合官网的文档,加入些自己的理解。官方原文链接https://ace.c9.io/#nav=higlighter
配置mode
在线调试语法高亮 https://ace.c9.io/tool/mode_creator.html,可以自己练手下
在ace项目的lib/ace/mode文件夹中已经定义了很多mode.下面介绍配置一个新的mode,其中包括 语法高亮规则,缩进规则,代码折叠规则。
语法高亮规则
语法高亮的规则可以继承其他规则,通过oop.inherits(MyNewHighlightRules, TextHighlightRules),比如php代码里面可以写html,所以继承HtmlHighlightRules。
ace高亮语法是一种状态机机制。每一个状态,里面可以包含多个 用正则表达式regex定义的token标记。token标记可以通过next属性决定下一个状态。
1 this.$rules = { 2 stateName: [{ 3 token: token, // String, Array, or Function: the CSS token to apply 4 regex: regex, // String or RegExp: the regexp to match 5 next: next // [Optional] String: next state to enter 6 }] 7 };
this.$rules对象用来定义状态机的规则。开始规则定义为start状态,按顺序检测start的token列表。
当检测到regex匹配的时候,文本包裹成`<span class="ace_${token}">${文本}</span>`,在编辑器显示。
1 token=='function' => class="ace_function" 2 token=='support.function' => class="ace_support ace_function"
定义regex
regex属性可以是正则表达式,也可以是正则表达式的字符串,字符串需要注意\转义
1 { 2 token : "constant.language.escape", 3 regex : /\$[\w\d]+/ 4 regex : “\\$[\\w\\d]+” 5 }
regex与token关系
regex不存在捕获,token可以是String, Function返回字符串
token函数也可以用来对regex匹配的内容,再进行细分
1 { 2 token: function(value) { 3 if (colors.hasOwnProperty(value.toLowerCase())) { 4 return "support.constant.color"; 5 } else if (fonts.hasOwnProperty(value.toLowerCase())) { 6 return "support.constant.fonts"; 7 } else { 8 return "text"; 9 } 10 }, 11 regex: "\\-?[a-zA-Z_][a-zA-Z0-9_\\-]*" 12 }
regex存在捕获分组,token可以是String(所有分组都用一样的定义), Array(长度和分组数量相同,与分组结果11对应),或Function返回Array
1 { 2 token : "constant", 3 regex : "INT_MAX|INT_MIN" 4 } // INT_MAX -> constant(INT_MAX) 5 6 { 7 token : ["constant", "keyword"], 8 regex : "^(#{1,6})(.+)$" 9 } // ### Header -> constant(###), keyword( Header) 10 11 { 12 token : "constant", 13 regex : "(a+)(b)(\\1)" 14 } // aabaa -> constant(aabaa) :: abaa -> constant(aba) + a 15 16 { 17 token : function (first, second) { 18 if (first == "a") return ["constant", "keyword"]; 19 return ["keyword", "constant"]; 20 }, 21 regex: "(.)(world)" 22 } // aworld -> constant(a), keyword(world) :: bworld -> keyword(a), constant(world)
分组需要覆盖所有匹配结果,如果需要不匹配的话,请使用(?:), (hel)lo => (hel)(?:lo)
定义状态
语法高亮状态机,一般处于start状态,如果你定义了next,会流转到next指定的状态,一直流转下去。最后,应该回到start状态,对后面的文本继续进行检测。
可以理解是一个词法分析的过程。
1 this.$rules = { 2 "start" : [{ 3 token : "text", 4 regex : "<\\!\\[CDATA\\[", 5 next : "cdata" 6 }], 7 "cdata" : [{ 8 token : "text", 9 regex : "\\]\\]>", 10 next : "start" 11 }, { 12 defaultToken : "text" 13 }] 14 };
上面示例中,当检测到"<![cdata["字符串,标记器从start状态移动到cdata状态,默认使用text标记,直到遇到关闭符号“]]>”,重新回到start状态继续检测。
扩展高亮规则
如果你的语法中间,可以通过(<?lua, <?php)标识符来嵌入其他语言块。Ace可以使用一些有用的函数来进行扩展
getRules 获取存在的规则
1 var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules; 2 this.$rules = new HtmlHighlightRules().getRules();
addRules 合并规则
1 this.$rules = { 2 "start": [ /* ... */ ] 3 }; 4 5 var newRules = { 6 "start": [ /* ... */ ] 7 } 8 9 this.addRules(newRules, "new-"); 10 11 /* 12 this.$rules = { 13 "start": [ ... ], 14 "new-start": [ ... ] 15 }; 16 */
embedRules 扩展规则
1 this.addRules(newRules, "new-") == embedRules(this.$rules, "new-", newRules); 2 3 var HtmlHighlightRules = require("./html_highlight_rules").HtmlHighlightRules; 4 var LuaHighlightRules = require("./lua_highlight_rules").LuaHighlightRules; 5 6 var LuaPageHighlightRules = function() { 7 this.$rules = new HtmlHighlightRules().getRules(); 8 9 for (var i in this.$rules) { 10 this.$rules[i].unshift({ 11 token: "keyword", 12 regex: "<\\%\\=?", 13 next: "lua-start" 14 }, { 15 token: "keyword", 16 regex: "<\\?lua\\=?", 17 next: "lua-start" 18 }); 19 } 20 this.embedRules(LuaHighlightRules, "lua-", [ 21 { 22 token: "keyword", 23 regex: "\\%>", 24 next: "start" 25 }, 26 { 27 token: "keyword", 28 regex: "\\?>", 29 next: "start" 30 } 31 ]); 32 }
当遇到'<%='或‘<?lua=’,就到lua-start状态,就使用LuaHighlightRules去检测下面的文本,直到遇到"%>", "?>"回到原来的HtmlHighlightRules;
上面例子会存在<%=?>的嵌套,存在些问题