在寫新的模式(mode)以前,仍是先來歸納一下CodeMirror的設計思路。CodeMirror分爲三部分:核心,模式,擴展。核心庫對外開放了不少接口,而這些接口就是實現新模式或者新擴展的基石。javascript
在使用CodeMirror的過程當中,若是咱們須要的mode不在CodeMirror自帶的Modes裏面,就須要咱們自定義mode,好比ini文件類型,CodeMirror自身並無實現,這時候就須要設計新的mode。css
順便提一下mode和language的區別,mode和language並非徹底一一對應,好比html代碼中,每每包含javascript 和css代碼,對於混雜着javascript和css代碼的html文檔,咱們抽象成一個htmlmixed模式,就是所謂的「多重模式」,而 javascript和css也有自身對於的mode。html
多重模式的實現對單一模式是有依賴的,這也體現了CodeMirror做者良好了設計功底,代碼儘可能獲得最大效率的利用,htmlmixed模式內部就是"javascript","css","xml"三種模式的輪詢。java
拿典型的javascript文件來講,要完成javascript模式,只要定義一個詞法分析程序就能夠,首先,CodeMirror核心程序已經將整個javascript文檔按照行作了拆分,你在mode裏所須要作的就是寫一個詞法分析程序分析每一行數據。數組
對於單行的字符串文本,詞法分析程序須要從字符串開始分析到字符串結束,在這過程當中,會對須要標識的字符或者字符串(好比[ ] {} (),this,function等)作特殊標識,而且返回一個css樣式(用於高亮)給對應被標識的字符或者字符串,這就是大概的處理過程。更高級的用 法還能夠控制代碼文檔的縮進等。app
在模式的實現腳本中,首先要調用CodeMirror.defineMode接口註冊模式,這個接口函數包含兩個參數:函數
第一個參數類型爲字符串,是模式的名稱,一概使用小寫,通常狀況下,註冊的模式名稱保持跟模式腳本名稱同樣,好比xml模式的實現腳本爲"xml.js",註冊的模式名就使用"xml"。this
第二個參數類型爲函數,該函數也有兩個入參:分別是CodeMirror的配置對象(傳遞給CodeMirror構造函數使用,參見配置一節)和mode的配置對象(包含哪些屬性由具體的mode決定)。該函數的返回值是mode對象,具體內容後面會提到。spa
在編寫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;
1、 token從字符串第一個字符開始分析,此時stream.pos爲0,遇到一個關鍵字"var",則返回一個css樣式"keyword",同時 stream.pos移動到2,CodeMirror將會給var外面加上span標籤,該標籤的class就是"cm-kerword"(被核心模塊加 上了"cm-"前綴,token函數返回的全部樣式都會被加上這個前綴),cm-kerword樣式定義在codemirror.css文件中。
2、 字符串尚未到結束,第二次調用token分析,遇到一個空格,返回null,也就是說空格不須要高亮,同時stream.pos移動到3;
3、字符串尚未到結束,第三次調用token分析,遇到一個屬性name,返回"properties",同時stream.pos移動到7,CodeMirror將會給name外面加上span標籤,該標籤的class是"cm-properties"。
4、字符串尚未到結束,繼續調用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>