User manual in Chinese中文用户手册

第一章 前言和基本用法

一、前言

我一直都想研究IDE是怎么实现的,但是由于种种困难,一直没有去做这项工作。我还特别想通过尝试进行翻译,来提高自己的英文水平。另外,我还希望通过研究别人的代码来提高自己的编程水平。

总之,在种种因素的推进之下,我打算开始着手做CodeMirror的官方文档翻译以及使用教程编写工作。我从来没有在国外的网站上见到过中文,因此我特意向CodeMirror的作者询问,是否可以在官方的论坛上写作中文文章,然后得到了作者的肯定。
CodeMirror是一个了不起的项目,其完成度高,功能丰富,作为一个开源的插件,堪称优秀。作者对于项目的维护非常活跃,他也一直关注着官方的论坛,几乎是有帖必回。

但是不得不说,在线IDE并不是一种应用广泛的插件,甚至可以说是应用面非常狭窄。尽管如此,它也绝对有独特的用武之地,尤其是用在一些固定格式的文本编写中。CodeMirror为使用者提供的编程接口,使得你可以非常容易地扩展其功能。
我今天着手做翻译工作,就是希望能给大家留下一份适合中文使用者浏览的资料,以东方人的思维和行文方式,尽量为大家学习和使用CodeMirror提供便利,当然,也是为我自己以后复习,留下宝贵的学习笔记。但是不得不说,我希望大家还是尽量提高英文水平,增强自己的英文阅读能力。我本身英文水平不高,翻译难免谬误,请大家见谅。也推荐大家像我一样,通过翻译英文来学习英文。
我本来已经进行了一部分的翻译工作,但是觉得做得不太好,单纯的英汉互译并没有太大的意义,google翻译可以为我们做这件事情,并且效果不错。经过一番思考,我觉得在翻译的基础上,增加自己的思考内容,我不是学js的,我本身是java程序员,所言内容如有谬误之处,欢迎指正。
最后,我认为我的教程应该达到以下特点:

  1. 一定是由浅入深的,从头到尾学习的人,一定会逐渐提高。
  2. 一定是模块分离的,对于有一定基础的人,能够任意选择章节阅读,而不会被其它章节束缚。
  3. 一定是丰富有益且简明易懂的,不能为了我自己写作时爽快,就写得让阅读者一头雾水,读起来磕磕绊绊。

最后,祝大家阅读愉快!欢迎多加指正。

二、基本用法

引入编辑器的主库文件

<script src="lib/codemirror.js"></script>

在5.20及以后的版本中,压缩包已经不提供codemirror.js文件了,需要自己去编译生成。但是我并不想去编译,因为我并不擅长这个,所以,我选择了5.19版本来使用。

实例化编辑器

方式1.构造函数

a.传入一个dom对象

var myCodeMirror = CodeMirror(document.body);

你现在将在页面上看到一个编辑器,简陋的很,像是一个普通纯文本编辑器。
入参是一个dom对象,源码通过调用这个dom对象的appendChild方法,将编辑器对象作为你传入的dom对象的孩子附加上去。由于这个方法是所有dom对象都有的,所以你可以传递任意dom对象作为参数。但是,像input、br这样的dom对象,很显然不能允许孩子的存在,虽然能调用方法生成节点,但是那将毫无意义。所以你最好将div这种对象传进去。

这种方式类似于:你需要提供一本书,让老师在上面盖上小红花印章。

b.传入一个函数

有的时候,也许你希望更直接的控制。你希望将小红花盖在你家的门上,你总不能把大门扛到老师这里来。你只需要把老师的盖小红花的印章拿过来就行了。
这个构造函数的入参也可以是一个函数:

var myCodeMirror = CodeMirror(function(editorObj) {
  // 用户代码 
      //myTextArea.parentNode.replaceChild(editorObj, somePlaceHodlerDiv);
});

editorObj就是编辑器对象。现在你可以在用户代码的位置将页面上一个你设置的用来占位置的div替换为这个elt编辑器:

someDomObj.replaceChild(editorObj, somePlaceHodlerDiv);

也可以在某个对象里面添加这个对象:

someDomObj.appendChild(editorObj);

或者是你喜欢的任何处理这个编辑器的方式,如jQuery
$(someObj).html(editorObj);
$(someObj).append(editorObj);

方式2.静态方法

通常情况下,我们不止是想在页面上展示一个编辑器而已。我们的最终目的是使用这个编辑器进行文本编辑,并且最后获得编辑器的值。对于上面方法实现的编辑器,我们当然有办法获得它的值,库函数会为我们提供这样的接口。但是,我们的文本编辑,往往是放在form表单里的,对吧?我们往往希望,表单在提交时,自动获得input、select、textarea等各种类型的值。

