后台应用通常会有很多的配置页面,现在在状态是直接搞一个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才仅仅是万里长征的第一步!