Vim - Jacky Liu's Blog
基于 gvim 的股票操作界面
---- 自己开发的基于 gvim 的股票操作界面。截图没开实盘,内容不具有实际操作意义,只做演示用。此文只谈界面设计,而且最终无可避免地回归到恶俗的主题—— Vim 的高端和我对它的钟爱上面来。
---- 基本显示形式就是图中那样,3 个窗口就够了。Vim 的任意一个窗口都可以加载任何一个缓冲区,所以 3 个窗口可以显示非常非常多的内容。左下两个辅助窗口的大小用自动命令的方式控制,光标进入时自动增大,离开时自动缩小。在窗口中切换缓冲区有一套自定义的按键。
---- 左边窗口显示的是 分析交易系统 的文件结构,背后的插件是 文件浏览器 插件。它并不只是显示文件结构这么简单,还可以跟 分析交易系统 配合,执行自定义的操作。对 股票分析交易系统 来讲,最常用的操作都定义成了 Vim 命令,比如 :Run :Quit :Update :Backup 这些。更多的操作都是预先写好的 python 脚本,以文件的形式显示在左边。需要执行的时候,只要在相应的脚本或目录名上按 \e 按键,这是更加灵活的方式。如果需要某个新功能或新操作,只要再写个 python 脚本,写好以后 \e 执行就可以了。
因为所有的插件都是自己写的,它们有共同的插件管理器,也有一套内部传送信号的定义。按 \e 按键的时候,文件浏览插件 就会把文件路径这些参数交给 分析交易系统 去加载执行。自写自用插件可以避免一些通用插件固有的问题,比如自定义命令过长(怕跟其它插件重复),插件之间无法配合,还有莫名其妙的冲突问题。插件之间冲突是 Vim 的痼疾,因为 Vim 的资源(窗口、缓冲区、系统选项、寄存器 ...)都是公有的,任意一个插件都可以动。虽然有经验的插件作者会有意避免,但是对于安装了许多插件的用户来说,冲突现象一定不陌生。
---- 中间最大的窗口是主窗口,用来显示 市场综合界面。可以建立多个 市场综合界面,用来显示不同内容,比如实盘时用来显示不同交易模式下属的符合条件的个股。不同综合界面之间可以通过自定义按键切换。
---- 市场综合界面 里用界面条目作为基本的显示单位,每一个界面条目对应一个后台数据结构,比如 个股、板块、交易模式 等等。界面条目之间有上下级关系,符合树状的体系结构。根节点是 市场行情缓存,它的下级数据分成几大类:
第一类是不同市场对象,有 个股、指数、板块;
第二类是自己定义的交易模式,分成 手动模式、自动模式、盘后开发模式。除了交易模式之外,还有 全局监控模式。交易模式 和 市场对象(个股)相互作用,符合匹配条件时会产生以个股代码和时间点为标志的 综合分析记录,分析记录在界面中会作为相应个股或交易模式的下级。
全局监控模式 目前有三种,实现在分析交易过程中需要的不同功能:
持仓模式,用来记录已持仓的个股,对持仓和资金情况进行统计,也负责仓位和资金管理;
关注模式,用来记录和管理手动设置的关注目标,便于后续操作。
预设指令模式,用来记录预设的条件交易指令,条件符合时自动执行。
第三类是一些比较复杂又专门的功能,在程序里单独作为一个组件,比如 历史分时交易模拟缓存器。分时模拟交易 是个不可缺少的功能,每种交易模式在投入实际运用之前,都需要用历史分时数据进行模拟交易,检验模式的成效。模拟过程会产生 分时模拟交易记录,这些模拟交易记录既不从属于个股也不从属于交易模式,而是从属于一个预设条件(比如可用资金多少)限制下的模拟过程,这些都需要一个专门的对象(分时交易模拟缓存器)来记录和管理。分时交易模拟缓存器也是 市场行情缓存 的下级。
---- 市场综合界面 中的数据条目有一些基本操作,可以展开(显示下级列表)、合上。下级列表由多个分列表组成,一个界面条目可以对应多种下级总列表,比如 市场行情缓存 展开后,可以显示市场全部个股,也可以显示全部交易模式。每个分表有自己独立的 成员过滤、排序、显示 定义,通过在分表标题的不同位置按 < 或 > 键,可以切换不同的过滤、排序、显示方式。比如一个交易模式下面当日符合条件的个股,可以按最新的收盘涨幅排序,也可以按开盘涨幅或最低涨幅排序,也可以按相关模式专门定义的特有指标排序。每种排序方式都可提供不同的关注角度,帮助快速找到值得关注的目标。
这里要补充一下,股票与其它品种(比如期货)相比,最大的好处是个股数量多,有几千个可选。即使在坏的市道下,也不难找到好的个股。但是要利用这种好处需要专门的技术,包括盘后基于日线行情的分析提取,更重要的是实盘时的监控。对一只个股来说,整个交易日之内价位合适的操作窗口可能只有几分钟而已。如果做不到对所有个股的监控和及时的反应,就很难达到预期操作目标。上面这些设计,都是为了强化实盘时的监控和反应速度。在不同的排序方式、过滤方式之间进行切换基本不需要时间。另外,自定义颜色也是一个很大的帮助,像图里显示的那样,让颜色的深浅来反映指标值的大小,这个后面细说。
以上所有这些,下级列表的内容,以及过滤、排序、显示方式,都用动态配置数据来控制,下面是动态配置数据和其中一个过滤函数的定义。
注意这些函数都是定义成文本的,因为它们需要在运行的时候动态加载。如果预设的 过滤、排序、显示 方式还是不能满足需求,可以在运行时通过修改这些文本来更改函数定义(通过底部辅助窗口执行),然后动态加载新的定义,结果会立即在 市场综合界面 里反映出来。
---- 回到前面的话题,在 市场综合界面 里,对于不同类型的条目还有专门的操作,比如对个股,可以在底部窗口显示它的 F10 信息,也可以绘制它的最新图形,也可以发出交易指令。这些操作都透过自定义按键来做。
---- 总之,市场综合界面 是最重要的一个界面。除了实盘时显示当天需要关注的个股以外,也可以在盘后运行,把目标日期设成历史日期,作为人工交易的操盘练习,或者作为交易模式的比较直观的验证。
---- 最后,是底部的辅助窗口,这个窗口对应多个缓存,每个缓存显示不同内容,缓存之间也是通过自定义按键切换:
*. 运行记录缓存,相当于 stdout,显示 分析交易系统 运行时的 logging 输出。
*. 全能输入缓存,用来在运行时输入 python 代码,然后动态加载执行。除了上面说的修改函数定义之外,还可以执行其它操作,比如输入比较复杂的预设交易指令,设置自动执行的条件参数,等等。代码文本都是预先写好的,多数情况下只需要把参数改成想要的值。
*. 实盘记录缓存,在实盘运行时显示重要的提示信息,比如某只个股达到了交易模式的匹配条件,某只已持仓个股达到了卖出条件,等等。不同类型的信息用不同颜色显示,重要的信息会搭配声音提示。目前还没试过怎样让 python 播放音频,不过对 python 来讲应该不是问题。
*. 全局信息缓存,用来显示数据对象的各种详细信息,比如个股的 F10 信息,或者程序运行时的状态,也用自定义按键触发。
---- Vim 为何适合做其它程序的界面?因为它有几个关键特性:
*. 界面的作用就是输入跟输出。对输入来说,Vim 有自定义按键和命令。命令可以带参数,按键还可以针对界面中不同的位置做不同处理。对输出来说,Vim 里有标签页、窗口,一个窗口可以对应多个 Vim 缓冲区,一个 Vim 缓冲区还可以对应多个后台数据缓存。总之,窗口不必开很多,但显示内容可以很丰富。
*. 可以根据内容的文法自定义内容的显示颜色,对 gvim 来说,可用的颜色数量丰富(与 html 相同)。抛开后台自定义的交易模式不谈,单这一点就是极大的好处。前面说过,对股票交易来说,全盘的监控、分析提取,以及条件符合时及时操作是关键。有了界面颜色的帮助,不需要专业训练的眼睛就可以在一大堆数据中迅速辨认出最值得关注的目标。文法匹配的定义可以很细致,比如说,同样是角度,结构顶角和趋势升角的含义就是不同的,对时间长度来说,波段长度和趋势长度含义也不同,它们都有不同的颜色定义。相比之下,一般的股软只是上涨用红色,下跌用绿色而已。
*. Vim 可以用 Python 语言进行扩展。而对于 Python 来讲,基本上想要什么功能都有。可以通过网络特性跟局域网内的系统前台交换数据,接收前台的实时行情并且发送交易指令,可以用 matplotlib 绘图,可以调用图片浏览器查看,还可以装一个 Selenium 接口,通过 Selenium 控制网络浏览器来玩微操盘,或者把 Vim 里编辑的内容通过一个自定义按键发上博客(这两个只是设想,没做)。当然 matplotlib 速度有点问题,以后可能会用 pyqt 来做一个绘图引擎,真正做到动态实时绘图,而且可以在图形上直接进行操作。
---- 以上就是 Vim 作为 UI 可以做到的事情。实际上,我已经把 分析交易系统 整个都改写成一个 Vim 插件了(原本是独立进程,通过 IPC 与 Vim 界面传递数据)。这样就节省掉了在进程间传递数据的种种麻烦,也因为如此,Vim 能做的事情更加广泛。比如在运行时,可以显示系统内部任何一个数据结构当前的值,因为整个系统已经成了一个 Vim 插件,它所有的部分 Vim 都看得到。这样用下来,并没有发现什么问题,效果很好。
继续 FSE 插件
---- 这是到目前为止写得最好,也是最有用的一个插件,各种爱不释手,现在用 Vim 时已经默认开在左边了。曾经看见有些小盆友在网上说什么“Vim 是个挺好的编辑器但是没法代替现代的 IDE”,各种鄙夷。我要是个程序员,一定按自己的喜好把 Vim 打造成最顺手的 IDE,用厂家的 IDE 只不过是在圈好的地里吃草而已。
---- 几个小改进:
1. 文件改名功能,也可以用来移动节点,用的是 Python 的 os.rename() 函数。
2. 新增一个自定义按键,在指定节点下打开文件浏览器 Nautilus
3. 批量删除功能,在 v 模式或 v line 模式下选中多个同级节点以后可以批量删除。
4. 智能定位按键在 v 模式和 v line 模式下也能用了,这样可以方便快速选中比较多的同级节点然后批量删除。
5. 浏览界面新增一个外观,节点前面添加了树形的分支符号,如图。因为字体的关系,符号之间有空隙,所以效果没有想像的那么好。总体上我觉得 Ubuntu 的 Courier 10 字体看着还是很顺眼的,只是没有十全十美。原来用 <Tab> 加不同颜色作缩进,是因为这样比较简单。这种方式现在还能用,靠选项可以切换。
---- 以后可能继续添加的功能:
1. 节点名过滤功能,通过自定义命令输入 py pattern,对节点名进行过滤。比如:
“只显示 .py 文件”
“不显示名称含有 'tmp' 的目录”
等等。
2. 标记功能,可以对经常访问的文件做标记,对应的高亮显示,快速跳转按键操作,等等。
3. 文件查找功能。与上面类似,通过自定义命令输入 py pattern 查找指定目录下的节点,结果在浏览界面里标识出来。
4. 改颜色 -- 收藏夹里的颜色难看到死,这我知道。FSE 是分两部分写的,中间隔比较久,写收藏夹那部分时,还不知道有这么多好看的 预定义颜色 可以用。有空再改改吧。
5. 在节点排序模式里添加 "bytype" 模式,按照类型来排序文件。这个比较麻烦,因为只靠扩展名来判断类型是不够的,比如图片文件和视频文件都有许多种扩展名,这个有时间慢慢弄。
6. 继续添加对文件类型的支持,能够调用合适的程序打开更多的文件类型
7. 添加对 soft link 和 mount 类型节点的支持。
---- 另外在改写插件的时候被一个非著名问题难住了:
在 Vim 里怎样得到当前光标所在列的列号?
写下这个问题时我就穿越了,人生真的就是循环往复。。。
这里说的列号不是 col('.'),那个是光标前的内容在内存里占多少字节,而我只是要知道光标前面有多少个字符而已。因为换用树形的符号作缩进以后,在相同位置上用 col('.') 或者 getpos('.') 得到的值与以前用 <Tab> 作缩进时不一样了,而如果不能正确判断列号的话,智能定位功能会有问题。我就纳闷,用 Vim 都 N 久了,怎么现在才遇到这个问题,而且一时还想不出办法来。
最后终于想出一个:
collen= len(vim.eval(' col(".")==1 ? "" : getline(".")[:col(".")-2] ')) + 1
上面这是 Python 代码,因为 FSE 插件是用 Python 接口写的。如果是纯 VimScript 的话,就得这样:
let collen= strlen( substitute( (col('.')==1 ? "" : getline('.')[:col('.')-2]) , '.', 'x', 'g') ) + 1
介个。。。拜托!谁有好点的办法没有?
------------------------------------------------------------------------- <补记> ---------------------------------------------------------------------------
---- 用 strchars() 函数好一点:
let collen= strchars(col('.')==1 ? "" : getline('.')[:col('.')-2]) + 1
另外,用 strwidth() 和 strdisplaywidth() 可以得到一个 string 在 Vim 里的显示宽度。
---- 对这几个函数怎么一点印象都没有 。。。
------------------------------------------------------------------------- <又补记> ---------------------------------------------------------------------------
---- virtcol() 函数,得到当前光标的显示位置(在第几个显示单元上)。功能不全一样,但也可用来判断光标位置与缩进等级之间的关系,感谢 依云 的回复。
Vim 的补全模式加速器,轻松玩转全部 15 种自动补全模式
---- 这是两年前写的一个小工具,是为了学习掌握 Vim 的补全模式,也是为了用起来方便。在那之前,我对 Vim 的补全功能还只是稍微了解,从没用过,因为补全功能默认的按键都是 emacs 式的,太蛋疼了,觉得这样还不如直接手敲。但是后来开始越来越多地写代码(我不是程序员,之前没写过多少),就觉得必须要熟悉 Vim 的补全功能,于是写了这个东东。它主要做两件事:
1: 把 Vim 的 15 种补全模式分成 5 组,映射到 <M-a> 到 <M-g> 5 个按键上,方便使用。好处有两个,一是不用去记 15 个按键组合(我是记不住),二是可以摆脱默认的那种 emacs 式的按键。
2: 在状态栏里做了一个补全模式状态显示器(可选,如下图),可以实时地显示补全模式的状态。不想要的话可以关闭,它就不会显示在状态栏里。
所以,就像是在众多 RPG 游戏里的“腰带”或“快速物品栏”一样,平时把常用的物品放在里面,需要时按一个键就能使用。这个工具自身并没有实质的补全功能,它等于是给 Vim 的补全模式添加了一个“快速物品栏”,里面各种补全模式的分组和相对位置都可以自己设置,可以把最常用的补全模式放在顺手的位置,需要时按一个键就能启动。Vim 提供的 15 种补全模式已经很厉害了,这个工具如同给它们再装上一个加速器,使它们用起来更顺手,当只按很少几个键就能自动补全很复杂的文本之时,编辑速度自然会进一步提高。
1. 关于 Vim 补全模式
---- Vim 一共提供了 15 种自动补全的模式(:help ins-completion)。其中有两种的补全列表内容与另外两种相同,只是排序不同,这 15 种模式:
1.1 文字编辑用的 3 种:
---- 文字编辑显然是不用 Vim 的,所以这三种模式比较悲催,我从没用过:
K 模式 (Vim 默认: CTRL-X CTRL-K) -- 字典补全,查找字典文件中的匹配单词,组成补全列表
H 模式 (Vim 默认: CTRL-X CTRL-T) -- 分类补全,查找分类文件(thesaurus 文件)中的匹配单词,组成补全列表
S 模式 (Vim 默认: CTRL-X s) -- 拼写建议
1.2 Vim 牛人用的两种:
---- 自定义的,我也没用过:
O 模式 (Vim 默认: CTRL-X CTRL-O) -- 全能补全,由一个自定义函数生成补全列表
U 模式 (Vim 默认: CTRL-X CTRL-U) -- 自定义补全,也是由自定义函数生成补全列表
1.3 所有人都喜欢的四种:
---- 我用来应付 99% 的情况:
n 模式 (Vim 默认: CTRL-N) -- 关键字补全,查找 'complete' 选项指定文件中的匹配单词,组成补全列表
N 模式 (Vim 默认: CTRL-X CTRL-N) -- 关键字补全,查找当前 buffer 里的匹配单词,组成补全列表
另外两种: p 模式与 P 模式,分别与 n 模式和 N 模式相同,只是补全列表中候选词的排序相反。
1.4 程序员用的三种:
---- 我不是程序员,也没用过(悲催了,当初干嘛要写这个 :(...):
T 模式 (Vim 默认: CTRL-X CTRL-]) -- tag 补全,查找 tag 中的匹配单词,组成补全列表
I 模式 (Vim 默认: CTRL-X CTRL-I) -- 头文件补全,查找当前 buffer 和 include file 中的匹配单词,组成补全列表
D 模式 (Vim 默认: CTRL-X CTRL-D) -- 定义补全,查找当前 buffer 与 include file 中匹配的名称定义,组成补全列表
1.5 特殊语境下专用的三种:
---- 还好,偶尔用一用 ...
V 模式 (Vim 默认: CTRL-X CTRL-V) -- Vim 补全,查找 Vim 的命令名, 函数名等等,组成补全列表
F 模式 (Vim 默认: CTRL-X CTRL-F) -- 文件名补全,查找匹配的路径或文件名,组成补全列表
L 模式 (Vim 默认: CTRL-X CTRL-L) -- 整行补全,查找 'complete' 选项指定文件中匹配的整行内容,组成补全列表
2. 使用:
2.1 显示器里状态显示的含义
---- 按 '\b' 可以切换补全状态显示器的开启和关闭。按下 '\b' 后显示器就会出现在状态栏里,再按 '\b' 显示器从状态栏里消失。下图显示的是 normal 模式下,补全模式未启用时的状态。状态显示器中黑色的字符是按键提示符,其它的是补全模式提示符。可以看到 15 种补全模式被分成 5 组,映射到 5 个按键上。带下划线的是对应分组里最近使用的模式,白色带下划线的是所有分组里最近使用的模式。进入 insert 模式后,按键提示符会“点亮”,开启某种补全模式后,相应的补全模式提示符的显示也会跟着调整。
2.2 使用常用的补全模式
---- 进入 insert 模式后,显示器里的按键提示符会“点亮”,提示这些按键可用,如下图。此时按相应的按键,就可以开启对应分组里的第一种补全模式。比如在 insert 模式下按 <M-d> 可以开启 p 模式,按 <M-g> 可以开启 V 模式。也就是说,一共有 5 种模式是只需要按一次键就能开启的。
2.3 使用其它补全模式
---- 要使用其它补全模式,先进入对应的分组然后按 <M-w> 或 <M-r> 往两边切换。比如,要使用 F 模式,就先按 <M-g> 进入 V 模式,再按 <M-r> 往右切换,此时就会先退出 V 模式再开启 F 模式,显示器里的状态显示也会跟着更新。要使用 L 模式时就再按一次 <M-r>,或者在 V 模式下直接按 <M-w> 也行。要使用 D 模式,就在 insert 模式下 <M-f><M-w> 或者 <M-f><M-r><M-r>。
2.4 选择候选单词,接受当前单词或退出
---- 当成功开启了某个补全模式,补全列表就会出现(即使只有一个匹配也会),此时按 i 和 k 可在列表中上下移动,选择候选单词,按空格接受当前选中的单词并留在 insert 模式下继续输入,按 <Esc> 拒绝候选单词,退出补全模式并留在 insert 模式下,再按一次 <Esc> 才会退出 insert 模式,回到 normal 模式。如果启用补全模式失败(没找到候选单词,或者相关选项没设置),就不会出现补全列表,但是显示器里显示的最近使用的模式仍会相应地更新。
---- 另外,如果补全列表比较长,也可以按 <C-f> 和 <C-b> 往前/往后翻动,以便快速定位到目标单词上面,这些操作与 normal 模式下的含义接近,用起来最顺手。
---- 选择 i 和 k 作为按键映射稍微限制了补全模式的功能,因为本来开启了补全模式以后按这些键是可以继续输入文本的,Vim 会根据新输入的内容即时查找匹配并不断调整补全列表的内容。但是对我来说,实时匹配功能不重要。如果对补全列表的内容不满意,我会先 <Esc> 退出补全模式,修改输入以后再重新开启。相比之下,按键用着方便对我来说最重要。
2.5 <M-e> 键
---- 这个键有两个功能。当进入 insert 模式还未启用补全的时候,按 <M-e> 会再次启用上次使用的补全模式(显示器中以白色显示)。当已经开启某个补全模式的时候(补全列表已显示),按 <M-e> 会先接受当前的候选单词,然后再次启用同样的补全模式,如下图:
图中显示了在 F 模式下,连续使用 <M-e> 键来依次补全各级目录名的情况。遇到多个选项就按 i 或 k 选择,然后再按 <M-e> 补全,一直到达要找的那个节点。
2.6 <Tab> 键
---- 按照 Vim 的设置,进入某些补全模式后再次按同样的按键可以启用一些后续的特殊补全行为模式,比如按 'C-x C-l' 进入 L 模式以后再按 'C-x C-l',还有按 'C-x C-p' 进入 p 模式后,再按 'C-x C-p' 或者 'C-x C-n',等等。在这个工具里,这些进入补全模式以后的后续按键都被映射到 <Tab> 上。也就是说,进入 L 模式后按 <Tab>,就相当于按 'C-x C-l'; 进入 p 模式后按 <Tab>,就相当于按 'C-x C-p',等等。
---- 当启用某种补全模式以后,如果显示器里的 <Tab> 按键提示符也点亮,就说明这种补全模式下 <Tab> 键可用。至于这种模式下 <Tab> 是什么含义,详细解释在文档里,一般就是 "连续补全" 或 "差异补全" 两种后续的补全方法。
---- "连续补全" 是指接受某个文本单元以后,继续把那个文本单元前期出现时相邻的下一个文本单元(一个单词或一行)作为新的补全列表,这样连续按 <Tab> 就可以连续补全前面出现过的内容,直到把一整段都复制到当前的位置来。而 "差异补全"(文档里所说的 "other context")是指输入了 'se' 以后,想要的补全列表不是 ['self', 'sex', 'select'],而是 ['se:xxx', 'se.yyy', 'se#zzz'] 这种。
---- 下图两个图显示了在 p 模式下的连续补全和差异补全:
连续补全: p 模式下连续按 <Tab>,可以把前面的一整段内容抄过来。
差异补全: p 模式下不选中任何候选词然后按 <Tab> 进入差异补全子模式,新出现的候选词与一般情况下不一样,其中后面几个都不是一般的词,前面两个 "正常" 的词是因为它们出现在其它 buffer 里却没出现在当前 buffer 里,这些都是故意要这样的,所以才叫 "差异补全"。
---- 所以。。。看明白了吧?基本上都是噱头。
2.7 关于补全模式的分组
---- 跟“快速物品栏”一样,15 种模式怎么分组,每组怎么排序,哪些模式放在每组的第一位,这些都可以在代码里设置。
3. 局限
---- 开启一种补全模式会牵涉到一系列后台操作,主要是扫描文件并生成补全列表,过程可能有些延迟(如果候选词列表较长),也有可能失败(比如相关的 Vim 选项没有设置)。如果出现这些情况,Vim 会“发呆”一段时间,期间有可能会“吃掉”一些输入字符。所以,如果在 Vim“发呆”的时候就迫不及待地连续按这些被映射过的按键,那结果可能不是想要的。这不是什么严重问题,最多导致一些不想要的文本被输入,按 'u' 可以取消,但是最好避免这种麻烦,等 Vim 有了反应,比如补全列表出现或者显示出了 error message 以后再做下一步操作。使用 Vim 默认的按键是不会有这个问题的,因为按那种 emacs 式的组合按键不可能按得这么快(emacs 的好处体现出来了)。
4. 代码
---- 代码现在分散在不同文件里,需要整理。等有空把它们整理好我就贴上来。
在 Vim 里控制外部程序,Vim 与 Emacs 与 Conque
---- 上一篇里说的想拿 Vim 当自编程序的 UI,基本已经做到了:
---- 底部的那个 buffer 属于 CSA 模块,这个新写的 Vim 插件就是负责充当外部程序在 Vim 里的界面,buffer 里显示的是外部程序的输出。关于用 Vim 来当 UI 的话题在 第一篇 里讲过。
---- 果然即使用 Python 接口建立了多个线程,也是没有办法让这些线程自动刷新 Vim buffer 里的显示的,况且还有上一篇里说的那个大 Bug。最后采用比较妥协的办法,负责监听的辅助线程还是建的,但是不产生任何输出,只修改后台数据。而主线程每次向外部程序发送输入之后,就 sleep 0.3 秒然后根据后台数据刷新显示,一般情况下都能即时显示外部程序的输出。另外再定义一个手动刷新的按键。
---- 要点记一下:
1. 上一篇里说的那个 bug,是 GUI 的问题,用终端版的 Vim 没事。GUI 版如果在后台建多个线程的话,只允许其中一个线程有可见的输出(也就是造成 Gvim 窗口内显示内容改变),其它线程只能修改后台数据。
2. 如上,即使多线程也不可能自动刷新 Vim buffer 的内容,但可以显示消息(不考虑 bug 的情况下)。自动刷新 Vim buffer 目前已知的只有一个办法:用 VimScript 的 feedkeys() 函数,但是有严重局限,相当于屏蔽了用户的其它输入。这个绝对不用。
3. 辅助线程监听外部程序的输出用的是 Python 的 select.select() 函数,可以监听一个 file descriptor 列表并返回其中有内容可读的 descriptor,但有一点要注意:在产生输出的外部程序已经终止以后,会出现 file descriptor 一直有效但读不到内容的情况,如果不注意这一点就有可能让监听过程陷入死循环,知道的话只要稍微处理一下就可以了。
---- 下一步打算升级程序的 Plotting 模块。现在这个模块还是测试性质的,上次试着画了 2000 多只股票自 2010 年以来的走势,全部单线程运行,花了两个多小时。现在打算改用 Python 的 multiprocessing 模块实现。据文档里说,这个模块与 threading 模块的接口十分类似,这就好了,以前那些关于 threading 的代码可以直接拿过来抄。以后批量执行绘图任务的时候,应该能省不少时间。
----------------------------------------------------- <以下补记> -------------------------------------------------------
---- 重大发现,感谢 依云 在回复里的提醒。以上要点第 2 条其实是不成立的。Vim 本身虽然是单线程的,但是通过 Python 完全能够实现多线程的特性。上面所说的辅助线程在监听到外部程序输出以后,就可以操作 vim.buffer 对象,这时 Vim buffer 实际上已经更新了,所缺的只是刷新显示的步骤而已。只要在刷新过程的最后添上一条 Vim 命令: "redraw!" 就可以了,出来的效果实在是 nice!
---- 这样一来,我完全想不到有什么是 Vim + Python 不能做的了。想起来在去年的时候,我想拿 Vim 当自编程序的界面,因为在 Vim 里找不到与外部进程交流的办法而被逼到去看 Emacs 。。。 如今看来,Vim + Python 真是个无敌组合,我忍不住要恶心一把。。。Emacs 你去 Shǐ 吧,哈哈,你的 Elisp 再强,能强得过 Python 吗?速度比不过 Vim,功能又比不过 Python。再想想 Elisp 里头那无数个括号,是要蛋疼到怎样啊?你那 N 个窗口,图片显示功能或许很酷,可是搁不住它慢啊!这事情一慢起来还有什么意思?好在 Emacs 早就顾及这一点,于是定义了更加蛋疼的操作方式,用 Emacs 的人,想快也快不起来,啊哈哈哈 ...
---- 以上讲笑,请浮云看待。如有用 Emacs 的看到,请先冷静 10 秒开骂 。。。
---- 既然这样,Conque 这个 Vim 插件就好拿出来再说一下了。确实像 依云 说的那样,它的实现可以有重大改进。Conque 是在 Vim 里模拟 Shell 终端的一个很流行的插件,它不同版本的实现曾经有过很大改变,但都使用了 Python 接口,插件负责底层交流的部分是 Python 写的。我在去年 7 月左右看过当时最新版的代码,学习了很多。首先,知道使用 Python 接口来编写 Vim 插件就是来自 Conque 的启发,这个还要感谢当时在 Vim-cn 群里跟我提起这个插件的网友 “Strange”。另外,Conque 使用了 Python 语言里负责底层系统操作的标准模块,比如 tty、select 这些,来与外部 Shell 进程交流,这些模块直接对应于底层的 Unix 系统调用,效率高而且可靠,我在自己写的程序里也用 select.select() 来监听外部进程,这也是从 Conque 里学来的。
---- 从当时的 Conque 的代码来看,它是单线程的,从键盘接收用户输入发送给 Shell 进程,而接收靠的是 Vim 函数 feedkeys(),以这个函数作为触发,不停地监听和提取 Shell 输出,经过格式、颜色处理以后发送到 Vim buffer 里。就像上面说的,用 feedkeys() 有严重局限,因为它完全模拟了用户的按键操作。这样一来,真正的用户就只能进行一个键的操作了。如果按键输入多于一个键,后面的键就会被 feedkeys() 冲掉。而 Conque 的解决办法,就是把键盘上几乎每一个键都做了映射,使得 Conque 能通过这些映射来区分是真正的用户输入,还是 feedkeys() 的输入,从而做不同处理。我觉得,这是个很无奈的做法。如果它对速度的影响还不那么糟糕的话,实际上它还屏蔽了 Vim 强大的编辑功能。如果只是把 Shell 原样不动地搬进 Vim 的窗口里是没多大意思的,能够在 Vim 里操作 Shell 的最大好处在于,在编辑那些火星文的 Shell 语句的时候,要能够毫无保留地使用 Vim 的强大编辑功能,这样才真正有意义。
---- 今天这个突破应该能够让以上成为现实,或许一半成为现实。。。因为还有 GUI 的那个大 Bug 在。是不是 Conque 的作者没有用多线程是因为,他当时就知道这个 bug 啊?为了顾及到插件的通用性,就只好放弃这个几近完美的解决方案了。因为这是 X 系统的 bug,我就叫你 X-bug 吧,万事具备,只欠踩死这只 bug!哈哈!
关于通过 vim 的 python 接口使用多线程特性的问题
---- Vim Bug 第三弹,以下代码:
python3 << EOF import threading import time print('xxx') def print_to_vim(): print('yyy') threading.Thread(name='test', target=print_to_vim).start() print('xxx') time.sleep(3) EOF
---- source 以后 gvim 窗口神奇蒸发,得到如下结果:
---- 结论: 通过 python 接口开了多个线程的话,只容许其中一个线程使用 print() 函数。不过这看起来像是 Gnome 或者 X 的问题,不是 Vim 的问题。
---- 我就是想拿 Vim 当我自编程序的用户界面而已,这至于吗。
---- 设计目标:
1. 从 Vim 窗口里通过自定义命令和按键控制外部程序,不需要进 Shell。
2. 程序输出通过 Vim 窗口来显示。利用 Python 的多线程特性,主线程处理日常功能,辅助线程专门监听程序的输出,一有输出就刷新 buffer 显示。
---- 以上。再配合用新写的 FSE 模块来浏览和管理程序文件,别的什么都不需要了。
---- 要是一切如常的话,搞定这些是完全没有问题的,就是怕这些层出不穷的 bug。我容易吗我。
FileSystemExplorer 写好了
---- 感谢 依云 解决了 Py3 接口内存泄漏的问题。已经下了 firefox 的源码来测试,一共 11 级目录 45000 多个节点,用扩展输出形式,一次打开花费 13 秒左右,反复关闭打开最多用 350 MB 内存,不会一直增加。
---- 左边是收藏夹功能,右边是文件结构浏览功能。可以建多个实例浏览不同路径,收藏夹只有一个。
---- 收藏夹界面里的操作:
'O': 添加同级标题 'o': 添加下级标题 '\a': 将当前主窗口内的路径加入收藏 '\s': 显示当前条目的信息 '\d': 删除当前光标下的标题(recursive)或节点 '\r': 修改当前标题
---- 浏览界面里的操作:
设置属性: :Set hidden 不显示隐藏文件(默认) :Set nohidden 显示隐藏文件 :Set byname 按名称排序(默认) :Set bysize 按大小排序 :Set bymtime 按修改时间排序 :Set byatime 按访问时间排序 :Set desc 逆序 :Set asc 正序(默认) :Set basic 显示基本信息(只有文件或目录名) :Set extend 显示扩展信息(默认,包括大小、修改时间、访问时间) :Set lock 锁定当前根节点,只能在其内部浏览 :Set unlock 解除根节点锁定(默认) 浏览: :CD {dir} 切换到指定节点 "Enter": 打开/关闭目录,打开文件。 "Shift-Enter": 递归式打开/关闭目录,打开文件。 "\r": 刷新显示。原来打开的节点仍打开,原来关闭的仍关闭 "\f": 显示当前节点的信息。 "\s": 把根节点切换到指定节点。 "\c": 关闭所有目录节点,除了当前节点和它的上级节点以外 "Alt-i, Alt-k" 定位到上一个/下一个节点。 1. 当前光标位于缩进部分的第 i 个 Tab 上时,定位到上一个/下一个 i 级节点。 2. 当光标位于其它位置时,定位到上一个/下一个同级节点。 文件操作: "o": 在指定目录下新建一个节点。 "O": 建立一个指定节点的同级节点。 "\d": 删除指定的节点。 "\t": 在指定目录或指定文件所在的目录下开启一个终端。
---- 源文件在 “Vim 插件” 页面里。但是自用插件写的时候完全没考虑通用性,所以换一台电脑八成是不能用的,只给感兴趣的人作参考。
Gvim7.3 + Python3 接口的内存使用完全失控
---- 为什么会有这种鸟事,为什么为什么。一度以为 Vim 已经很熟不需要再搞了,谁知道风平浪静的日子是没有的。快抓狂了。Vim 是这么成熟的编辑器,这种事难道该发生么。
---- 原本以为是 Python 代码写的有问题,对 Python 不熟,内存泄漏什么的。逐渐排除了以后开始锁定到 Python 接口上面,具体讲就是 vim.buffer 这个东西。这个是测试代码:
lcd %:h tabedit tmpbuffer setlocal buftype=nofile python3 << EOF for i in range(3): flines= ['x'*200] * 50000 vim.command("%s+\\_.*++g") for fl in flines: vim.current.buffer.append(fl) del flines[:] EOF
---- 而这个是结果:
在 gvim7.2 + Python2 接口下面:
第 1 次 source: 46.8 MB 内存,关闭 tmpbuffer 后不变
第 2 次 source: 59.2 MB 内存,关闭 tmpbuffer 后 58.1
第 3 次 source: 61.6 MB 内存,关闭 tmpbuffer 后 60.4
第 4 次 source: 63.9 MB 内存,关闭 tmpbuffer 后 63.6
第 5 次 source: 66.3 MB 内存,关闭 tmpbuffer 后 65.1
在 gvim7.3 + Python3 接口下面:
第 1 次 source: 142.6 MB 内存,关闭 tmpbuffer 后不变
第 2 次 source: 238.5 MB 内存,关闭 tmpbuffer 后不变
第 3 次 source: 334.5 MB 内存,关闭 tmpbuffer 后不变
---- buftype 选项其实对结果无影响。第一个看上去还好。第二个,好像 gvim7.3 的 Python3 接口是没有任何内存回收的,就这么任由它增长。另外初次运行的内存消耗也是 gvim7.2 的三倍。这没问题么? 问题大了。后来索性一直运行,这个是最后的结果:
---- 我表示这不是我的问题。考虑把这个贴到 vim-use 群里面。
---- 这个很打击我写插件的热情。不过还是要写,不写就亏大了,尼玛感觉就跟炒股被套一样。昨天新加的:
1. 丰富了颜色
2. 添加了功能: 除当前节点和它的上级节点之外,关闭其它所有打开的节点。
3. 添加了功能: 锁定根节点,只能在根节点内部浏览。
用来演示 gvim 预定义颜色项的脚本
---- 原来贴在 vim-cn 群里的一个脚本,这里也贴一下。
---- gvim 有好几百种预定义的颜色名称,比如 'DarkBlue', 'LightRed' 之类。这个脚本用来展示这些颜色在你的 gvim 里看起来如何。
---- 纯 VimScript 写的。用 gvim 打开脚本,然后
:source %
就可以了。
---- 会开启一个叫 'ShowVimColors.tmp' 的临时 buffer 用作显示,是 “纯” buffer 没有磁盘文件。内容分两部分,前一部分展示颜色本身,
---- 如果想搞 DIY 自己配色,记得 依云 在她的 blog 里写过一个 gvim 调色板,在 这里。也可以自己先写出来,比如 '#aabbcc',加进这个脚本里再运行一遍就能看见效果。
"========================================================================================================================
"======= ShowVimColors.vim
"========================================================================================================================
"======= 作者:Jacky Liu
"======= bluegene8210@gmail.com
"========================================================================================================================
" 网上找到的 Vim 里的预定义颜色
let s:VimColors=[
\ 'Snow', 'GhostWhite', 'WhiteSmoke', 'Gainsboro', 'FloralWhite', 'OldLace', 'Linen',
\ 'AntiqueWhite', 'PapayaWhip', 'BlanchedAlmond', 'Bisque', 'PeachPuff', 'NavajoWhite', 'Azure',
\ 'Moccasin', 'Cornsilk', 'Ivory', 'LemonChiffon', 'Seashell', 'Honeydew', 'MintCream',
\ 'AliceBlue', 'Lavender', 'LavenderBlush', 'MistyRose', 'White', 'Black', 'DarkSlateGray',
\ 'DimGray', 'SlateGray', 'LightSlateGray', 'Gray', 'LightGray', 'MidnightBlue', 'Navy',
\ 'NavyBlue', 'CornflowerBlue', 'DarkSlateBlue', 'SlateBlue', 'MediumSlateBlue',
\ 'LightSlateBlue', 'MediumBlue', 'RoyalBlue', 'Blue', 'DodgerBlue', 'DeepSkyBlue',
\ 'SkyBlue', 'LightSkyBlue', 'SteelBlue', 'LightSteelBlue', 'LightBlue', 'PowderBlue',
\ 'PaleTurquoise', 'DarkTurquoise', 'MediumTurquoise', 'Turquoise', 'Cyan', 'LightCyan',
\ 'CadetBlue', 'MediumAquamarine', 'Aquamarine', 'DarkGreen', 'DarkOliveGreen',
\ 'DarkSeaGreen', 'SeaGreen', 'MediumSeaGreen', 'LightSeaGreen', 'PaleGreen',
\ 'SpringGreen', 'LawnGreen', 'Green', 'Chartreuse', 'MediumSpringGreen', 'GreenYellow',
\ 'LimeGreen', 'YellowGreen', 'ForestGreen', 'OliveDrab', 'DarkKhaki', 'Khaki',
\ 'PaleGoldenrod', 'LightGoldenrodYellow', 'LightYellow', 'Yellow', 'Gold',
\ 'LightGoldenrod', 'Goldenrod', 'DarkGoldenrod', 'RosyBrown', 'IndianRed', 'SaddleBrown',
\ 'Sienna', 'Peru', 'Burlywood', 'Beige', 'Wheat', 'SandyBrown', 'Tan', 'Chocolate', 'Firebrick',
\ 'Brown', 'DarkSalmon', 'Salmon', 'LightSalmon', 'Orange', 'DarkOrange', 'Coral', 'LightCoral',
\ 'Tomato', 'OrangeRed', 'Red', 'HotPink', 'DeepPink', 'Pink', 'LightPink', 'PaleVioletRed',
\ 'Maroon', 'MediumVioletRed', 'VioletRed', 'Magenta', 'Violet', 'Plum', 'Orchid',
\ 'MediumOrchid', 'DarkOrchid', 'DarkViolet', 'BlueViolet', 'Purple', 'MediumPurple',
\ 'Thistle', 'Snow1', 'Snow2', 'Snow3', 'Snow4', 'Seashell1', 'Seashell2', 'Seashell3',
\ 'Seashell4', 'AntiqueWhite1', 'AntiqueWhite2', 'AntiqueWhite3', 'AntiqueWhite4',
\ 'Bisque1', 'Bisque2', 'Bisque3', 'Bisque4', 'PeachPuff1', 'PeachPuff2', 'PeachPuff3',
\ 'PeachPuff4', 'NavajoWhite1', 'NavajoWhite2', 'NavajoWhite3', 'NavajoWhite4',
\ 'LemonChiffon1', 'LemonChiffon2', 'LemonChiffon3', 'LemonChiffon4', 'Cornsilk1',
\ 'Cornsilk2', 'Cornsilk3', 'Cornsilk4', 'Ivory1', 'Ivory2', 'Ivory3', 'Ivory4', 'Honeydew1',
\ 'Honeydew2', 'Honeydew3', 'Honeydew4', 'LavenderBlush1', 'LavenderBlush2',
\ 'LavenderBlush3', 'LavenderBlush4', 'MistyRose1', 'MistyRose2', 'MistyRose3',
\ 'MistyRose4', 'Azure1', 'Azure2', 'Azure3', 'Azure4', 'SlateBlue1', 'SlateBlue2',
\ 'SlateBlue3', 'SlateBlue4', 'RoyalBlue1', 'RoyalBlue2', 'RoyalBlue3', 'RoyalBlue4',
\ 'Blue1', 'Blue2', 'Blue3', 'Blue4', 'DodgerBlue1', 'DodgerBlue2', 'DodgerBlue3',
\ 'DodgerBlue4', 'SteelBlue1', 'SteelBlue2', 'SteelBlue3', 'SteelBlue4', 'DeepSkyBlue1',
\ 'DeepSkyBlue2', 'DeepSkyBlue3', 'DeepSkyBlue4', 'SkyBlue1', 'SkyBlue2', 'SkyBlue3',
\ 'SkyBlue4', 'LightSkyBlue1', 'LightSkyBlue2', 'LightSkyBlue3', 'LightSkyBlue4',
\ 'SlateGray1', 'SlateGray2', 'SlateGray3', 'SlateGray4', 'LightSteelBlue1',
\ 'LightSteelBlue2', 'LightSteelBlue3', 'LightSteelBlue4', 'LightBlue1', 'LightBlue2',
\ 'LightBlue3', 'LightBlue4', 'LightCyan1', 'LightCyan2', 'LightCyan3', 'LightCyan4',
\ 'PaleTurquoise1', 'PaleTurquoise2', 'PaleTurquoise3', 'PaleTurquoise4', 'CadetBlue1',
\ 'CadetBlue2', 'CadetBlue3', 'CadetBlue4', 'Turquoise1', 'Turquoise2', 'Turquoise3',
\ 'Turquoise4', 'Cyan1', 'Cyan2', 'Cyan3', 'Cyan4', 'DarkSlateGray1', 'DarkSlateGray2',
\ 'DarkSlateGray3', 'DarkSlateGray4', 'Aquamarine1', 'Aquamarine2', 'Aquamarine3',
\ 'Aquamarine4', 'DarkSeaGreen1', 'DarkSeaGreen2', 'DarkSeaGreen3', 'DarkSeaGreen4',
\ 'SeaGreen1', 'SeaGreen2', 'SeaGreen3', 'SeaGreen4', 'PaleGreen1', 'PaleGreen2',
\ 'PaleGreen3', 'PaleGreen4', 'SpringGreen1', 'SpringGreen2', 'SpringGreen3',
\ 'SpringGreen4', 'Green1', 'Green2', 'Green3', 'Green4', 'Chartreuse1', 'Chartreuse2',
\ 'Chartreuse3', 'Chartreuse4', 'OliveDrab1', 'OliveDrab2', 'OliveDrab3', 'OliveDrab4',
\ 'DarkOliveGreen1', 'DarkOliveGreen2', 'DarkOliveGreen3', 'DarkOliveGreen4', 'Khaki1',
\ 'Khaki2', 'Khaki3', 'Khaki4', 'LightGoldenrod1', 'LightGoldenrod2', 'LightGoldenrod3',
\ 'LightGoldenrod4', 'LightYellow1', 'LightYellow2', 'LightYellow3', 'LightYellow4',
\ 'Yellow1', 'Yellow2', 'Yellow3', 'Yellow4', 'Gold1', 'Gold2', 'Gold3', 'Gold4', 'Goldenrod1',
\ 'Goldenrod2', 'Goldenrod3', 'Goldenrod4', 'DarkGoldenrod1', 'DarkGoldenrod2',
\ 'DarkGoldenrod3', 'DarkGoldenrod4', 'RosyBrown1', 'RosyBrown2', 'RosyBrown3',
\ 'RosyBrown4', 'IndianRed1', 'IndianRed2', 'IndianRed3', 'IndianRed4', 'Sienna1', 'Sienna2',
\ 'Sienna3', 'Sienna4', 'Burlywood1', 'Burlywood2', 'Burlywood3', 'Burlywood4', 'Wheat1',
\ 'Wheat2', 'Wheat3', 'Wheat4', 'Tan1', 'Tan2', 'Tan3', 'Tan4', 'Chocolate1', 'Chocolate2',
\ 'Chocolate3', 'Chocolate4', 'Firebrick1', 'Firebrick2', 'Firebrick3', 'Firebrick4',
\ 'Brown1', 'Brown2', 'Brown3', 'Brown4', 'Salmon1', 'Salmon2', 'Salmon3', 'Salmon4',
\ 'LightSalmon1', 'LightSalmon2', 'LightSalmon3', 'LightSalmon4', 'Orange1', 'Orange2',
\ 'Orange3', 'Orange4', 'DarkOrange1', 'DarkOrange2', 'DarkOrange3', 'DarkOrange4', 'Coral1',
\ 'Coral2', 'Coral3', 'Coral4', 'Tomato1', 'Tomato2', 'Tomato3', 'Tomato4', 'OrangeRed1',
\ 'OrangeRed2', 'OrangeRed3', 'OrangeRed4', 'Red1', 'Red2', 'Red3', 'Red4', 'DeepPink1',
\ 'DeepPink2', 'DeepPink3', 'DeepPink4', 'HotPink1', 'HotPink2', 'HotPink3', 'HotPink4',
\ 'Pink1', 'Pink2', 'Pink3', 'Pink4', 'LightPink1', 'LightPink2', 'LightPink3', 'LightPink4',
\ 'PaleVioletRed1', 'PaleVioletRed2', 'PaleVioletRed3', 'PaleVioletRed4', 'Maroon1',
\ 'Maroon2', 'Maroon3', 'Maroon4', 'VioletRed1', 'VioletRed2', 'VioletRed3', 'VioletRed4',
\ 'Magenta1', 'Magenta2', 'Magenta3', 'Magenta4', 'Orchid1', 'Orchid2', 'Orchid3', 'Orchid4',
\ 'Plum1', 'Plum2', 'Plum3', 'Plum4', 'MediumOrchid1', 'MediumOrchid2', 'MediumOrchid3',
\ 'MediumOrchid4', 'DarkOrchid1', 'DarkOrchid2', 'DarkOrchid3', 'DarkOrchid4', 'Purple1',
\ 'Purple2', 'Purple3', 'Purple4', 'MediumPurple1', 'MediumPurple2', 'MediumPurple3',
\ 'MediumPurple4', 'Thistle1', 'Thistle2', 'Thistle3', 'Thistle4',
\ 'DarkCyan', 'DarkMagenta', 'DarkRed', 'LightGreen',
\ '#132b4a', '#233b5a', '#005c70', '#031b4a', '#d0d0e0',
\ 'NONE']
let s:statements= [] " 用来定义 syntax group 以及调用 matchadd() 的 VimScript 语句。
let s:filelines= [] " 加到临时 buffer 内的内容
call add(s:filelines, '输出的第一部分,展示颜色本身')
call add(s:filelines, '')
for vcolor in s:VimColors
call add(s:filelines, vcolor . repeat(' ', 160-len(vcolor)))
if vcolor =~ '^#' | let vcname= vcolor[1:] | else | let vcname= vcolor | endif
" syntax group container
"======================================================================================================================
let sgcontname= vcname . '_cont'
call add(s:statements, 'syntax match ' . sgcontname . ' /^' . vcolor . '\> *$/')
" 指定主体,默认背景
"======================================================================================================================
let fghigrpname= '_' . vcname . '_' . 'NONE' " hi group name (as frontground)
call add(s:statements, 'hi ' . fghigrpname . ' guifg=' . vcolor . ' guibg=' . 'NONE') " 定义 hi group
let fgsyngrpname= 'sgrp_' . fghigrpname " syntax group name (as frontground)
call add(s:statements, 'syntax match ' . fgsyngrpname . ' /^' . vcolor . '\>/ containedin=' . sgcontname . ' contained') " 定义 syntax group
call add(s:statements, 'hi link '. fgsyngrpname . ' ' . fghigrpname) " syntax group 与 hi group 联系起来
" 默认主体,指定背景
"======================================================================================================================
let bghigrpname= '_' . 'NONE' . '_' . vcname " hi group name (as background)
call add(s:statements, 'hi ' . bghigrpname . ' guifg=' . 'NONE' . ' guibg=' . vcolor) " 定义 hi group
let bgsyngrpname= 'sgrp_' . bghigrpname " syntax group name (as background)
call add(s:statements, 'syntax match ' . bgsyngrpname . ' / \+/' . ' containedin=' . sgcontname . ' contained') " 同上
call add(s:statements, 'hi link '. bgsyngrpname . ' ' . bghigrpname) " 同上
endfor
call add(s:filelines, '')
call add(s:filelines, '输出的第二部分,展示作为主体色或作为背景色与黑白两色的搭配效果')
call add(s:filelines, '')
for vcolor in s:VimColors
if vcolor =~ '^#' | let vcname= vcolor[1:] | else | let vcname= vcolor | endif
" 指定主体,黑色背景
"======================================================================================================================
let gname_1= '_' . vcname . '_' . 'Black' " syntax group name
call add(s:statements, 'hi ' . gname_1 . ' guifg=' . vcolor . ' guibg=' . 'Black') " 定义语句
let skwname= 'skw_' . gname_1
call add(s:statements, 'syntax keyword ' . skwname . ' ' . gname_1)
call add(s:statements, 'hi link ' . skwname . ' ' . gname_1)
" 黑色主体,指定背景
"======================================================================================================================
let gname_2= '_' . 'Black' . '_' . vcname " syntax group name
call add(s:statements, 'hi ' . gname_2 . ' guifg=' . 'Black' . ' guibg=' . vcolor) " 定义语句
let skwname= 'skw_' . gname_2
call add(s:statements, 'syntax keyword ' . skwname . ' ' . gname_2)
call add(s:statements, 'hi link ' . skwname . ' ' . gname_2)
" 指定主体,白色背景
"======================================================================================================================
let gname_3= '_' . vcname . '_' . 'White' " syntax group name
call add(s:statements, 'hi ' . gname_3 . ' guifg=' . vcolor . ' guibg=' . 'White') " 定义语句
let skwname= 'skw_' . gname_3
call add(s:statements, 'syntax keyword ' . skwname . ' ' . gname_3)
call add(s:statements, 'hi link ' . skwname . ' ' . gname_3)
" 白色主体,指定背景
"======================================================================================================================
let gname_4= '_' . 'White' . '_' . vcname " syntax group name
call add(s:statements, 'hi ' . gname_4 . ' guifg=' . 'White' . ' guibg=' . vcolor) " 定义语句
let skwname= 'skw_' . gname_4
call add(s:statements, 'syntax keyword ' . skwname . ' ' . gname_4)
call add(s:statements, 'hi link ' . skwname . ' ' . gname_4)
" buffer 新加入一行
"======================================================================================================================
let newline= gname_1 . repeat(' ', 30-len(gname_1)) .
\ gname_2 . repeat(' ', 30-len(gname_2)) .
\ gname_3 . repeat(' ', 30-len(gname_3)) . gname_4
call add(s:filelines, newline)
endfor
tabedit ShowVimColors.tmp " 开启临时 buffer
setlocal buftype=nofile " 设置 buffer 属性
call append(0, s:filelines) " 加入内容
for stat in s:statements " “上色”
exe stat
endfor
" autocmd BufWinLeave ShowVimColors.tmp call clearmatches() " 走之前清掉。不然 Vim 会一直很慢
FileSystemExplorer 终于成形了
---- 外部的 Vim 插件我基本都是装上试试然后就卸掉,用到的基本都自己写。在文件操作方面,因为自带的 Netrw 实在不够用,一些 VimScript 写的外部插件(比如 NerdTree)又有慢的问题。自己写的 FileSystemExplorer 这个插件自从起个头以后名不副实了很久,现在终于成形了。
---- 主体还是 Python 写的(通过 Vim 的 Python 接口),纯 VimScript 写的太慢。但是目前还是很菜。以下是设想中,准备逐步添加的功能:
1. 刷新(也可以用于删除、新建操作之后)
2. 改变排序方式(也可以用于递归式打开目录之后)
3. 删除目录或文件
4. 批量删除
5. 转移目录或文件
6. 改名
7. 递归式打开目录
8. 新建目录或文件
9. 快速定位(到同级上一个/下一个节点,到上级节点)
10. 智能化开启文件(通过后台调用其它程序)
---- 基本上 Vim 和 Python 折腾到现在,不确定性已经越来越少。接下来的事可能跟工厂做工差不多。继续开工 。。。
上线两个新模块
---- 前一阵子花了些时间,把以前写的几乎所有的 Vim 插件都用 Python 接口改 写了一遍,主体结构全部放在脚本的 Python 部分,效果非常好。实际上 Vim 的编程语言接口是早就有了的,而现在 Vim 自带的 VimScript 语言基本上是 7.0 版以后才成形,所以 Vim 的本意实际上是让用户使用已有的语言来编写 Vim 上使用的脚本,而不是想要再发明一种新的语言。Vim 的作者在接受访谈时也表达过这个意思。两个新的模块也是用 Python 接口写的,使用了 Python 语言的一些关键特性。
---- 第一个是 FileSystemExplorer,名头起的很大,因为开始想得比较复杂,但实际上现在只有一个类似于收藏夹的功能。把常用的目录和文件分类收集起来,方便一键打开。本来还想 实现像 NerdTree 那样的树形文件结构浏览的功能,但后来想想算了,就停留在这样一个名不副实的状态。顺便说下,NerdTree 虽然用起来速度比较慢(因为用 VimScript 的内部数据结构来模拟了文件系统的结构),但是插件本身写得非常好。虽然以前我也上过 C++ 的课,但是关于 OOP 的概念还是从 NerdTree 里学到的最多。
---- 第二个是 WebFileBrowser,同样是好大喜功的一个名字。原本的设想是这样:
1. 通过自定义命令, :Get {url} 可以把网页下载下来,将源文件显示在 output buffer 里面。
2. 有一个 index 子模块,可以分析下载的网页文件,形成一个 element 的目录,显示在辅助窗口里。目录的内容应该具有很顺眼的颜色,能清楚地显示网页的内容结构,而且具有跳转功能,能跳到源文件里对应的地方。
3. 在 output buffer 里有一些快捷键操作,可以跳转到 start tag,end tag 等等,可以提取 tag 内容或属性,或许还能通过自定义命令执行更复杂的 Xpath 查找操作,还有像浏览器那样的跳转功能,等等。
4. 有一个 cache 子模块,像浏览器的历史栏那样,保存已下载的网页文件和它们的 url。必要时重新打开。
---- 而现在实现的只有 1 和 2, 至于 3 和 4 以后再说(“再说” == 永远都没有)。
---- 写这个插件最初是为了学习网络相关的东西,html,javascript,flash 这些,尤其想弄明白一个问题:flash 到底是怎么显示出来的?有没有可能通过包含 flash 的网页下载到决定了 flash 内容的那些原始数据?而且这些原始数据必须是能读懂的,可用的才行。比方说新浪的网页能用 flash 显示股票 K 线图,能通过这些 flash 直接下载到背后的行情和技术指标数据吗?
---- 这个问题基本解决了,不过是通过 Google 解决的,研究 html 本身并没有多大帮助。看起来好像是这样:浏览器解读了含有 flash 的网页之后,会下载背后的 swf 文件,然后浏览器内部的 flash 插件负责解读这些 swf 文件并根据它的内容显示 flash 图形。swf 是二进制格式的图形描述文件,并不含有形成这些图形的原始数据。
---- 第一个插件使用了 Python 的 pickle 特性,用来存放 “收藏夹” 的内容,下次开启 Vim 时自动读入。第二个插件使用 Python 标准库里的 urllib.request 模块下载网页文件,然后使用第三方模块 lxml 来 parse 网页内容。lxml 这个模块很强,关键是什么网页都能吃,包括有问题的网页。而且它是支持 Python 3 的(内牛满面 :-(...),要不然这个插件只能停留在设想中。
PS:图看起来太大了。怎么让它用原始尺寸显示啊?!