为了达到自动获取编辑器的值并作为表单域的目的。我们可以使用CodeMirro的静态方法fromTextArea。
首先,要想在表单里面提交,一个作为表单域的textArea肯定是必不可少的,然后,为了能在提交时,把这个域的值自动替换成我们编辑器里的值,我们需要使一个编辑器与这个textArea关联。这些都不需要我们做,我们只需要调用库提供的方法就可以了:

<body>
	<textarea id="editor" name="userComment"></textarea>
	<script type="text/javascript">
		var myTextarea = document.getElementById("editor");
		var myCodeMirror = CodeMirror(myTextarea);
	</script>
</body>

它的效果是这样的:页面上会有一个textarea元素,但是是不显示的。textarea元素会有一个紧挨着的兄弟节点,就是我们的编辑器对象。在提交表单时,textarea的value被自动替换为editor的值。
当使用这种方式实例化一个编辑器时,将会给实例附加上以下的方法:

cm.save()
 把编辑器的内容,复制到textArea的value中去
cm.toTextArea()
    移除编辑器,还原textarea,将编辑器的内容放到其中。
    如果你动态创建和删除一个编辑器,而没有破坏它所在的form,必须保证调用toTextArea方法来移除编辑
    器。否则提交表单可能造成内存泄露。
cm.getTextArea() → TextAreaElement
    返回textarea对象

方式3.在初始化编辑时,控制编辑器的配置

无论是构造函数,还是fromtextArea方法,其实都带有第二个参数。这个参数是一个对象,里面包含了CodeMirror支持的各种配置。所有你没有主动去配置的选项都采用默认值。
####构造函数

var myCodeMirror = CodeMirror(document.body, {
  value: "这些内容被作为编辑器的初始内容",
  mode:  "javascript"
});

var myCodeMirror = CodeMirror(function(elt) {
  myTextArea.parentNode.replaceChild(elt, myTextArea);
}, {value: myTextArea.value});

静态函数

var myCodeMirror = CodeMirror.fromTextArea(myTextArea,{});

本章总结

现在,我们来使用我们本章知道的所有知识,并且,规划一下我们接下来的学习方向。请在阅读这段代码时,顺便回忆前面讲过的所有知识点。动动你的脑子去回忆,别只用眼睛学习。

<body>
	<form>
		<textarea id="editor" name="fieldName"></textarea>
		<script type="text/javascript">
			var myTextarea = document.getElementById("editor");
			var myCodeMirror = CodeMirror(myTextarea,{
				value: "function myScript(){return 100;}\n",
	 			mode:  "javascript"
			});
		</script>
	</form>
</body>

疑点:配置里面的选项都是什么意思?
没错,我们下一节就是要讲,可以对CodeMirror进行哪些配置。

第二章 主题风格(theme and style)

作为一名开发人员,一定要明白一件事,在软件编程方面,我们能获取的文档有两种:

  1. 条理清晰,但是破碎不堪,晦涩难懂,如字典般,用哪查哪。
    如:javadoc、很多软件的官方指南的参数说明部分(包括CodeMirror的官方用户指南的多数章节)。

  2. 从应用角度出发,逻辑清晰,跟着学就能成功。这类文档,通常称之为教程。

我写的这系列文章,与上述两种文档,也有很多不同。本教程不会详细介绍所有的参数选项(想知道所有参数请查阅官方手册),也不会太过详细介绍软件的使用细节。而是着重于引发读者的思考。在展示基本用法的基础上,鼓励读者自己去尝试和探索软件的使用、去优化和改造自己的程序。

艺术家,其实只是艺术的第一个接触者,当作品被创造出来之后,这件作品到底蕴含了什么,就不是创造他的艺术家能说了算的了。
一个软件的功能是有限的,一篇教程的内容是有限的,而人的想象力和创造力是无限的。希望你能在CodeMirror学到的东西比我们能给你的更多。

一、主题风格

在CodeMirror的初始化配置里面,有这样一个选项:

theme: string
这个选项配置了编辑器所使用的主题。

1.加载主题文件

在theme文件夹下,随便选一个css文件,如rubyblue.css,加载进来。

<link rel="stylesheet" href="/CodeMirrorTest/css/rubyblue.css">

现在我们就可以把theme选项设置为rubyblue了。编辑器会显示一些特定的颜色。

var myCodeMirror = CodeMirror.fromTextArea(myTextarea,{theme:"rubyblue"});

原理是什么?打开这个css文件,可以看到.cm-s-rubyblue这样的写法。也就是说,你所配置的主题
名称,必须保证.cm-s-[name]这样的css样式已经被加载进来。只要有这样的样式你就可以配置相应的主题。样式写在哪里是你的自由,但是我们最
好像官方示例这样,一个主题一个css文件。

官方提供的主题,都在theme文件夹里面。如果你想实现自己的主题,照猫画虎就可以了。

我们也可以同时使用多个主题,毕竟css样式的class是可以同时存在的。如theme:“foo bar”

2.默认主题

