CodeMirror js代码加亮使用总结


Posted in Javascript onMarch 25, 2017

CodeMirror是一个基于JavaScript的代码编辑器,CodeMirror支持大量语言的语法高亮,也包括css,html,js等的高亮显示。此外,CodeMirror还支持代码自动完成、搜索/替换、HTML预览、行号、选择/搜索结果高亮、可视化tab、代码自动格式等。

CodeMirror源码的github地址:https://github.com/marijnh/CodeMirror/。这几天除了上课之外有空我都是在啃着它的源码,在网上相关资料基本一点都没找到,发觉看起来真是很吃力,这篇总结也只是说个大概原理,具体细节我也很多不明白,虽然很多代码都读得懂,但是串联起来有很大问题,没注释,源码大部分变量都是猜它的意思,大部分函数也真是知道个大概实现什么功能。

CodeMirror之所以能够支持这么多语言的高亮,是由于在它的mode包中定义了多种语言的解析方式,然后对外提供统一的接口。源码中也把这部分内容分为一个层次。下面我主要是对CodeMirror库自带的对JS和CSS代码加亮脚本为例进行了研究。

github:https://github.com/marijnh/CodeMirror/blob/master/mode/javascript/javascript.js

这个是它定义的js解析方式,下面我用mode.js代替该js文件
mode.js中主要定义了两个函数:

