编写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文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...
随机推荐
- jQuery选择表格中的列
以选择table中的第一列为例: 方式1: $("table tr").find("td:first").html("first column&quo ...
- [转]编译 JavaScriptCore For iOS
from: http://phoboslab.org/log/2011/06/javascriptcore-project-files-for-ios github: https://github.c ...
- WPF之TreeList的实现方法1
WPF之TreeList的实现方法(一) 做项目的时候根据需求,WPF现有的控件不能完全满足我们的需求, 很多时候我们需要对现有的控件做一下加工. 最简单的我们可能会把Tree转换成List形式有的叫 ...
- JS简单验证密码强度
<input type="password" id="password" value=""/><button id=&qu ...
- 桥接模式及C++实现 C++设计模式-AbstractFactory抽象工厂模式
桥接模式及C++实现 桥接模式 先说说桥接模式的定义:将抽象化(Abstraction)与实现化(Implementation)分离,使得二者可以独立地变化. 桥接模式号称设计模式中最难理解的模式之一 ...
- 使用Func<T1, T2, TResult>
使用Func<T1, T2, TResult> 委托返回匿名对象 Func<T1, T2, TResult> 委托 封装一个具有两个参数并返回 TResult 参数指定的类 ...
- linux 消息队列例子
/author:DriverMonkey //phone:13410905075 //mail:bookworepeng@Hotmail.com //qq:196568501 #include < ...
- Coursera Machine Leaning 课程总结
最近机器学习比较火热,身边很多同学都有兴趣,恰好Coursera上面有这门课.讲授这门课的Andrew教授任职斯坦福大学,是coursera的联合创建者,在机器学习领域颇有成就,身边的同学也有几位上这 ...
- (Python学习9)Python虚拟机中的一般表达式
1.准备工作 执行.py程序时,Python解释器对PyCodeObject的co_code存储的字节码进行解释执行,同时co_consts存储了常量,co_names存储了变量名称.用compile ...
- javascript深入之location对象和history对象
浏览器的location 和history对象: 一.location对象: 1>location.reload() 相当于按浏览器上的“刷新”(IE)或“Reload”(Netscape)键. ...