WsztRush

用ACE来写代码(一)

后台应用通常会有很多的配置页面,现在在状态是直接搞一个textarea来搞:

这种粗糙、QJ用户的行为显然不是一个有节操的程序员该干的,为了这种配置的页面好用最少应该提供三个功能:

  1. 高亮:提示关键字、类型,可以在第一时间发现简单错误
  2. 缩进:增加配置可读性的最简单的办法
  3. 折叠:有一大堆的配置的时候折叠还是挺好用的

在网上逛了一圈发现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结构,其中:

  1. editor.setTheme:设置主题,有点皮肤的意思
  2. editor.getSession().setMode:设置模式,上面这段设置的是JavaScript的模式,其中包括了高亮、缩进、折叠凳功能

内部有大部分语言的模式,正常情况下你是不需要考虑后面的内容的。后来有同事用drools做规则引擎,这个没有现成的只能自己来搞!

高亮

让代码高亮显示的思路很简单,比如将关键字用<span class="ace_keyword"></span>包裹起来并在CSS中设置样式即可,那么关键问题就是如何对源码进行词法分析了。

在ACE中实现的时候有点像状态机,在处理时不断地从当前状态的规则集中找到匹配regex的规则,之后跳转到对应的next状态:

this.$rules = {
    "start" : [
        {
            token: <token>, // class名称
            regex: <regex>, // 正则匹配串
            next:  <next>   // 下个状态
        }
    ]
};

相比普通的词法分析器,一个状态就相当于一个小的隔离环境,在这个隔离环境中在做匹配的时候难度要小很多,你只需要考虑在该状态内可能出现的TOKEN即可:

另外比较好用的几点:

  1. 设置merge:true来合并连续的token
  2. 利用createKeywordMapper来简化关键字列表的编写

那么简单来做个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方法即可,会将其结果自动添加到新行的开头,参数含义为:

  1. state:状态
  2. line:当前行内容
  3. tab:缩进符号

来看个例子(如果是{[(结尾的,那么下一行的缩进加一):

(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):

  1. startRow:开始行
  2. startColumn:开始列
  3. endRow:结束行
  4. endColumn:结束列

实现折叠需要提供两个方法:

  1. getFoldWidget:折叠开始的地方
  2. getFoldWidgetRange:需要折叠的范围

下面我们为drools实现一个简单的折叠逻辑,也就是将ruleend之间的部分能够折叠隐藏:

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才仅仅是万里长征的第一步!