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和ajax的关系详细介绍
Nov 29 Javascript
探寻Javascript执行效率问题
Nov 12 Javascript
JS onkeypress兼容性写法详解
Apr 27 Javascript
AngularJS教程之简单应用程序示例
Aug 16 Javascript
touch.js 拖动、缩放、旋转 (鼠标手势)功能代码
Feb 04 Javascript
Vue和Bootstrap的整合思路详解
Jun 30 Javascript
Angularjs的键盘事件的绑定
Jul 27 Javascript
Vue配合iView实现省市二级联动的示例代码
Jul 27 Javascript
JavaScript ES2019中的8个新特性详解
Feb 20 Javascript
ES6 Proxy实现Vue的变化检测问题
Jun 11 Javascript
js实现简单五子棋游戏
May 28 Javascript
在js文件中引入(调用)另一个js文件的三种方法
Sep 11 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多线程抓取网页实现代码
2010/07/22 PHP
探讨如何把session存入数据库
2013/06/07 PHP
php arsort 数组降序排序详细介绍
2016/11/17 PHP
Yii框架参数化查询中IN查询只能查询一个的解决方法
2017/05/20 PHP
laravel 配置路由 api和web定义的路由的区别详解
2019/09/03 PHP
利用谷歌地图API获取点与点的距离的js代码
2012/10/11 Javascript
关于火狐(firefox)及ie下event获取的两种方法
2012/12/27 Javascript
在HTML中插入JavaScript代码的示例
2015/06/03 Javascript
PhantomJS快速入门教程(服务器端的 JavaScript API 的 WebKit)
2015/08/06 Javascript
jQuery实现多级联动下拉列表查询框
2016/01/18 Javascript
AngularJS中如何使用$parse或$eval在运行时对Scope变量赋值
2016/01/25 Javascript
Bootstrap 粘页脚效果
2016/03/28 Javascript
jquery html动态添加的元素绑定事件详解
2016/05/24 Javascript
不得不看之JavaScript构造函数及new运算符
2017/08/21 Javascript
jquery实现回车键触发事件(实例讲解)
2017/11/21 jQuery
在vue中更换字体,本地存储字体非引用在线字体库的方法
2018/09/28 Javascript
过滤器vue.filters的使用方法实现
2019/09/18 Javascript
如何手写简易的 Vue Router
2020/10/10 Javascript
Python def函数的定义、使用及参数传递实现代码
2014/08/10 Python
python实现带声音的摩斯码翻译实现方法
2015/05/20 Python
Python制作动态字符图的实例
2019/01/27 Python
PyQt5 QTableView设置某一列不可编辑的方法
2019/06/25 Python
33个Python爬虫项目实战(推荐)
2019/07/08 Python
Django实现CAS+OAuth2的方法示例
2019/10/30 Python
python Scrapy爬虫框架的使用
2021/01/21 Python
谷歌浏览器小字体处理方案即12px以下字体
2013/12/17 HTML / CSS
欧洲著名的珠宝和手表网上商城:uhrcenter
2017/04/10 全球购物
英国领先的葡萄酒专家:Majestic Wine
2017/05/30 全球购物
Gucci法国官方网站:意大利奢侈品牌
2018/07/25 全球购物
主题婚礼策划方案
2014/02/10 职场文书
毕业生应聘求职信
2014/07/10 职场文书
关于晚自习早退的检讨书
2014/09/13 职场文书
计算机考试作弊检讨书1000字
2015/01/01 职场文书
房地产公司工程部经理岗位职责
2015/04/09 职场文书
Java并发编程必备之Future机制
2021/06/30 Java/Android
mysql 直接拷贝data 目录下文件还原数据的实现
2021/07/25 MySQL