如果不配置theme,会怎样?这个选项的值会是“default”。那么.cm-s-default的颜色配置在哪里?在和codemirror.js同目录的lib目录下。
名称为:codemirror.css

3.自定义样式

如何实现自定义主题呢?想要使各种元素显示我们定义的颜色。只需要像上面提到的,提供对应名称的样式就可以了。
但是,css能控制的可不只是颜色,还有宽高等其它信息。这些信息在theme下面的css文件中是没有的,因为theme只控制显示的颜色风格。
而编辑器的其它样式,在codemirror.css中定义。

a.

下面,翻译一下官方说明,告诉你哪些css样式是控制什么的:

    对codemirror.css中有关编辑器外观的样式的解释
    CodeMirror
        这是编辑器最外层的div的class。因此,这里设置整个编辑器的宽高和位置、边框等,或者为编辑器设置背景。
        在这里设置的一些东西也会被应用到它的子元素上,所以,适合在这里设置全局的字体、字号等。
        如果将高度设置为auto,将会使编辑器适应其内容。如果这样做,也推荐同时设置viewportMargin为Infinity。
    CodeMirror-gutters
        这是所有槽的最外层div,所有槽都包括在这里面。可以用它给整个槽设置背景颜色。也可以通过它给槽设置右边框。 
    CodeMirror-linenumbers
        使用这个来给“行号槽”设置宽度和背景颜色。
    CodeMirror-linenumber
        用这个来控制特定行号槽的样式。这个样式是会覆盖上面的CodeMirror-linenumbers的, 可以用它来设置对齐和文本属性.
    CodeMirror-lines
        可见的行,这个样式用来控制行与行中间的间隙。
        
    CodeMirror-focused
        当编辑器获得焦点,顶层元素就会获得这个class
    CodeMirror-selected
        选中的内容会被span包裹,并且span会带有这个class.
        
    CodeMirror-cursor
        控制光标的显示方式,光标使用绝对定位来指示位置。
    CodeMirror-matchingbracket, CodeMirror-nonmatchingbracket
        控制匹配成对的括号和没有成对的括号的样式。
b.

上面提到的关于行号和关于槽的class,需要开启行号才行。我们顺便回头学几个配置,这几个配置和我们本章的theme是同级别的配置:

CodeMirror的一些显示相关的配置项
var myCodeMirror = CodeMirror.fromTextArea(myTextarea,{
theme:"rubyblue",
lineNumbers:true,
firstLineNumber:1,
lineNumberFormatter: function(line: integer) → string,
gutters: array<string>,
fixedGutter: boolean
});

lineNumbers: boolean
    这个选项配置了是否开启显示行号。
firstLineNumber: integer
    这个选项配置了,行号从数字几开始。
lineNumberFormatter: function(line: integer) → string
    这个选项配置了一个函数,传入一个行号数字,返回格式化后的字符串。也就是说,行号不一定是数字行号,也可以是任意字符串。
gutters: array<string>
    这个选项是一个css的class名称的数组,每一个都定义了宽度和背景(由于外层的槽的宽度固定,所以这个宽度最好用百分比。
    背景是可选的),也可以把前面提到的CodeMirror-linenumbers这样式包含进来,这样就显式指定了行号样式的位置,如果不
    显式指定,行号样式是放在最右侧的。
    
    这些样式会在行号样式之前,或者取代行号样式的位置。最终效果类似于:
    设置为:gutters:["aa","bb"]
    <div class="CodeMirror-gutters" style="height: 36px;">//这是行号槽
        <div class="CodeMirror-gutter aa">//这是第一个样式
        </div><div class="CodeMirror-gutter bb">//这是第二个样式
        </div><div class="CodeMirror-gutter CodeMirror-linenumbers" style="width: 35px;"></div>这是行号样式
    </div>
fixedGutter: boolean
    当编辑器左右滚动时,行号槽固定不动还是随着内容滚动。
scrollbarStyle: string
    当默认是"native",使用浏览器的滚动条,可以设置为“null”,不显示滚动条。通过插件,可以实现其他类型的滚动条。
coverGutterNextToScrollbar: boolean
    当水平方向出现滚动条,而行号槽又是设置为固定不动,那么必然面临一个问题:一个水平条和一个垂直条的交点处,谁该显示在上面?
    默认设置是行号槽会显示在滚动条左侧,也就是设置为false。如果将选项设置为true,那么会有一个带有CodeMirror-gutter-filler的
    样式的div覆盖在交叉点。总之,无论如何滚动条都不会延伸到交叉区域。
showCursorWhenSelecting: boolean
    当一段文字被选中,光标是否显示。默认fanlse。
autofocus: boolean
    刚初始化的时候,编辑器是否具有焦点。默认为false。

虽然上面详细介绍了各种配置和样式是怎么控制行号、光标、焦点、选中项、滚动条的显示的。但是为了真正了解其实际情况。你最好还是通过浏览器查看dom元素,
来观察这些元素和样式是怎么应用和变化的。

