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 相关文章推荐
代码精简的可以实现元素圆角的js函数
Jul 21 Javascript
Javascript String对象扩展HTML编码和解码的方法
Jun 02 Javascript
javascript遍历控件实例详细解析
Jan 10 Javascript
移动设备web开发首选框架:zeptojs介绍
Jan 29 Javascript
JavaScript判断表单中多选框checkbox选中个数的方法
Aug 17 Javascript
js下将金额数字每三位一逗号分隔
Feb 19 Javascript
angular第三方包开发整理(小结)
Apr 19 Javascript
vue-router实现编程式导航的代码实例
Jan 19 Javascript
Vue加载json文件的方法简单示例
Jan 28 Javascript
如何换个角度使用VUE过滤器详解
Sep 11 Javascript
javascript 函数的暂停和恢复实例详解
Apr 25 Javascript
原生JavaScript实现刮刮乐
Sep 29 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
发现的以前不知道的函数
2006/09/19 Javascript
input按钮的事件处理大全
2010/12/10 Javascript
javascript 数字格式化输出的实现代码
2013/12/10 Javascript
纯js写的分页表格数据为json串
2014/02/18 Javascript
javascript字母大小写转换的4个函数详解
2014/05/09 Javascript
jquery层级选择器的实现(匹配后代元素div)
2016/09/05 Javascript
将JSON字符串转换成Map对象的方法
2016/11/30 Javascript
使用JavaScript判断用户输入的是否为正整数(两种方法)
2017/02/05 Javascript
vue2实现移动端上传、预览、压缩图片解决拍照旋转问题
2017/04/13 Javascript
mac上node.js环境的安装测试
2017/07/03 Javascript
详解javascript常用工具类的封装
2018/01/30 Javascript
vue2.0 axios跨域并渲染的问题解决方法
2018/03/08 Javascript
vue中使用element-ui进行表单验证的实例代码
2018/06/22 Javascript
elementUI select组件使用及注意事项详解
2019/05/29 Javascript
vue 使用element-ui中的Notification自定义按钮并实现关闭功能及如何处理多个通知
2019/08/17 Javascript
基于 Vue 的 Electron 项目搭建过程图文详解
2020/07/22 Javascript
详解Python异常处理中的Finally else的功能
2017/12/29 Python
Python找出最小的K个数实例代码
2018/01/04 Python
python处理数据,存进hive表的方法
2018/07/04 Python
Python Selenium 之数据驱动测试的实现
2019/08/01 Python
Django 解决model 反向引用中的related_name问题
2020/05/19 Python
Python用来做Web开发的优势有哪些
2020/08/05 Python
python爬虫中采集中遇到的问题整理
2020/11/27 Python
Python 爬虫批量爬取网页图片保存到本地的实现代码
2020/12/24 Python
CSS3,线性渐变(linear-gradient)的使用总结
2017/01/09 HTML / CSS
Dr. Martens马汀博士法国官网:马丁靴鼻祖
2020/01/15 全球购物
Myprotein比利时官方网站:欧洲第一运动营养品牌
2020/10/04 全球购物
学生自我鉴定模板
2013/12/30 职场文书
施工班组长岗位职责
2014/01/05 职场文书
中专毕业生自我鉴定
2014/02/02 职场文书
餐厅经理岗位职责和岗位目标
2014/02/13 职场文书
二年级学生期末评语
2014/12/26 职场文书
幼儿园迎新生欢迎词
2015/09/30 职场文书
2016最新离婚协议书范本及程序
2016/03/18 职场文书
合同补充协议书
2016/03/24 职场文书
完美解决golang go get私有仓库的问题
2021/05/05 Golang