这是一个大坑... 跳坑多年仍然没出来.
The craft of text editing 讲得比较全了, 我再补充一些.
编辑器大致分为源代码编辑器和富文本两种.
源代码编辑器为了省事可以设置相同的行高方便计算, 不过现在多数支持变化行高了可以从富文本编辑器开始做.
首先, 挑一个 GUI 框架
跨平台的 GUI 通常很吸引人, 例如 Fox, FLTK, Tk, GTK+, Qt, WxWidgets 等, 大部分都有一个编辑源代码的控件, 而这个控件基本是 Scintilla 之上的包装 (再研究 Scintilla 你会发现其实各种 GUI 框架的编程模型包装都不需要, 按照 Scintilla 的设计去用就可以了). 学习下来你会发现各个 GUI 框架都自带一个特别的观感: Fox 的光标是个不可改变的巨型铁轨截面, wxWidgets 尽量模仿原生组件 (E-texteditor 就是用 wxWidgets 做的), GTK 就尽量自己画... 其编程模式实质差异并不大, 因为都是 C 和 C++ ... 最惨的是在 Windows 看着还可以, 一放到 Mac 就觉得丑爆了. 做了其他语言的绑定还是感觉在写 C 和 C++. 当然也有做得不一样的:
另一大类 GUI 框架是 XUL. 写个 XML 界面, 然后在 XML 界面上画东西. XHTML+JavaScript 就是一种 XUL 方案. Sun, 很小很柔软, 摸斯拉 等等大公司都推过自家的 XUL 方案. 然而 XML 根本就不适合人类编写, 作为 model 格式也过于巨大不好维护. 最初魔兽世界的插件也是推荐 XML 写界面然后绑定 lua 的动作, 但由于太不灵活也没有一个拖控件的界面, 所以玩家开发了 Ace 系列的 UI lib, 完全不用 XML 纯用 lua 写了. 拖拽式画界面只能骗骗小朋友, PaintCode 也比 XML 解释器性能更好, 所以现在 XUL 基本绝迹, 连直接用 HTML 写界面都不时髦了.
如果不跨平台, 用图形操作系统的 GUI 框架会更能解决很多实际问题, 性能也有保证. Win32API, MFC, ATL, WinForms, WPF, Carbon, Cocoa, CGContext, CoreText ... 就是操作系统商人心狠手辣变幻无常, 一心搞个大新闻还处处夹带私货, 一路学来也是挺累人的. 另一方面嘛 X11 这种更难学, 我就卡在了 motif ...
虽然跨平台的 GUI 框架在慢慢衰亡, 但 OpenGL 这类更接近底层硬件的图形库给人类提供了新的希望. 利用 OpenGL 的成功案例就有 Sublime Text. 我觉得 Cairo, SDL 这种半 GUI 框架的高性能图形库是比较适合的, 就是用的人少了点.
鉴于图形化界面的巨坑... 何不写个纯命令行的编辑器呢? 这时候我们有各种行编辑库可以用: readline, libedit, termcap, Antirez 的轻量 linenoise ... 再用脚本语言的话, 由于内建正则语法和一些字符串处理函数, 很容易在一两万行内写个功能齐全的编辑器解决战斗, 例如 Daikonos.
就算用 C, 如果只实现最简单的功能, 1024 行以内也是可以的: Writing an editor in less than 1000 lines of code, just for fun
纯字符界面缺点也很多, 平滑滚动没有, 动画高亮没有, 文字显示揪细点想调个 kerning 啊 ligature 啊也没办法. 那就自己做一个图形框架? Eclipse 就自掘巨坑组合 C++ 和 Awt 搞出个 SWT. 其实 Awt 和 Swing (NetBeans, IntJ 都是基于 Swing) 处理 Unicode 都有大量的坑, 我都不喜欢... 曾经有个我关注的编辑器 Redcar, 最初用 GTK 编写, 后来转成了 Swing, 然后逐渐就做不动了... jEdit 作者弃编辑器坑, 后来挖了个基于栈的语言新坑 Factor. 后来? 后来也不搞了...
现在 GUI 基本被 ES 的大流统治. 用 Web 做编辑器可以做出一些非常棒的用户体验, 现在浏览器引擎也优化得比几年前好太多. Atom, Monaco Code Editor 都是在 Web 上做的成功案例. 为了容易上手估计 ES 是首选. 缺点是某些细的 UX 不好实现, 正经的优化会花掉更多时间 (例如 Monaco 为了分析性能点连 IR Hydra 都用上了).
介绍两个 Helloworld, GUI 框架 + Scintilla 实现常见一个编辑框
基于 FxRuby 的:
require 'fox16' include Fox app = FXApp.new window = FXMainWindow.new app, "My Editor", nil, nil, DECOR_ALL, 100, 100, 710, 550 sci = FXScintilla.new window, nil, 0, LAYOUT_FILL_X|LAYOUT_FILL_Y app.create window.show app.run
基于我自己写的 GUI 框架的就更简单了 (谁不年少轻狂造过几个 GUI 框架轮子?)
require 'cici' app = Cici.app 'scintilla' c = app.paint [600, 600], Cici::ZoomLayout c.scintilla [500, 500] app.message_loop
其实还有各种 GUI 框架的编辑器 hello world 都差不多, 但用框架就是跟着别人走, 很难做出更好的用户体验.
如果从更底层点的地方开始, 例如 Win32API 和 Carbon, 站稳脚跟学习图形界面编程, 前面的道路会... 更狭窄 (公司刚裁了很多桌面程序员并对 Web 产品加大投入...). 不过你理解事件模型的实现和常见优化手段以后, 就算编辑器不成功, 也可以自己写个游戏引擎玩玩嘛.
然后, 挑一个 text storage 数据结构
例如 Cocoa 就自己提供了一个 NSTextStorage, 自己造大约有几个主流选项:
text storage 到界面显示之前, 需要一个排版引擎. 最简单的排版引擎就是把显示区域等分成很多格子, 把等宽字符直接填到格子里 -- 但是这并不好看. 一个文本框的显示得考虑:
在浏览器里有一套默认的设置, 还有 font-kerning, letter-spacing 等 CSS 属性的帮助, 处理这些问题要简易很多.
text storage (文本模型) + text container (排版引擎) + text view (显示引擎) 是不是就够了呢? 你还得考虑输入法和文字方向... 这一块光看文档是不够的, 自绘的文本输入框, 连很多大厂都没把输入法兼容好... Windows 的 input method editor 和 Cocoa 的 NSTextInput 都能让你抓狂很久.
然后, 考虑一下语言模型...
很多浏览器的文本编辑框里可以用中文词为单位移动光标 (ctrl + 方向键 / opt + 方向键), 这是怎么实现的呢? 有个库叫 ICU, 里面提供了很多边界分析(分字分词分句)用的函数. 浏览器就是用它实现的. ICU 甚至提供了多语言的排版引擎.
如果你要做智能提示和自动完成... 所谓智能提示往往并没有那么智能, 不如参考 vim, 用更依赖用户主动性的设计, 把自动完成的快捷键分为 line complete, dictionary complete, omni complete 等等, 给程序一个更 specific 的指令, 它就能完成得更精准快捷.
但现在的主流是被动性编程, 编辑器/IDE 给你一堆选项, 让你挨个选... 实现这类型的智能提示, 你得写很多代码把语法/类型/先验知识编进去. 而提升智能的方式是, 分析当前的 skip-gram 中最高概率出现的词, 把它排到更优先的位置去 --- 所以先学点计算语言学, 把 word2vec 玩熟吧.
------
除了上述几个大的 design decision 以外, 还有很多很多设计和编码的 Topic 可以讲...
当初我只是想用趁手的编辑器写篇博客而已, 好几年过去了, 现在都还在写脚本语言... 所以请慎重跳坑...
———
2020 年补充:现在不写脚本语言了,在写编译器。还要山寨 Revery 。再次忠告请慎重跳坑。
另外,现在对文本编辑器后端,有不少成熟的 buffer 管理项目