请使用浏览器的查看元素功能,定位到编辑器节点。你会看到,编辑器由很多的div来实现,样式由div的class来控制,因此,你所熟知的css的
样式继承规则等,会帮助你更好地设计这些显示风格。

第三章 模式设置(mode)

一、用途

CodeMirror是一款在线代码编辑器,什么是代码编辑器?编辑某种编程语言的代码文本所用的工具,根据语言的语法规则给文本内容中的具有特殊
含义的文字标记颜色,从而帮助我们更容易地编写符合语言规范的代码。

这样的文本编辑器已经数不胜数,CodeMirror存在的意义何在?

  1. CodeMirror是js编写的,可以在线使用,而无需本地安装。
  2. CodeMirror可以通过编程接口,定义语言模式。

二、实例

在CodeMirror的初始化配置里面,有这样一个选项:

mode: object
    这个选项配置了编辑器所编辑的文本的模式。如果没有配置这个选项,但是已经有模式被加载,那么会默认将第一个被加载的模式作为当前
    模式。
    配置选项可以是一个字符串,字符串代表模式的名称或者代表模式所关联的MIME的类型。
    此外,配置选项也可以是一个对象,如:{name: "javascript", json: true},其中必须包含模式名称name,其它属性是为这个模式配置的参数。
    每种模式的demo页面,都显示了模式所支持的配置参数。
    你可以查看CodeMirror定义了哪些mode和哪些MIME:
         CodeMirror.modes映射了模式名称和构造函数。
         CodeMirror.mimeModes 映射了MIME类型和模式规格说明

1.加载模式文件

在mode文件夹下,随便选一个文件夹,都是一个模式。我们拿JavaScript作为例子,加载进来。

<script type="text/javascript" src="/CodeMirrorTest/js/javascript.js"></script>

现在我们就可以把mode选项设置为javascript了。

var myCodeMirror = CodeMirror.fromTextArea(myTextarea,{theme:"rubyblue", mode: "javascript"});    

比如输入内容:
var jha =function(){
    var i= undefined;
    return l;  
}
var return 会被渲染为紫色,而undefined被加为黄色。

现在,在编辑器里输入javascript关键字,就会被加上颜色。

2.模式的原理

编辑器是怎么知道要将哪些文字加上特殊颜色的呢?又是怎么区分不同的词加上不同的颜色的呢?
我们直接打开javascript.js,查看源码。