CodeMirror.defineMode("javascript",function(config,parserConfig){}
CodeMirror.defineMIME("text/javascript", "javascript");

这两个define的作用主要是挂靠到CodeMirror这个主体类中

mode.js 对外提供的接口主要是:

return{
  startState:function(basecolumn){...}
  token:function(stream,state){...}
  indent:function(state,textAfter){...}      
}

现在解析这三个函数:

(1)startState:主要是定义函数解析执行的上下文环境,起始的状态,如果没有这个方法的话,相当于在解析过程中没有了语义。
startState键虽然不是必选但也十分重要,因为高亮往往涉及语境,即目前高亮的短语处于一个什么样的上下文中,通常影响语义和颜色的选取。所以需要一个startState来初始化一个状态物体,而这个状态物体具体包含什么内容完全由具体应用决定,CodeMirror没有硬性规定。
(2)token:这是最主要的解析语法函数,通过调用state.tokenize(stream,state)执行 function jsTokenBase(stream, state) {...},下面我会解析这个函数的主要内容.
(3)indent:这个是可有可无的

说下jsTokenBase这个函数,通过stream.next()读取下一个字符,并对字符进行判断,主要用到了正则匹配,返回的结果???

function jsTokenBase(stream,state){
  var ch = stream.next();
  if(ch == '”' || ch=”'”)
    return ...;          //判断是否存在下个”或',return [“string”,”string”]
  else if(/[\[\]{}\(\),;\:\.]/.test(ch))
    return .. ;          //匹配[]{}()...这几个,return ch
  else if(ch==”0” && stream.eat(/x/i)){
    stream.eatwhile(/[\da-f]/i); //0x**,解析16进制数
    return ret(“number”,”number”);//返回一个自己封装好的对象function ret(tp,style,cont)
  }
  else if(/\d/.test(ch) || ch ==“”&&stream.eat(/\d/)) 
    return ret(“number”,”number”);//匹配数字
  else if (ch == "/") {       //匹配注释
    if(stream.eat(“*”)) return [“comment”,”comment”];     //判断“/*”
    else if(stream.eat(“/”)) return [“comment”,”comment”];  //判断“//”
else if (state.lastType == "operator" || state.lastType == "keyword c" || /^[\[{}\(,;:]$/.test(state.lastType)) {}                     //??
    else if(stream.eatWhile(isOperatorChar)) return ret(“operator”); //判断/之后的操作符
  }
  else if(ch == "#") return [“error”,”error”]; //返回语句是错误的
  else if(isOperatorChar.test(ch)) return ret(“operator”); //返回操作符
  else { stream.eatWhile(/[\w\$_]/); return ..} //返回匹配字符串 
}

上面这个只是判断每一个ch = stream.next() 是属于什么类型的字符,也就是知道现在的字符是属于符号,字符串,数字,注释还是其他的.
接着,更重点的还是后面的字符串栈,其实在代码里面是可以看到栈的影子的。就像编译原理里面的语法分析和语义分析,你需要扫描字符串中的每个字符,并判断是否进栈或者规约,这学期的编译原理没特别认真去学,还得重新复习一遍。在前面举例子时其实就已经感受到,加亮JS或CSS代码需要上下文,而JS或CSS的大括号、冒号这种层级关系从上往下从左往右读时恰好是一个压栈的过程。

For example:
  function pushcontext(){...}
  function popcontext() {...}
  function pushlex(type,info){..}
  function poplex(){...}

然后通过function statement(type){}等进行调用。

另外要说的一点是,上面判断中为什么需要标记这么多的状态?因为高亮并不是一次性完成的,当用户完输入代码后,可能会将光标移动到任意一个点,然后修改代码,这时难道要重新解析整个代码吗?不是,但是某种程度上来说也是。是,因为用户修改点之后的代码必须重新高亮,因为用户可能输入一个大括号,从而改变所有之后代码的层级(一个大括号入栈,之后的代码的栈环境均发生改变,而加亮方案要靠栈的元素决定)。也不是。因为之前的代码当然可以很安全地认为是不需要重新加亮的,所以如果重新加亮整个代码是没必要的,试想若是几千行的代码,用户每次按键都要重新加亮,岂不是非常低效。所以,当每次捕获加亮任务,程序应该从这个修改点往后进行加亮。而实际上CodeMirror也是这么做的。这个多状态物体,就是为了能很快的重新从某个点开始重新加亮。CodeMirror其实会帮你“备份”这些状态物体(copystate函数),对于源代码中的copyState函数实现细节还真是不懂....

相比JS的mode文件,CSS会感觉简单点,容易理解点..原理也差不多,就不多说一遍了,总体上是定义大堆的keyword,然后对于每个关键符号进行判断,也用到了stack.
github源码:https://github.com/marijnh/CodeMirror/blob/master/mode/css/css.js

现在转到CodeMirror的主函数,html的调用方式为:

var editor = CodeMirror.fromTextArea(document.getElementById("code"), {
mode: "application/xml",

styleActiveLine: true, //line选择是是否加亮

lineNumbers: true, //是否显示行数

lineWrapping: true, //是否自动换行
});

其实调用过程中还可以传递更多自定义的参数,不过这里就不是讨论重点了。总之是把自定义的属性整合到CodeMirror的defaultConfig中。
在CodeMirror里通过functionhighlightLine(cm,line,state){}调用 function runMode(cm,text,mode,state,f) 再者通过 mode.token(stream,state) 调用mode.js对外公开的接口token。
在hightlightLine()函数执行前进行了大量的配置定义和分行然后格式化对应的字符串.剩下部分前两天看了不过现在还真得再看一遍才能理清楚思路啦,几千行的代码,用最笨的方法看..

如果简单的词语高亮,而且不需要考虑到很复杂的语义,用正则表达式可以简单解决..如:

var kw1 = new RegExp("(if|while|with|else|do|try|finally|return|break|continue|new|delete|throw|var|function|catch|for|switch|case|default|typeof|instanceof|true|false|null|undefined|NaN)"), //匹配关键字
kw2 = new RegExp("(\\/\\/[^\n<]*(?:\n|$))(?!<\\/)"), //匹配注释

但是正则有时候也会出很多问题,在有语义的情况下,写正则表达式是很麻烦的.高亮一般的方式是采用编译原理里面的语法分析+语义分析,这部分是有点难度的。本来还想在CodeMirror基础上改进些东西,但是发现很难,还不如自己写个简单的,过些天有空我会自己尝试下。最近开始期末考,然后还有大把综合实验,看书时间都少了。淡定!!保持心态...

附:
CodeMirror的应用可以参考:http://codemirror.net/

Javascript 相关文章推荐
Jquery优化效率 提升性能解决方案
Sep 06 Javascript
js获取TreeView控件选中节点的Text和Value值的方法
Nov 24 Javascript
jquery实现checkbox 全选/全不选的通用写法
Feb 22 Javascript
node.js调用C++开发的模块实例
Jul 03 Javascript
jQuery插件zTree实现单独选中根节点中第一个节点示例
Mar 08 Javascript
JS闭包可被利用的常见场景小结
Apr 09 Javascript
详解vue+vueRouter+webpack的简单实例
Jun 17 Javascript
vue中路由参数传递可能会遇到的坑
Dec 07 Javascript
angular实现页面打印局部功能的思考与方法
Apr 13 Javascript
Vue 使用 Mint UI 实现左滑删除效果CellSwipe
Apr 27 Javascript
Angular6 发送手机验证码按钮倒计时效果实现方法
Jan 08 Javascript
30分钟用Node.js构建一个API服务器的步骤详解
May 24 Javascript
js 去掉字符串前后空格实现代码集合
Mar 25 #Javascript
在js中做数字字符串补0(js补零)
Mar 25 #Javascript
JavaScript 字符串数字左补位,右补位,取固定长度,截位扩展函数代码
Mar 25 #Javascript
JS去掉字符串前后空格或去掉所有空格的用法
Mar 25 #Javascript
javascript作用域链与执行环境详解
Mar 25 #Javascript
vue中用动态组件实现选项卡切换效果
Mar 25 #Javascript
使用vue.js写一个tab选项卡效果
Mar 25 #Javascript
You might like
全国FM电台频率大全 - 11 浙江省
2020/03/11 无线电
php 多关键字 高亮显示实现代码
2012/04/23 PHP
php数组排序usort、uksort与sort函数用法
2014/11/17 PHP
PHP数学运算函数大汇总(经典值得收藏)
2016/04/01 PHP
PHP 爬取网页的主要方法
2018/07/13 PHP
php微信公众号开发之翻页查询
2018/10/20 PHP
PHP5.5基于mysqli连接MySQL数据库和读取数据操作实例详解
2019/02/16 PHP
php设计模式之抽象工厂模式分析【星际争霸游戏案例】
2020/01/23 PHP
PHP中SESSION过期设置
2021/03/09 PHP
JQuery textlimit 显示用户输入的字符数 限制用户输入的字符数
2009/05/14 Javascript
jquery获取对象的方法足以应付常见的各种类型的对象
2014/05/14 Javascript
javascript作用域问题实例分析
2015/07/13 Javascript
浅谈jQuery 中的事件冒泡和阻止默认行为
2016/05/28 Javascript
JSON 必知必会 观后记
2016/10/27 Javascript
详解Vue2.0里过滤器容易踩到的坑
2017/06/01 Javascript
jQuery实现倒计时功能 jQuery实现计时器功能
2017/09/19 jQuery
自定义vue组件发布到npm的方法
2018/05/09 Javascript
使用JavaScript破解web
2018/09/28 Javascript
vue element-ui之怎么封装一个自己的组件的详解
2019/05/20 Javascript
关于layui的下拉搜索框异步加载数据的解决方法
2019/09/28 Javascript
python常用函数详解
2016/09/13 Python
python用pickle模块实现“增删改查”的简易功能
2017/06/07 Python
在pandas中一次性删除dataframe的多个列方法
2018/04/10 Python
Python文件常见操作实例分析【读写、遍历】
2018/12/10 Python
TensorFlow卷积神经网络之使用训练好的模型识别猫狗图片
2019/03/14 Python
Python如何实现转换URL详解
2019/07/02 Python
Windows下PyCharm2018.3.2 安装教程(图文详解)
2019/10/24 Python
Django继承自带user表并重写的例子
2019/11/18 Python
python代码实现猜拳小游戏
2020/11/30 Python
在C语言中实现抽象数据类型什么方法最好
2014/06/26 面试题
计算机相关的自我评价
2014/01/15 职场文书
公司建议书怎么写
2014/05/15 职场文书
高温慰问简报
2015/07/21 职场文书
《圆的面积》教学反思
2016/02/19 职场文书
七年级写作指导之游记作文
2019/10/07 职场文书
css中:last-child不生效的解决方法
2022/08/05 HTML / CSS