在写新的模式(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. window wamp下xhprof的安装使用,Graphviz配置

    在新的工作安排下来前赶紧把手上工作优化下,本来是靠xdebug+grindview来的,不过还是麻烦,就换上这个轻量级的性能测试了.网上搜的大多都是lamp环境下的,wamp下的倒是不多,而且不好用, ...

  2. 文本框文字垂直居中 CSS

    <html> <head> <style type="text/css"> #text { height:20px; vertical-alig ...

  3. 用来代替本机IP的万能IP:127.0.0.1

    用来代替本机IP的通用IP:127.0.0.1

  4. UIButton根据不同的状态设置layer

    这需要用到KVO,监听button的highlighted属性的变化,在监听回调里根据监听到得属性值设置layer 设置监听如下 [button  addObserver:self forKeyPat ...

  5. jQuery Validation让验证变得如此容易(二)

    上一个例子我们是统一引用jquery.validate.js这样所有必填字段的提示信息都将是This field is required. 现在要改成动态提示,比如姓名如果为空则提示姓名不能为空,密码 ...

  6. ASP.NET MVC学习笔记-----Filter2

    ASP.NET MVC学习笔记-----Filter(2) 接上篇ASP.NET MVC学习笔记-----Filter(1) Action Filter Action Filter可以基于任何目的使用 ...

  7. linux培训笔记1

    第五章 文件和目录的管理   linux命令的基本格式   #命令 [选项] [参数]   1.linux下的常用命令   (1)ls    查看(列出)目录下的内容      -l    查看文件详 ...

  8. Swift3.0服务端开发(二) 静态文件添加、路由配置以及表单提交

    今天博客中就来聊一下Perfect框架的静态文件的添加与访问,路由的配置以及表单的提交.虽然官网上有聊静态文件的访问的部分,但是在使用Perfect框架来访问静态文件时还是有些点需要注意的,这些关键点 ...

  9. Wget 命令详解

    Wget主要用于下载文件,在安装软件时会经常用到,以下对wget做简单说明. 1.下载单个文件:wget http://www.baidu.com.命令会直接在当前目录下载一个index.html的文 ...

  10. C# 关于委托的小例子

    本例子是一个关于委托的小例子[猫叫,狗跳,人喊]. 委托是C#开发中一个非常重要的概念,理解起来也和常规的方法不同,但一旦理解清楚,就可以信手拈来,随处可用. 委托是对方法的抽象.它存储的就是一系列具 ...