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操作select下拉列表框的代码
Jun 04 Javascript
JavaScript生成SQL查询表单的方法
Aug 13 Javascript
jQuery中delegate()方法的用法详解
Oct 13 Javascript
bootstrap日历插件datetimepicker使用方法
Dec 14 Javascript
JS实现iframe自适应高度的方法示例
Jan 07 Javascript
JSON 数据详解及实例代码分析
Jan 20 Javascript
原生js实现旋转木马轮播图效果
Feb 27 Javascript
JavaScript两个变量交换值的实现方法
Mar 01 Javascript
Bootstrap 设置datetimepicker在屏幕上面弹出设置方法
Mar 21 Javascript
angular+ionic返回上一页并刷新页面
Aug 08 Javascript
代码分析vue中如何配置less
Sep 28 Javascript
详解vue使用$http服务端收不到参数
Apr 19 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
php 多线程上下文中安全写文件实现代码
2009/12/28 PHP
PHP代码保护--Zend Guard的使用详解
2013/06/03 PHP
php防止站外远程提交表单的方法
2014/10/20 PHP
PHP函数实现从一个文本字符串中提取关键字的方法
2015/07/01 PHP
CodeIgniter连贯操作的底层原理分析
2016/05/17 PHP
php根据年月获取当月天数及日期数组的方法
2016/11/30 PHP
js中关于String对象的replace使用详解
2011/05/24 Javascript
Javascript算符的优先级介绍
2013/03/20 Javascript
JQuery中如何传递参数如click(),change()等具体实现
2013/04/28 Javascript
javascript中普通函数的使用介绍
2013/12/19 Javascript
JavaScript检测弹出窗口是否已经关闭的方法
2015/03/24 Javascript
对JavaScript客户端应用编程的一些建议
2015/06/24 Javascript
AngularJS入门心得之directive和controller通信过程
2016/01/25 Javascript
jQuery Easyui Tabs扩展根据自定义属性打开页签
2016/08/15 Javascript
在Docker快速部署Node.js应用的详细步骤
2016/09/02 Javascript
JavaScript函数绑定用法实例分析
2017/11/14 Javascript
用vue写一个仿简书的轮播图的示例代码
2018/03/13 Javascript
修改node.js默认的npm安装目录实例
2018/05/15 Javascript
深入理解JavaScript 中的执行上下文和执行栈
2018/10/23 Javascript
[42:32]Secret vs Optic 2018国际邀请赛小组赛BO2 第二场 8.18
2018/08/19 DOTA
python根据时间生成mongodb的ObjectId的方法
2015/03/13 Python
Python3操作SQL Server数据库(实例讲解)
2017/10/21 Python
如何优雅地处理Django中的favicon.ico图标详解
2018/07/05 Python
详解django实现自定义manage命令的扩展
2019/08/13 Python
Nike英国官网:Nike.com (UK)
2017/02/13 全球购物
美国领先的奢侈美容零售商:Bluemercury
2017/07/26 全球购物
英国Boots旗下太阳镜网站:Boots Designer Sunglasses
2018/07/07 全球购物
灵泰克Java笔试题
2016/01/09 面试题
奶茶专卖店创业计划书
2014/01/18 职场文书
初三化学教学反思
2014/01/23 职场文书
副总经理任命书
2014/06/05 职场文书
工作汇报开头与结尾怎么写
2014/11/08 职场文书
美丽人生观后感
2015/06/03 职场文书
2016简历自荐信优秀范文
2016/01/29 职场文书
2019年度行政文员工作计划范本!
2019/07/04 职场文书
python工具dtreeviz决策树可视化和模型可解释性
2022/03/03 Python