CodeMirror.defineMode("javascript", function(config, parserConfig) {...}

可以看到,CodeMirror的静态方法defineMode用于注册模式,这个方法有两个参数,第一个参数是模式的名称,即我们在配置里面应该写的名称。
第二个参数是函数function(config, parserConfig)

这个函数又有两个参数:
    config是一个配置对象,也就是CodeMirror构造函数中的那个配置对象(构造函数的第二个参数)。
    parserConfig是一个给模式的配置对象,就是构造函数中的配置选项的mode:{name: "javascript", json: true},这个值对象。
这个函数返回一个模式对象。
整个模式的一切都应该写在这个函数的里面,而不能泄露都全局作用域去。

3.自定义模式

阅读纯文字文档,是一项非常痛苦的工作。我在阅读官方的原文档时,花了很多时间,仍不能再在脑海里建立起模式运行的整个框架。所以,我
推荐的学习方式是:通过自己实现一个模式,来学习模式。在此过程中,又通过逐步实现模式的功能,来实现整个模式。

我们先看看一段官方文档:

So, to summarize, a mode must provide a token method, and it may provide startState, copyState, and indent methods.
For an example of a trivial mode, see the diff mode, for a more involved example, see the C-like mode.

总的来说,一个“模式”,必须提供token方法,可以提供startState, copyState, 和 indent methods方法。
对于简单的模式,参考diff模式。对于更进一步的模式,参考C-like模式。

我们就如其所述,打开mode文件夹下的diff文件夹里面的diff.js。

CodeMirror.defineMode("diff", function() {

  //diff文件里面只有三种内置符号,+-@
  var TOKEN_NAMES = {
    '+': 'positive',
    '-': 'negative',
    '@': 'meta'
  };

  return {
  //必须有的函数,token
    token: function(stream) {
      //找到字符串之间的tab分隔符位置
      var tw_pos = stream.string.search(/[\t ]+?$/);

      //如果当前不在一行的开头,或者这一行tab分隔符在行开头,那么就是出错的。
      if (!stream.sol() || tw_pos === 0) {
        stream.skipToEnd();
        return ("error " + (
          TOKEN_NAMES[stream.string.charAt(0)] || '')).replace(/ $/, '');
      }

      //根据这个串的第一个字符,取出对应的class。如果不存在对应class,这个串就不用标颜色,串就跳到行尾。
      var token_name = TOKEN_NAMES[stream.peek()] || stream.skipToEnd();
      
      //如果没有空白分隔符,说明这一行不是修改的内容,则直接跳到行尾
      if (tw_pos === -1) {
        stream.skipToEnd();
      } else {
      //有空白分隔符,说明这一行是修改的内容,串移动到分隔符的位置,以便下一次从那里开始
        stream.pos = tw_pos;
      }
     //返回对应的class名称,这个名称对应的颜色,在theme文件或者codemirror.css里面有。你也可以自己定义一个专门的颜色,
     //但是要注意,需要是.cm-comment这种形式定义的。在这里可以返回多个样式名称,用空格分隔就可以了。如果是"line-" 或者
     //"line-background-"为前缀时,将被应用到整行上。
    
    return token_name;
    }
  };
});

我们将这个模式加载进来,通过调试的方式,来了解其运行过程。或者你可以直接在mode/diff文件夹下打开index.html,在这个demo页面上直接调试。
在此之前,我们必须先了解一个对象:stream。

eol() → boolean
    只有当流在行尾时才返回true。
sol() → boolean
    只有当流处于行首时返回true。
    
peek() → string
    返回流中的下一个字符而不前进。将在行结束时返回一个null。
next() → string
    返回流中的下一个字符并使其前进。没有更多的字符可用时返回null
    。
eat(match: string|regexp|function(char: string) → boolean) → string
    match可以是一个字符,一个正则表达式或一个接受一个字符并返回一个布尔值的函数。如果流中的下一个字符'匹配'给定的参数,它将被消耗并返回。否则,undefined 返回。
eatWhile(match: string|regexp|function(char: string) → boolean) → boolean
    反复调用eat给定的参数,直到失败。如果有任何字符被吃掉,则返回true。
eatSpace() → boolean
    eatWhile匹配空格时的快捷键。
match(pattern: string, ?consume: boolean, ?caseFold: boolean) → boolean
match(pattern: regexp, ?consume: boolean) → array<string>
    就像一个多字符eat- 如果consume是真的或者没有给出的 - 或者是一个不更新流位置的前瞻 - 如果它是假的。pattern可以是以字符串开头的正则表达式^。当它是一个字符串时,caseFold可以设置为true以使匹配不区分大小写。当成功匹配一个正则表达式时,返回的值将是返回的数组match,以防需要提取匹配的组。

关于上面这几个eat到match的方法,你可以这样理解。我们从一个字符串的某个“起点”开始,一直“吃掉”符合条件的字符,直到遇到不符合条件的字符。这时候,我们需要返回一个class的名称,这个
class将会用来渲染我们刚才吃掉的所有字符。然后,我们的“起点”就转移到了当前的位置。如果我们返回的是一个null,那么被我们吃掉的字符,将不会被着色。


skipToEnd()
    将位置移动到行的末尾。
skipTo(ch: string) → boolean
    如果在当前行中找到,则跳到下一个给定字符的出现处(如果字符不在行上,则不会前进该流)。如果找到该字符,则返回true。
//stream对象,只提供了这两个,跳到行尾和跳到某字符的方法。如果想跳到指定位置,该怎么做?可以通过
// stream.pos = somepos;来实现,但是这样不是很安全,需要多加小心。
backUp(n: integer)
    备份流n个字符。比当前令牌的开始进一步支持会导致事情中断,所以要小心。
column() → integer
    返回当前标记开始的列(考虑选项卡)。
indentation() → integer
    告诉你当前行在空格中缩进多远。更正制表符。
current() → string
    获取当前令牌开始和当前流位置之间的字符串。

总而言之,我们可以看出,自定义模式的方式,就是通过解析一个字符串,然后根据不同字符截取字符串中的不同子串,判断该返回的class。截取字符串的API在stream对象里面。

如果你想更进一步,了解复杂mode的实现方式,可以参考mode文件夹下的多种mode。挑选一个你熟悉的语言,比如html、js、css、sql等,看看人家是怎么实现的。直接阅读源码外加思考,
会使你更有收获。

##4. 简单模式插件
addon/mode文件夹下,是一些关于mode的插件。这些插件,定义了一些通用的mode接口。
将simple.js加载进项目:

simple.js为CodeMirror原型添加 了一个方法,使得CodeMirror可以通过这个方法,为编辑器定义符合接口的简单模式。
CodeMirror.defineSimpleMode = function(name, states) {
    CodeMirror.defineMode(name, function(config) {
      return CodeMirror.simpleMode(config, states);
    });
  };

我在实际中应用过一次简单模式,但是觉得不太好用,就再也没使用过。读者可以通过阅读原文档来学习这个插件。时间关系,这个插件的使用,暂时就不翻译了。

第四章 快捷键和命令(keymap and command)

一、说明

无需多言,我们都熟悉什么是快捷键,什么是命令。快捷键就是我们通常按下的Ctrl+V这样的键组合,而命令就是Ctrl+V所触发的粘贴功能。
在CodeMirror里面,快捷键既可以映射到编辑器本身提供的这些命令上去,也可以映射到你自己写的函数代码上去。我们需要做的,一是了解编辑器具有哪些命令,
以便我们给它设置快捷键,或者使用它已有的快捷键来调用命令;二是了解怎么配置快捷键,从而为我们设置自己的快捷键。

二、命令

1.命令主要用于绑定到快捷键上。

CodeMirror定义了许多常用的命令,大多数已经绑定了默认的快捷键。
绑定自己的命令,只需要向CodeMirror.commands上绑定属性就可以了。我们在CodeMirror.js里面找到CodeMirror.commands的位置,可以看到:

  var commands = CodeMirror.commands = {
    selectAll: function(cm) {cm.setSelection(Pos(cm.firstLine(), 0), Pos(cm.lastLine()), sel_dontScroll);},
    singleSelection: function(cm) {
        cm.setSelection(cm.getCursor("anchor"), cm.getCursor("head"), sel_dontScroll);
    }
    ……
    }
    属性的键是一个字符串,值是一个只包含一个参数,即以编辑器实例为入参的函数。

我们再看看快捷键是在哪里定义的,打开keymap文件夹下任意一个keymap文件,如emacs.js:

    找到selectAll
    
     var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
        "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
        ……
        "Ctrl-X H": "selectAll"
      });
      
      可以看到keymap的定义方式:
      key:commandName
      键组合字符串(可以是多个快捷键):命令名称字符串。

