后台应用通常会有很多的配置页面,现在在状态是直接搞一个textarea来搞:
这种粗糙、QJ用户的行为显然不是一个有节操的程序员该干的,为了这种配置的页面好用最少应该提供三个功能:
在网上逛了一圈发现Colud9的ACE还是挺好用的(GitHub上10000+的Star也说明了实力),可以在这里体验下,国内的coding.net平台所使用的也是该工具,但是做出来的效果离Colud9还是有不小的差距。。。
由于网上的资料不多,学习过程非常痛苦,有些东西是直接看代码去猜测运行的原理,希望对需要的人有一点帮助!
使用方法很经典(和HighCharts等差不多):
<!DOCTYPE html> <head> <style type="text/css" media="screen"> #editor { position: absolute; top: 0; right: 0; bottom: 0; left: 0; } </style> </head> <body> <div id="editor">function foo(items) { var x = "All this is syntax highlighted"; return x; }</div> <script src="https://ace.c9.io/build/src/ace.js" type="text/javascript" charset="utf-8"></script> <script> var editor = ace.edit("editor"); editor.setTheme("ace/theme/monokai"); editor.getSession().setMode("ace/mode/javascript"); </script> </body> </html>
完成后ACE会在<div id="editor"></div>
渲染出对应的DOM结构,其中:
内部有大部分语言的模式,正常情况下你是不需要考虑后面的内容的。后来有同事用drools做规则引擎,这个没有现成的只能自己来搞!
让代码高亮显示的思路很简单,比如将关键字用<span class="ace_keyword"></span>
包裹起来并在CSS中设置样式即可,那么关键问题就是如何对源码进行词法分析了。
在ACE中实现的时候有点像状态机,在处理时不断地从当前状态的规则集中找到匹配regex
的规则,之后跳转到对应的next
状态:
this.$rules = { "start" : [ { token: <token>, // class名称 regex: <regex>, // 正则匹配串 next: <next> // 下个状态 } ] };
相比普通的词法分析器,一个状态就相当于一个小的隔离环境,在这个隔离环境中在做匹配的时候难度要小很多,你只需要考虑在该状态内可能出现的TOKEN即可:
另外比较好用的几点:
那么简单来做个drools高亮的Mode如下:
ace.define("ace/mode/drools_highlight_rules", function(require, exports, module) { "use strict"; var oop = require("../lib/oop"); var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules; var DroolsHighlightRules = function(){ var keywordMapper = this.createKeywordMapper({ "keyword": "when|then|rule|end|salience" }, "identifier"); this.$rules = { "start" :[{ token : keywordMapper, regex : "[a-zA-Z_$][a-zA-Z0-9_$]*\\b" }] }; }; oop.inherits(DroolsHighlightRules, TextHighlightRules); exports.DroolsHighlightRules = DroolsHighlightRules; }); ace.define("ace/mode/drools", function(require, exports, module) { "use strict"; var oop = require("../lib/oop"); var TextMode = require("./text").Mode; var DroolsHighlightRules = require("./drools_highlight_rules").DroolsHighlightRules; var DroolsMode = function(){ this.HighlightRules = DroolsHighlightRules; }; oop.inherits(DroolsMode, TextMode); (function() { this.$id = "ace/mode/drools" }).call(DroolsMode.prototype), exports.Mode = DroolsMode; });
效果如下:
更多的功能可以在这里看到,不过貌似很不稳定- -!
ACE为MODE扩展缩进预留了接口,你只需要实现getNextLineIndent方法即可,会将其结果自动添加到新行的开头,参数含义为:
来看个例子(如果是{[(结尾的,那么下一行的缩进加一):
(function(){ this.getNextLineIndent = function(state, line, tab) { var indent = this.$getIndent(line); if (state == "start") { var match = line.match(/^.*[\{\(\[]\s*$/); // 如果是{[(结尾的,那么下一行的缩进加一 if (match) { indent += tab; } } return indent; }; }).call(DroolsMode.prototype);
对大部分的缩进需求这种方式完全能搞定了。
折叠的实现涉及到范围对象new Range(Number startRow, Number startColumn, Number endRow, Number endColumn):
实现折叠需要提供两个方法:
下面我们为drools实现一个简单的折叠逻辑,也就是将rule与end之间的部分能够折叠隐藏:
ace.define("ace/mode/folding/drools_fold", function(require, exports, module) { "use strict"; var Range = require("../../range").Range; var FoldMode = exports.FoldMode = function() {}; (function() { this.getFoldWidget = function(session, foldStyle, row) { var line = session.getLine(row); if (line == "rule") return "start"; return ""; }; this.getFoldWidgetRange = function(session, foldStyle, row) { var startRow = row, startColumn = 4; var endRow = row+1, endColumn = 3; while(session.getLine(endRow) != "end"){ endRow += 1; } return new Range(startRow, startColumn, endRow, endColumn); }; }).call(FoldMode.prototype); });
然后需要在drools的Mode中设置foldingRules:
var DroolsFoldMode = require("ace/mode/folding/drools_fold").FoldMode; var DroolsMode = function(){ this.foldingRules = new DroolsFoldMode(); };
在ACE中提供了一个基本的折叠块:fold_mode,用来折叠相同的缩进部分,如果需要直接集成即可!
实现了这三部分功能已经可以大幅度提升简单配置页面的体验,但是作为一个在线的IDE才仅仅是万里长征的第一步!