接说上文, 这一片主要跟随作者的blog: codemirror2究竟是咋回事: https://codemirror.net/doc/internals.html

前言

  • 作者明言: 这一片是之前那一片code1咋回事的后续.
  • 这个code2会比较简明, 不像code1那么绕.
code1的问题是啥?
  • designmode/editable的浏览器支持有各种问题. 不仅仅是ie, webkit和firefox都有各自的问题, 不过ie还是很顽固, ie的问题在于, 一旦他有问题, 你就改不动.
  • frame是另一问题, 回退和初始化以及跨域都会有各自的问题.
  • 选择selection也出现了问题 ie的做法与众不同的愚蠢, opera也是刚刚才修好了问题, codemirror1里面有700行代码处理selection.
  • 由此导致了, 系统中的辅助性(兼容性)代码比例太高了, 高到了一个可怕的量. 每次浏览器更新都可能引起灾难性的后果.
2 想干啥?
  • 最主要的就是避开1面临的问题, 这里作者感谢ace给出的解决方案. (这个也是我想要的解决方案).
  • 但是, marijn并不想完全控制用户的输入. 不仅仅是key event本身就有各种问题, 还在于根本没办法处理类似东亚字符集这样的输入.
所以
  • 所以marijn构建了一个隐藏的textarea获得focus, 并且让浏览器认为用户正在这个textarea里面输入.
  • 然后我们展示给用户的是一个dom, 从用户的输入生成的dom.
  • 只要再把光标(弄一个假的)显示正确, 那么看上去这个编辑器就很正常了.
附加的好处
  • 不必再处理整个文档了. (只要处理最近输入的就行.)
  • editable的dom本身比正常dom要慢.
  • 剩下的就是处理onscroll了.

输入

  • ace只是使用textarea输入文字, 光标移动的删除, 都是直接用key event处理.
  • codemirror期望让浏览器尽量多的处理. 后面发现这是一个天真的想法, 之所以可以这么干就是一个原因: 功能还不够完善, 最终结果, codemirror和ace用了同样的方案.
有一个思路
  • 有一个思路, 就是把整个文档到放到这个textarea里面. 然后每个key event都更新对应的dom.
  • 这样做会比较简单(这个我有深深的疑问, 恐怕不那么简单).
  • marijn认为这样会比较慢.
cm2的思路
  • textarea里面保留当前行的前一行和后一行.
  • 这个有道理, 这样确实不用处理一般情况的光标移动和删除了.
  • 而且无论如何也要随时更新的, 这样的更新范围还是比较合理的.
  • marijn只需要读取textarea里面的cursor, 然后更新外边的cursor就好了.
  • copy和paste也直接按照系统的情况来…….(这个我做的时候要想一下, 是否需要把dom->md->dom, 当然dom->dom或许也可以).
  • 如果用户操作跨越了textarea的范围, 例如page up/down, ctrl - end, 或者鼠标点击, 这些事件都要单独处理了.
选择selection
  • textarea里面的selection很好处理, 用selectionstart和selectionend就可以了.
  • 此处有了疑问, 就是说选择内容都维护在textarea里面? 用鼠标选择的内容咋办? 估计也维护到textarea里面.
  • 最终marijn还是用key event来处理这个. 并且记录用户移动光标所用的key组合.
  • 我估计鼠标选择也要基于key event, 比如shift和cmd
    • 点选需要shift
    • 拖选恐怕就是拖动事件了
更新
  • 每个状态都更新, 一是不赶趟, 而是, 等你更新的时候, 你发现又有新的更新了, 最佳方案是你放弃现在的更新.
  • marjin的做法是做一个记录, 到了事件的终点再用这些信息一步更新.
  • codemirror再每次的开始调用一个函数清除所有状态, 记录当前状态. 结束的时候再调用另一个函数. 根据状态变化, 进行更新.
  • 这个做嵌套是很容易的, 压栈就可以了.
  • 我们随时维护一个map, 保存没有被修改的内容, 修改结束, 就比较这个map和当前的显示内容(因为有滚动条), 然后进行更改.
  • 有两个更新算法
  • 全更新.
  • patch批量更新.
  • 最终结果是完全刷新这个算法被删除了, 还是批量更新快.
  • 更新并没有使用dom操作, 而是使用了innerHTML, 这个比较快.
parser可以很简单
  • 在codemirror1时代, 我做了比较重型的抽象. 用iterator来抽象, iterator互相调用. copy和resume(续传)都是用闭包来搞定的.
  • 这样做的系统相当不错, parser是固定分离的子模块, 不幸的是, 三四个iterator嵌套会很慢, 并且对于不熟悉函数式编程的人而言, 这种风格也很费解.
  • parser/tokenizer拿到一个状态和一个字符流, token本身就是一个字符流, 返回值还包含这个token对应的style. 这个状态是可以copy的, 这样就可以做到resume(断点续做).
  • 最大的速度的提升是, parser和dom分离了, codemirror1里面parser需要从dom拿到输入, 然后把处理过的dom作为输出. codemirror2处理的是纯文本.
codemirror2的优势
  • 小 45k
  • 轻量级, 他很快.
  • 文档支持丰富(实话说2的文档真的很好).
  • 可扩展的api.
  • 支持tab窗口
  • style正常化, div比editable的iframe处理样式正常多了.

但是, 因为codemirror2不是一个native的editable, 所以很多东西不能做, 比如从浏览器菜单选择select-all

november 13th 2011, 文档有更新

内容展示
  • 一开始用的是一维行数组(数组中每个元素代表文档的一行), 这个开销确实很好.
  • 但是, 最近做了自动换行wrap和代码折叠缩起code fold. 一行占据的高度就不固定了.
  • 新方案基于B树. 没有基于regular二叉树是因为更新成本. 但是作者并没有刻意保持树的平衡性.
keymaps 快捷键
  • marijn在这里反思了, 一开始作者期望不要捕捉所有的键盘事件, 而是用一些hack方法去搞定选择变动(selection change), 但是marijn发现这么干还不如直接捕捉键盘事件靠谱.
  • 如果考虑了折行和折叠, 那么依赖textarea的光标移动会变成噩梦. 因此作者决定不再依赖textarea的光标移动.
  • 和ace一样marijn开始自己hold光标移动的键盘事件了.
  • codemirror.commands把command map到function.
  • hidden textarea现在仅仅是hold当前的selection, 不再有多余的字符出现在里面.
  • 这样可以很容易的判断用户是否输入了文字.
  • 这么干的原因是输入中文或者日文这样的东亚字符集的时候, 很多浏览器都没有合适的输入事件.输入结束的时候是有时间的, 但是输入进行的时候, 没有合适的方式做映射, 如果维护再textarea里面就很合适直接映射出来.