2.命令也可以用该execCommand 方法运行

这是codemirror实例的一个方法:
cm.execCommand(name: string)
在编辑器上运行带有给定名称的命令。

3.默认命令

下面的一些命令在默认的键映射中被引用,但是没有被核心库定义。这些旨在由用户代码或插件定义。注意,这些翻译是用chrome浏览器自带的翻译功能翻译的,如果
含义不好理解,请查看原文

selectAllCtrl-A(PC),Cmd-A(Mac)
    选择编辑器的全部内容。
singleSelection退出
    当存在多个选择时,这将取消选择除主选择以外的所有选择。
killLineCtrl-K(Mac)
    Emacs式的线路查杀。删除光标后面的部分。如果只包含空格,行尾的换行符也会被删除。
deleteLineCtrl-D(PC),Cmd-D(Mac)
    删除光标下的整行,最后包括换行符。
delLineLeft
    删除光标前面的部分。
delWrappedLineLeftCmd-Backspace(Mac)
    删除光标所在视线左侧的光标部分。
delWrappedLineRightCmd-Delete(Mac)
    从光标移到光标所在视线右侧的部分。
undoCtrl-Z(PC),Cmd-Z(Mac)
    撤消上次更改。
redoCtrl-Y(PC),Shift-Cmd-Z(Mac),Cmd-Y(Mac)
    重做上次撤消的更改。
undoSelectionCtrl-U(PC),Cmd-U(Mac)
    撤销对选择的最后更改,或者如果历史记录顶部没有只进行选择更改,请撤消上次更改。
redoSelectionAlt-U(PC),Shift-Cmd-U(Mac)
    重做最后的选择更改,或者如果没有选择更改,最后的文本更改。
goDocStartCtrl-Home(PC),Cmd-Up(Mac),Cmd-Home(Mac)
    将光标移动到文档的开头。
goDocEndCtrl-End(PC),Cmd-End(Mac),Cmd-Down(Mac)
    将光标移动到文档的末尾。
goLineStartAlt-Left(PC),Ctrl-A(Mac)
    将光标移动到行的开头。
goLineStartSmart家
    移动到文本行的开头,或者如果我们已经在那里,则移到行的实际开始处(包括空格)。
goLineEndAlt-Right(PC),Ctrl-E(Mac)
    将光标移动到行的末尾。
goLineRightCmd-Right(Mac)
    将光标移动到所在视线的右侧。
goLineLeftCmd-Left(Mac)
    将光标移动到所在视线的左侧。如果这一行被封装,那可能不是行的开始。
goLineLeftSmart
    将光标移动到所在视线的左侧。如果这到了行的开始,就像goLineStartSmart。
goLineUp向上,Ctrl-P(Mac)
    将光标向上移动一行。
goLineDown向下,Ctrl-N(Mac)
    向下移动一行。
goPageUpPageUp,Shift-Ctrl-V(Mac)
    将光标向上移动一个屏幕,并向上滚动相同的距离。
goPageDownPageDown,Ctrl-V(Mac)
    将光标向下移动一个屏幕,然后向下滚动相同的距离。
goCharLeft左,Ctrl-B(Mac)
    将光标向左移动一个字符,当点击行的开始时,转到上一行。
