编写CodeMirror Modes详解
在写新的模式(mode)之前,还是先来概括一下CodeMirror的设计思路。CodeMirror分为三部分:核心,模式,扩展。核心库对外开放了很多接口,而这些接口就是实现新模式或者新扩展的基石。
在使用CodeMirror的过程中,如果我们需要的mode不在CodeMirror自带的Modes里面,就需要我们自定义mode,比如ini文件类型,CodeMirror自身并没有实现,这时候就需要设计新的mode。
顺便提一下mode和language的区别,mode和language并不是完全一一对应,比如html代码中,往往包含javascript 和css代码,对于混杂着javascript和css代码的html文档,我们抽象成一个htmlmixed模式,就是所谓的“多重模式”,而 javascript和css也有自身对于的mode。
多重模式的实现对单一模式是有依赖的,这也体现了CodeMirror作者良好了设计功底,代码尽量得到最大效率的利用,htmlmixed模式内部就是"javascript","css","xml"三种模式的轮询。
大致原理:
拿典型的javascript文件来说,要完成javascript模式,只要定义一个词法分析程序就可以,首先,CodeMirror核心程序已经将整个javascript文档按照行做了拆分,你在mode里所需要做的就是写一个词法分析程序分析每一行数据。
对于单行的字符串文本,词法分析程序需要从字符串开始分析到字符串结束,在这过程中,会对需要标识的字符或者字符串(比如[ ] {} (),this,function等)做特殊标识,并且返回一个css样式(用于高亮)给对应被标识的字符或者字符串,这就是大概的处理过程。更高级的用 法还可以控制代码文档的缩进等。
注册模式:
在模式的实现脚本中,首先要调用CodeMirror.defineMode接口注册模式,这个接口函数包含两个参数:
第一个参数类型为字符串,是模式的名称,一律使用小写,一般情况下,注册的模式名称保持跟模式脚本名称一样,比如xml模式的实现脚本为"xml.js",注册的模式名就使用"xml"。
第二个参数类型为函数,该函数也有两个入参:分别是CodeMirror的配置对象(传递给CodeMirror构造函数使用,参见配置一节)和mode的配置对象(包含哪些属性由具体的mode决定)。该函数的返回值是mode对象,具体内容后面会提到。
代码约束:
在编写Mode代码的时候,所有代码都要控制在与调用CodeMirror.defineMode接口的同一作用域内,所有函数变量的申明都要写在CodeMirror.defineMode第二个参数(函数)内部。这样可以避免mode变量对全局变量造成污染。
状态对象
Mode的主要作用就是对行文本进行词法分析进而进行文本标识(高亮),当然了主要功能也是基础功能,大部分mode的设计要求远远不止对文本进行高亮,同时,某种语言的复杂程度也影响着mode的复杂程度。
有些规则很简洁明确的语言,可能每行的词法分析都是跟上一行没有关系的,比如ini文件,它的注释,块,键值肯定在同一行内,不可能跨行,词法分析函数关起门来潜心练就九阴真经就行了。
不幸的是,大多数语言的规则都是比较复杂的,就拿javascript来讲,一个数组可以在行内写,也可以跨行写,如果跨行写的话,那么后一行的词法分析肯定就会对前一行的文本有依赖,要不然无法进行正确的分析。
鉴于这个原因,CodeMirror设计了状态对象(state Object),该对象是唯一的,也可以认为是每一行共享的(类似全局变量),它的属性在词法分析函数内部做维护,如此一来不同行之前就有了联系,词法分 析函数在分析当前行文本的时候,可以从状态对象获取到自己感兴趣的信息。
在需要使用状态对象的Modes内部,mode对象必须定义一个startState属性,该属性是一个函数,函数返回一个对象,这个对象便是状态 对象,在开始对整个文档进行分析之前,CodeMirror就会利用startState生成一个状态对象,而这个状态对象里面有哪些属性,完全是根据 mode的设计要求和思路来决定的。
词法分析函数
在mode的设计中,mode对象的词法分析函数(token(stream,state))是最重要的,所有的mode都要定义这个方法。
首先token函数有两个入参:
stream:CodeMirror核心内部定义的Stream类的实例,它不光包含当前行的文本信息,还包含已经分析到的字符位置(字符串分析的时候,是一个个字符顺序分析的),也包含一系列使用的字符串处理方法;
state:就是状态对象,每次词法分析,都会传入这个参数,然后在词法分析内部维护这个参数的属性。
在分析同一行文本的时候,token函数会被反复调用,只要是遇到一个需要标识的子字符串片段或者空白字符,token都会return,直到分析到整行结尾,看个例子,有以下一行javascript代码:
var name = this.name;
一、 token从字符串第一个字符开始分析,此时stream.pos为0,遇到一个关键字"var",则返回一个css样式"keyword",同时 stream.pos移动到2,CodeMirror将会给var外面加上span标签,该标签的class就是"cm-kerword"(被核心模块加 上了"cm-"前缀,token函数返回的所有样式都会被加上这个前缀),cm-kerword样式定义在codemirror.css文件中。
二、 字符串还没有到结束,第二次调用token分析,遇到一个空格,返回null,也就是说空格不需要高亮,同时stream.pos移动到3;
三、字符串还没有到结束,第三次调用token分析,遇到一个属性name,返回"properties",同时stream.pos移动到7,CodeMirror将会给name外面加上span标签,该标签的class是"cm-properties"。
四、字符串还没有到结束,继续调用token,直到分析完这个字符串才会结束。
其实词法分析的概要过程就是这样的,是不是很简单呢?具体实现的细节其实还是比较繁琐的。
样式化
样式化是在词法分析过程中完成的,也是词法分析的目的,CodeMirror支持好几种样式化的方式,都是通过特殊关键字来区别的
1. 普通样式,不包含"line-"和"line-background-"前缀的都是普通样式,作用在整行字符串的某个词汇或子字符串上。
2. 带有"line-"前缀的样式,作用在整行文字上,在DOM结构里,该样式是加在pre标签上的。需要注意的是在整行字符串分析的过程过,只要有一次返回值包含"line-"前缀,pre标签就会被加上对应样式,多次返回不同值,则会增加多个样式。
3. 带有"line-background-"前缀的样式作用整行文字的背景,该样式会创建一个跟pre平行的块元素,其z-index小于pre标 签,pre自身的背景被设置为透明,所以新创建的块元素模拟了整行文本的背景色。跟"line-"前缀一样,多次返回便是增加多个样式
4. token还可以返回多重样式(使用空格做分割),例如返回"string error",则是字符串样式(着色)和Error(划线)样式的叠加。
PS:codemirror.css文件没有的样式都需要自行定义,一般自定义的样式文件写在跟mode脚本文件同目录同名。
看个被标识好的整行DOM结构或许会更清楚:
<!-- 整个一行的DOM结构 -->
<div class="" style="position: relative;">
<!-- 只有token函数返回带有line-background-关键字的样式,这个节点才会被创建 -->
<!-- 本示例token返回值包含line-background-linebgc -->
<!-- CodeMirror-linebackground样式定义在codemirror.css中,linebgc则需要自己定义-->
<div class="linebgc CodeMirror-linebackground"></div>
<!-- 行号对应的DOM 不在token的作用范围内 -->
<div class="CodeMirror-gutter-wrapper" style="position: absolute; left: -29px;">
<div class="CodeMirror-linenumber CodeMirror-gutter-elt" style="left: 0px; width: 20px;">3</div>
</div>
<!-- 正式的整行文本对应的DOM,该标签的背景被CodeMirror设置为透明 -->
<!-- pre标签被增加了"linetext"样式,是因为token返回值中包含了"line-linetext",有"line-"前缀,所以生成了这个样式 -->
<!-- linetext 样式需要自行定义 -->
<pre class="linetext ">
<span style="padding-right: 0.1px;">
<!-- token返回值包含key -->
<span class="cm-key">innodb_log_file_size</span>
<!-- token返回值包含"equal-symbol strong" 是一个多重样式 -->
<span class="cm-equal-symbol cm-strong">=</span>
<!-- token返回值包含"vaule" -->
<span class="cm-value">10M</span>
<!-- token返回值包含"comment" -->
<span class="cm-comment">;ddad</span>
</span>
</pre>
</div>
编写CodeMirror Modes详解的更多相关文章
- Vue插件编写、用法详解(附demo)
Vue插件编写.用法详解(附demo) 1.概述 简单来说,插件就是指对Vue的功能的增强或补充. 比如说,让你在每个单页面的组件里,都可以调用某个方法,或者共享使用某个变量,或者在某个方法之前执行一 ...
- 如何编写JQuery 插件详解
转载自:http://blog.sina.com.cn/s/blog_6154bf970101jam7.html 如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jq ...
- 如何为开发项目编写规范的README文件(windows),此文详解
为什么要写这篇博客? 其实我是一个入坑已经半年的程序员,因为不是计算机专业,只能自己摸索,所以我深知博客的重要性.每次我的学习笔记啊,项目的,面试题啊,有的,只要有时间,我肯定上传上来,一方面自己可以 ...
- 前端html、CSS快速编写代码插件-Emmet使用方法技巧详解
前端html.CSS快速编写代码插件-Emmet使用方法技巧详解 Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来 ...
- HBase 协处理器编程详解,第二部分:客户端代码编写
实现 Client 端代码 HBase 提供了客户端 Java 包 org.apache.hadoop.hbase.client.coprocessor.它提供以下三种方法来调用协处理器提供的服务: ...
- nand flash详解及驱动编写
https://www.crifan.com/files/doc/docbook/linux_nand_driver/release/html/linux_nand_driver.html#nand_ ...
- [转帖]helm模板文件chart编写语法详解
helm模板文件chart编写语法详解 https://blog.51cto.com/qujunorz/2421328 需要学习一下. charts编写介绍 开始 快速创建一个chart模板,helm ...
- python编写微信公众号首图思路详解
前言 之前一直在美图秀秀调整自己的微信公众号首图,效果也不尽如人意,老是调来调去,最后发出来的图片被裁剪了一大部分,丢失部分关键信息,十分恼火,于是想着用python写一个程序,把微信公众号首图的模式 ...
- Linux dts 设备树详解(二) 动手编写设备树dts
Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...
随机推荐
- WordPress搭建Personal Blog【转】
早就想搭建一个专属于自己的博客了,用来记录自己生活.学习的点点滴滴.之所以选WordPress,主要是因为它可以支持Latex,而且特别喜欢其简约的风格. WordPress有个the famous ...
- rcp(插件开发) The activator X for bundle Y is invalid 解决办法
最近在做插件产品的重构,重构的过程当中难免有一些细节的地方 忘记修改 ,导致出现莫名的问题. 比如这个问题: The activator X for bundle Y is invalid 这个问题从 ...
- [原]逆向iOS SDK -- _UIImageAtPath 的实现(SDK 5.1)
注释过的反汇编代码:http://pan.baidu.com/share/link?shareid=3491166579&uk=537224442 伪代码(不精确,仅供参考): NSStrin ...
- 企业架构研究总结(37)——TOGAF企业连续体和工具之架构资源库及架构工具的选择
3. 架构资源库 在一个企业,尤其是在一个大型企业中,建设一个成熟的架构往往会产生大量的工作产品.为了很好地管理和利用这些工作产品,企业需要制定一个正式的针对不同类型架构资产的分类方法,并且还需要专门 ...
- hibernate查询出的数据和数据库不一致
之前直接使用hibernate的时候就出现过已经进行物理存储后的数据,查询不出来的情况,既然是已经存储后的数据,说明事务已经提交,想必问题出在查询时,查询的缓存,没有查询数据库.时有时无就很奇怪. 现 ...
- .NET自带IOC
.NET自带IOC 本文主要把MEF作为一种IOC容器进行讲解,.net中可用的IOC容器非常多,如 CastleWindsor,Unity,Autofac,ObjectBuilder,Structu ...
- Ubuntu环境搭建1
Ubuntu环境搭建(一) 其实每次重装Ubuntu系统的时候都要进行一次基本到环境配置,而且每次总会忘记一些环境配置到东西,所以就写下这个博文,方便自己以后重装系统的时候回顾,同时也给大家做为重装系 ...
- java applet初探之计算器
这里是用java写的一个计算器,用appllet的方式在浏览器中运行,如果电脑上装有java运行环境jre就能一试.将html代码保存为*.html(名称能够自定),applettest编译为clas ...
- Python 3语法小记(五)字符串
Python 3 的源码的默认编码方式为 UTF-8 在Python 3,所有的字符串都是使用Unicode编码的字符序列. utf-8 是一种将字符编码成字节序列的方式.字节即字节,并非字符.字符在 ...
- Ext JS4百强应用: 用grid.plugin.CellEditing做高级查询 --第10强
Ext JS4,用grid.plugin.CellEditing做高级查询: 写了90%,界面出来了,小兴奋就贴出来,还有细节要调整,基本能用. 代码: Ext.define('chenghao.ad ...