在写新的模式(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详解的更多相关文章

  1. Vue插件编写、用法详解(附demo)

    Vue插件编写.用法详解(附demo) 1.概述 简单来说,插件就是指对Vue的功能的增强或补充. 比如说,让你在每个单页面的组件里,都可以调用某个方法,或者共享使用某个变量,或者在某个方法之前执行一 ...

  2. 如何编写JQuery 插件详解

    转载自:http://blog.sina.com.cn/s/blog_6154bf970101jam7.html 如今做web开发,jquery 几乎是必不可少的,就连vs神器在2010版本开始将Jq ...

  3. 如何为开发项目编写规范的README文件(windows),此文详解

    为什么要写这篇博客? 其实我是一个入坑已经半年的程序员,因为不是计算机专业,只能自己摸索,所以我深知博客的重要性.每次我的学习笔记啊,项目的,面试题啊,有的,只要有时间,我肯定上传上来,一方面自己可以 ...

  4. 前端html、CSS快速编写代码插件-Emmet使用方法技巧详解

    前端html.CSS快速编写代码插件-Emmet使用方法技巧详解   Emmet的前身是大名鼎鼎的Zen coding,如果你从事Web前端开发的话,对该插件一定不会陌生.它使用仿CSS选择器的语法来 ...

  5. HBase 协处理器编程详解,第二部分:客户端代码编写

    实现 Client 端代码 HBase 提供了客户端 Java 包 org.apache.hadoop.hbase.client.coprocessor.它提供以下三种方法来调用协处理器提供的服务: ...

  6. nand flash详解及驱动编写

    https://www.crifan.com/files/doc/docbook/linux_nand_driver/release/html/linux_nand_driver.html#nand_ ...

  7. [转帖]helm模板文件chart编写语法详解

    helm模板文件chart编写语法详解 https://blog.51cto.com/qujunorz/2421328 需要学习一下. charts编写介绍 开始 快速创建一个chart模板,helm ...

  8. python编写微信公众号首图思路详解

    前言 之前一直在美图秀秀调整自己的微信公众号首图,效果也不尽如人意,老是调来调去,最后发出来的图片被裁剪了一大部分,丢失部分关键信息,十分恼火,于是想着用python写一个程序,把微信公众号首图的模式 ...

  9. Linux dts 设备树详解(二) 动手编写设备树dts

    Linux dts 设备树详解(一) 基础知识 Linux dts 设备树详解(二) 动手编写设备树dts 文章目录 前言 硬件结构 设备树dts文件 前言 在简单了解概念之后,我们可以开始尝试写一个 ...

随机推荐

  1. Power Designer导出实体类和NHibernate xml文件

    Power Designer导出实体类和NHibernate xml文件 今天研究了一下通过PowerDesigner生成实体类和NHibernate所需要的xml文件,方法是通过Power Desi ...

  2. C++指针数组和指向指针的指针

    指针数组 定义: 如果一个 数组,其元素均为指针型数据,该数组为指针数组,也就是说,指针数组中的每一个元素相当于一个指针变量,它的值都是地址. 形式: 一维指针数组的定义形式为: int[类型名] * ...

  3. Router

    backbone库学习-Router backbone库的结构http://www.cnblogs.com/nuysoft/archive/2012/03/19/2404274.html 本文的例子来 ...

  4. HTML5小游戏UI美化版

    HTML5小游戏[是男人就下一百层]UI美化版 之前写的小游戏,要么就比较简单,要么就是比较难看,或者人物本身是不会动的. 结合了其它人的经验,研究了一下精灵运动,就写一个简单的小游戏来试一下. 介绍 ...

  5. nginx-push-stream模块源码学习(三)——发布

    一.概述     发布:发布者将MSG post到某一特定通道上,channel将信息缓存 在说明发布流程之前有必要说明下channel和msg的数据结构. 二.数据结构 2.1 MSG     发布 ...

  6. DNS:域名系统

    DNS:域名系统 1.DNS      DNS 是计算机域名系统(Domain Name System 或Domain Name Service) 的缩写,它是由解析器以及域名服务器组成的.域名服务器 ...

  7. [每日一题] OCP1z0-047 :2013-07-22 group by 子句

    这道题就是考where group by having的顺序... 答案A不正确:where应该放在group by前面 答案B不正确:having子句是用多行函数(sum,avg,max,min,c ...

  8. fastboot,bootloader,recovery

    Fastboot模式,在这个模式下,我们可以为手机进行解锁.可以在该模式下连接电脑进行软件或者指令解锁(s-off),这个模式也称“下载模式” Fastboot fastboot是一种比recover ...

  9. 设置session超时

    在web应用中,设置session超时有三种方法: 1.在web.xml文件中配置:单位是分钟,范围是针对本项目所有用户的session <session-config> <sess ...

  10. Data Guard组件等相关介绍

    1.Data Guard组件介绍  Data Guard架构归类为3个主要的组件. Data Guard 重做传输服务  重做传输服务用来将主数据库生成的重做数据传输给备用数据库. Data Guar ...