goCharRight右键,Ctrl-F(Mac)
    将光标向右移动一个字符,直到行结束时转到下一行。
goColumnLeft
    将光标左移一个字符,但不要跨越行边界。
goColumnRight
    将光标右移一个字符,不要跨越行边界。
goWordLeftAlt-B(Mac)
    将光标移到上一个单词的开头。
goWordRightAlt-F(Mac)
    将光标移到下一个单词的末尾。
goGroupLeftCtrl-Left(PC),Alt-Left(Mac)
    移到光标前面的组的左侧。一个组是一段单词字符,一段标点字符,一个换行符,或者多于一个 空格字符。
goGroupRightCtrl-Right(PC),Alt-Right(Mac)
    移动到该组的光标后的权利(见上文)。
delCharBeforeShift-Backspace,Ctrl-H(Mac)
    删除光标前的字符。
delCharAfter删除,Ctrl-D(Mac)
    删除光标后面的字符。
delWordBeforeAlt-Backspace(Mac)
    删除到光标前的单词的开头。
delWordAfterAlt-D(Mac)
    删除光标后面的单词末尾。
delGroupBeforeCtrl-Backspace(PC),Alt-Backspace(Mac)
    删除光标前面的组左侧。
delGroupAfterCtrl-Delete(PC),Ctrl-Alt-Backspace(Mac),Alt-Delete(Mac)
    在光标后删除到组的开始。
indentAutoShift-Tab键
    自动缩进当前行或选择。
indentMoreCtrl-](PC),Cmd-](Mac)
    缩进当前行或通过一个缩进单位进行选择。
indentLessCtrl- [(PC),Cmd- [(Mac)
    缩进当前行或由一个缩进单位选择。
insertTab
    在光标处插入一个制表符。
insertSoftTab
    插入与光标位置处的选项卡宽度相匹配的空间量。
defaultTab标签
    如果选择了某个缩进单元,请将其缩进。如果没有选择,请插入一个制表符。
transposeCharsCtrl-T(Mac)
    在光标之前和之后交换字符。
newlineAndIndent输入
    插入换行符并自动缩进新行。
toggleOverwrite插
 翻转覆盖标志。
saveCtrl-S(PC),Cmd-S(Mac)
    核心库没有定义,只在关键地图中提到。旨在为用户代码提供一个简单的方法来定义一个保存命令。
findCtrl-F(PC),Cmd-F(Mac)
findNextCtrl-G(PC),Cmd-G(Mac)
findPrevShift-Ctrl-G(PC),Shift-Cmd-G(Mac)
replaceShift-Ctrl-F(PC),Cmd-Alt-F(Mac)
replaceAllShift-Ctrl-R(PC),Shift-Cmd-Alt-F(Mac)
    不是由核心库定义的,而是在搜索插件(或自定义客户端插件)中定义的。

三、快捷键

既然上面说到了快捷键,我们再来看看什么是快捷键,keymap。
keymap键映射是将键与功能相关联的方式。CodeMirror自带了几种键映射,Emacs,Vim和Sublime Text- style键盘映射。都在分发包的keymap文件夹下。

快捷键

快捷键由名称或者字符标识。

1.名称绑定

在CodeMirror.keyNames里面定义了所有键的名称,也就是说这些名称对应着对应的键。比如:Enter 对应回车键。
在快捷键前可以用Shift-,Cmd-,Ctrl-,Alt-修饰,代表按下键时同时按下的控制键。

常见的一个例子是,当按下tab符号时,插入空格,而不是插入一个\t。

editor.setOption("extraKeys", {
  Tab: function(cm) {
    var spaces = Array(cm.getOption("indentUnit") + 1).join(" ");
    cm.replaceSelection(spaces);
  }
});

Tab就是键名称,后面的函数类似于命令定义一样,是一个只有一个入参的函数。

这里又提到了一个editor.setOption.这个方法的作用是给编辑器设置一个配置项,也就是我们在初始化编辑器时,那个配置对象里面的配置项。那么也就是说,我们可以在
初始化的时候,直接配置这个配置项,因此我们这里顺便学习一配置项:

keyMap: string
使用的keymap,默认是"default", 定义在codemirror.js 里面
customKeys: keymap
提供快捷键,覆盖默认的快捷键
extraKeys: keymap
添加快捷键,而不是替换它们。

这里翻译的不是很准确,并且原文的说法也并不是很好理解,希望以后能在使用中再去详细了解。

2. 字符绑定

也可以通过单个字符来指定按键,由于浏览器触发按键事件的方式受到限制,因此这些事件可能不会以修饰符作为前缀。也就是可能无法触发Shift-,Cmd-,Ctrl-,Alt-

3. 多快捷键

可以绑定多个快捷键:空格分隔键名就可以了。“Ctrl-X Ctrl-V”.当有这种绑定多个快捷键的情况,或者有案件顺序很奇怪的情况(Shift-Cmd-Ctrl-Alt)时,需要调用CodeMirror.normalizeKeyMap才能使它们正确生效。
这个方法接受一个keymap参数,将其整理之后又返回一个keymap。例如前文的emacs中的调用:

  var keyMap = CodeMirror.keyMap.emacs = CodeMirror.normalizeKeyMap({
        "Ctrl-W": function(cm) {kill(cm, cm.getCursor("start"), cm.getCursor("end"));},
        ……
        "Ctrl-X H": "selectAll"
      });

按键值

键映射中的属性值可以是单个参数(CodeMirror实例)的函数,字符串或 false。
字符串指的是命令名称。false则是将按键的处理留给浏览器。函数如果返回CodeMirror.Pass,则表示不处理按键,让出处理权给其它程序,如其它keymap。

如果映射的是以go开头的命令或者映射的是有motion属性为true的函数,则会无视Shift修饰符的影响。如:按下键盘上的编辑区的向上箭头键,即Up代表的按键
"Up": "goLineUp"
这将既匹配单按键(箭头),也匹配组合件SHift+箭头。

多个keymap

keymap可以定义fallthrough属性,这将让在没有匹配到keymap时,搜索其它keymap。

第五章 事件(events)

一、注册事件

在codemirror实例上,可以调用on和off方法注册或者取消事件处理程序:

cm.on(type: string, func: (...args))
    在编辑器实例上注册给定事件类型(字符串)的事件处理程序。还有一个CodeMirror.on(object, type, func)版本允许在任何对象上注册事件。
cm.off(type: string, func: (...args))
    删除编辑器实例上的事件处理程序。一个等同物CodeMirror.off(object, type, func)也存在。

事件处理程序即监听器。我们来简单尝试一下:

<textarea id="editor"></textarea>
<script type="text/javascript">
	var myTextarea = document.getElementById("editor");
	var myCodeMirror = CodeMirror.fromTextArea(myTextarea,{
		    theme:"rubyblue",
		    lineNumbers:true,
		    coverGutterNextToScrollbar:true,
		    mode:"javascript"
	});
	
	myCodeMirror.on("change", function(editor,infoObj){
		debugger;
	});
</script>

我向编辑器输入了一个“1”。调试器在debugger;处果然停下,并且可以调试到以下信息:   
    from:CodeMirror.Pos {line: 0, ch: 0}
    origin:"+input"
    removed:[""]
    text : ["1"]
    to:CodeMirror.Pos {line: 0, ch: 0}

效果一目了然。我们只需要分别了解各个事件的功能就行了。本文不做赘述。

二、自定义事件

使用CodeMirror.signal(target, name, args…)注册自己的事件。target是非DOM节点对象。

在CodeMirror中可以看到这个方法:

var signal = CodeMirror.signal = function(emitter, type /*, values...*/) {
    var handlers = getHandlers(emitter, type, true)
    if (!handlers.length) return;
    var args = Array.prototype.slice.call(arguments, 2);
    for (var i = 0; i < handlers.length; ++i)
        handlers[i].apply(null, args);
};

第六章 实际应用

一、说明

文档中尚且有很多东西,没有翻译,解释如下:

1.  对于编程API,只有很简短的描述,从英文的角度来说,阅读原文并不是很困难,
    从中文的角度来说,我所翻译或者转述的内容未必能使读者更明白。所以,这部分内容请阅读原文 
2.  对于插件,插件里面有着完整的示例,研究示例一定会更简单快捷。所以,这部分内容我也不画蛇添足了。
3.  Codemirror的文档并没有详尽描述其构造原理及功能,肯定有很多地方可以让我们去发现。另外Codemirror
    软件本身也有很多不足的地方,值得我们自己去完善。因此,我主张,多研究多思考。

那么,接下来我的文章将会做哪些事情?

1.  我会不断研究源码和手册,争取将没有注意到的地方补上,不断维护我的系列手册。
2.  我会多去思考和关注CodeMirror尚未完善的地方,争取能为项目贡献代码。

还有哪些值得我们去做的东西?

1.  编写更多的插件。
2.  扩展更多模式
3.  扩展更多功能。

我们值得花时间在这样一个项目上吗?

1.  令我们提高的永远都不是项目本身,而是我们思考的过程和采取的行动。
2.  作为一个开源项目,你可以通过它练习git和github的使用。
3.  作为一个js项目,你可以通过它学习JavaScript,尤其是学习作者的设计技巧。
4.  作为一个文本编辑项目,你可以把它和文档处理联系起来,想想那些处理pdf、word、xml等文档的程序和解析器,
    想想solr、分词等这些东西。你能够学到和值得研究的东西还有很多。没准你的努力和独到见解,会让你成为文本处理方面的专家。

感谢作者为我们翻译了一份简洁的中文入门教程,但确实如作者所言,您的专业并不是javascript,翻译的质量会误导使用者